1#![cfg(feature = "font_loading")]
2
3use azul_css::{AzString, U8Vec};
4use rust_fontconfig::{FcFontCache, FontSource};
5
6pub mod loading {
7 #![cfg(feature = "std")]
8 #![cfg(feature = "font_loading")]
9 #![cfg_attr(not(feature = "std"), no_std)]
10
11 use std::io::Error as IoError;
12
13 use azul_css::{AzString, StringVec, U8Vec};
14 use rust_fontconfig::FcFontCache;
15
16 #[cfg(not(miri))]
17 pub fn build_font_cache() -> FcFontCache {
18 FcFontCache::build()
19 }
20
21 #[cfg(miri)]
22 pub fn build_font_cache() -> FcFontCache {
23 FcFontCache::default()
24 }
25
26 #[derive(Debug)]
27 pub enum FontReloadError {
28 Io(IoError, AzString),
29 FontNotFound(AzString),
30 FontLoadingNotActive(AzString),
31 }
32
33 impl Clone for FontReloadError {
34 fn clone(&self) -> Self {
35 use self::FontReloadError::*;
36 match self {
37 Io(err, path) => Io(IoError::new(err.kind(), "Io Error"), path.clone()),
38 FontNotFound(id) => FontNotFound(id.clone()),
39 FontLoadingNotActive(id) => FontLoadingNotActive(id.clone()),
40 }
41 }
42 }
43
44 azul_core::impl_display!(FontReloadError, {
45 Io(err, path_buf) => format!("Could not load \"{}\" - IO error: {}", path_buf.as_str(), err),
46 FontNotFound(id) => format!("Could not locate system font: \"{:?}\" found", id),
47 FontLoadingNotActive(id) => format!("Could not load system font: \"{:?}\": crate was not compiled with --features=\"font_loading\"", id)
48 });
49}
50pub mod mock {
51 use std::collections::BTreeMap;
57
58 use crate::text3::cache::LayoutFontMetrics;
59
60 #[derive(Debug, Clone)]
65 pub struct MockFont {
66 pub font_metrics: LayoutFontMetrics,
68 pub space_width: Option<usize>,
70 pub glyph_advances: BTreeMap<u16, u16>,
72 pub glyph_sizes: BTreeMap<u16, (i32, i32)>,
74 pub glyph_indices: BTreeMap<u32, u16>,
76 }
77
78 impl MockFont {
79 pub fn new(font_metrics: LayoutFontMetrics) -> Self {
81 MockFont {
82 font_metrics,
83 space_width: Some(10),
84 glyph_advances: BTreeMap::new(),
85 glyph_sizes: BTreeMap::new(),
86 glyph_indices: BTreeMap::new(),
87 }
88 }
89
90 pub fn with_space_width(mut self, width: usize) -> Self {
92 self.space_width = Some(width);
93 self
94 }
95
96 pub fn with_glyph_advance(mut self, glyph_index: u16, advance: u16) -> Self {
98 self.glyph_advances.insert(glyph_index, advance);
99 self
100 }
101
102 pub fn with_glyph_size(mut self, glyph_index: u16, size: (i32, i32)) -> Self {
104 self.glyph_sizes.insert(glyph_index, size);
105 self
106 }
107
108 pub fn with_glyph_index(mut self, unicode: u32, index: u16) -> Self {
110 self.glyph_indices.insert(unicode, index);
111 self
112 }
113 }
114}
115
116pub mod parsed {
117 use core::fmt;
118 use std::{collections::BTreeMap, sync::Arc};
119
120 use allsorts::{
121 binary::read::ReadScope,
122 font_data::FontData,
123 layout::{GDEFTable, LayoutCache, LayoutCacheData, GPOS, GSUB},
124 outline::{OutlineBuilder, OutlineSink},
125 pathfinder_geometry::{line_segment::LineSegment2F, vector::Vector2F},
126 subset::{subset as allsorts_subset, whole_font, CmapTarget, SubsetProfile},
127 tables::{
128 cmap::owned::CmapSubtable as OwnedCmapSubtable,
129 glyf::{
130 Glyph, GlyfVisitorContext, LocaGlyf, Point,
131 VariableGlyfContext, VariableGlyfContextStore,
132 },
133 kern::owned::KernTable,
134 FontTableProvider, HheaTable, MaxpTable,
135 },
136 tag,
137 };
138 use azul_core::resources::{
139 GlyphOutline, GlyphOutlineOperation, OutlineCubicTo, OutlineLineTo, OutlineMoveTo,
140 OutlineQuadTo, OwnedGlyphBoundingBox,
141 };
142 use azul_css::props::basic::FontMetrics as CssFontMetrics;
143
144 pub use crate::font::mock::MockFont;
146 use crate::text3::cache::LayoutFontMetrics;
147
148 pub type GsubCache = Arc<LayoutCacheData<GSUB>>;
150 pub type GposCache = Arc<LayoutCacheData<GPOS>>;
152
153 struct GlyphOutlineCollector {
159 contours: Vec<GlyphOutline>,
160 current_contour: Vec<GlyphOutlineOperation>,
161 }
162
163 impl GlyphOutlineCollector {
164 fn new() -> Self {
165 Self {
166 contours: Vec::new(),
167 current_contour: Vec::new(),
168 }
169 }
170
171 fn into_outlines(mut self) -> Vec<GlyphOutline> {
172 if !self.current_contour.is_empty() {
173 self.contours.push(GlyphOutline {
174 operations: std::mem::take(&mut self.current_contour).into(),
175 });
176 }
177 self.contours
178 }
179 }
180
181 impl OutlineSink for GlyphOutlineCollector {
182 fn move_to(&mut self, to: Vector2F) {
183 if !self.current_contour.is_empty() {
184 self.contours.push(GlyphOutline {
185 operations: std::mem::take(&mut self.current_contour).into(),
186 });
187 }
188 self.current_contour.push(GlyphOutlineOperation::MoveTo(OutlineMoveTo {
189 x: to.x() as i16,
190 y: to.y() as i16,
191 }));
192 }
193
194 fn line_to(&mut self, to: Vector2F) {
195 self.current_contour.push(GlyphOutlineOperation::LineTo(OutlineLineTo {
196 x: to.x() as i16,
197 y: to.y() as i16,
198 }));
199 }
200
201 fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) {
202 self.current_contour.push(GlyphOutlineOperation::QuadraticCurveTo(
203 OutlineQuadTo {
204 ctrl_1_x: ctrl.x() as i16,
205 ctrl_1_y: ctrl.y() as i16,
206 end_x: to.x() as i16,
207 end_y: to.y() as i16,
208 },
209 ));
210 }
211
212 fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F) {
213 self.current_contour.push(GlyphOutlineOperation::CubicCurveTo(
214 OutlineCubicTo {
215 ctrl_1_x: ctrl.from_x() as i16,
216 ctrl_1_y: ctrl.from_y() as i16,
217 ctrl_2_x: ctrl.to_x() as i16,
218 ctrl_2_y: ctrl.to_y() as i16,
219 end_x: to.x() as i16,
220 end_y: to.y() as i16,
221 },
222 ));
223 }
224
225 fn close(&mut self) {
226 self.current_contour.push(GlyphOutlineOperation::ClosePath);
227 self.contours.push(GlyphOutline {
228 operations: std::mem::take(&mut self.current_contour).into(),
229 });
230 }
231 }
232
233 #[derive(Clone)]
241 pub struct ParsedFont {
242 pub hash: u64,
244 pub font_metrics: LayoutFontMetrics,
246 pub pdf_font_metrics: PdfFontMetrics,
248 pub num_glyphs: u16,
250 pub hhea_table: HheaTable,
252 pub hmtx_data: Vec<u8>,
254 pub vmtx_data: Vec<u8>,
256 pub maxp_table: MaxpTable,
258 pub gsub_cache: Option<GsubCache>,
260 pub gpos_cache: Option<GposCache>,
262 pub opt_gdef_table: Option<Arc<GDEFTable>>,
264 pub opt_kern_table: Option<Arc<KernTable>>,
266 pub glyph_records_decoded: BTreeMap<u16, OwnedGlyph>,
268 pub space_width: Option<usize>,
270 pub cmap_subtable: Option<OwnedCmapSubtable>,
272 pub mock: Option<Box<MockFont>>,
274 pub reverse_glyph_cache: std::collections::BTreeMap<u16, String>,
276 pub original_bytes: Vec<u8>,
278 pub original_index: usize,
280 pub index_to_cid: BTreeMap<u16, u16>,
282 pub font_type: FontType,
284 pub font_name: Option<String>,
286 }
287
288 #[derive(Debug, Clone, PartialEq)]
293 pub enum FontType {
294 TrueType,
296 OpenTypeCFF(Vec<u8>),
299 }
300
301 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
306 #[repr(C)]
307 pub struct PdfFontMetrics {
308 pub units_per_em: u16,
311 pub font_flags: u16,
313 pub x_min: i16,
315 pub y_min: i16,
317 pub x_max: i16,
319 pub y_max: i16,
321
322 pub ascender: i16,
325 pub descender: i16,
327 pub line_gap: i16,
329 pub advance_width_max: u16,
331 pub caret_slope_rise: i16,
333 pub caret_slope_run: i16,
335
336 pub x_avg_char_width: i16,
339 pub us_weight_class: u16,
341 pub us_width_class: u16,
343 pub y_strikeout_size: i16,
345 pub y_strikeout_position: i16,
347 }
348
349 impl Default for PdfFontMetrics {
350 fn default() -> Self {
351 PdfFontMetrics::zero()
352 }
353 }
354
355 impl PdfFontMetrics {
356 pub const fn zero() -> Self {
357 PdfFontMetrics {
358 units_per_em: 1000,
359 font_flags: 0,
360 x_min: 0,
361 y_min: 0,
362 x_max: 0,
363 y_max: 0,
364 ascender: 0,
365 descender: 0,
366 line_gap: 0,
367 advance_width_max: 0,
368 caret_slope_rise: 0,
369 caret_slope_run: 0,
370 x_avg_char_width: 0,
371 us_weight_class: 0,
372 us_width_class: 0,
373 y_strikeout_size: 0,
374 y_strikeout_position: 0,
375 }
376 }
377 }
378
379 #[derive(Debug, Clone)]
384 pub struct SubsetFont {
385 pub bytes: Vec<u8>,
387 pub glyph_mapping: BTreeMap<u16, (u16, char)>,
389 }
390
391 impl SubsetFont {
392 pub fn subset_text(&self, text: &str) -> String {
396 text.chars()
397 .filter_map(|c| {
398 self.glyph_mapping.values().find_map(|(ngid, ch)| {
399 if *ch == c {
400 char::from_u32(*ngid as u32)
401 } else {
402 None
403 }
404 })
405 })
406 .collect()
407 }
408 }
409
410 impl PartialEq for ParsedFont {
411 fn eq(&self, other: &Self) -> bool {
412 self.hash == other.hash
413 }
414 }
415
416 impl Eq for ParsedFont {}
417
418 const FONT_B64_START: &str = "data:font/ttf;base64,";
419
420 impl serde::Serialize for ParsedFont {
421 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
422 use base64::Engine;
423 let s = format!(
424 "{FONT_B64_START}{}",
425 base64::prelude::BASE64_STANDARD.encode(&self.to_bytes(None).unwrap_or_default())
426 );
427 s.serialize(serializer)
428 }
429 }
430
431 impl<'de> serde::Deserialize<'de> for ParsedFont {
432 fn deserialize<D: serde::Deserializer<'de>>(
433 deserializer: D,
434 ) -> Result<ParsedFont, D::Error> {
435 use base64::Engine;
436 let s = String::deserialize(deserializer)?;
437 let b64 = if s.starts_with(FONT_B64_START) {
438 let b = &s[FONT_B64_START.len()..];
439 base64::prelude::BASE64_STANDARD.decode(&b).ok()
440 } else {
441 None
442 };
443
444 let mut warnings = Vec::new();
445 ParsedFont::from_bytes(&b64.unwrap_or_default(), 0, &mut warnings).ok_or_else(|| {
446 serde::de::Error::custom(format!("Font deserialization error: {warnings:?}"))
447 })
448 }
449 }
450
451 impl fmt::Debug for ParsedFont {
452 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453 f.debug_struct("ParsedFont")
454 .field("hash", &self.hash)
455 .field("font_metrics", &self.font_metrics)
456 .field("num_glyphs", &self.num_glyphs)
457 .field("hhea_table", &self.hhea_table)
458 .field(
459 "hmtx_data",
460 &format_args!("<{} bytes>", self.hmtx_data.len()),
461 )
462 .field("maxp_table", &self.maxp_table)
463 .field(
464 "glyph_records_decoded",
465 &format_args!("{} entries", self.glyph_records_decoded.len()),
466 )
467 .field("space_width", &self.space_width)
468 .field("cmap_subtable", &self.cmap_subtable)
469 .finish()
470 }
471 }
472
473 #[derive(Debug, Clone, PartialEq, Eq)]
475 pub struct FontParseWarning {
476 pub severity: FontParseWarningSeverity,
478 pub message: String,
480 }
481
482 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
484 pub enum FontParseWarningSeverity {
485 Info,
487 Warning,
489 Error,
491 }
492
493 impl FontParseWarning {
494 pub fn info(message: String) -> Self {
496 Self {
497 severity: FontParseWarningSeverity::Info,
498 message,
499 }
500 }
501
502 pub fn warning(message: String) -> Self {
504 Self {
505 severity: FontParseWarningSeverity::Warning,
506 message,
507 }
508 }
509
510 pub fn error(message: String) -> Self {
512 Self {
513 severity: FontParseWarningSeverity::Error,
514 message,
515 }
516 }
517 }
518
519 impl ParsedFont {
520 pub fn from_bytes(
532 font_bytes: &[u8],
533 font_index: usize,
534 warnings: &mut Vec<FontParseWarning>,
535 ) -> Option<Self> {
536 use std::{
537 collections::hash_map::DefaultHasher,
538 hash::{Hash, Hasher},
539 };
540
541 use allsorts::{
542 binary::read::ReadScope,
543 font_data::FontData,
544 tables::{
545 cmap::{owned::CmapSubtable as OwnedCmapSubtable, CmapSubtable},
546 FontTableProvider, HeadTable, HheaTable, MaxpTable,
547 },
548 tag,
549 };
550
551 let scope = ReadScope::new(font_bytes);
552 let font_file = match scope.read::<FontData<'_>>() {
553 Ok(ff) => {
554 warnings.push(FontParseWarning::info(
555 "Successfully read font data".to_string(),
556 ));
557 ff
558 }
559 Err(e) => {
560 warnings.push(FontParseWarning::error(format!(
561 "Failed to read font data: {}",
562 e
563 )));
564 return None;
565 }
566 };
567 let provider = match font_file.table_provider(font_index) {
568 Ok(p) => {
569 warnings.push(FontParseWarning::info(format!(
570 "Successfully loaded font at index {}",
571 font_index
572 )));
573 p
574 }
575 Err(e) => {
576 warnings.push(FontParseWarning::error(format!(
577 "Failed to get table provider for font index {}: {}",
578 font_index, e
579 )));
580 return None;
581 }
582 };
583
584 let font_name = provider.table_data(tag::NAME).ok().and_then(|name_data| {
586 ReadScope::new(&name_data?)
587 .read::<allsorts::tables::NameTable>()
588 .ok()
589 .and_then(|name_table| {
590 name_table.string_for_id(allsorts::tables::NameTable::POSTSCRIPT_NAME)
591 })
592 });
593
594 let head_table = provider
595 .table_data(tag::HEAD)
596 .ok()
597 .and_then(|head_data| ReadScope::new(&head_data?).read::<HeadTable>().ok())?;
598
599 let maxp_table = provider
600 .table_data(tag::MAXP)
601 .ok()
602 .and_then(|maxp_data| ReadScope::new(&maxp_data?).read::<MaxpTable>().ok())
603 .unwrap_or(MaxpTable {
604 num_glyphs: 0,
605 version1_sub_table: None,
606 });
607
608 let num_glyphs = maxp_table.num_glyphs as usize;
609
610 let hmtx_data = provider
611 .table_data(tag::HMTX)
612 .ok()
613 .and_then(|s| Some(s?.to_vec()))
614 .unwrap_or_default();
615
616 let vmtx_data = provider
617 .table_data(tag::VMTX)
618 .ok()
619 .and_then(|s| Some(s?.to_vec()))
620 .unwrap_or_default();
621
622 let hhea_table = provider
623 .table_data(tag::HHEA)
624 .ok()
625 .and_then(|hhea_data| ReadScope::new(&hhea_data?).read::<HheaTable>().ok())
626 .unwrap_or(unsafe { std::mem::zeroed() });
627
628 let font_metrics = LayoutFontMetrics {
630 units_per_em: if head_table.units_per_em == 0 {
631 1000
632 } else {
633 head_table.units_per_em
634 },
635 ascent: hhea_table.ascender as f32,
636 descent: hhea_table.descender as f32,
637 line_gap: hhea_table.line_gap as f32,
638 };
639
640 let pdf_font_metrics =
642 Self::parse_pdf_font_metrics(font_bytes, font_index, &head_table, &hhea_table);
643
644 let has_glyf = provider.has_table(tag::GLYF) && provider.has_table(tag::LOCA);
648
649 let glyph_records_decoded: BTreeMap<u16, OwnedGlyph> = if has_glyf {
650 warnings.push(FontParseWarning::info(
651 "Parsing glyph outlines via allsorts OutlineBuilder (composite-safe)".to_string(),
652 ));
653
654 match LocaGlyf::load(&provider) {
656 Ok(mut loca_glyf) => {
657 let var_store = VariableGlyfContextStore::read(&provider).ok();
659 let var_context = var_store.as_ref()
660 .and_then(|store| VariableGlyfContext::new(store).ok());
661
662 let mut visitor = GlyfVisitorContext::new(
663 &mut loca_glyf,
664 var_context,
665 );
666
667 let mut map = BTreeMap::new();
668 for glyph_index in 0..num_glyphs.min(u16::MAX as usize) {
669 let gid = glyph_index as u16;
670 let horz_advance = allsorts::glyph_info::advance(
671 &maxp_table, &hhea_table, &hmtx_data, gid,
672 ).unwrap_or_default();
673
674 let mut collector = GlyphOutlineCollector::new();
676 let visit_result = visitor.visit(gid, None, &mut collector);
678
679 let outlines = match visit_result {
680 Ok(()) => collector.into_outlines(),
681 Err(_) => Vec::new(),
682 };
683
684 let (min_x, min_y, max_x, max_y) = compute_outline_bbox(&outlines);
686
687 map.insert(gid, OwnedGlyph {
688 horz_advance,
689 bounding_box: OwnedGlyphBoundingBox {
690 min_x, min_y, max_x, max_y,
691 },
692 outline: outlines,
693 phantom_points: None,
694 });
695 }
696 map
697 }
698 Err(e) => {
699 warnings.push(FontParseWarning::warning(format!(
700 "Failed to load LocaGlyf: {} — falling back to hmtx-only", e
701 )));
702 (0..num_glyphs.min(u16::MAX as usize))
704 .map(|glyph_index| {
705 let gid = glyph_index as u16;
706 let horz_advance = allsorts::glyph_info::advance(
707 &maxp_table, &hhea_table, &hmtx_data, gid,
708 ).unwrap_or_default();
709 (gid, OwnedGlyph {
710 horz_advance,
711 bounding_box: OwnedGlyphBoundingBox {
712 min_x: 0, min_y: 0,
713 max_x: horz_advance as i16, max_y: 0,
714 },
715 outline: Vec::new(),
716 phantom_points: None,
717 })
718 })
719 .collect()
720 }
721 }
722 } else {
723 warnings.push(FontParseWarning::info(format!(
725 "Using hmtx-only fallback for {} glyphs (CFF font or no glyf table)",
726 num_glyphs
727 )));
728 (0..num_glyphs.min(u16::MAX as usize))
729 .map(|glyph_index| {
730 let gid = glyph_index as u16;
731 let horz_advance = allsorts::glyph_info::advance(
732 &maxp_table, &hhea_table, &hmtx_data, gid,
733 ).unwrap_or_default();
734
735 (gid, OwnedGlyph {
736 horz_advance,
737 bounding_box: OwnedGlyphBoundingBox {
738 min_x: 0, min_y: 0,
739 max_x: horz_advance as i16, max_y: 0,
740 },
741 outline: Vec::new(),
742 phantom_points: None,
743 })
744 })
745 .collect::<BTreeMap<_, _>>()
746 };
747
748 let mut font_data_impl = allsorts::font::Font::new(provider).ok()?;
749
750 let gsub_cache = font_data_impl.gsub_cache().ok().and_then(|s| s);
752 let gpos_cache = font_data_impl.gpos_cache().ok().and_then(|s| s);
753 let opt_gdef_table = font_data_impl.gdef_table().ok().and_then(|o| o);
754 let num_glyphs = font_data_impl.num_glyphs();
755
756 let opt_kern_table = font_data_impl
757 .kern_table()
758 .ok()
759 .and_then(|s| Some(s?.to_owned()));
760
761 let cmap_data = font_data_impl.cmap_subtable_data();
762 let cmap_subtable = ReadScope::new(cmap_data);
763 let cmap_subtable = cmap_subtable
764 .read::<CmapSubtable<'_>>()
765 .ok()
766 .and_then(|s| s.to_owned());
767
768 let mut hasher = DefaultHasher::new();
770 font_bytes.hash(&mut hasher);
771 font_index.hash(&mut hasher);
772 let hash = hasher.finish();
773
774 let mut font = ParsedFont {
775 hash,
776 font_metrics,
777 pdf_font_metrics,
778 num_glyphs,
779 hhea_table,
780 hmtx_data,
781 vmtx_data,
782 maxp_table,
783 gsub_cache,
784 gpos_cache,
785 opt_gdef_table,
786 opt_kern_table,
787 cmap_subtable,
788 glyph_records_decoded,
789 space_width: None,
790 mock: None,
791 reverse_glyph_cache: BTreeMap::new(),
792 original_bytes: font_bytes.to_vec(),
793 original_index: font_index,
794 index_to_cid: BTreeMap::new(), font_type: FontType::TrueType, font_name,
797 };
798
799 let space_width = font.get_space_width_internal();
801
802 let _ = (|| {
805 let space_gid = font.lookup_glyph_index(' ' as u32)?;
806 if font.glyph_records_decoded.contains_key(&space_gid) {
807 return None; }
809 let space_width_val = space_width?;
810 let space_record = OwnedGlyph {
811 bounding_box: OwnedGlyphBoundingBox {
812 max_x: 0,
813 max_y: 0,
814 min_x: 0,
815 min_y: 0,
816 },
817 horz_advance: space_width_val as u16,
818 outline: Vec::new(),
819 phantom_points: None,
820 };
821 font.glyph_records_decoded.insert(space_gid, space_record);
822 Some(())
823 })();
824
825 font.space_width = space_width;
826
827 Some(font)
828 }
829
830 fn parse_pdf_font_metrics(
832 font_bytes: &[u8],
833 font_index: usize,
834 head_table: &allsorts::tables::HeadTable,
835 hhea_table: &allsorts::tables::HheaTable,
836 ) -> PdfFontMetrics {
837 use allsorts::{
838 binary::read::ReadScope,
839 font_data::FontData,
840 tables::{os2::Os2, FontTableProvider},
841 tag,
842 };
843
844 let scope = ReadScope::new(font_bytes);
845 let font_file = scope.read::<FontData<'_>>().ok();
846 let provider = font_file
847 .as_ref()
848 .and_then(|ff| ff.table_provider(font_index).ok());
849
850 let os2_table = provider
851 .as_ref()
852 .and_then(|p| p.table_data(tag::OS_2).ok())
853 .and_then(|os2_data| {
854 let data = os2_data?;
855 let scope = ReadScope::new(&data);
856 scope.read_dep::<Os2>(data.len()).ok()
857 });
858
859 let base = PdfFontMetrics {
861 units_per_em: head_table.units_per_em,
862 font_flags: head_table.flags,
863 x_min: head_table.x_min,
864 y_min: head_table.y_min,
865 x_max: head_table.x_max,
866 y_max: head_table.y_max,
867 ascender: hhea_table.ascender,
868 descender: hhea_table.descender,
869 line_gap: hhea_table.line_gap,
870 advance_width_max: hhea_table.advance_width_max,
871 caret_slope_rise: hhea_table.caret_slope_rise,
872 caret_slope_run: hhea_table.caret_slope_run,
873 ..PdfFontMetrics::zero()
874 };
875
876 os2_table
878 .map(|os2| PdfFontMetrics {
879 x_avg_char_width: os2.x_avg_char_width,
880 us_weight_class: os2.us_weight_class,
881 us_width_class: os2.us_width_class,
882 y_strikeout_size: os2.y_strikeout_size,
883 y_strikeout_position: os2.y_strikeout_position,
884 ..base
885 })
886 .unwrap_or(base)
887 }
888
889 fn get_space_width_internal(&self) -> Option<usize> {
894 if let Some(mock) = self.mock.as_ref() {
895 return mock.space_width;
896 }
897 let glyph_index = self.lookup_glyph_index(' ' as u32)?;
898
899 allsorts::glyph_info::advance(
900 &self.maxp_table,
901 &self.hhea_table,
902 &self.hmtx_data,
903 glyph_index,
904 )
905 .ok()
906 .map(|s| s as usize)
907 }
908
909 pub fn lookup_glyph_index(&self, codepoint: u32) -> Option<u16> {
911 let cmap = self.cmap_subtable.as_ref()?;
912 cmap.map_glyph(codepoint).ok().flatten()
913 }
914
915 pub fn get_horizontal_advance(&self, glyph_index: u16) -> u16 {
917 if let Some(mock) = self.mock.as_ref() {
918 return mock.glyph_advances.get(&glyph_index).copied().unwrap_or(0);
919 }
920 self.glyph_records_decoded
921 .get(&glyph_index)
922 .map(|gi| gi.horz_advance)
923 .unwrap_or_default()
924 }
925
926 pub fn num_glyphs(&self) -> u16 {
928 self.num_glyphs
929 }
930
931 pub fn has_glyph(&self, codepoint: u32) -> bool {
933 self.lookup_glyph_index(codepoint).is_some()
934 }
935
936 pub fn get_vertical_metrics(
941 &self,
942 _glyph_id: u16,
943 ) -> Option<crate::text3::cache::VerticalMetrics> {
944 None
946 }
947
948 pub fn get_font_metrics(&self) -> crate::text3::cache::LayoutFontMetrics {
950 let descent = if self.font_metrics.descent > 0.0 {
952 self.font_metrics.descent
953 } else {
954 -self.font_metrics.descent
955 };
956
957 crate::text3::cache::LayoutFontMetrics {
958 ascent: self.font_metrics.ascent,
959 descent,
960 line_gap: self.font_metrics.line_gap,
961 units_per_em: self.font_metrics.units_per_em,
962 }
963 }
964
965 pub fn to_bytes(&self, tags: Option<&[u32]>) -> Result<Vec<u8>, String> {
971 let scope = ReadScope::new(&self.original_bytes);
972 let font_file = scope.read::<FontData<'_>>().map_err(|e| e.to_string())?;
973 let provider = font_file
974 .table_provider(self.original_index)
975 .map_err(|e| e.to_string())?;
976
977 let tags_to_use = tags.unwrap_or(&[
978 tag::CMAP,
979 tag::HEAD,
980 tag::HHEA,
981 tag::HMTX,
982 tag::MAXP,
983 tag::NAME,
984 tag::OS_2,
985 tag::POST,
986 tag::GLYF,
987 tag::LOCA,
988 ]);
989
990 whole_font(&provider, tags_to_use).map_err(|e| e.to_string())
991 }
992
993 pub fn subset(
1005 &self,
1006 glyph_ids: &[(u16, char)],
1007 cmap_target: CmapTarget,
1008 ) -> Result<(Vec<u8>, BTreeMap<u16, (u16, char)>), String> {
1009 let scope = ReadScope::new(&self.original_bytes);
1010 let font_file = scope.read::<FontData<'_>>().map_err(|e| e.to_string())?;
1011 let provider = font_file
1012 .table_provider(self.original_index)
1013 .map_err(|e| e.to_string())?;
1014
1015 let glyph_mapping: BTreeMap<u16, (u16, char)> = glyph_ids
1017 .iter()
1018 .enumerate()
1019 .map(|(new_id, &(original_id, ch))| (original_id, (new_id as u16, ch)))
1020 .collect();
1021
1022 let ids: Vec<u16> = glyph_ids.iter().map(|(id, _)| *id).collect();
1024
1025 let font_bytes = allsorts_subset(&provider, &ids, &SubsetProfile::Pdf, cmap_target)
1027 .map_err(|e| format!("Subset error: {:?}", e))?;
1028
1029 Ok((font_bytes, glyph_mapping))
1030 }
1031
1032 pub fn get_glyph_width_internal(&self, glyph_index: u16) -> Option<usize> {
1034 allsorts::glyph_info::advance(
1035 &self.maxp_table,
1036 &self.hhea_table,
1037 &self.hmtx_data,
1038 glyph_index,
1039 )
1040 .ok()
1041 .map(|s| s as usize)
1042 }
1043
1044 #[inline]
1046 pub const fn get_space_width(&self) -> Option<usize> {
1047 self.space_width
1048 }
1049
1050 pub fn cache_glyph_mapping(&mut self, glyph_id: u16, cluster_text: &str) {
1054 self.reverse_glyph_cache
1055 .insert(glyph_id, cluster_text.to_string());
1056 }
1057
1058 pub fn get_glyph_cluster_text(&self, glyph_id: u16) -> Option<&str> {
1061 self.reverse_glyph_cache.get(&glyph_id).map(|s| s.as_str())
1062 }
1063
1064 pub fn get_glyph_primary_char(&self, glyph_id: u16) -> Option<char> {
1068 self.reverse_glyph_cache
1069 .get(&glyph_id)
1070 .and_then(|text| text.chars().next())
1071 }
1072
1073 pub fn clear_glyph_cache(&mut self) {
1075 self.reverse_glyph_cache.clear();
1076 }
1077
1078 pub fn get_glyph_bbox_size(&self, glyph_index: u16) -> Option<(i32, i32)> {
1081 let g = self.glyph_records_decoded.get(&glyph_index)?;
1082 let glyph_width = g.horz_advance as i32;
1083 let glyph_height = g.bounding_box.max_y as i32 - g.bounding_box.min_y as i32;
1084 Some((glyph_width, glyph_height))
1085 }
1086 }
1087
1088 fn compute_outline_bbox(outlines: &[GlyphOutline]) -> (i16, i16, i16, i16) {
1090 let mut min_x = i16::MAX;
1091 let mut min_y = i16::MAX;
1092 let mut max_x = i16::MIN;
1093 let mut max_y = i16::MIN;
1094 let mut has_points = false;
1095
1096 for outline in outlines {
1097 for op in outline.operations.as_slice() {
1098 let points: &[(i16, i16)] = match op {
1099 GlyphOutlineOperation::MoveTo(m) => &[(m.x, m.y)],
1100 GlyphOutlineOperation::LineTo(l) => &[(l.x, l.y)],
1101 GlyphOutlineOperation::QuadraticCurveTo(q) => {
1102 min_x = min_x.min(q.ctrl_1_x).min(q.end_x);
1104 min_y = min_y.min(q.ctrl_1_y).min(q.end_y);
1105 max_x = max_x.max(q.ctrl_1_x).max(q.end_x);
1106 max_y = max_y.max(q.ctrl_1_y).max(q.end_y);
1107 has_points = true;
1108 continue;
1109 }
1110 GlyphOutlineOperation::CubicCurveTo(c) => {
1111 min_x = min_x.min(c.ctrl_1_x).min(c.ctrl_2_x).min(c.end_x);
1112 min_y = min_y.min(c.ctrl_1_y).min(c.ctrl_2_y).min(c.end_y);
1113 max_x = max_x.max(c.ctrl_1_x).max(c.ctrl_2_x).max(c.end_x);
1114 max_y = max_y.max(c.ctrl_1_y).max(c.ctrl_2_y).max(c.end_y);
1115 has_points = true;
1116 continue;
1117 }
1118 GlyphOutlineOperation::ClosePath => continue,
1119 };
1120 for &(x, y) in points {
1121 min_x = min_x.min(x);
1122 min_y = min_y.min(y);
1123 max_x = max_x.max(x);
1124 max_y = max_y.max(y);
1125 has_points = true;
1126 }
1127 }
1128 }
1129
1130 if has_points {
1131 (min_x, min_y, max_x, max_y)
1132 } else {
1133 (0, 0, 0, 0)
1134 }
1135 }
1136
1137 #[derive(Debug, Clone)]
1138 pub struct OwnedGlyph {
1139 pub bounding_box: OwnedGlyphBoundingBox,
1140 pub horz_advance: u16,
1141 pub outline: Vec<GlyphOutline>,
1142 pub phantom_points: Option<[Point; 4]>,
1143 }
1144
1145 impl crate::text3::cache::ShallowClone for ParsedFont {
1148 fn shallow_clone(&self) -> Self {
1149 self.clone() }
1151 }
1152
1153 impl crate::text3::cache::ParsedFontTrait for ParsedFont {
1154 fn shape_text(
1155 &self,
1156 text: &str,
1157 script: crate::font_traits::Script,
1158 language: crate::font_traits::Language,
1159 direction: crate::font_traits::BidiDirection,
1160 style: &crate::font_traits::StyleProperties,
1161 ) -> Result<Vec<crate::font_traits::Glyph>, crate::font_traits::LayoutError> {
1162 crate::text3::default::shape_text_for_parsed_font(
1164 self, text, script, language, direction, style,
1165 )
1166 }
1167
1168 fn get_hash(&self) -> u64 {
1169 self.hash
1170 }
1171
1172 fn get_glyph_size(
1173 &self,
1174 glyph_id: u16,
1175 font_size_px: f32,
1176 ) -> Option<azul_core::geom::LogicalSize> {
1177 self.glyph_records_decoded.get(&glyph_id).map(|record| {
1178 let units_per_em = self.font_metrics.units_per_em as f32;
1179 let scale_factor = if units_per_em > 0.0 {
1180 font_size_px / units_per_em
1181 } else {
1182 0.01
1183 };
1184 let bbox = &record.bounding_box;
1185 azul_core::geom::LogicalSize {
1186 width: (bbox.max_x - bbox.min_x) as f32 * scale_factor,
1187 height: (bbox.max_y - bbox.min_y) as f32 * scale_factor,
1188 }
1189 })
1190 }
1191
1192 fn get_hyphen_glyph_and_advance(&self, font_size: f32) -> Option<(u16, f32)> {
1193 let glyph_id = self.lookup_glyph_index('-' as u32)?;
1194 let advance_units = self.get_horizontal_advance(glyph_id);
1195 let scale_factor = if self.font_metrics.units_per_em > 0 {
1196 font_size / (self.font_metrics.units_per_em as f32)
1197 } else {
1198 return None;
1199 };
1200 let scaled_advance = advance_units as f32 * scale_factor;
1201 Some((glyph_id, scaled_advance))
1202 }
1203
1204 fn get_kashida_glyph_and_advance(&self, font_size: f32) -> Option<(u16, f32)> {
1205 let glyph_id = self.lookup_glyph_index('\u{0640}' as u32)?;
1206 let advance_units = self.get_horizontal_advance(glyph_id);
1207 let scale_factor = if self.font_metrics.units_per_em > 0 {
1208 font_size / (self.font_metrics.units_per_em as f32)
1209 } else {
1210 return None;
1211 };
1212 let scaled_advance = advance_units as f32 * scale_factor;
1213 Some((glyph_id, scaled_advance))
1214 }
1215
1216 fn has_glyph(&self, codepoint: u32) -> bool {
1217 self.lookup_glyph_index(codepoint).is_some()
1218 }
1219
1220 fn get_vertical_metrics(
1221 &self,
1222 glyph_id: u16,
1223 ) -> Option<crate::text3::cache::VerticalMetrics> {
1224 None
1226 }
1227
1228 fn get_font_metrics(&self) -> crate::text3::cache::LayoutFontMetrics {
1229 self.font_metrics.clone()
1230 }
1231
1232 fn num_glyphs(&self) -> u16 {
1233 self.num_glyphs
1234 }
1235 }
1236}