use pdf_writer::{Content, Str};
use zenith_core::FontProvider;
use zenith_scene::Color;
use super::color;
use super::content::{FONT_PREFIX, LinkAnnot, PageResources, apply_alpha, name};
use super::font::FontPlan;
use super::geometry::GlyphPen;
#[derive(Clone, Copy)]
pub(super) struct GlyphRun<'a> {
pub(super) x: f64,
pub(super) y: f64,
pub(super) font_id: &'a str,
pub(super) font_size: f32,
pub(super) color: &'a Color,
pub(super) link: Option<&'a str>,
pub(super) selectable: bool,
pub(super) glyphs: &'a [zenith_scene::SceneGlyph],
}
pub(super) fn emit_glyph_run(
content: &mut Content,
res: &mut PageResources,
fonts: &dyn FontProvider,
font_plan: &FontPlan,
run: GlyphRun<'_>,
) {
let Some(font_data) = fonts.by_id(run.font_id) else {
return;
};
let Ok(face) = ttf_parser::Face::parse(&font_data.bytes, font_data.index) else {
return;
};
let units_per_em = face.units_per_em();
if units_per_em == 0 {
return;
}
if run.selectable && font_plan.resource_index(run.font_id).is_some() {
emit_text_run(content, res, font_plan, &face, run);
return;
}
emit_outline_run(content, res, &face, units_per_em, run);
}
fn emit_text_run(
content: &mut Content,
res: &mut PageResources,
font_plan: &FontPlan,
face: &ttf_parser::Face<'_>,
run: GlyphRun<'_>,
) {
let GlyphRun {
x,
y,
font_id,
font_size,
color,
link,
glyphs,
..
} = run;
let Some(font_idx) = font_plan.resource_index(font_id) else {
return;
};
let units_per_em = face.units_per_em();
let scale = font_size / f32::from(units_per_em);
res.font_indices.insert(font_idx);
content.save_state();
apply_alpha(content, res, color);
color::set_fill(content, color);
content.begin_text();
content.set_font(name(FONT_PREFIX, font_idx).as_name(), font_size);
for glyph in glyphs {
let Some((_, cid)) = font_plan.cid_of(font_id, glyph.glyph_id) else {
continue;
};
let tx = x as f32 + glyph.dx;
let ty = y as f32 + glyph.dy;
content.set_text_matrix([1.0, 0.0, 0.0, -1.0, tx, ty]);
content.show(Str(&[(cid >> 8) as u8, (cid & 0xFF) as u8]));
}
content.end_text();
content.restore_state();
if let Some(url) = link
&& !glyphs.is_empty()
{
let ascent = f32::from(face.ascender()) * scale;
let descent = f32::from(face.descender()) * scale; let mut left = f32::INFINITY;
let mut right = f32::NEG_INFINITY;
for glyph in glyphs {
let adv = face
.glyph_hor_advance(ttf_parser::GlyphId(glyph.glyph_id))
.map_or(0.0, |a| f32::from(a) * scale);
left = left.min(glyph.dx);
right = right.max(glyph.dx + adv);
}
if left.is_finite() && right.is_finite() {
res.links.push(LinkAnnot {
x0: x + f64::from(left),
y0: y - f64::from(ascent),
x1: x + f64::from(right),
y1: y - f64::from(descent),
url: url.to_owned(),
});
}
}
}
fn emit_outline_run(
content: &mut Content,
res: &mut PageResources,
face: &ttf_parser::Face<'_>,
units_per_em: u16,
run: GlyphRun<'_>,
) {
let GlyphRun {
x,
y,
font_size,
color,
glyphs,
..
} = run;
let scale = font_size / f32::from(units_per_em);
content.save_state();
apply_alpha(content, res, color);
color::set_fill(content, color);
let mut any = false;
for glyph in glyphs {
if face
.glyph_raster_image(ttf_parser::GlyphId(glyph.glyph_id), font_size as u16)
.is_some()
{
continue;
}
let origin_x = x as f32 + glyph.dx;
let baseline_y = y as f32 + glyph.dy;
let mut pen = GlyphPen::new(content, origin_x, baseline_y, scale);
if face
.outline_glyph(ttf_parser::GlyphId(glyph.glyph_id), &mut pen)
.is_some()
{
any = true;
}
}
if any {
content.fill_nonzero();
} else {
content.end_path();
}
content.restore_state();
}