///|
// Drawing a set line, as Prawn's `Text::Formatted::Box#draw_fragment` does:
// each fragment the line holds in turn (one with nothing to show on the
// line too), in its own font and colour (`begin_fragment`), what its
// callbacks put behind it, its text (or what the `draw_text_callback`
// draws instead), then its underline and strikethrough, its links and
// what its callbacks put in front. Each goes on the page current when it
// is drawn, which a callback may change.
///|
/// Draw `line`: its left edge at `left`, its baseline at `baseline`, in
/// the coordinates of the bounds.
fn Document::ink_line(
self : Document,
line : Line,
left : Double,
baseline : Double,
) -> Unit raise PrawnError {
for held in line.held {
let (fragment, x, style, face) = held
let runs = line.runs.filter(r => r.fragment == fragment)
let x = match runs.get(0) {
Some(r) => r.x
None => x
}
let mut width = 0.0
let text = StringBuilder()
for run in runs {
width += run.width
text.write_string(run.text)
}
self.draw_fragment(
runs,
left + x,
x,
width,
baseline,
style,
face,
text.to_string(),
)
}
}
///|
/// Prawn's `draw_fragment`: a fragment from its runs on the line, starting
/// at `left` (`x` into the line), its baseline at `baseline` (both in the
/// coordinates of the bounds), `width` wide, its text `text`.
fn Document::draw_fragment(
self : Document,
runs : Array[LineRun],
left : Double,
x : Double,
width : Double,
baseline : Double,
style : Style,
face : Face,
text : String,
) -> Unit raise PrawnError {
// the fragment's font and colour, to its end
self.begin_fragment(style)
// and back to the font before, whatever happens (Prawn's `save_font`)
defer self.end_fragment()
// where it is on the page current at each step (pagelayout's page
// coordinates): a callback may start another
let on_page = fn() {
let box = self.current_box()
(box.left + left, self.page_h() - baseline - box.bottom_at(self.y()))
}
// its callbacks (none in a dry run), which see it placed as it starts
let callbacks = match self.fragment_meta.get(style.callback) {
Some(meta) if !self.dry_run => meta.callbacks
_ => []
}
let placed = placed_fragment(left, width, baseline, style, face, text)
for callback in callbacks {
match callback.render_behind {
Some(f) => f(placed)
None => ()
}
}
let rise = script_rise(style, face)
match self.draw_text_callback {
Some(draw) =>
// called with the fragment's text where it starts (Prawn's default
// kerning); a dry run draws nothing
if !self.dry_run {
draw(text, (left, baseline + rise), true)
}
None => {
// 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 (bold, italic) = style.face_style()
let font = self.model.add_font({ family: style.family, bold, italic, })
// in the fill colour current now: the fragment's own, or what a
// callback set
let color = self.gs().fill
let items = self.page().items
let (x_now, baseline_now) = on_page()
for run in runs {
items.push(
Text({
font,
size_pt: style.size,
x_pt: x_now + (run.x - x),
baseline_pt: baseline_now - rise,
text: run.text,
advances_pt: run.advances,
color,
}),
)
}
}
}
let (x_now, baseline_now) = on_page()
self.draw_overlays(x_now, width, baseline_now - rise, style, face)
for callback in callbacks {
match callback.render_in_front {
Some(f) => f(placed)
None => ()
}
}
}
///|
/// Prawn's `draw_fragment_overlay_styles`, link and anchor: the underline
/// and strikethrough, stroked as `stroke_line` strokes them in the current
/// stroke colour and line style, 1.25 points below the baseline (`base`,
/// where a sub- or superscript sits) and 0.3 of the ascender above it;
/// then the URI's annotation and the destination's, as wide as the
/// fragment.
fn Document::draw_overlays(
self : Document,
x : Double,
width : Double,
base : Double,
style : Style,
face : Face,
) -> Unit {
let a = face.ascender_pt(style.size)
let d = face.descender_pt(style.size)
let gs = self.gs()
let items = self.page().items
let line = fn(y : Double) -> @pagelayout.PageItem {
Graphic({
x_pt: 0.0,
y_pt: 0.0,
ops: [
Path({
segments: [MoveTo(x, y), LineTo(x + width, y)],
fill: None,
fill_rule: NonZero,
stroke: Some(Solid(gs.stroke)),
stroke_style: {
width: if gs.line_width == 0.0 {
Hairline
} else {
Width(gs.line_width)
},
cap: gs.cap,
join: gs.join,
miter_limit: 10.0,
dash: gs.dash.copy(),
dash_phase: gs.dash_phase,
},
}),
],
})
}
if style.underline {
items.push(line(base + 1.25))
}
if style.strike {
items.push(line(base - a * 0.3))
}
let link = fn(target) {
items.push(
Link({ x_pt: x, y_pt: base - a, w_pt: width, h_pt: a + d, target, }),
)
}
match style.link {
Some(target) => link(target)
None => ()
}
match self.fragment_meta.get(style.callback) {
Some({ anchor: Some(name), .. }) => link(Named(name))
_ => ()
}
}
///|
/// How far a sub- or superscript's baseline is raised (Prawn's arranger
/// sets a superscript 0.85 of its ascender up, a subscript its descender
/// down).
fn script_rise(style : Style, face : Face) -> Double {
match style.script {
1 => 0.85 * face.ascender_pt(style.size)
-1 => -face.descender_pt(style.size)
_ => 0.0
}
}
///|
/// Prawn's warning about text beyond ASCII in a built-in font.
let m17n_warning : String =
#|PDF's built-in fonts have very limited support for internationalized text.
#|If you need full UTF-8 support, consider using an external font instead.
#|
#|To disable this warning, add the following line to your code:
#|Prawn::Fonts::AFM.hide_m17n_warning = true
#|