diff --git a/Cargo.lock b/Cargo.lock index 49ad84eb..f1037bcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -433,6 +433,9 @@ name = "float-cmp" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] [[package]] name = "flume" @@ -740,6 +743,7 @@ dependencies = [ "cosmic-text", "difference", "flate2", + "float-cmp", "fontdb", "image 0.25.2", "miniz_oxide", diff --git a/Cargo.toml b/Cargo.toml index 582a04ec..4992e7ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ parley = {git = "https://github.com/linebender/parley", rev = "66979c5"} flate2 = "1.0.30" fontdb = "0.20.0" yoke = { version = "0.7.4", features = ["derive"] } +float-cmp = "0.9.0" [patch.crates-io] tiny-skia-path = {git = "https://github.com/RazrFalcon/tiny-skia", rev="eec0a47"} diff --git a/src/font/cosmic_text.rs b/src/font/cosmic_text.rs index 39e1a81d..3df1415a 100644 --- a/src/font/cosmic_text.rs +++ b/src/font/cosmic_text.rs @@ -5,17 +5,18 @@ mod tests { use crate::serialize::SerializeSettings; use crate::stream::Glyph; + use crate::util::SliceExt; use crate::Fill; use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping}; use fontdb::Source; use skrifa::GlyphId; use std::sync::Arc; - #[test] #[ignore] + #[test] fn cosmic_text_integration() { let mut font_system = FontSystem::new_with_fonts([Source::Binary(Arc::new(std::fs::read("/Users/lstampfl/Programming/GitHub/resvg/crates/resvg/tests/fonts/NotoSans-Regular.ttf").unwrap()))]); - let metrics = Metrics::new(14.0, 20.0); + let metrics = Metrics::new(18.0, 20.0); let mut buffer = Buffer::new(&mut font_system, metrics); buffer.set_size(&mut font_system, Some(200.0), None); let attrs = Attrs::new(); @@ -24,7 +25,7 @@ mod tests { buffer.shape_until_scroll(&mut font_system, false); let page_size = tiny_skia_path::Size::from_wh(200.0, 400.0).unwrap(); - let mut document_builder = Document::new(SerializeSettings::default()); + let mut document_builder = Document::new(SerializeSettings::default_test()); let mut builder = document_builder.start_page(page_size); let mut surface = builder.surface(); @@ -33,21 +34,38 @@ mod tests { // Inspect the output runs for run in buffer.layout_runs() { let y_offset = run.line_y; - let glyphs = run + + let segmented = run .glyphs - .iter() - .map(|g| { - Glyph::new( - font_map.get(&g.font_id).unwrap().clone(), - GlyphId::new(g.glyph_id as u32), - g.w, - g.x_offset, - g.font_size, - g.start..g.end, - ) - }) - .collect::>(); - surface.draw_glyph_run(0.0, y_offset, Fill::::default(), &glyphs, run.text); + .group_by_key(|g| font_map.get(&g.font_id).unwrap().clone()); + + let mut x = 0.0; + for (font, glyphs) in segmented { + let start_x = x; + let glyphs = glyphs + .iter() + .map(|glyph| { + x += glyph.w; + Glyph::new( + GlyphId::new(glyph.glyph_id as u32), + glyph.w, + glyph.x_offset, + glyph.y_offset, + glyph.start..glyph.end, + glyph.font_size, + ) + }) + .collect::>(); + + surface.draw_glyph_run( + start_x, + y_offset, + Fill::::default(), + &glyphs, + font, + run.text, + ); + } } surface.finish(); diff --git a/src/font/mod.rs b/src/font/mod.rs index e7138d87..6c61af88 100644 --- a/src/font/mod.rs +++ b/src/font/mod.rs @@ -1,6 +1,8 @@ -use crate::serialize::SvgSettings; +use crate::serialize::{Object, SerializerContext, SvgSettings}; use crate::surface::Surface; +use crate::type3_font::Type3ID; use crate::util::Prehashed; +use pdf_writer::{Chunk, Ref}; use skrifa::instance::Location; use skrifa::outline::OutlinePen; use skrifa::prelude::{LocationRef, Size}; @@ -63,7 +65,6 @@ pub struct FontInfo { pub(crate) units_per_em: u16, global_bbox: Rect, postscript_name: Option, - is_type3_font: bool, ascent: FiniteF32, descent: FiniteF32, cap_height: Option, @@ -136,17 +137,6 @@ impl FontInfo { } }; - // Right now, we decide whether to embed a font as a Type3 font solely based on whether one of these - // tables exist. This is not the most "efficient" method, because it is possible a font has a `COLR` table, - // but there are still some glyphs which are not in COLR but still in `glyf` or `CFF`. In this case, - // we would still choose a Type3 font for the outlines, even though they could be embedded as a CID font. - // For now, we make the simplifying assumption that a font is either mapped to a series of Type3 fonts - // or to a single CID font, but not a mix of both. - let is_type3_font = font_ref.svg().is_ok() - || font_ref.colr().is_ok() - || font_ref.sbix().is_ok() - || font_ref.cff2().is_ok(); - Some(FontInfo { index, checksum, @@ -160,7 +150,6 @@ impl FontInfo { weight, italic_angle, global_bbox, - is_type3_font, }) } } @@ -175,6 +164,15 @@ impl Font { data: Arc + Send + Sync>, index: u32, location: Location, + ) -> Option { + let font_info = FontInfo::new(data.as_ref().as_ref(), index, location)?; + + Font::new_with_info(data, Arc::new(font_info)) + } + + pub(crate) fn new_with_info( + data: Arc + Send + Sync>, + font_info: Arc, ) -> Option { let font_ref_yoke = Yoke::, Arc + Send + Sync>>::attach_to_cart( @@ -184,11 +182,9 @@ impl Font { }, ); - let font_info = FontInfo::new(data.as_ref().as_ref(), index, location)?; - Some(Font(Arc::new(Prehashed::new(Repr { font_ref_yoke, - font_info: Arc::new(font_info), + font_info, })))) } @@ -228,8 +224,8 @@ impl Font { self.0.font_info.italic_angle.get() } - pub fn units_per_em(&self) -> u16 { - self.0.font_info.units_per_em + pub fn units_per_em(&self) -> f32 { + self.0.font_info.units_per_em as f32 } pub fn bbox(&self) -> Rect { @@ -240,10 +236,6 @@ impl Font { (&self.0.font_info.location).into() } - pub fn is_type3_font(&self) -> bool { - self.0.font_info.is_type3_font - } - pub fn font_ref(&self) -> &FontRef { &self.0.font_ref_yoke.get().font_ref } @@ -288,6 +280,33 @@ pub fn draw_glyph( glyph_type } +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +pub struct CIDIdentifer(pub Font); +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +pub struct Type3Identifier(pub Font, pub Type3ID); + +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +pub enum FontIdentifier { + Cid(CIDIdentifer), + Type3(Type3Identifier), +} + +impl FontIdentifier { + pub fn font(&self) -> Font { + match self { + FontIdentifier::Cid(cid) => cid.0.clone(), + FontIdentifier::Type3(t3) => t3.0.clone(), + } + } +} + +// TODO: Remove? +impl Object for FontIdentifier { + fn serialize_into(&self, _: &mut SerializerContext, _: Ref) -> Chunk { + unreachable!() + } +} + #[cfg(test)] fn draw(font_data: Arc>, glyphs: Option>, name: &str) { use crate::document::Document; @@ -354,14 +373,8 @@ fn draw(font_data: Arc>, glyphs: Option>, name: & 0.0, 0.0, crate::Fill::::default(), - &[Glyph::new( - font.clone(), - i, - 0.0, - 0.0, - size as f32, - 0..text.len(), - )], + &[Glyph::new(i, 0.0, 0.0, 0.0, 0..text.len(), size as f32)], + font.clone(), &text, ); // let res = single_glyph(&font, GlyphId::new(i), &mut builder); diff --git a/src/font/simple_shape.rs b/src/font/simple_shape.rs index f52431f4..1cff662d 100644 --- a/src/font/simple_shape.rs +++ b/src/font/simple_shape.rs @@ -27,6 +27,24 @@ mod tests { Direction::LeftToRight, 14.0, ), + ( + "DejaVuSansMono.ttf", + "Here with a mono font, some longer text.", + Direction::LeftToRight, + 16.0, + ), + ( + "NotoSans-Regular.ttf", + "z͈̤̭͖̉͑́a̳ͫ́̇͑̽͒ͯlͨ͗̍̀̍̔̀ģ͔̫̫̄o̗̠͔̦̳͆̏̓͢", + Direction::LeftToRight, + 14.0, + ), + ( + "NotoSans-Regular.ttf", + " birth\u{ad}day ", + Direction::LeftToRight, + 14.0, + ), ( "NotoSansCJKsc-Regular.otf", "你好世界,这是一段很长的测试文章", @@ -40,7 +58,7 @@ mod tests { 14.0, ), ]; - let page_size = tiny_skia_path::Size::from_wh(200.0, 200.0).unwrap(); + let page_size = tiny_skia_path::Size::from_wh(200.0, 300.0).unwrap(); let mut document_builder = Document::new(SerializeSettings::default_test()); let mut builder = document_builder.start_page(page_size); let mut surface = builder.surface(); @@ -48,14 +66,10 @@ mod tests { for (font, text, dir, size) in data { let font_data = load_font(font); let font = Font::new(Arc::new(font_data), 0, Location::default()).unwrap(); - let glyphs = simple_shape(text, dir, font, size); - - // eprintln!("{:?}", glyphs.iter().map(|g| g.glyph_id).collect::>()); - // panic!(); - - surface.draw_glyph_run(0.0, y, Fill::::default(), &glyphs, text); + let glyphs = simple_shape(text, dir, font.clone(), size); + surface.draw_glyph_run(0.0, y, Fill::::default(), &glyphs, font, text); - y += size * 1.5; + y += size * 2.0; } surface.finish(); diff --git a/src/lib.rs b/src/lib.rs index ef8b1303..7a5136bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,12 +159,12 @@ pub(crate) mod test_utils { .map_or(text.len(), |info| info.cluster as usize); glyphs.push(Glyph::new( - font.clone(), GlyphId::new(start_info.glyph_id), (pos.x_advance as f32 / font.units_per_em() as f32) * size, (pos.x_offset as f32 / font.units_per_em() as f32) * size, - size, + (pos.y_offset as f32 / font.units_per_em() as f32) * size, start..end, + size, )); } diff --git a/src/object/cid_font.rs b/src/object/cid_font.rs index 8cf046ca..88c6dbab 100644 --- a/src/object/cid_font.rs +++ b/src/object/cid_font.rs @@ -1,4 +1,4 @@ -use crate::font::Font; +use crate::font::{CIDIdentifer, Font, FontIdentifier}; use crate::serialize::{Object, SerializerContext, SipHashable}; use crate::util::{RectExt, SliceExt}; use pdf_writer::types::{CidFontType, FontFlags, SystemInfo, UnicodeCmap}; @@ -16,28 +16,31 @@ const SYSTEM_INFO: SystemInfo = SystemInfo { supplement: 0, }; +pub type Cid = u16; + /// A CID-keyed font. #[derive(Debug, Clone)] pub struct CIDFont { /// The _actual_ underlying font of the CID-keyed font. font: Font, - /// A mapper that maps the original glyph IDs to their corresponding glyph ID in the font + /// A mapper that maps GIDs from the original font to CIDs, i.e. the corresponding GID in the font /// subset. The subsetter will ensure that for CID-keyed CFF fonts, the CID-to-GID mapping /// will be the identity mapping, regardless of what the mapping was in the original font. This - /// allows us to index both font types transparently using GIDs instead of having to distinguish - /// according to the underlying font. See the PDF reference for more information on how glyphs - /// are indexed in a CID-keyed font. + /// allows us to index both, CFF and glyf-based fonts, transparently using GIDs, + /// instead of having to distinguish according to the underlying font. See section + /// 9.7.4.2 for more information on how glyphs are indexed in a CID-keyed font. glyph_remapper: GlyphRemapper, - /// A mapping from glyph IDs to their string in the original text. + /// A mapping from CIDs to their string in the original text. cmap_entries: BTreeMap, - /// The widths of the glyphs, _index by their new GID_. + /// The widths of the glyphs, _index by their CID_. widths: Vec, } impl CIDFont { - /// Create a new CID font from a font. + /// Create a new CID-keyed font. pub fn new(font: Font) -> CIDFont { - // Always include the .notdef glyph. Will also always be included by the subsetter. + // Always include the .notdef glyph. Will also always be included by the subsetter in + // the glyph remapper. let widths = vec![font.advance_width(GlyphId::new(0)).unwrap_or(0.0)]; Self { @@ -48,26 +51,27 @@ impl CIDFont { } } - /// Get the advance width of a glyph, _indexed by the new GID from the subsetted font_, - /// in PDF font units. - pub fn advance_width(&self, glyph_id: u16) -> Option { - self.widths - .get(glyph_id as usize) - .map(|v| self.to_pdf_font_units(*v)) + pub fn font(&self) -> Font { + self.font.clone() } - /// Rescale a value from the original text-space units to PDF font units. - /// Fonts in PDF are processed with a upem of 1000, see section 9.4.4 of the spec. pub fn to_pdf_font_units(&self, val: f32) -> f32 { - val / self.font.units_per_em() as f32 * 1000.0 + val / self.font.units_per_em() * 1000.0 + } + + pub fn get_cid(&self, glyph_id: GlyphId) -> Option { + self.glyph_remapper + .get(u16::try_from(glyph_id.to_u32()).unwrap()) } - /// Register a glyph and return its glyph ID in the subsetted version of the font. - pub fn add_glyph(&mut self, glyph_id: GlyphId) -> GlyphId { - let new_id = GlyphId::new(self.glyph_remapper.remap(glyph_id.to_u32() as u16) as u32); + /// Add a new glyph (if it has not already been added) and return its CID. + pub fn add_glyph(&mut self, glyph_id: GlyphId) -> Cid { + let new_id = self + .glyph_remapper + .remap(u16::try_from(glyph_id.to_u32()).unwrap()); // This means that the glyph ID has been newly assigned, and thus we need to add its width. - if new_id.to_u32() >= self.widths.len() as u32 { + if new_id as usize >= self.widths.len() { self.widths .push(self.font.advance_width(glyph_id).unwrap_or(0.0)); } @@ -75,8 +79,16 @@ impl CIDFont { new_id } - pub fn set_cmap_entry(&mut self, glyph_id: u16, text: String) { - self.cmap_entries.insert(glyph_id, text); + pub fn get_codepoints(&self, cid: Cid) -> Option<&str> { + self.cmap_entries.get(&cid).map(|s| s.as_str()) + } + + pub fn set_codepoints(&mut self, cid: Cid, text: String) { + self.cmap_entries.insert(cid, text); + } + + pub fn identifier(&self) -> FontIdentifier { + FontIdentifier::Cid(CIDIdentifer(self.font.clone())) } } @@ -253,8 +265,12 @@ mod tests { let mut sc = sc(); let font_data = Arc::new(load_font("NotoSans-Regular.ttf")); let font = Font::new(font_data, 0, Location::default()).unwrap(); - sc.map_glyph(font.clone(), GlyphId::new(36)); - sc.map_glyph(font.clone(), GlyphId::new(37)); + sc.create_or_get_font_container(font.clone()) + .borrow_mut() + .add_glyph(GlyphId::new(36)); + sc.create_or_get_font_container(font.clone()) + .borrow_mut() + .add_glyph(GlyphId::new(37)); check_snapshot("cid_font/noto_sans_two_glyphs", sc.finish().as_bytes()); } @@ -263,10 +279,18 @@ mod tests { let mut sc = sc(); let font_data = Arc::new(load_font("LatinModernRoman-Regular.otf")); let font = Font::new(font_data, 0, Location::default()).unwrap(); - sc.map_glyph(font.clone(), GlyphId::new(58)); - sc.map_glyph(font.clone(), GlyphId::new(54)); - sc.map_glyph(font.clone(), GlyphId::new(69)); - sc.map_glyph(font.clone(), GlyphId::new(71)); + sc.create_or_get_font_container(font.clone()) + .borrow_mut() + .add_glyph(GlyphId::new(58)); + sc.create_or_get_font_container(font.clone()) + .borrow_mut() + .add_glyph(GlyphId::new(54)); + sc.create_or_get_font_container(font.clone()) + .borrow_mut() + .add_glyph(GlyphId::new(69)); + sc.create_or_get_font_container(font.clone()) + .borrow_mut() + .add_glyph(GlyphId::new(71)); check_snapshot("cid_font/latin_modern_four_glyphs", sc.finish().as_bytes()); } } diff --git a/src/object/type3_font.rs b/src/object/type3_font.rs index 3f97581a..dd9457e6 100644 --- a/src/object/type3_font.rs +++ b/src/object/type3_font.rs @@ -1,5 +1,5 @@ use crate::font; -use crate::font::{Font, GlyphType}; +use crate::font::{Font, FontIdentifier, GlyphType, Type3Identifier}; use crate::object::xobject::XObject; use crate::resource::{Resource, ResourceDictionaryBuilder, XObjectResource}; use crate::serialize::{Object, SerializerContext}; @@ -11,14 +11,17 @@ use skrifa::GlyphId; use std::collections::{BTreeMap, BTreeSet}; use tiny_skia_path::{Rect, Transform}; +pub type Gid = u8; + // TODO: Add FontDescriptor, required for Tagged PDF #[derive(Debug)] pub struct Type3Font { font: Font, glyphs: Vec, widths: Vec, - cmap_entries: BTreeMap, + cmap_entries: BTreeMap, glyph_set: BTreeSet, + index: usize, } const CMAP_NAME: Name = Name(b"Custom"); @@ -29,17 +32,18 @@ const SYSTEM_INFO: SystemInfo = SystemInfo { }; impl Type3Font { - pub fn new(font: Font) -> Self { + pub fn new(font: Font, index: usize) -> Self { Self { font, glyphs: Vec::new(), cmap_entries: BTreeMap::new(), widths: Vec::new(), glyph_set: BTreeSet::new(), + index, } } - pub fn to_font_units(&self, val: f32) -> f32 { + pub fn to_pdf_font_units(&self, val: f32) -> f32 { val } @@ -55,7 +59,7 @@ impl Type3Font { self.glyph_set.contains(&glyph) } - pub fn get_glyph(&mut self, glyph_id: GlyphId) -> Option { + pub fn get_gid(&self, glyph_id: GlyphId) -> Option { self.glyphs .iter() .position(|g| *g == glyph_id) @@ -63,11 +67,12 @@ impl Type3Font { } pub fn add_glyph(&mut self, glyph_id: GlyphId) -> u8 { - if let Some(pos) = self.get_glyph(glyph_id) { + if let Some(pos) = self.get_gid(glyph_id) { return pos; } else { assert!(self.glyphs.len() < 256); + self.glyph_set.insert(glyph_id); self.glyphs.push(glyph_id); self.widths .push(self.font.advance_width(glyph_id).unwrap_or(0.0)); @@ -75,19 +80,20 @@ impl Type3Font { } } - pub fn set_cmap_entry(&mut self, glyph_id: u8, text: String) { - self.cmap_entries.insert(glyph_id, text); + pub fn get_codepoints(&self, gid: Gid) -> Option<&str> { + self.cmap_entries.get(&gid).map(|s| s.as_str()) + } + + pub fn set_codepoints(&mut self, gid: Gid, text: String) { + self.cmap_entries.insert(gid, text); } - pub fn units_per_em(&self) -> u16 { - self.font.units_per_em() + pub fn font(&self) -> Font { + self.font.clone() } - pub fn advance_width(&self, glyph_id: u8) -> Option { - self.widths - .get(glyph_id as usize) - .copied() - .map(|n| self.to_font_units(n)) + pub fn identifier(&self) -> FontIdentifier { + FontIdentifier::Type3(Type3Identifier(self.font.clone(), self.index)) } } @@ -193,8 +199,8 @@ impl Object for Type3Font { type3_font.to_unicode(cmap_ref); type3_font.matrix( Transform::from_scale( - 1.0 / (self.font.units_per_em() as f32), - 1.0 / (self.font.units_per_em() as f32), + 1.0 / (self.font.units_per_em()), + 1.0 / (self.font.units_per_em()), ) .to_pdf_transform(), ); @@ -235,3 +241,79 @@ impl Object for Type3Font { chunk } } + +pub type Type3ID = usize; + +#[derive(Debug)] +pub struct Type3FontMapper { + font: Font, + fonts: Vec, +} + +impl Type3FontMapper { + pub fn new(font: Font) -> Type3FontMapper { + Self { + font, + fonts: Vec::new(), + } + } +} + +impl Type3FontMapper { + pub fn id_from_glyph(&self, glyph_id: GlyphId) -> Option { + self.fonts + .iter() + .position(|f| f.covers(glyph_id)) + .map(|p| self.fonts[p].identifier()) + } + + pub fn font_from_id(&self, identifier: FontIdentifier) -> Option<&Type3Font> { + let pos = self + .fonts + .iter() + .position(|f| f.identifier() == identifier)?; + self.fonts.get(pos) + } + + pub fn font_mut_from_id(&mut self, identifier: FontIdentifier) -> Option<&mut Type3Font> { + let pos = self + .fonts + .iter() + .position(|f| f.identifier() == identifier)?; + self.fonts.get_mut(pos) + } + + pub fn fonts(&self) -> &[Type3Font] { + &self.fonts + } + + pub fn add_glyph(&mut self, glyph_id: GlyphId) -> (FontIdentifier, Gid) { + if let Some(id) = self.id_from_glyph(glyph_id) { + let gid = self + .font_from_id(id.clone()) + .unwrap() + .get_gid(glyph_id) + .unwrap(); + return (id, gid); + } + + if let Some(last_font) = self.fonts.last_mut() { + if last_font.is_full() { + let mut font = Type3Font::new(self.font.clone(), self.fonts.len()); + let id = font.identifier(); + let gid = font.add_glyph(glyph_id); + self.fonts.push(font); + (id, gid) + } else { + let id = last_font.identifier(); + (id, last_font.add_glyph(glyph_id)) + } + } else { + let mut font = Type3Font::new(self.font.clone(), self.fonts.len()); + let id = font.identifier(); + let gid = font.add_glyph(glyph_id); + self.fonts.push(font); + (id, gid) + } + } +} diff --git a/src/resource.rs b/src/resource.rs index c3416813..c6b2b8d5 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,4 +1,4 @@ -use crate::font::Font; +use crate::font::FontIdentifier; use crate::object::color_space::device_cmyk::DeviceCmyk; use crate::object::color_space::luma::{DeviceGray, SGray}; use crate::object::color_space::rgb::{DeviceRgb, Srgb}; @@ -58,7 +58,7 @@ pub(crate) enum Resource { ExtGState(ExtGState), ColorSpace(ColorSpaceEnum), Shading(ShadingFunction), - Font(FontResource), + Font(FontIdentifier), } impl Object for Resource { @@ -137,7 +137,7 @@ pub(crate) struct ResourceDictionaryBuilder { pub patterns: ResourceMapper, pub x_objects: ResourceMapper, pub shadings: ResourceMapper, - pub fonts: ResourceMapper, + pub fonts: ResourceMapper, } impl ResourceDictionaryBuilder { @@ -178,7 +178,7 @@ impl ResourceDictionaryBuilder { self.shadings.remap_with_name(shading) } - fn register_font(&mut self, font: FontResource) -> String { + fn register_font(&mut self, font: FontIdentifier) -> String { self.fonts.remap_with_name(font) } @@ -212,7 +212,7 @@ pub(crate) struct ResourceDictionary { pub patterns: ResourceList, pub x_objects: ResourceList, pub shadings: ResourceList, - pub fonts: ResourceList, + pub fonts: ResourceList, } impl ResourceDictionary { @@ -344,25 +344,6 @@ where pub type ResourceNumber = u32; -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub struct FontResource { - pub font: Font, - pub pdf_index: usize, -} - -impl FontResource { - pub fn new(font: Font, pdf_index: usize) -> Self { - Self { font, pdf_index } - } -} - -impl Object for FontResource { - fn serialize_into(&self, _: &mut SerializerContext, _: Ref) -> Chunk { - // Fonts are written manually by the serializer in the end, so this should never be called. - unreachable!() - } -} - #[derive(Debug, Eq, PartialEq, Hash, Clone)] pub enum ColorSpaceEnum { Srgb(Srgb), @@ -386,9 +367,9 @@ impl Object for ColorSpaceEnum { impl RegisterableObject for ColorSpaceEnum {} -impl RegisterableObject for FontResource {} +impl RegisterableObject for FontIdentifier {} -impl ResourceTrait for FontResource { +impl ResourceTrait for FontIdentifier { fn get_dict<'a>(resources: &'a mut Resources) -> Dict<'a> { resources.fonts() } diff --git a/src/serialize.rs b/src/serialize.rs index 7296cc84..db86507f 100644 --- a/src/serialize.rs +++ b/src/serialize.rs @@ -1,12 +1,12 @@ -use crate::font::{Font, FontInfo}; +use crate::font::{Font, FontIdentifier, FontInfo}; use crate::object::cid_font::CIDFont; use crate::object::color_space::luma::SGray; use crate::object::color_space::rgb::Srgb; use crate::object::color_space::{DEVICE_GRAY, DEVICE_RGB}; use crate::object::outline::Outline; use crate::object::page::{Page, PageLabelContainer}; -use crate::object::type3_font::Type3Font; -use crate::resource::{ColorSpaceEnum, FontResource}; +use crate::object::type3_font::Type3FontMapper; +use crate::resource::ColorSpaceEnum; use crate::stream::PdfFont; use crate::util::NameExt; use fontdb::{Database, ID}; @@ -15,9 +15,11 @@ use siphasher::sip128::{Hasher128, SipHasher13}; use skrifa::instance::Location; use skrifa::GlyphId; use std::borrow::Cow; +use std::cell::RefCell; use std::collections::HashMap; use std::hash::Hash; use std::sync::Arc; +use skrifa::raw::TableProvider; use tiny_skia_path::Rect; #[derive(Copy, Clone, Debug)] @@ -40,6 +42,7 @@ pub struct SerializeSettings { pub ascii_compatible: bool, pub compress_content_streams: bool, pub no_device_cs: bool, + pub force_type3_fonts: bool, pub svg_settings: SvgSettings, } @@ -50,6 +53,7 @@ impl SerializeSettings { ascii_compatible: true, compress_content_streams: false, no_device_cs: false, + force_type3_fonts: false, svg_settings: SvgSettings::default(), } } @@ -61,6 +65,7 @@ impl Default for SerializeSettings { ascii_compatible: true, compress_content_streams: true, no_device_cs: false, + force_type3_fonts: false, svg_settings: SvgSettings::default(), } } @@ -84,7 +89,7 @@ pub trait RegisterableObject: Object + SipHashable {} pub struct SerializerContext { font_cache: HashMap, Font>, - font_map: HashMap, + font_map: HashMap>, catalog_ref: Ref, page_tree_ref: Ref, page_labels_ref: Option, @@ -99,6 +104,7 @@ pub struct SerializerContext { pub serialize_settings: SerializeSettings, } +#[derive(Clone, Copy)] pub enum PDFGlyph { Type3(u8), CID(u16), @@ -111,6 +117,16 @@ impl PDFGlyph { PDFGlyph::CID(n) => *n, } } + + pub fn encode_into(&self, slice: &mut Vec) { + match self { + PDFGlyph::Type3(cg) => slice.push(*cg), + PDFGlyph::CID(cid) => { + slice.push((cid >> 8) as u8); + slice.push((cid & 0xff) as u8); + } + } + } } impl SerializerContext { @@ -194,6 +210,36 @@ impl SerializerContext { } } + pub fn create_or_get_font_container(&mut self, font: Font) -> &RefCell { + self.font_map.entry(font.clone()).or_insert_with(|| { + self.font_cache + .insert(font.font_info().clone(), font.clone()); + + // Right now, we decide whether to embed a font as a Type3 font + // solely based on whether one of these tables exist (or if + // the settings tell us to force it). This is not the most "efficient" + // method, because it is possible a font has a `COLR` table, but + // there are still some glyphs which are not in COLR but in `glyf` + // or `CFF`. In this case, we would still choose a Type3 font for + // the outlines, even though they could be embedded as a CID font. + // For now, we make the simplifying assumption that a font is either mapped + // to a series of Type3 fonts or to a single CID font, but not a mix of both. + let font_ref = font.font_ref(); + let use_type3 = self.serialize_settings.force_type3_fonts + || font_ref.svg().is_ok() + || font_ref.colr().is_ok() + || font_ref.sbix().is_ok() + || font_ref.cff2().is_ok(); + + if use_type3 { + RefCell::new(FontContainer::Type3(Type3FontMapper::new(font.clone()))) + } else { + RefCell::new(FontContainer::CIDFont(CIDFont::new(font.clone()))) + } + }) + } + + // TODO: Don't use generics here pub fn add_font(&mut self, object: T) -> Ref where T: RegisterableObject, @@ -208,41 +254,6 @@ impl SerializerContext { } } - pub(crate) fn font_container_mut(&mut self, font: Font) -> &mut FontContainer { - self.font_map.entry(font.clone()).or_insert_with(|| { - self.font_cache - .insert(font.font_info().clone(), font.clone()); - - if font.is_type3_font() { - FontContainer::Type3(Type3FontMapper::new(font.clone())) - } else { - FontContainer::CIDFont(CIDFont::new(font.clone())) - } - }) - } - - pub fn map_glyph(&mut self, font: Font, glyph_id: GlyphId) -> (FontResource, PDFGlyph) { - let font_container = self.font_container_mut(font.clone()); - - match font_container { - FontContainer::Type3(font_mapper) => { - let (pdf_index, glyph_id) = font_mapper.add_glyph(glyph_id); - - ( - FontResource::new(font, pdf_index), - PDFGlyph::Type3(glyph_id), - ) - } - FontContainer::CIDFont(cid) => { - let new_gid = cid.add_glyph(glyph_id); - ( - FontResource::new(font, 0), - PDFGlyph::CID(new_gid.to_u32() as u16), - ) - } - } - } - pub fn convert_fontdb(&mut self, db: &mut Database, ids: Option>) -> HashMap { let mut map = HashMap::new(); @@ -260,12 +271,13 @@ impl SerializerContext { if let Some(font_info) = FontInfo::new(font_data.as_ref().as_ref(), index, location.clone()) { + let font_info = Arc::new(font_info); // TODO: Prevent font info from being computed twice? let font = self .font_cache - .get(&Arc::new(font_info)) + .get(&font_info.clone()) .cloned() - .unwrap_or(Font::new(font_data, index, location).unwrap()); + .unwrap_or(Font::new_with_info(font_data, font_info).unwrap()); map.insert(id, font); } } @@ -274,13 +286,6 @@ impl SerializerContext { map } - pub fn get_pdf_font(&self, font_resource: &FontResource) -> Option { - self.font_map.get(&font_resource.font).map(|f| match f { - FontContainer::Type3(fm) => PdfFont::Type3(&fm.fonts[font_resource.pdf_index]), - FontContainer::CIDFont(cid) => PdfFont::CID(cid), - }) - } - fn push_chunk(&mut self, chunk: Chunk) { self.chunks_len += chunk.len(); self.chunks.push(chunk); @@ -326,17 +331,17 @@ impl SerializerContext { // Write fonts // TODO: Make more efficient let fonts = std::mem::take(&mut self.font_map); - for (font, font_container) in fonts { - match font_container { + for font_container in fonts.values() { + match &*font_container.borrow() { FontContainer::Type3(font_mapper) => { - for (pdf_index, mapper) in font_mapper.fonts.into_iter().enumerate() { - let ref_ = self.add_font(FontResource::new(font.clone(), pdf_index)); - let chunk = mapper.serialize_into(&mut self, ref_); + for t3_font in font_mapper.fonts() { + let ref_ = self.add_font(t3_font.identifier()); + let chunk = t3_font.serialize_into(&mut self, ref_); self.push_chunk(chunk) } } FontContainer::CIDFont(cid_font) => { - let ref_ = self.add_font(FontResource::new(font, 0)); + let ref_ = self.add_font(cid_font.identifier()); let chunk = cid_font.serialize_into(&mut self, ref_); self.push_chunk(chunk) } @@ -405,8 +410,8 @@ where #[derive(Copy, Clone)] pub enum CSWrapper { - Ref(pdf_writer::Ref), - Name(pdf_writer::Name<'static>), + Ref(Ref), + Name(Name<'static>), } impl pdf_writer::Primitive for CSWrapper { @@ -419,67 +424,74 @@ impl pdf_writer::Primitive for CSWrapper { } #[derive(Debug)] -pub(crate) enum FontContainer { +pub enum FontContainer { Type3(Type3FontMapper), CIDFont(CIDFont), } -#[derive(Debug)] -pub struct Type3FontMapper { - font: Font, - fonts: Vec, -} - -impl Type3FontMapper { - pub fn new(font: Font) -> Type3FontMapper { - Self { - font, - fonts: Vec::new(), +impl FontContainer { + pub fn font_identifier(&self, glyph_id: GlyphId) -> Option { + match self { + FontContainer::Type3(t3) => t3.id_from_glyph(glyph_id), + FontContainer::CIDFont(cid) => cid.get_cid(glyph_id).map(|_| cid.identifier()), } } -} -impl Type3FontMapper { - pub fn type_3_font(&mut self, glyph_id: GlyphId) -> Option<(&mut Type3Font, u8)> { - if let Some(index) = self.fonts.iter().position(|f| f.covers(glyph_id)) { - let glyph = self.fonts[index].get_glyph(glyph_id)?; - return Some((&mut self.fonts[index], glyph)); + pub fn get_from_identifier_mut( + &mut self, + font_identifier: FontIdentifier, + ) -> Option<&mut dyn PdfFont> { + match self { + FontContainer::Type3(t3) => { + if let Some(t3_font) = t3.font_mut_from_id(font_identifier) { + return Some(t3_font); + } else { + None + } + } + FontContainer::CIDFont(cid) => { + if cid.identifier() == font_identifier { + return Some(cid); + } else { + None + } + } } - - None } - pub fn set_cmap_entry(&mut self, glyph_id: GlyphId, text: String) -> Option<()> { - self.type_3_font(glyph_id) - .map(|(f, g)| f.set_cmap_entry(g, text)) - } - - pub fn add_glyph(&mut self, glyph_id: GlyphId) -> (usize, u8) { - if let Some(index) = self.fonts.iter().position(|f| f.covers(glyph_id)) { - return (index, self.fonts[index].add_glyph(glyph_id)); - } - - let glyph_id = if let Some(last_font) = self.fonts.last_mut() { - if last_font.is_full() { - let mut font = Type3Font::new(self.font.clone()); - let gid = font.add_glyph(glyph_id); - self.fonts.push(font); - gid - } else { - last_font.add_glyph(glyph_id) + pub fn get_from_identifier( + &self, + font_identifier: FontIdentifier, + ) -> Option<&dyn PdfFont> { + match self { + FontContainer::Type3(t3) => { + if let Some(t3_font) = t3.font_from_id(font_identifier) { + return Some(t3_font); + } else { + None + } } - } else { - let mut font = Type3Font::new(self.font.clone()); - let gid = font.add_glyph(glyph_id); - self.fonts.push(font); - gid - }; - - (self.fonts.len() - 1, glyph_id) + FontContainer::CIDFont(cid) => { + if cid.identifier() == font_identifier { + return Some(cid); + } else { + None + } + } + } } - pub fn index(&self) -> u32 { - self.font.index() + pub fn add_glyph(&mut self, glyph_id: GlyphId) -> (FontIdentifier, PDFGlyph) { + match self { + FontContainer::Type3(t3) => { + let (identifier, gid) = t3.add_glyph(glyph_id); + (identifier, PDFGlyph::Type3(gid)) + }, + FontContainer::CIDFont(cid_font) => { + let cid = cid_font.add_glyph(glyph_id); + (cid_font.identifier(), PDFGlyph::CID(cid)) + }, + } } } diff --git a/src/stream.rs b/src/stream.rs index 76b8da4e..6cca6385 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,4 +1,4 @@ -use crate::font::Font; +use crate::font::{Font, FontIdentifier}; use crate::graphics_state::GraphicsStates; use crate::object::cid_font::CIDFont; use crate::object::color_space::{Color, ColorSpace}; @@ -11,16 +11,17 @@ use crate::object::tiling_pattern::TilingPattern; use crate::object::type3_font::Type3Font; use crate::object::xobject::XObject; use crate::resource::{ - FontResource, PatternResource, Resource, ResourceDictionary, ResourceDictionaryBuilder, - XObjectResource, + PatternResource, Resource, ResourceDictionary, ResourceDictionaryBuilder, XObjectResource, }; use crate::serialize::{FontContainer, PDFGlyph, SerializerContext}; use crate::transform::TransformWrapper; use crate::util::{calculate_stroke_bbox, LineCapExt, LineJoinExt, NameExt, RectExt, TransformExt}; use crate::{Fill, FillRule, LineCap, LineJoin, Paint, Stroke}; +use float_cmp::approx_eq; use pdf_writer::types::TextRenderingMode; use pdf_writer::{Content, Finish, Name, Str, TextStr}; use skrifa::GlyphId; +use std::cell::RefCell; use std::ops::Range; use std::sync::Arc; use tiny_skia_path::{FiniteF32, NormalizedF32, Path, PathSegment, Rect, Size, Transform}; @@ -216,6 +217,7 @@ impl ContentBuilder { sc: &mut SerializerContext, fill: Fill, glyphs: &[Glyph], + font: Font, text: &str, ) { self.graphics_states.save_state(); @@ -239,6 +241,7 @@ impl ContentBuilder { ) }, glyphs, + font, text, ); @@ -252,6 +255,7 @@ impl ContentBuilder { sc: &mut SerializerContext, stroke: Stroke, glyphs: &[Glyph], + font: Font, text: &str, ) { self.graphics_states.save_state(); @@ -275,40 +279,29 @@ impl ContentBuilder { ) }, glyphs, + font, text, ); self.graphics_states.restore_state(); } - fn encode_single<'a>( + fn encode_consecutive_glyph_run( &mut self, cur_x: &mut f32, cur_y: f32, - cur_font: FontResource, - cur_size: f32, - text: &str, - actual_text: bool, - sc: &mut SerializerContext, - glyphs: &[Glyph], + font_identifier: FontIdentifier, + size: f32, + glyphs: &[InstanceGlyph], ) { let font_name = self .rd_builder - .register_resource(Resource::Font(cur_font.clone())); - self.content.set_font(font_name.to_pdf_name(), cur_size); + .register_resource(Resource::Font(font_identifier)); + self.content.set_font(font_name.to_pdf_name(), size); self.content.set_text_matrix( Transform::from_row(1.0, 0.0, 0.0, -1.0, *cur_x, cur_y).to_pdf_transform(), ); - if actual_text { - let mut actual_text = self - .content - .begin_marked_content_with_properties(Name(b"Span")); - actual_text - .properties() - .actual_text(TextStr(&text[glyphs[0].range.clone()])); - } - let mut positioned = self.content.show_positioned(); let mut items = positioned.items(); @@ -316,49 +309,28 @@ impl ContentBuilder { let mut encoded = vec![]; for glyph in glyphs { - let (_, gid) = sc.map_glyph(glyph.font.clone(), glyph.glyph_id); - - let font_container = sc.font_container_mut(glyph.font.clone()); - - match font_container { - FontContainer::Type3(t3) => { - t3.set_cmap_entry(glyph.glyph_id, text[glyph.range.clone()].to_string()); - } - FontContainer::CIDFont(cid) => { - cid.set_cmap_entry(gid.get(), text[glyph.range.clone()].to_string()) - } - } - - let font = sc.get_pdf_font(&cur_font).unwrap(); - - let actual_advance = glyph.x_advance / glyph.size * 1000.0; - adjustment += glyph.x_offset; - if adjustment != 0.0 { + // Make sure we don't write miniscule adjustments + if !approx_eq!(f32, adjustment, 0.0, epsilon = 0.001) { if !encoded.is_empty() { items.show(Str(&encoded)); encoded.clear(); } - items.adjust(font.to_font_units(-adjustment)); + items.adjust(-adjustment); adjustment = 0.0; } - match gid { - PDFGlyph::Type3(cg) => encoded.push(cg), - PDFGlyph::CID(cid) => { - encoded.push((cid >> 8) as u8); - encoded.push((cid & 0xff) as u8); - } - } + glyph.pdf_glyph.encode_into(&mut encoded); - if let Some(advance) = font.advance_width(gid.get()).map(|f| font.to_font_units(f)) { - adjustment += actual_advance - advance; + if let Some(font_advance) = glyph.font_advance { + adjustment += glyph.x_advance - font_advance; } adjustment -= glyph.x_offset; - *cur_x += glyph.x_advance; + // cur_x/cur_y and glyph metrics are in user space units, so don't convert here. + *cur_x += glyph.user_space_x_advance; } if !encoded.is_empty() { @@ -367,10 +339,6 @@ impl ContentBuilder { items.finish(); positioned.finish(); - - if actual_text { - self.content.end_marked_content(); - } } fn fill_stroke_glyph_run( @@ -381,6 +349,7 @@ impl ContentBuilder { text_rendering_mode: TextRenderingMode, action: impl FnOnce(&mut ContentBuilder, &mut SerializerContext), glyphs: &[Glyph], + font: Font, text: &str, ) { let mut cur_x = x; @@ -390,11 +359,35 @@ impl ContentBuilder { sb.content.begin_text(); sb.content.set_text_rendering_mode(text_rendering_mode); - let segmented = GroupByGlyphs::new(sc, glyphs).collect::>(); + let font_container = sc.create_or_get_font_container(font.clone()); + + let spanned = TextSpanner::new(glyphs, text, font_container); + + for fragment in spanned { + if let Some(text) = fragment.actual_text() { + let mut actual_text = sb + .content + .begin_marked_content_with_properties(Name(b"Span")); + actual_text.properties().actual_text(TextStr(text)); + } - for (size, font, actual_text, glyphs) in segmented { - sb.encode_single(&mut cur_x, y, font, size, text, actual_text, sc, glyphs) + let segmented = GlyphGrouper::new(font_container, fragment.glyphs()); + + for glyph_group in segmented { + sb.encode_consecutive_glyph_run( + &mut cur_x, + y - glyph_group.y_offset, + glyph_group.font_identifier, + glyph_group.size, + &glyph_group.glyphs, + ) + } + + if fragment.actual_text().is_some() { + sb.content.end_marked_content(); + } } + sb.content.end_text(); }) } @@ -730,110 +723,210 @@ impl ContentBuilder { #[derive(Debug, Clone)] pub struct Glyph { - pub font: Font, pub glyph_id: GlyphId, pub range: Range, pub x_advance: f32, pub x_offset: f32, + pub y_offset: f32, pub size: f32, } +pub struct InstanceGlyph { + pub pdf_glyph: PDFGlyph, + pub x_advance: f32, + pub user_space_x_advance: f32, + pub font_advance: Option, + pub x_offset: f32, +} + impl Glyph { pub fn new( - font: Font, glyph_id: GlyphId, x_advance: f32, x_offset: f32, - size: f32, + y_offset: f32, range: Range, + size: f32, ) -> Self { Self { - font, glyph_id, x_advance, x_offset, - size, + y_offset, range, + size, + } + } +} + +pub trait PdfFont { + fn identifier(&self) -> FontIdentifier; + fn to_pdf_font_units(&self, val: f32) -> f32; + fn font(&self) -> Font; + fn get_codepoints(&self, pdf_glyph: PDFGlyph) -> Option<&str>; + fn set_codepoints(&mut self, pdf_glyph: PDFGlyph, text: String); + fn get_gid(&self, gid: GlyphId) -> Option; +} + +impl PdfFont for Type3Font { + fn identifier(&self) -> FontIdentifier { + self.identifier() + } + + fn to_pdf_font_units(&self, val: f32) -> f32 { + Type3Font::to_pdf_font_units(self, val) + } + + fn font(&self) -> Font { + Type3Font::font(self) + } + + fn get_codepoints(&self, pdf_glyph: PDFGlyph) -> Option<&str> { + match pdf_glyph { + PDFGlyph::Type3(t3) => self.get_codepoints(t3), + PDFGlyph::CID(_) => panic!("attempted to pass cid to type 3 font"), + } + } + + fn set_codepoints(&mut self, pdf_glyph: PDFGlyph, text: String) { + match pdf_glyph { + PDFGlyph::Type3(t3) => self.set_codepoints(t3, text), + PDFGlyph::CID(_) => panic!("attempted to pass cid to type 3 font"), + } + } + + fn get_gid(&self, gid: GlyphId) -> Option { + self.get_gid(gid).map(|g| PDFGlyph::Type3(g)) + } +} + +impl PdfFont for CIDFont { + fn identifier(&self) -> FontIdentifier { + self.identifier() + } + + fn to_pdf_font_units(&self, val: f32) -> f32 { + CIDFont::to_pdf_font_units(self, val) + } + + fn font(&self) -> Font { + CIDFont::font(self) + } + + fn get_codepoints(&self, pdf_glyph: PDFGlyph) -> Option<&str> { + match pdf_glyph { + PDFGlyph::Type3(_) => panic!("attempted to pass cid to type 3 font"), + PDFGlyph::CID(cid) => self.get_codepoints(cid), } } + + fn set_codepoints(&mut self, pdf_glyph: PDFGlyph, text: String) { + match pdf_glyph { + PDFGlyph::Type3(_) => panic!("attempted to pass cid to type 3 font"), + PDFGlyph::CID(cid) => self.set_codepoints(cid, text), + } + } + + fn get_gid(&self, gid: GlyphId) -> Option { + self.get_cid(gid).map(|g| PDFGlyph::CID(g)) + } } -pub enum PdfFont<'a> { - Type3(&'a Type3Font), - CID(&'a CIDFont), +pub enum TextSpan<'a> { + Unspanned(&'a [Glyph]), + Spanned(&'a [Glyph], &'a str), } -impl PdfFont<'_> { - pub fn to_font_units(&self, val: f32) -> f32 { +impl TextSpan<'_> { + pub fn glyphs(&self) -> &[Glyph] { match self { - PdfFont::Type3(t3) => t3.to_font_units(val), - PdfFont::CID(cid) => cid.to_pdf_font_units(val), + TextSpan::Unspanned(glyphs) => glyphs, + TextSpan::Spanned(glyphs, _) => glyphs, } } - pub fn advance_width(&self, glyph: u16) -> Option { + pub fn actual_text(&self) -> Option<&str> { match self { - PdfFont::Type3(t3) => t3.advance_width(glyph as u8), - PdfFont::CID(cid) => cid.advance_width(glyph), + TextSpan::Unspanned(_) => None, + TextSpan::Spanned(_, text) => Some(text), } } } -pub struct GroupByGlyphs<'a, 'b> { - sc: &'b mut SerializerContext, +pub struct TextSpanner<'a, 'b> { slice: &'a [Glyph], + font_container: &'b RefCell, + text: &'a str, } -impl<'a, 'b> GroupByGlyphs<'a, 'b> { - pub fn new(sc: &'b mut SerializerContext, slice: &'a [Glyph]) -> Self { - Self { sc, slice } +impl<'a, 'b> TextSpanner<'a, 'b> { + pub fn new( + slice: &'a [Glyph], + text: &'a str, + font_container: &'b RefCell, + ) -> Self { + Self { + slice, + text, + font_container, + } } } -impl<'a> Iterator for GroupByGlyphs<'a, '_> { - type Item = (f32, FontResource, bool, &'a [Glyph]); +impl<'a> Iterator for TextSpanner<'a, '_> { + type Item = TextSpan<'a>; fn next(&mut self) -> Option { - struct TempGlyph { - font_resource: FontResource, - range: Range, - size: f32, - } - - let mut func = |g: &Glyph| { - let (font_resource, _) = self.sc.map_glyph(g.font.clone(), g.glyph_id); - TempGlyph { - font_resource, - range: g.range.clone(), - size: g.size, + let func = |g: &Glyph| { + let mut font_container = self.font_container.borrow_mut(); + let (identifier, pdf_glyph) = font_container.add_glyph(g.glyph_id); + let pdf_font = font_container + .get_from_identifier_mut(identifier.clone()) + .unwrap(); + + let range = g.range.clone(); + let text = &self.text[range.clone()]; + let codepoints = pdf_font.get_codepoints(pdf_glyph); + let incompatible_codepoint = codepoints.is_some() && codepoints != Some(text); + + if !incompatible_codepoint { + pdf_font.set_codepoints(pdf_glyph, text.to_string()); } + + (range, incompatible_codepoint) }; - let mut same_range = None; + let mut use_span = None; let mut count = 1; let mut iter = self.slice.iter(); - let first = (func)(iter.next()?); - let mut last_range = first.range.clone(); + let (first_range, first_incompatible) = (func)(iter.next()?); - while let Some(next) = iter.next() { - let temp_glyph = func(next); + let mut last_range = first_range.clone(); - if first.font_resource != temp_glyph.font_resource || first.size != next.size { - break; - } + while let Some(next) = iter.next() { + let (next_range, next_incompatible) = func(next); - match same_range { + match use_span { None => { - same_range = Some(last_range == temp_glyph.range); + if first_incompatible { + use_span = Some(true); + break; + } + + use_span = Some(last_range == next_range); } Some(true) => { - if last_range != temp_glyph.range { + if next_incompatible || last_range != next_range { break; } } Some(false) => { - if last_range == temp_glyph.range { + if next_incompatible { + break; + } + + if last_range == next_range { count -= 1; break; } @@ -846,11 +939,132 @@ impl<'a> Iterator for GroupByGlyphs<'a, '_> { let (head, tail) = self.slice.split_at(count); self.slice = tail; - Some(( - first.size, - first.font_resource, - same_range.unwrap_or(false), - head, - )) + + let fragment = match use_span.unwrap_or(false) { + true => TextSpan::Spanned(head, &self.text[first_range]), + false => TextSpan::Unspanned(head), + }; + Some(fragment) + } +} + +pub struct GlyphGroup { + font_identifier: FontIdentifier, + glyphs: Vec, + size: f32, + y_offset: f32, +} + +impl GlyphGroup { + pub fn new( + font_identifier: FontIdentifier, + glyphs: Vec, + size: f32, + y_offset: f32, + ) -> Self { + GlyphGroup { + font_identifier, + glyphs, + size, + y_offset, + } + } +} + +pub struct GlyphGrouper<'a, 'b> { + font_container: &'b RefCell, + slice: &'a [Glyph], +} + +impl<'a, 'b> GlyphGrouper<'a, 'b> { + pub fn new(font_container: &'b RefCell, slice: &'a [Glyph]) -> Self { + Self { + font_container, + slice, + } + } +} + +impl<'a> Iterator for GlyphGrouper<'a, '_> { + type Item = GlyphGroup; + + fn next(&mut self) -> Option { + // Guarantees: All glyphs in `head` have the font identifier that is given in + // `props`, the same size and the same y offset. + let (head, tail, props) = { + struct GlyphProps { + font_identifier: FontIdentifier, + size: f32, + y_offset: f32, + } + + let func = |g: &Glyph| { + let font_container = self.font_container.borrow_mut(); + // Safe because we've already added all glyphs in the text spanner. + let font_identifier = font_container.font_identifier(g.glyph_id).unwrap(); + + GlyphProps { + font_identifier, + size: g.size, + y_offset: g.y_offset, + } + }; + + let mut count = 1; + + let mut iter = self.slice.iter(); + let first = (func)(iter.next()?); + + while let Some(next) = iter.next() { + let temp_glyph = func(next); + + if first.font_identifier != temp_glyph.font_identifier + || first.y_offset != temp_glyph.y_offset + || first.size != temp_glyph.size + { + break; + } + + count += 1; + } + + let (head, tail) = self.slice.split_at(count); + (head, tail, first) + }; + + self.slice = tail; + + let font_container = self.font_container.borrow(); + let pdf_font = font_container + .get_from_identifier(props.font_identifier.clone()) + .unwrap(); + + let glyphs = head + .iter() + .map(move |g| { + // Safe because we've already added all glyphs in the text spanner. + let pdf_glyph = pdf_font.get_gid(g.glyph_id).unwrap(); + + let user_units_to_font_units = |val| { + pdf_font.to_pdf_font_units(val / g.size * pdf_font.font().units_per_em()) + }; + + InstanceGlyph { + pdf_glyph, + user_space_x_advance: g.x_advance, + x_advance: user_units_to_font_units(g.x_advance), + font_advance: pdf_font + .font() + .advance_width(g.glyph_id) + .map(|n| pdf_font.to_pdf_font_units(n)), + x_offset: user_units_to_font_units(g.x_offset), + } + }) + .collect::>(); + + let glyph_group = + GlyphGroup::new(props.font_identifier, glyphs, props.size, props.y_offset); + + Some(glyph_group) } } diff --git a/src/surface.rs b/src/surface.rs index 812eb5e1..ec471506 100644 --- a/src/surface.rs +++ b/src/surface.rs @@ -98,6 +98,7 @@ impl<'a> Surface<'a> { y: f32, mode: impl Into>, glyphs: &[Glyph], + font: Font, text: &str, ) where T: ColorSpace, @@ -105,11 +106,11 @@ impl<'a> Surface<'a> { match mode.into() { FillOrStroke::Fill(fill) => { Self::cur_builder(&mut self.root_builder, &mut self.sub_builders) - .fill_glyph_run(x, y, self.sc, fill, glyphs, text); + .fill_glyph_run(x, y, self.sc, fill, glyphs, font, text); } FillOrStroke::Stroke(stroke) => { Self::cur_builder(&mut self.root_builder, &mut self.sub_builders) - .stroke_glyph_run(x, y, self.sc, stroke, glyphs, text); + .stroke_glyph_run(x, y, self.sc, stroke, glyphs, font, text); } }; } diff --git a/src/svg/text.rs b/src/svg/text.rs index 1c9a5510..677a6b91 100644 --- a/src/svg/text.rs +++ b/src/svg/text.rs @@ -65,14 +65,15 @@ pub fn render(text: &usvg::Text, surface: &mut Surface, process_context: &mut Pr 0.0, fill, &[Glyph::new( - font, GlyphId::new(glyph.id.0 as u32), // Don't care about those, since we render only one glyph. 0.0, 0.0, - span.font_size.get(), + 0.0, 0..glyph.text.len(), + span.font_size.get(), )], + font, &glyph.text, ); }; @@ -83,14 +84,15 @@ pub fn render(text: &usvg::Text, surface: &mut Surface, process_context: &mut Pr 0.0, stroke, &[Glyph::new( - font, GlyphId::new(glyph.id.0 as u32), // Don't care about those, since we render only one glyph. 0.0, 0.0, - span.font_size.get(), + 0.0, 0..glyph.text.len(), + span.font_size.get(), )], + font, &glyph.text, ); }; diff --git a/tests/fonts/DejaVuSansMono.ttf b/tests/fonts/DejaVuSansMono.ttf new file mode 100644 index 00000000..f5786022 Binary files /dev/null and b/tests/fonts/DejaVuSansMono.ttf differ diff --git a/tests/visreg.rs b/tests/visreg.rs index 7fbed220..1f450fe9 100644 --- a/tests/visreg.rs +++ b/tests/visreg.rs @@ -5,6 +5,7 @@ use krilla::document::Document; use krilla::rgb::Rgb; use krilla::serialize::SerializeSettings; use krilla::stream::Glyph; +use krilla::util::SliceExt; use krilla::{rgb, Fill, LinearGradient, Paint, SpreadMethod, Stop}; use sitro::{ render_ghostscript, render_mupdf, render_pdfbox, render_pdfium, render_pdfjs, render_poppler, @@ -249,21 +250,38 @@ generate_renderer_tests!(cosmic_text, |renderer| { // Inspect the output runs for run in buffer.layout_runs() { let y_offset = run.line_y; - let glyphs = run + + let segmented = run .glyphs - .iter() - .map(|g| { - Glyph::new( - font_map.get(&g.font_id).unwrap().clone(), - GlyphId::new(g.glyph_id as u32), - g.w, - g.x_offset, - g.font_size, - g.start..g.end, - ) - }) - .collect::>(); - surface.draw_glyph_run(0.0, y_offset, Fill::::default(), &glyphs, &run.text); + .group_by_key(|g| (font_map.get(&g.font_id).unwrap().clone(), g.font_size)); + + let mut x = 0.0; + for ((font, size), glyphs) in segmented { + let start_x = x; + let glyphs = glyphs + .iter() + .map(|glyph| { + x += glyph.w; + Glyph::new( + GlyphId::new(glyph.glyph_id as u32), + glyph.w, + glyph.x_offset, + glyph.y_offset, + glyph.start..glyph.end, + size, + ) + }) + .collect::>(); + + surface.draw_glyph_run( + start_x, + y_offset, + Fill::::default(), + &glyphs, + font, + run.text, + ); + } } surface.finish();