diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-10-10 13:59:00 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-10 11:59:00 +0000 |
| commit | 6257e4d6cf060f367c3f2eb2d005a091b6432e88 (patch) | |
| tree | 9ae46a48c3b3d4c90d3cfc9da77df5951e942fa4 /crates/typst-pdf | |
| parent | 9ee80762a55c0d173664d9ac33cb762b79341a96 (diff) | |
More robust glyph drawing (#5159)
Diffstat (limited to 'crates/typst-pdf')
| -rw-r--r-- | crates/typst-pdf/src/color_font.rs | 44 | ||||
| -rw-r--r-- | crates/typst-pdf/src/content.rs | 85 |
2 files changed, 76 insertions, 53 deletions
diff --git a/crates/typst-pdf/src/color_font.rs b/crates/typst-pdf/src/color_font.rs index 5182a059..f6fea396 100644 --- a/crates/typst-pdf/src/color_font.rs +++ b/crates/typst-pdf/src/color_font.rs @@ -12,10 +12,11 @@ use indexmap::IndexMap; use pdf_writer::types::UnicodeCmap; use pdf_writer::writers::WMode; use pdf_writer::{Filter, Finish, Name, Rect, Ref}; -use typst::diag::SourceResult; +use typst::diag::{bail, error, SourceDiagnostic, SourceResult}; +use typst::foundations::Repr; use typst::layout::Em; -use typst::text::color::frame_for_glyph; -use typst::text::Font; +use typst::text::color::glyph_frame; +use typst::text::{Font, Glyph, TextItemView}; use crate::content; use crate::font::{base_font_name, write_font_descriptor, CMAP_NAME, SYSTEM_INFO}; @@ -211,9 +212,10 @@ impl ColorFontMap<()> { pub fn get( &mut self, options: &PdfOptions, - font: &Font, - gid: u16, + text: &TextItemView, + glyph: &Glyph, ) -> SourceResult<(usize, u8)> { + let font = &text.item.font; let color_font = self.map.entry(font.clone()).or_insert_with(|| { let global_bbox = font.ttf().global_bounding_box(); let bbox = Rect::new( @@ -230,7 +232,7 @@ impl ColorFontMap<()> { } }); - Ok(if let Some(index_of_glyph) = color_font.glyph_indices.get(&gid) { + Ok(if let Some(index_of_glyph) = color_font.glyph_indices.get(&glyph.id) { // If we already know this glyph, return it. (color_font.slice_ids[index_of_glyph / 256], *index_of_glyph as u8) } else { @@ -242,9 +244,13 @@ impl ColorFontMap<()> { self.total_slice_count += 1; } - let frame = frame_for_glyph(font, gid); - let width = - font.advance(gid).unwrap_or(Em::new(0.0)).get() * font.units_per_em(); + let (frame, tofu) = glyph_frame(font, glyph.id); + if options.standards.pdfa && tofu { + bail!(failed_to_convert(text, glyph)); + } + + let width = font.advance(glyph.id).unwrap_or(Em::new(0.0)).get() + * font.units_per_em(); let instructions = content::build( options, &mut self.resources, @@ -252,8 +258,8 @@ impl ColorFontMap<()> { None, Some(width as f32), )?; - color_font.glyphs.push(ColorGlyph { gid, instructions }); - color_font.glyph_indices.insert(gid, index); + color_font.glyphs.push(ColorGlyph { gid: glyph.id, instructions }); + color_font.glyph_indices.insert(glyph.id, index); (color_font.slice_ids[index / 256], index as u8) }) @@ -321,3 +327,19 @@ pub struct ColorFontSlice { /// represent the subset of the TTF font we are interested in. pub subfont: usize, } + +/// The error when the glyph could not be converted. +#[cold] +fn failed_to_convert(text: &TextItemView, glyph: &Glyph) -> SourceDiagnostic { + let mut diag = error!( + glyph.span.0, + "the glyph for {} could not be exported", + text.glyph_text(glyph).repr() + ); + + if text.item.font.ttf().tables().cff2.is_some() { + diag.hint("CFF2 fonts are not currently supported"); + } + + diag +} diff --git a/crates/typst-pdf/src/content.rs b/crates/typst-pdf/src/content.rs index 79323b6a..babb3e57 100644 --- a/crates/typst-pdf/src/content.rs +++ b/crates/typst-pdf/src/content.rs @@ -10,15 +10,15 @@ use pdf_writer::types::{ }; use pdf_writer::writers::PositionedItems; use pdf_writer::{Content, Finish, Name, Rect, Str}; -use typst::diag::{bail, SourceResult}; +use typst::diag::{bail, error, SourceDiagnostic, SourceResult}; use typst::foundations::Repr; use typst::layout::{ Abs, Em, Frame, FrameItem, GroupItem, Point, Ratio, Size, Transform, }; use typst::model::Destination; use typst::syntax::Span; -use typst::text::color::is_color_glyph; -use typst::text::{Font, TextItem, TextItemView}; +use typst::text::color::should_outline; +use typst::text::{Font, Glyph, TextItem, TextItemView}; use typst::utils::{Deferred, Numeric, SliceExt}; use typst::visualize::{ FillRule, FixedStroke, Geometry, Image, LineCap, LineJoin, Paint, Path, PathItem, @@ -418,46 +418,27 @@ fn write_group(ctx: &mut Builder, pos: Point, group: &GroupItem) -> SourceResult /// Encode a text run into the content stream. fn write_text(ctx: &mut Builder, pos: Point, text: &TextItem) -> SourceResult<()> { - if ctx.options.standards.pdfa { - let last_resort = text.font.info().is_last_resort(); - for g in &text.glyphs { - if last_resort || g.id == 0 { - bail!( - g.span.0, - "the text {} could not be displayed with any font", - TextItemView::full(text).glyph_text(g).repr(), - ); - } - } - } - - let ttf = text.font.ttf(); - let tables = ttf.tables(); - - // If the text run contains either only color glyphs (used for emojis for - // example) or normal text we can render it directly - let has_color_glyphs = tables.sbix.is_some() - || tables.cbdt.is_some() - || tables.svg.is_some() - || tables.colr.is_some(); - if !has_color_glyphs { - write_normal_text(ctx, pos, TextItemView::full(text))?; - return Ok(()); + if ctx.options.standards.pdfa && text.font.info().is_last_resort() { + bail!( + Span::find(text.glyphs.iter().map(|g| g.span.0)), + "the text {} could not be displayed with any font", + &text.text, + ); } - let color_glyph_count = - text.glyphs.iter().filter(|g| is_color_glyph(&text.font, g)).count(); + let outline_glyphs = + text.glyphs.iter().filter(|g| should_outline(&text.font, g)).count(); - if color_glyph_count == text.glyphs.len() { - write_color_glyphs(ctx, pos, TextItemView::full(text))?; - } else if color_glyph_count == 0 { + if outline_glyphs == text.glyphs.len() { write_normal_text(ctx, pos, TextItemView::full(text))?; + } else if outline_glyphs == 0 { + write_complex_glyphs(ctx, pos, TextItemView::full(text))?; } else { - // Otherwise we need to split it in smaller text runs + // Otherwise we need to split it into smaller text runs. let mut offset = 0; let mut position_in_run = Abs::zero(); - for (color, sub_run) in - text.glyphs.group_by_key(|g| is_color_glyph(&text.font, g)) + for (should_outline, sub_run) in + text.glyphs.group_by_key(|g| should_outline(&text.font, g)) { let end = offset + sub_run.len(); @@ -468,11 +449,12 @@ fn write_text(ctx: &mut Builder, pos: Point, text: &TextItem) -> SourceResult<() let pos = pos + Point::new(position_in_run, Abs::zero()); position_in_run += text_item_view.width(); offset = end; - // Actually write the sub text-run - if color { - write_color_glyphs(ctx, pos, text_item_view)?; - } else { + + // Actually write the sub text-run. + if should_outline { write_normal_text(ctx, pos, text_item_view)?; + } else { + write_complex_glyphs(ctx, pos, text_item_view)?; } } } @@ -534,6 +516,10 @@ fn write_normal_text( // Write the glyphs with kerning adjustments. for glyph in text.glyphs() { + if ctx.options.standards.pdfa && glyph.id == 0 { + bail!(tofu(&text, glyph)); + } + adjustment += glyph.x_offset; if !adjustment.is_zero() { @@ -596,7 +582,7 @@ fn show_text(items: &mut PositionedItems, encoded: &[u8]) { } /// Encodes a text run made only of color glyphs into the content stream -fn write_color_glyphs( +fn write_complex_glyphs( ctx: &mut Builder, pos: Point, text: TextItemView, @@ -621,12 +607,17 @@ fn write_color_glyphs( .or_default(); for glyph in text.glyphs() { + if ctx.options.standards.pdfa && glyph.id == 0 { + bail!(tofu(&text, glyph)); + } + // Retrieve the Type3 font reference and the glyph index in the font. let color_fonts = ctx .resources .color_fonts .get_or_insert_with(|| Box::new(ColorFontMap::new())); - let (font, index) = color_fonts.get(ctx.options, &text.item.font, glyph.id)?; + + let (font, index) = color_fonts.get(ctx.options, &text, glyph)?; if last_font != Some(font) { ctx.content.set_font( @@ -824,3 +815,13 @@ fn to_pdf_line_join(join: LineJoin) -> LineJoinStyle { LineJoin::Bevel => LineJoinStyle::BevelJoin, } } + +/// The error when there is a tofu glyph. +#[cold] +fn tofu(text: &TextItemView, glyph: &Glyph) -> SourceDiagnostic { + error!( + glyph.span.0, + "the text {} could not be displayed with any font", + text.glyph_text(glyph).repr(), + ) +} |
