summaryrefslogtreecommitdiff
path: root/crates/typst-pdf
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2024-10-10 13:59:00 +0200
committerGitHub <noreply@github.com>2024-10-10 11:59:00 +0000
commit6257e4d6cf060f367c3f2eb2d005a091b6432e88 (patch)
tree9ae46a48c3b3d4c90d3cfc9da77df5951e942fa4 /crates/typst-pdf
parent9ee80762a55c0d173664d9ac33cb762b79341a96 (diff)
More robust glyph drawing (#5159)
Diffstat (limited to 'crates/typst-pdf')
-rw-r--r--crates/typst-pdf/src/color_font.rs44
-rw-r--r--crates/typst-pdf/src/content.rs85
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(),
+ )
+}