1use std::{path::Path, sync::Arc};
11
12use allsorts::{
13 gpos,
14 gsub::{self, Feature, FeatureInfo, FeatureMask, FeatureMaskExt},
15};
16use azul_core::geom::LogicalSize;
17use azul_css::props::basic::FontRef;
18
19use crate::{
20 font::parsed::ParsedFont,
21 text3::{
22 cache::{
23 BidiDirection, BidiLevel, FontManager, FontSelector, FontVariantCaps,
24 FontVariantLigatures, FontVariantNumeric, Glyph, GlyphOrientation, GlyphSource,
25 LayoutError, LayoutFontMetrics, ParsedFontTrait, Point, ShallowClone, StyleProperties,
26 TextCombineUpright, TextDecoration, TextOrientation, VerticalMetrics, WritingMode,
27 },
28 script::Script,
29 },
30};
31
32#[must_use] pub fn font_ref_from_bytes(
45 font_bytes: &[u8],
46 font_index: usize,
47 parse_outlines: bool,
48) -> Option<FontRef> {
49 let mut warnings = Vec::new();
51 let parsed_font = ParsedFont::from_bytes(font_bytes, font_index, &mut warnings)?;
52
53 Some(crate::parsed_font_to_font_ref(parsed_font))
54}
55
56#[derive(Copy, Debug, Default, Clone)]
61pub struct PathLoader;
62
63impl PathLoader {
64 #[must_use] pub const fn new() -> Self {
66 Self
67 }
68
69 pub(crate) fn load_from_path(self, path: &Path, font_index: usize) -> Result<FontRef, LayoutError> {
74 let font_bytes = std::fs::read(path).map_err(|_| {
75 LayoutError::FontNotFound(FontSelector {
76 family: path.to_string_lossy().into_owned(),
77 weight: rust_fontconfig::FcWeight::Normal,
78 style: crate::text3::cache::FontStyle::Normal,
79 unicode_ranges: Vec::new(),
80 })
81 })?;
82 let arc_owned = Arc::<[u8]>::from(font_bytes);
83 let bytes = Arc::new(rust_fontconfig::FontBytes::Owned(arc_owned));
84 self.load_font_shared(bytes, font_index)
85 }
86
87 pub fn load_font_shared(
104 &self,
105 font_bytes: Arc<rust_fontconfig::FontBytes>,
106 font_index: usize,
107 ) -> Result<FontRef, LayoutError> {
108 let mut warnings = Vec::new();
109 let parsed_font = ParsedFont::from_bytes_shared(font_bytes, font_index, &mut warnings)
110 .ok_or_else(|| {
111 LayoutError::ShapingError("Failed to parse font with allsorts".to_string())
112 })?;
113 Ok(crate::parsed_font_to_font_ref(parsed_font))
114 }
115}
116
117impl FontManager<FontRef> {
118 #[allow(clippy::cast_possible_truncation)] pub fn evict_unused(&self, idle: std::time::Duration) -> usize {
130 use crate::font::parsed::ParsedFont;
131 let Ok(parsed) = self.parsed_fonts.lock() else {
132 return 0;
133 };
134 let cutoff = idle.as_nanos() as u64;
139 let now_nanos = crate::font::parsed::monotonic_now_nanos();
140 let mut evicted = 0usize;
141 for font_ref in parsed.values() {
142 let font: &ParsedFont = crate::font_ref_to_parsed_font(font_ref);
143 let last = font.last_used_nanos();
144 let stale = last == 0 || now_nanos.saturating_sub(last) >= cutoff;
147 if stale && font.evict_loca_glyf() {
148 evicted += 1;
149 }
150 }
151 evicted
152 }
153}
154
155
156impl ShallowClone for FontRef {
160 fn shallow_clone(&self) -> Self {
161 self.clone()
163 }
164}
165
166impl ParsedFontTrait for FontRef {
169 fn shape_text(
171 &self,
172 text: &str,
173 script: Script,
174 language: crate::text3::script::Language,
175 direction: BidiDirection,
176 style: &StyleProperties,
177 ) -> Result<Vec<Glyph>, LayoutError> {
178 let parsed = crate::font_ref_to_parsed_font(self);
180 parsed.shape_text_for_font_ref(self, text, script, language, direction, style)
181 }
182
183 fn get_hash(&self) -> u64 {
184 crate::font_ref_to_parsed_font(self).hash
185 }
186
187 fn get_glyph_size(&self, glyph_id: u16, font_size: f32) -> Option<LogicalSize> {
188 crate::font_ref_to_parsed_font(self).get_glyph_size(glyph_id, font_size)
189 }
190
191 fn get_hyphen_glyph_and_advance(&self, font_size: f32) -> Option<(u16, f32)> {
192 crate::font_ref_to_parsed_font(self).get_hyphen_glyph_and_advance(font_size)
193 }
194
195 fn get_kashida_glyph_and_advance(&self, font_size: f32) -> Option<(u16, f32)> {
196 crate::font_ref_to_parsed_font(self).get_kashida_glyph_and_advance(font_size)
197 }
198
199 fn has_glyph(&self, codepoint: u32) -> bool {
200 crate::font_ref_to_parsed_font(self).has_glyph(codepoint)
201 }
202
203 fn get_vertical_metrics(&self, glyph_id: u16) -> Option<VerticalMetrics> {
204 crate::font_ref_to_parsed_font(self).get_vertical_metrics(glyph_id)
205 }
206
207 fn get_font_metrics(&self) -> LayoutFontMetrics {
208 crate::font_ref_to_parsed_font(self).font_metrics
209 }
210
211 fn num_glyphs(&self) -> u16 {
212 crate::font_ref_to_parsed_font(self).num_glyphs
213 }
214
215 fn get_space_width(&self) -> Option<usize> {
216 crate::font_ref_to_parsed_font(self).get_space_width()
217 }
218}
219
220pub trait FontRefExt {
224 fn get_bytes(&self) -> &[u8];
230 fn get_full_font_metrics(&self) -> azul_css::props::basic::FontMetrics;
232}
233
234impl FontRefExt for FontRef {
235 fn get_bytes(&self) -> &[u8] {
236 crate::font_ref_to_parsed_font(self)
237 .original_bytes
238 .as_ref()
239 .map_or(&[], |b| b.as_slice())
240 }
241
242 fn get_full_font_metrics(&self) -> azul_css::props::basic::FontMetrics {
243 use azul_css::{OptionI16, OptionU16, OptionU32};
244
245 let parsed = crate::font_ref_to_parsed_font(self);
246 let pdf = &parsed.pdf_font_metrics;
247
248 azul_css::props::basic::FontMetrics {
250 ul_code_page_range1: OptionU32::None,
252 ul_code_page_range2: OptionU32::None,
253
254 ul_unicode_range1: 0, ul_unicode_range2: 0, ul_unicode_range3: 0, ul_unicode_range4: 0, ach_vend_id: 0, s_typo_ascender: OptionI16::None,
263 s_typo_descender: OptionI16::None,
264 s_typo_line_gap: OptionI16::None,
265 us_win_ascent: OptionU16::None,
266 us_win_descent: OptionU16::None,
267
268 sx_height: OptionI16::None,
270 s_cap_height: OptionI16::None,
271 us_default_char: OptionU16::None,
272 us_break_char: OptionU16::None,
273 us_max_context: OptionU16::None,
274
275 us_lower_optical_point_size: OptionU16::None,
277 us_upper_optical_point_size: OptionU16::None,
278
279 units_per_em: pdf.units_per_em,
281 font_flags: pdf.font_flags,
282 x_min: pdf.x_min,
283 y_min: pdf.y_min,
284 x_max: pdf.x_max,
285 y_max: pdf.y_max,
286
287 ascender: pdf.ascender,
289 descender: pdf.descender,
290 line_gap: pdf.line_gap,
291 advance_width_max: pdf.advance_width_max,
292 min_left_side_bearing: 0, min_right_side_bearing: 0, x_max_extent: 0, caret_slope_rise: pdf.caret_slope_rise,
296 caret_slope_run: pdf.caret_slope_run,
297 caret_offset: 0, num_h_metrics: 0, x_avg_char_width: pdf.x_avg_char_width,
302 us_weight_class: pdf.us_weight_class,
303 us_width_class: pdf.us_width_class,
304 fs_type: 0, y_subscript_x_size: 0, y_subscript_y_size: 0, y_subscript_x_offset: 0, y_subscript_y_offset: 0, y_superscript_x_size: 0, y_superscript_y_size: 0, y_superscript_x_offset: 0, y_superscript_y_offset: 0, y_strikeout_size: pdf.y_strikeout_size,
314 y_strikeout_position: pdf.y_strikeout_position,
315 s_family_class: 0, fs_selection: 0, us_first_char_index: 0, us_last_char_index: 0, panose: azul_css::props::basic::Panose::zero(),
322 }
323 }
324}
325
326impl ParsedFont {
334 fn shape_text_for_font_ref(
337 &self,
338 _font_ref: &FontRef,
339 text: &str,
340 script: Script,
341 language: crate::text3::script::Language,
342 direction: BidiDirection,
343 style: &StyleProperties,
344 ) -> Result<Vec<Glyph>, LayoutError> {
345 shape_text_internal(self, text, script, language, direction, style)
349 }
350
351 const fn get_hash(&self) -> u64 {
352 self.hash
353 }
354
355 fn get_glyph_size(&self, glyph_id: u16, font_size_px: f32) -> Option<LogicalSize> {
356 self.get_or_decode_glyph(glyph_id).map(|record| {
357 let units_per_em = f32::from(self.font_metrics.units_per_em);
358 let scale_factor = if units_per_em > 0.0 {
359 font_size_px / units_per_em
360 } else {
361 FALLBACK_SCALE
362 };
363
364 let bbox = &record.bounding_box;
366
367 LogicalSize {
368 width: f32::from(bbox.max_x - bbox.min_x) * scale_factor,
369 height: f32::from(bbox.max_y - bbox.min_y) * scale_factor,
370 }
371 })
372 }
373
374 fn get_hyphen_glyph_and_advance(&self, font_size: f32) -> Option<(u16, f32)> {
375 let glyph_id = self.lookup_glyph_index('-' as u32)?;
376 let advance_units = self.get_horizontal_advance(glyph_id);
377 let scale_factor = if self.font_metrics.units_per_em > 0 {
378 font_size / f32::from(self.font_metrics.units_per_em)
379 } else {
380 return None;
381 };
382 let scaled_advance = f32::from(advance_units) * scale_factor;
383 Some((glyph_id, scaled_advance))
384 }
385
386 fn get_kashida_glyph_and_advance(&self, font_size: f32) -> Option<(u16, f32)> {
387 let glyph_id = self.lookup_glyph_index('\u{0640}' as u32)?;
389 let advance_units = self.get_horizontal_advance(glyph_id);
390 let scale_factor = if self.font_metrics.units_per_em > 0 {
391 font_size / f32::from(self.font_metrics.units_per_em)
392 } else {
393 return None;
394 };
395 let scaled_advance = f32::from(advance_units) * scale_factor;
396 Some((glyph_id, scaled_advance))
397 }
398}
399
400const FALLBACK_SCALE: f32 = 0.01;
402
403#[allow(clippy::match_same_arms)] fn build_feature_mask_for_script(script: Script) -> FeatureMask {
416 use Script::{Arabic, Devanagari, Bengali, Gujarati, Gurmukhi, Kannada, Malayalam, Oriya, Tamil, Telugu, Myanmar, Khmer, Thai, Hebrew, Hangul, Ethiopic, Latin, Greek, Cyrillic, Georgian, Hiragana, Katakana, Mandarin, Sinhala};
417
418 let mut mask = FeatureMask::default_mask(); match script {
423 Arabic => {
425 mask |= Feature::INIT; mask |= Feature::MEDI; mask |= Feature::FINA; mask |= Feature::ISOL; }
432
433 Devanagari | Bengali | Gujarati | Gurmukhi | Kannada | Malayalam | Oriya | Tamil
435 | Telugu => {
436 mask |= Feature::NUKT; mask |= Feature::AKHN; mask |= Feature::RPHF; mask |= Feature::RKRF; mask |= Feature::PREF; mask |= Feature::BLWF; mask |= Feature::ABVF; mask |= Feature::HALF; mask |= Feature::PSTF; mask |= Feature::VATU; mask |= Feature::CJCT; }
448
449 Myanmar => {
451 mask |= Feature::PREF; mask |= Feature::BLWF; mask |= Feature::PSTF; }
455
456 Khmer => {
458 mask |= Feature::PREF; mask |= Feature::BLWF; mask |= Feature::ABVF; mask |= Feature::PSTF; }
463
464 Thai => {
466 }
469
470 Hebrew => {
472 }
475
476 Hangul => {
478 }
482
483 Ethiopic => {
485 }
488
489 Latin | Greek | Cyrillic => {
491 }
497
498 Georgian => {
500 }
502
503 Hiragana | Katakana | Mandarin => {
505 }
509
510 Sinhala => {
512 mask |= Feature::AKHN; mask |= Feature::RPHF; mask |= Feature::VATU; }
516 }
517
518 mask
519}
520
521#[allow(clippy::match_same_arms)] const fn to_opentype_script_tag(script: Script) -> u32 {
524 use Script::{Arabic, Bengali, Cyrillic, Devanagari, Ethiopic, Georgian, Greek, Gujarati, Gurmukhi, Hangul, Hebrew, Hiragana, Kannada, Katakana, Khmer, Latin, Malayalam, Mandarin, Myanmar, Oriya, Sinhala, Tamil, Telugu, Thai};
525 match script {
527 Arabic => u32::from_be_bytes(*b"arab"),
528 Bengali => u32::from_be_bytes(*b"beng"),
529 Cyrillic => u32::from_be_bytes(*b"cyrl"),
530 Devanagari => u32::from_be_bytes(*b"deva"),
531 Ethiopic => u32::from_be_bytes(*b"ethi"),
532 Georgian => u32::from_be_bytes(*b"geor"),
533 Greek => u32::from_be_bytes(*b"grek"),
534 Gujarati => u32::from_be_bytes(*b"gujr"),
535 Gurmukhi => u32::from_be_bytes(*b"guru"),
536 Hangul => u32::from_be_bytes(*b"hang"),
537 Hebrew => u32::from_be_bytes(*b"hebr"),
538 Hiragana => u32::from_be_bytes(*b"kana"),
541 Kannada => u32::from_be_bytes(*b"knda"),
542 Katakana => u32::from_be_bytes(*b"kana"),
543 Khmer => u32::from_be_bytes(*b"khmr"),
544 Latin => u32::from_be_bytes(*b"latn"),
545 Malayalam => u32::from_be_bytes(*b"mlym"),
546 Mandarin => u32::from_be_bytes(*b"hani"),
547 Myanmar => u32::from_be_bytes(*b"mymr"),
548 Oriya => u32::from_be_bytes(*b"orya"),
549 Sinhala => u32::from_be_bytes(*b"sinh"),
550 Tamil => u32::from_be_bytes(*b"taml"),
551 Telugu => u32::from_be_bytes(*b"telu"),
552 Thai => u32::from_be_bytes(*b"thai"),
553 }
554}
555
556fn parse_font_feature(feature_str: &str) -> Option<(u32, u32)> {
559 let mut parts = feature_str.split('=');
560 let tag_str = parts.next()?.trim();
561 let value_str = parts.next().unwrap_or("1").trim(); if tag_str.len() > 4 {
565 return None;
566 }
567 let padded_tag_str = format!("{tag_str:<4}");
569
570 let tag = u32::from_be_bytes(padded_tag_str.as_bytes().try_into().ok()?);
571 let value = value_str.parse::<u32>().ok()?;
572
573 Some((tag, value))
574}
575
576fn add_variant_features(style: &StyleProperties, features: &mut Vec<FeatureInfo>) {
578 let mut add_on = |tag_str: &[u8; 4]| {
580 features.push(FeatureInfo {
581 feature_tag: u32::from_be_bytes(*tag_str),
582 alternate: None,
583 });
584 };
585
586 match style.font_variant_ligatures {
594 FontVariantLigatures::Discretionary => add_on(b"dlig"),
595 FontVariantLigatures::Historical => add_on(b"hlig"),
596 FontVariantLigatures::Contextual => add_on(b"calt"),
597 _ => {} }
599
600 match style.font_variant_caps {
602 FontVariantCaps::SmallCaps => add_on(b"smcp"),
603 FontVariantCaps::AllSmallCaps => {
604 add_on(b"c2sc");
605 add_on(b"smcp");
606 }
607 FontVariantCaps::PetiteCaps => add_on(b"pcap"),
608 FontVariantCaps::AllPetiteCaps => {
609 add_on(b"c2pc");
610 add_on(b"pcap");
611 }
612 FontVariantCaps::Unicase => add_on(b"unic"),
613 FontVariantCaps::TitlingCaps => add_on(b"titl"),
614 FontVariantCaps::Normal => {}
615 }
616
617 match style.font_variant_numeric {
619 FontVariantNumeric::LiningNums => add_on(b"lnum"),
620 FontVariantNumeric::OldstyleNums => add_on(b"onum"),
621 FontVariantNumeric::ProportionalNums => add_on(b"pnum"),
622 FontVariantNumeric::TabularNums => add_on(b"tnum"),
623 FontVariantNumeric::DiagonalFractions => add_on(b"frac"),
624 FontVariantNumeric::StackedFractions => add_on(b"afrc"),
625 FontVariantNumeric::Ordinal => add_on(b"ordn"),
626 FontVariantNumeric::SlashedZero => add_on(b"zero"),
627 FontVariantNumeric::Normal => {}
628 }
629}
630
631#[cfg(feature = "text_layout_hyphenation")]
633#[allow(clippy::match_same_arms)] const fn to_opentype_lang_tag(lang: hyphenation::Language) -> u32 {
635 use hyphenation::Language::{Afrikaans, Albanian, Armenian, Assamese, Basque, Belarusian, Bengali, Bulgarian, Catalan, Chinese, Coptic, Croatian, Czech, Danish, Dutch, EnglishGB, EnglishUS, Esperanto, Estonian, Ethiopic, Finnish, FinnishScholastic, French, Friulan, Galician, Georgian, German1901, German1996, GermanSwiss, GreekAncient, GreekMono, GreekPoly, Gujarati, Hindi, Hungarian, Icelandic, Indonesian, Interlingua, Irish, Italian, Kannada, Kurmanji, Latin, LatinClassic, LatinLiturgical, Latvian, Lithuanian, Macedonian, Malayalam, Marathi, Mongolian, NorwegianBokmal, NorwegianNynorsk, Occitan, Oriya, Pali, Panjabi, Piedmontese, Polish, Portuguese, Romanian, Romansh, Russian, Sanskrit, SerbianCyrillic, SerbocroatianCyrillic, SerbocroatianLatin, SlavonicChurch, Slovak, Slovenian, Spanish, Swedish, Tamil, Telugu, Thai, Turkish, Turkmen, Ukrainian, Uppersorbian, Welsh};
636 let tag_bytes = match lang {
639 Afrikaans => *b"AFK ",
640 Albanian => *b"SQI ",
641 Armenian => *b"HYE ",
642 Assamese => *b"ASM ",
643 Basque => *b"EUQ ",
644 Belarusian => *b"BEL ",
645 Bengali => *b"BEN ",
646 Bulgarian => *b"BGR ",
647 Catalan => *b"CAT ",
648 Chinese => *b"ZHS ",
649 Coptic => *b"COP ",
650 Croatian => *b"HRV ",
651 Czech => *b"CSY ",
652 Danish => *b"DAN ",
653 Dutch => *b"NLD ",
654 EnglishGB => *b"ENG ",
655 EnglishUS => *b"ENU ",
656 Esperanto => *b"ESP ",
657 Estonian => *b"ETI ",
658 Ethiopic => *b"ETH ",
659 Finnish => *b"FIN ",
660 FinnishScholastic => *b"FIN ",
661 French => *b"FRA ",
662 Friulan => *b"FRL ",
663 Galician => *b"GLC ",
664 Georgian => *b"KAT ",
665 German1901 => *b"DEU ",
666 German1996 => *b"DEU ",
667 GermanSwiss => *b"DES ",
668 GreekAncient => *b"GRC ",
669 GreekMono => *b"ELL ",
670 GreekPoly => *b"ELL ",
671 Gujarati => *b"GUJ ",
672 Hindi => *b"HIN ",
673 Hungarian => *b"HUN ",
674 Icelandic => *b"ISL ",
675 Indonesian => *b"IND ",
676 Interlingua => *b"INA ",
677 Irish => *b"IRI ",
678 Italian => *b"ITA ",
679 Kannada => *b"KAN ",
680 Kurmanji => *b"KUR ",
681 Latin => *b"LAT ",
682 LatinClassic => *b"LAT ",
683 LatinLiturgical => *b"LAT ",
684 Latvian => *b"LVI ",
685 Lithuanian => *b"LTH ",
686 Macedonian => *b"MKD ",
687 Malayalam => *b"MAL ",
688 Marathi => *b"MAR ",
689 Mongolian => *b"MNG ",
690 NorwegianBokmal => *b"NOR ",
691 NorwegianNynorsk => *b"NYN ",
692 Occitan => *b"OCI ",
693 Oriya => *b"ORI ",
694 Pali => *b"PLI ",
695 Panjabi => *b"PAN ",
696 Piedmontese => *b"PMS ",
697 Polish => *b"PLK ",
698 Portuguese => *b"PTG ",
699 Romanian => *b"ROM ",
700 Romansh => *b"RMC ",
701 Russian => *b"RUS ",
702 Sanskrit => *b"SAN ",
703 SerbianCyrillic => *b"SRB ",
704 SerbocroatianCyrillic => *b"SHC ",
705 SerbocroatianLatin => *b"SHL ",
706 SlavonicChurch => *b"CSL ",
707 Slovak => *b"SKY ",
708 Slovenian => *b"SLV ",
709 Spanish => *b"ESP ",
710 Swedish => *b"SVE ",
711 Tamil => *b"TAM ",
712 Telugu => *b"TEL ",
713 Thai => *b"THA ",
714 Turkish => *b"TRK ",
715 Turkmen => *b"TUK ",
716 Ukrainian => *b"UKR ",
717 Uppersorbian => *b"HSB ",
718 Welsh => *b"CYM ",
719 };
720 u32::from_be_bytes(tag_bytes)
721}
722
723#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss, clippy::cast_sign_loss)] #[allow(clippy::similar_names)] #[allow(clippy::too_many_lines)] fn shape_text_internal(
729 parsed_font: &ParsedFont,
730 text: &str,
731 script: Script,
732 language: crate::text3::script::Language,
733 direction: BidiDirection,
734 style: &StyleProperties,
735) -> Result<Vec<Glyph>, LayoutError> {
736 let script_tag = to_opentype_script_tag(script);
737 #[cfg(feature = "text_layout_hyphenation")]
738 let lang_tag = to_opentype_lang_tag(language);
739 #[cfg(not(feature = "text_layout_hyphenation"))]
740 let lang_tag = 0u32;
741
742 let mut user_features: Vec<FeatureInfo> = style
745 .font_features
746 .iter()
747 .filter_map(|s| parse_font_feature(s))
748 .map(|(tag, value)| FeatureInfo {
749 feature_tag: tag,
750 alternate: if value > 1 {
751 Some(value as usize)
752 } else {
753 None
754 },
755 })
756 .collect();
757 add_variant_features(style, &mut user_features);
758
759 let opt_gdef = parsed_font.opt_gdef_table.as_deref();
760
761 let mut raw_glyphs: Vec<gsub::RawGlyph<()>> = Vec::new();
762 {
763 let mut ci = 0usize;
764 while ci < text.len() {
765 let Some(ch) = text[ci..].chars().next() else {
766 break;
767 };
768 let glyph_index = parsed_font.lookup_glyph_index(ch as u32).unwrap_or(0);
769 raw_glyphs.push(gsub::RawGlyph {
780 unicodes: tinyvec::tiny_vec![[char; 1] => ch],
781 glyph_index,
782 liga_component_pos: 0,
783 glyph_origin: gsub::GlyphOrigin::Char(ch),
784 flags: gsub::RawGlyphFlags::empty(),
785 extra_data: (),
786 variation: None,
787 });
788 ci += ch.len_utf8();
789 }
790 }
791
792 if let Some(gsub) = parsed_font.gsub() {
793 let feature_mask = build_feature_mask_for_script(script);
801 let custom_features: &[FeatureInfo] = user_features.as_slice();
802
803 let dotted_circle_index = parsed_font
804 .lookup_glyph_index(allsorts::DOTTED_CIRCLE as u32)
805 .unwrap_or(0);
806 gsub::apply(
807 dotted_circle_index,
808 gsub,
809 opt_gdef,
810 script_tag,
811 Some(lang_tag),
812 feature_mask,
813 custom_features,
814 None,
815 parsed_font.num_glyphs(),
816 &mut raw_glyphs,
817 )
818 .map_err(|e| LayoutError::ShapingError(e.to_string()))?;
819 }
820
821 let mut infos = gpos::Info::init_from_glyphs(opt_gdef, raw_glyphs);
822
823 if let Some(gpos) = parsed_font.gpos() {
824 let kern_table = parsed_font
825 .opt_kern_table
826 .as_ref()
827 .map(|kt| kt.as_borrowed());
828 let apply_kerning = true; gpos::apply(
830 gpos,
831 opt_gdef,
832 kern_table,
833 apply_kerning,
834 FeatureMask::empty(),
835 &user_features,
836 None,
837 script_tag,
838 Some(lang_tag),
839 &mut infos,
840 )
841 .map_err(|e| LayoutError::ShapingError(e.to_string()))?;
842 } else {
843 let kern_table = parsed_font
848 .opt_kern_table
849 .as_ref()
850 .map(|kt| kt.as_borrowed());
851 gpos::apply_fallback(kern_table, script_tag, &mut infos)
852 .map_err(|e| LayoutError::ShapingError(e.to_string()))?;
853 }
854
855 let font_size = style.font_size_px;
856 let scale_factor = if parsed_font.font_metrics.units_per_em > 0 {
857 font_size / f32::from(parsed_font.font_metrics.units_per_em)
858 } else {
859 FALLBACK_SCALE
860 };
861
862 let font_hash = parsed_font.get_hash();
863 let font_metrics = LayoutFontMetrics {
864 ascent: parsed_font.font_metrics.ascent,
865 descent: parsed_font.font_metrics.descent,
866 line_gap: parsed_font.font_metrics.line_gap,
867 units_per_em: parsed_font.font_metrics.units_per_em,
868 x_height: parsed_font.font_metrics.x_height,
869 cap_height: parsed_font.font_metrics.cap_height,
870 };
871 let style_arc = Arc::new(style.clone());
872 let bidi_level = BidiLevel::new(u8::from(direction.is_rtl()));
873
874 let mut shaped_glyphs = Vec::new();
875 let mut byte_cursor = 0usize;
883 for info in &infos {
884 let uni_len: usize = info.glyph.unicodes.iter().map(|c| c.len_utf8()).sum();
885 let (byte_index, byte_len) = if info.glyph.multi_subst_dup() || uni_len == 0 {
886 (byte_cursor.min(text.len()), 0)
887 } else {
888 let start = byte_cursor.min(text.len());
889 byte_cursor = (byte_cursor + uni_len).min(text.len());
890 (start, uni_len)
891 };
892 let cluster = byte_index as u32;
893 let source_char = info
894 .glyph
895 .unicodes
896 .first()
897 .copied()
898 .or_else(|| text.get(byte_index..).and_then(|s| s.chars().next()))
899 .unwrap_or('\u{FFFD}');
900
901 let base_advance = parsed_font.get_horizontal_advance(info.glyph.glyph_index);
902 let ppem = font_size.round().max(1.0) as u16;
908 let advance = parsed_font
909 .get_hinted_advance_px(info.glyph.glyph_index, ppem)
910 .map_or_else(
911 || f32::from(base_advance) * scale_factor,
912 |hinted| hinted * font_size / f32::from(ppem),
913 );
914 let kerning = f32::from(info.kerning) * scale_factor;
915
916 let (offset_x_units, offset_y_units) =
917 if let gpos::Placement::Distance(x, y) = info.placement {
918 (x, y)
919 } else {
920 (0, 0)
921 };
922 let offset_x = offset_x_units as f32 * scale_factor;
923 let offset_y = offset_y_units as f32 * scale_factor;
924
925 let vert = parsed_font.get_vertical_metrics(info.glyph.glyph_index);
926 let glyph = Glyph {
927 glyph_id: info.glyph.glyph_index,
928 codepoint: source_char,
929 font_hash,
930 font_metrics,
931 style: Arc::clone(&style_arc),
932 source: GlyphSource::Char,
933 logical_byte_index: byte_index,
934 logical_byte_len: byte_len,
935 content_index: 0,
936 cluster,
937 advance,
938 kerning,
939 offset: Point {
940 x: offset_x,
941 y: offset_y,
942 },
943 vertical_advance: vert.as_ref().map_or(0.0, |v| v.advance * font_size),
944 vertical_origin_y: vert.as_ref().map_or(0.0, |v| v.origin_y * font_size),
945 vertical_bearing: vert
946 .map_or(Point { x: 0.0, y: 0.0 }, |v| Point { x: v.bearing_x * font_size, y: v.bearing_y * font_size }),
947 orientation: GlyphOrientation::Horizontal,
948 script,
949 bidi_level,
950 };
951 shaped_glyphs.push(glyph);
952 }
953
954 Ok(shaped_glyphs)
955}
956
957pub fn shape_text_for_parsed_font(
963 parsed_font: &ParsedFont,
964 text: &str,
965 script: Script,
966 language: crate::text3::script::Language,
967 direction: BidiDirection,
968 style: &StyleProperties,
969) -> Result<Vec<Glyph>, LayoutError> {
970 shape_text_internal(parsed_font, text, script, language, direction, style)
972}