///|
/// Prawn's `draw_text`: `text` on one line, its baseline starting at `at`
/// (a point of the bounds), in the current font at `size` (the current
/// size), in `style` (by default the current one), kerned unless `kerning` is false, turned `rotate` degrees
/// about `at`. No wrapping, no page break; the cursor does not move.
pub fn Document::draw_text(
self : Document,
text : String,
at~ : (Double, Double),
size? : Double,
style? : FontStyle,
rotate? : Double = 0.0,
kerning? : Bool = true,
) -> Unit raise {
// Prawn's `process_text_options`: the current font in `style`, which a
// font of no family cannot take
let saved_font = self.apply_style(
style,
BadFontFamily("PDF::Core::Text::BadFontFamily"),
)
defer self.restore_style(saved_font)
let base = self.style(size?)
// the whole string is encoded first (Prawn's `normalize_encoding`), in
// its own font only: draw_text looks in no fallback
self.check(text, base, None, fallbacks=false)
let draw = () => {
let face = self.face_of(base)
// Prawn's `draw_text!`: text beyond ASCII in a built-in font is warned
// about, once
if !self.dry_run &&
!self.m17n_warned &&
face.standard &&
text.iter().any(c => c.to_int() > 0x7F) {
self.m17n_warned = true
(self.warn)(m17n_warning)
}
let scale = base.size / 1000.0
let codes = text.iter().map(c => c.to_int()).to_array()
// one advance per UTF-16 unit, as the text is drawn: a character
// beyond the BMP's second unit advances nothing
let advances = []
for i, code in codes {
let kern = if kerning && i + 1 < codes.length() {
face.kerning(code, codes[i + 1])
} else {
0.0
}
advances.push((face.drawn_width(code) + kern) * scale)
if code > 0xFFFF {
advances.push(0.0)
}
}
// on the page: Prawn's `map_to_absolute`
let b = self.bounds()
let x = b.absolute_left + at.0
let y = b.absolute_top - b.height + at.1
let (bold, italic) = base.face_style()
let font = self.model.add_font({ family: base.family, bold, italic, })
self.page().items.push(
Text({
font,
size_pt: base.size,
x_pt: x,
baseline_pt: self.page_h() - y,
text,
advances_pt: advances,
color: self.gs().fill,
}),
)
}
if rotate == 0.0 {
draw()
} else {
// Prawn turns the text matrix about the point: on the page, the same
// as drawing it under that rotation
self.rotate(rotate, origin=at, () => draw())
}
}
///|
/// Prawn's `stroke_axis`: dashed axes from `at` (a point of the bounds,
/// by default their origin), `width` and `height` long (to the bounds' edge
/// by default) and `negative_axes_length` back, in `color`, with a dot and
/// a label every `step_length` points.
pub fn Document::stroke_axis(
self : Document,
at? : (Double, Double) = (0.0, 0.0),
width? : Double,
height? : Double,
step_length? : Double = 100.0,
negative_axes_length? : Double = 20.0,
color? : Color = Color::rgb(0, 0, 0),
) -> Unit raise {
let b = self.bounds()
let width = width.unwrap_or(b.width - at.0)
let height = height.unwrap_or(b.height - at.1)
self.save_graphics_state(() => {
self.set_fill_color(color)
self.set_stroke_color(color)
self.dash([1.0], space=4.0)
self.horizontal_line(at.0 - negative_axes_length, at.0 + width, at=at.1)
self.stroke()
self.vertical_line(at.1 - negative_axes_length, at.1 + height, at=at.0)
self.stroke()
self.undash()
self.circle(at, 1.0)
self.fill()
// Ruby's `Range#step` refuses a step that goes nowhere, the axes drawn
if step_length <= 0.0 {
raise ArgumentError(
"step can't be \{if step_length == 0.0 { "0" } else { "negative" }}",
)
}
// Ruby's `(step..width).step(step)`: every step up to the end, each
// labelled as Ruby prints it
// a range with a fraction anywhere is one of Floats, which Ruby prints
// with one (`51.0`)
let label = fn(point : Double, to : Double) {
if [step_length, to].iter().any(x => x != x.floor()) {
ruby_float_string(point)
} else {
ruby_number(point)
}
}
for point in ruby_steps(step_length, width, step_length) {
self.circle((at.0 + point, at.1), 1.0)
self.fill()
self.draw_text(
label(point, width),
at=(at.0 + point - 5.0, at.1 - 10.0),
size=7.0,
)
}
for point in ruby_steps(step_length, height, step_length) {
self.circle((at.0, at.1 + point), 1.0)
self.fill()
self.draw_text(
label(point, height),
at=(at.0 - 17.0, at.1 + point - 2.0),
size=7.0,
)
}
})
}
///|
/// A number as Ruby prints a whole one: without a fraction.
pub fn ruby_number(x : Double) -> String {
if x == x.floor() && x.abs() < 1.0e15 {
x.to_int64().to_string()
} else {
x.to_string()
}
}
///|
/// The values Ruby's `(from..to).step(by)` yields for floats: each
/// `from + i * by`, as many as fit with a little slack for rounding, the
/// last one no further than `to` (Ruby's `ruby_float_step`).
fn ruby_steps(from : Double, to : Double, by : Double) -> Array[Double] {
let n = (to - from) / by
let mut err = (from.abs() + to.abs() + (to - from).abs()) /
by.abs() *
2.220446049250313e-16
if err > 0.5 {
err = 0.5
}
let count = (n + err).floor()
let out = []
let mut i = 0.0
while i <= count {
// (Ruby's C may fuse this into one rounding, depending on how it was
// compiled: a step with no exact binary value may then differ in its
// last digit)
let d = i * by + from
out.push(if by >= 0.0 && to < d { to } else { d })
i += 1.0
}
out
}
///|
/// A Float as Ruby prints it: a whole one with `.0`.
fn ruby_float_string(x : Double) -> String {
let s = x.to_string()
if s.contains(".") || s.contains("e") || s.contains("N") || s.contains("I") {
s
} else {
s + ".0"
}
}