azul_layout/text/
shaping.rs

1use alloc::{boxed::Box, collections::btree_map::BTreeMap, rc::Rc, vec::Vec};
2
3use allsorts_subset_browser::{
4    binary::read::ReadScope,
5    font_data::FontData,
6    gsub::RawGlyphFlags,
7    layout::{GDEFTable, LayoutCache, GPOS, GSUB},
8    tables::{
9        cmap::{owned::CmapSubtable as OwnedCmapSubtable, CmapSubtable},
10        glyf::{BoundingBox, GlyfRecord, GlyfTable, Glyph},
11        loca::{LocaOffsets, LocaTable},
12        FontTableProvider, HeadTable, HheaTable, MaxpTable,
13    },
14};
15use azul_core::app_resources::{
16    Advance, Anchor, FontMetrics, GlyphInfo, GlyphOrigin, Placement, RawGlyph, VariationSelector,
17};
18use tinyvec::tiny_vec;
19
20pub fn get_font_metrics(font_bytes: &[u8], font_index: usize) -> FontMetrics {
21    #[derive(Default)]
22    struct Os2Info {
23        x_avg_char_width: i16,
24        us_weight_class: u16,
25        us_width_class: u16,
26        fs_type: u16,
27        y_subscript_x_size: i16,
28        y_subscript_y_size: i16,
29        y_subscript_x_offset: i16,
30        y_subscript_y_offset: i16,
31        y_superscript_x_size: i16,
32        y_superscript_y_size: i16,
33        y_superscript_x_offset: i16,
34        y_superscript_y_offset: i16,
35        y_strikeout_size: i16,
36        y_strikeout_position: i16,
37        s_family_class: i16,
38        panose: [u8; 10],
39        ul_unicode_range1: u32,
40        ul_unicode_range2: u32,
41        ul_unicode_range3: u32,
42        ul_unicode_range4: u32,
43        ach_vend_id: u32,
44        fs_selection: u16,
45        us_first_char_index: u16,
46        us_last_char_index: u16,
47        s_typo_ascender: Option<i16>,
48        s_typo_descender: Option<i16>,
49        s_typo_line_gap: Option<i16>,
50        us_win_ascent: Option<u16>,
51        us_win_descent: Option<u16>,
52        ul_code_page_range1: Option<u32>,
53        ul_code_page_range2: Option<u32>,
54        sx_height: Option<i16>,
55        s_cap_height: Option<i16>,
56        us_default_char: Option<u16>,
57        us_break_char: Option<u16>,
58        us_max_context: Option<u16>,
59        us_lower_optical_point_size: Option<u16>,
60        us_upper_optical_point_size: Option<u16>,
61    }
62
63    let scope = ReadScope::new(font_bytes);
64    let font_file = match scope.read::<FontData<'_>>() {
65        Ok(o) => o,
66        Err(_) => return FontMetrics::default(),
67    };
68    let provider = match font_file.table_provider(font_index) {
69        Ok(o) => o,
70        Err(_) => return FontMetrics::default(),
71    };
72    let font = match allsorts_subset_browser::font::Font::new(provider).ok() {
73        Some(s) => s,
74        _ => return FontMetrics::default(),
75    };
76
77    // read the HHEA table to get the metrics for horizontal layout
78    let hhea_table = &font.hhea_table;
79    let head_table = match font.head_table().ok() {
80        Some(Some(s)) => s,
81        _ => return FontMetrics::default(),
82    };
83
84    let os2_table = match font.os2_table().ok() {
85        Some(Some(s)) => Os2Info {
86            x_avg_char_width: s.x_avg_char_width,
87            us_weight_class: s.us_weight_class,
88            us_width_class: s.us_width_class,
89            fs_type: s.fs_type,
90            y_subscript_x_size: s.y_subscript_x_size,
91            y_subscript_y_size: s.y_subscript_y_size,
92            y_subscript_x_offset: s.y_subscript_x_offset,
93            y_subscript_y_offset: s.y_subscript_y_offset,
94            y_superscript_x_size: s.y_superscript_x_size,
95            y_superscript_y_size: s.y_superscript_y_size,
96            y_superscript_x_offset: s.y_superscript_x_offset,
97            y_superscript_y_offset: s.y_superscript_y_offset,
98            y_strikeout_size: s.y_strikeout_size,
99            y_strikeout_position: s.y_strikeout_position,
100            s_family_class: s.s_family_class,
101            panose: s.panose,
102            ul_unicode_range1: s.ul_unicode_range1,
103            ul_unicode_range2: s.ul_unicode_range2,
104            ul_unicode_range3: s.ul_unicode_range3,
105            ul_unicode_range4: s.ul_unicode_range4,
106            ach_vend_id: s.ach_vend_id,
107            fs_selection: s.fs_selection.bits(),
108            us_first_char_index: s.us_first_char_index,
109            us_last_char_index: s.us_last_char_index,
110
111            s_typo_ascender: s.version0.as_ref().map(|q| q.s_typo_ascender),
112            s_typo_descender: s.version0.as_ref().map(|q| q.s_typo_descender),
113            s_typo_line_gap: s.version0.as_ref().map(|q| q.s_typo_line_gap),
114            us_win_ascent: s.version0.as_ref().map(|q| q.us_win_ascent),
115            us_win_descent: s.version0.as_ref().map(|q| q.us_win_descent),
116
117            ul_code_page_range1: s.version1.as_ref().map(|q| q.ul_code_page_range1),
118            ul_code_page_range2: s.version1.as_ref().map(|q| q.ul_code_page_range2),
119
120            sx_height: s.version2to4.as_ref().map(|q| q.sx_height),
121            s_cap_height: s.version2to4.as_ref().map(|q| q.s_cap_height),
122            us_default_char: s.version2to4.as_ref().map(|q| q.us_default_char),
123            us_break_char: s.version2to4.as_ref().map(|q| q.us_break_char),
124            us_max_context: s.version2to4.as_ref().map(|q| q.us_max_context),
125
126            us_lower_optical_point_size: s.version5.as_ref().map(|q| q.us_lower_optical_point_size),
127            us_upper_optical_point_size: s.version5.as_ref().map(|q| q.us_upper_optical_point_size),
128        },
129        _ => Os2Info::default(),
130    };
131
132    FontMetrics {
133        // head table
134        units_per_em: if head_table.units_per_em == 0 {
135            1000_u16
136        } else {
137            head_table.units_per_em
138        },
139        font_flags: head_table.flags,
140        x_min: head_table.x_min,
141        y_min: head_table.y_min,
142        x_max: head_table.x_max,
143        y_max: head_table.y_max,
144
145        // hhea table
146        ascender: hhea_table.ascender,
147        descender: hhea_table.descender,
148        line_gap: hhea_table.line_gap,
149        advance_width_max: hhea_table.advance_width_max,
150        min_left_side_bearing: hhea_table.min_left_side_bearing,
151        min_right_side_bearing: hhea_table.min_right_side_bearing,
152        x_max_extent: hhea_table.x_max_extent,
153        caret_slope_rise: hhea_table.caret_slope_rise,
154        caret_slope_run: hhea_table.caret_slope_run,
155        caret_offset: hhea_table.caret_offset,
156        num_h_metrics: hhea_table.num_h_metrics,
157
158        // os/2 table
159        x_avg_char_width: os2_table.x_avg_char_width,
160        us_weight_class: os2_table.us_weight_class,
161        us_width_class: os2_table.us_width_class,
162        fs_type: os2_table.fs_type,
163        y_subscript_x_size: os2_table.y_subscript_x_size,
164        y_subscript_y_size: os2_table.y_subscript_y_size,
165        y_subscript_x_offset: os2_table.y_subscript_x_offset,
166        y_subscript_y_offset: os2_table.y_subscript_y_offset,
167        y_superscript_x_size: os2_table.y_superscript_x_size,
168        y_superscript_y_size: os2_table.y_superscript_y_size,
169        y_superscript_x_offset: os2_table.y_superscript_x_offset,
170        y_superscript_y_offset: os2_table.y_superscript_y_offset,
171        y_strikeout_size: os2_table.y_strikeout_size,
172        y_strikeout_position: os2_table.y_strikeout_position,
173        s_family_class: os2_table.s_family_class,
174        panose: os2_table.panose,
175        ul_unicode_range1: os2_table.ul_unicode_range1,
176        ul_unicode_range2: os2_table.ul_unicode_range2,
177        ul_unicode_range3: os2_table.ul_unicode_range3,
178        ul_unicode_range4: os2_table.ul_unicode_range4,
179        ach_vend_id: os2_table.ach_vend_id,
180        fs_selection: os2_table.fs_selection,
181        us_first_char_index: os2_table.us_first_char_index,
182        us_last_char_index: os2_table.us_last_char_index,
183        s_typo_ascender: os2_table.s_typo_ascender.into(),
184        s_typo_descender: os2_table.s_typo_descender.into(),
185        s_typo_line_gap: os2_table.s_typo_line_gap.into(),
186        us_win_ascent: os2_table.us_win_ascent.into(),
187        us_win_descent: os2_table.us_win_descent.into(),
188        ul_code_page_range1: os2_table.ul_code_page_range1.into(),
189        ul_code_page_range2: os2_table.ul_code_page_range2.into(),
190        sx_height: os2_table.sx_height.into(),
191        s_cap_height: os2_table.s_cap_height.into(),
192        us_default_char: os2_table.us_default_char.into(),
193        us_break_char: os2_table.us_break_char.into(),
194        us_max_context: os2_table.us_max_context.into(),
195        us_lower_optical_point_size: os2_table.us_lower_optical_point_size.into(),
196        us_upper_optical_point_size: os2_table.us_upper_optical_point_size.into(),
197    }
198}
199
200#[derive(Clone)]
201pub struct ParsedFont {
202    pub font_metrics: FontMetrics,
203    pub num_glyphs: u16,
204    pub hhea_table: HheaTable,
205    pub hmtx_data: Vec<u8>,
206    pub maxp_table: MaxpTable,
207    pub gsub_cache: Option<LayoutCache<GSUB>>,
208    pub gpos_cache: Option<LayoutCache<GPOS>>,
209    pub opt_gdef_table: Option<Rc<GDEFTable>>,
210    pub glyph_records_decoded: BTreeMap<u16, OwnedGlyph>,
211    pub space_width: Option<usize>,
212    pub cmap_subtable: Option<OwnedCmapSubtable>,
213}
214
215#[derive(Debug, Clone, PartialEq, PartialOrd)]
216#[repr(C, u8)]
217pub enum GlyphOutlineOperation {
218    MoveTo(OutlineMoveTo),
219    LineTo(OutlineLineTo),
220    QuadraticCurveTo(OutlineQuadTo),
221    CubicCurveTo(OutlineCubicTo),
222    ClosePath,
223}
224
225#[derive(Debug, Clone, PartialEq, PartialOrd)]
226#[repr(C)]
227pub struct OutlineMoveTo {
228    pub x: f32,
229    pub y: f32,
230}
231
232#[derive(Debug, Clone, PartialEq, PartialOrd)]
233#[repr(C)]
234pub struct OutlineLineTo {
235    pub x: f32,
236    pub y: f32,
237}
238
239#[derive(Debug, Clone, PartialEq, PartialOrd)]
240#[repr(C)]
241pub struct OutlineQuadTo {
242    pub ctrl_1_x: f32,
243    pub ctrl_1_y: f32,
244    pub end_x: f32,
245    pub end_y: f32,
246}
247
248#[derive(Debug, Clone, PartialEq, PartialOrd)]
249#[repr(C)]
250pub struct OutlineCubicTo {
251    pub ctrl_1_x: f32,
252    pub ctrl_1_y: f32,
253    pub ctrl_2_x: f32,
254    pub ctrl_2_y: f32,
255    pub end_x: f32,
256    pub end_y: f32,
257}
258
259#[derive(Debug, Clone, PartialEq, PartialOrd)]
260#[repr(C)]
261pub struct GlyphOutline {
262    pub operations: GlyphOutlineOperationVec,
263}
264
265#[derive(Debug, Clone, PartialEq, PartialOrd)]
266struct GlyphOutlineBuilder {
267    operations: Vec<GlyphOutlineOperation>,
268}
269
270impl Default for GlyphOutlineBuilder {
271    fn default() -> Self {
272        GlyphOutlineBuilder {
273            operations: Vec::new(),
274        }
275    }
276}
277
278impl ttf_parser::OutlineBuilder for GlyphOutlineBuilder {
279    fn move_to(&mut self, x: f32, y: f32) {
280        self.operations
281            .push(GlyphOutlineOperation::MoveTo(OutlineMoveTo { x, y }));
282    }
283    fn line_to(&mut self, x: f32, y: f32) {
284        self.operations
285            .push(GlyphOutlineOperation::LineTo(OutlineLineTo { x, y }));
286    }
287    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
288        self.operations
289            .push(GlyphOutlineOperation::QuadraticCurveTo(OutlineQuadTo {
290                ctrl_1_x: x1,
291                ctrl_1_y: y1,
292                end_x: x,
293                end_y: y,
294            }));
295    }
296    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
297        self.operations
298            .push(GlyphOutlineOperation::CubicCurveTo(OutlineCubicTo {
299                ctrl_1_x: x1,
300                ctrl_1_y: y1,
301                ctrl_2_x: x2,
302                ctrl_2_y: y2,
303                end_x: x,
304                end_y: y,
305            }));
306    }
307    fn close(&mut self) {
308        self.operations.push(GlyphOutlineOperation::ClosePath);
309    }
310}
311
312azul_css::impl_vec!(
313    GlyphOutlineOperation,
314    GlyphOutlineOperationVec,
315    GlyphOutlineOperationVecDestructor
316);
317azul_css::impl_vec_clone!(
318    GlyphOutlineOperation,
319    GlyphOutlineOperationVec,
320    GlyphOutlineOperationVecDestructor
321);
322azul_css::impl_vec_debug!(GlyphOutlineOperation, GlyphOutlineOperationVec);
323azul_css::impl_vec_partialord!(GlyphOutlineOperation, GlyphOutlineOperationVec);
324azul_css::impl_vec_partialeq!(GlyphOutlineOperation, GlyphOutlineOperationVec);
325
326#[derive(Debug, Clone)]
327#[repr(C)]
328pub struct OwnedGlyphBoundingBox {
329    pub max_x: i16,
330    pub max_y: i16,
331    pub min_x: i16,
332    pub min_y: i16,
333}
334
335#[derive(Debug, Clone)]
336pub struct OwnedGlyph {
337    pub bounding_box: OwnedGlyphBoundingBox,
338    pub horz_advance: u16,
339    pub outline: Option<GlyphOutline>,
340}
341
342impl OwnedGlyph {
343    fn from_glyph_data<'a>(glyph: Glyph<'a>, horz_advance: u16) -> Option<Self> {
344        let bbox = glyph.bounding_box()?;
345        Some(Self {
346            bounding_box: OwnedGlyphBoundingBox {
347                max_x: bbox.x_max,
348                max_y: bbox.y_max,
349                min_x: bbox.x_min,
350                min_y: bbox.y_min,
351            },
352            horz_advance,
353            outline: None,
354        })
355    }
356}
357
358impl ParsedFont {
359    pub fn from_bytes(
360        font_bytes: &[u8],
361        font_index: usize,
362        parse_glyph_outlines: bool,
363    ) -> Option<Self> {
364        use allsorts_subset_browser::tag;
365
366        let scope = ReadScope::new(font_bytes);
367        let font_file = scope.read::<FontData<'_>>().ok()?;
368        let provider = font_file.table_provider(font_index).ok()?;
369
370        let head_table = provider
371            .table_data(tag::HEAD)
372            .ok()
373            .and_then(|head_data| ReadScope::new(&head_data?).read::<HeadTable>().ok());
374
375        let maxp_table = provider
376            .table_data(tag::MAXP)
377            .ok()
378            .and_then(|maxp_data| ReadScope::new(&maxp_data?).read::<MaxpTable>().ok())
379            .unwrap_or(MaxpTable {
380                num_glyphs: 0,
381                version1_sub_table: None,
382            });
383
384        let index_to_loc = head_table
385            .map(|s| s.index_to_loc_format)
386            .unwrap_or(allsorts_subset_browser::tables::IndexToLocFormat::Long);
387        let num_glyphs = maxp_table.num_glyphs as usize;
388
389        let loca_table = provider.table_data(tag::LOCA).ok();
390        let loca_table = loca_table
391            .as_ref()
392            .and_then(|loca_data| {
393                ReadScope::new(&loca_data.as_ref()?)
394                    .read_dep::<LocaTable<'_>>((num_glyphs, index_to_loc))
395                    .ok()
396            })
397            .unwrap_or(LocaTable {
398                offsets: LocaOffsets::Long(
399                    allsorts_subset_browser::binary::read::ReadArray::empty(),
400                ),
401            });
402
403        let glyf_table = provider.table_data(tag::GLYF).ok();
404        let mut glyf_table = glyf_table
405            .as_ref()
406            .and_then(|glyf_data| {
407                ReadScope::new(&glyf_data.as_ref()?)
408                    .read_dep::<GlyfTable<'_>>(&loca_table)
409                    .ok()
410            })
411            .unwrap_or(GlyfTable::new(Vec::new()).unwrap());
412
413        let hmtx_data = provider
414            .table_data(tag::HMTX)
415            .ok()
416            .and_then(|s| Some(s?.to_vec()))
417            .unwrap_or_default();
418
419        let hhea_table = provider
420            .table_data(tag::HHEA)
421            .ok()
422            .and_then(|hhea_data| ReadScope::new(&hhea_data?).read::<HheaTable>().ok())
423            .unwrap_or(unsafe { std::mem::zeroed() });
424
425        let font_metrics = get_font_metrics(font_bytes, font_index);
426
427        // not parsing glyph outlines can save lots of memory
428        let glyph_records_decoded = glyf_table
429            .records_mut()
430            .into_iter()
431            .enumerate()
432            .filter_map(|(glyph_index, glyph_record)| {
433                if glyph_index > (u16::MAX as usize) {
434                    return None;
435                }
436                glyph_record.parse().ok()?;
437                let glyph_index = glyph_index as u16;
438                let horz_advance = allsorts_subset_browser::glyph_info::advance(
439                    &maxp_table,
440                    &hhea_table,
441                    &hmtx_data,
442                    glyph_index,
443                )
444                .unwrap_or_default();
445
446                match glyph_record {
447                    GlyfRecord::Present { .. } => None,
448                    GlyfRecord::Parsed(g) => OwnedGlyph::from_glyph_data(g.clone(), horz_advance)
449                        .map(|g| (glyph_index, g)),
450                }
451            })
452            .collect::<Vec<_>>();
453
454        let glyph_records_decoded = glyph_records_decoded.into_iter().collect();
455
456        let mut font_data_impl = allsorts_subset_browser::font::Font::new(provider).ok()?;
457
458        // required for font layout: gsub_cache, gpos_cache and gdef_table
459        let gsub_cache = font_data_impl.gsub_cache().ok().and_then(|s| s);
460        let gpos_cache = font_data_impl.gpos_cache().ok().and_then(|s| s);
461        let opt_gdef_table = font_data_impl.gdef_table().ok().and_then(|o| o);
462        let num_glyphs = font_data_impl.num_glyphs();
463
464        let cmap_subtable = ReadScope::new(font_data_impl.cmap_subtable_data());
465        let cmap_subtable = cmap_subtable
466            .read::<CmapSubtable<'_>>()
467            .ok()
468            .and_then(|s| s.to_owned());
469
470        let mut font = ParsedFont {
471            font_metrics,
472            num_glyphs,
473            hhea_table,
474            hmtx_data,
475            maxp_table,
476            gsub_cache,
477            gpos_cache,
478            opt_gdef_table,
479            cmap_subtable,
480            glyph_records_decoded,
481            space_width: None,
482        };
483
484        let space_width = font.get_space_width_internal();
485        font.space_width = space_width;
486
487        Some(font)
488    }
489
490    fn get_space_width_internal(&mut self) -> Option<usize> {
491        let glyph_index = self.lookup_glyph_index(' ' as u32)?;
492        allsorts_subset_browser::glyph_info::advance(
493            &self.maxp_table,
494            &self.hhea_table,
495            &self.hmtx_data,
496            glyph_index,
497        )
498        .ok()
499        .map(|s| s as usize)
500    }
501
502    /// Returns the width of the space " " character
503    #[inline]
504    pub const fn get_space_width(&self) -> Option<usize> {
505        self.space_width
506    }
507
508    pub fn get_horizontal_advance(&self, glyph_index: u16) -> u16 {
509        self.glyph_records_decoded
510            .get(&glyph_index)
511            .map(|gi| gi.horz_advance)
512            .unwrap_or_default()
513    }
514
515    // get the x and y size of a glyph in unscaled units
516    pub fn get_glyph_size(&self, glyph_index: u16) -> Option<(i32, i32)> {
517        let g = self.glyph_records_decoded.get(&glyph_index)?;
518        let glyph_width = g.bounding_box.max_x as i32 - g.bounding_box.min_x as i32; // width
519        let glyph_height = g.bounding_box.max_y as i32 - g.bounding_box.min_y as i32; // height
520        Some((glyph_width, glyph_height))
521    }
522
523    pub fn shape(&self, text: &[u32], script: u32, lang: Option<u32>) -> ShapedTextBufferUnsized {
524        shape(self, text, script, lang).unwrap_or_default()
525    }
526
527    pub fn lookup_glyph_index(&self, c: u32) -> Option<u16> {
528        match self
529            .cmap_subtable
530            .as_ref()
531            .and_then(|s| s.map_glyph(c).ok())
532        {
533            Some(Some(c)) => Some(c),
534            _ => None,
535        }
536    }
537}
538
539#[derive(Debug, PartialEq, Default)]
540pub struct ShapedTextBufferUnsized {
541    pub infos: Vec<GlyphInfo>,
542}
543
544impl ShapedTextBufferUnsized {
545    /// Get the word width in unscaled units (respects kerning)
546    pub fn get_word_visual_width_unscaled(&self) -> usize {
547        self.infos
548            .iter()
549            .map(|s| s.size.get_x_advance_total_unscaled() as usize)
550            .sum()
551    }
552}
553
554/// Generate a 4-byte font table tag from byte string
555///
556/// Example:
557///
558/// ```
559/// assert_eq!(tag!(b"glyf"), 0x676C7966);
560/// ```
561macro_rules! tag {
562    ($w:expr) => {
563        tag(*$w)
564    };
565}
566
567const fn tag(chars: [u8; 4]) -> u32 {
568    ((chars[3] as u32) << 0)
569        | ((chars[2] as u32) << 8)
570        | ((chars[1] as u32) << 16)
571        | ((chars[0] as u32) << 24)
572}
573
574/// Estimate the language and the script from the text (uses trigrams)
575#[allow(dead_code)]
576pub fn estimate_script_and_language(text: &str) -> (u32, Option<u32>) {
577    use crate::text::script::Script; // whatlang::Script
578
579    // https://docs.microsoft.com/en-us/typography/opentype/spec/scripttags
580
581    const TAG_ADLM: u32 = tag!(b"adlm"); // Adlam
582    const TAG_AHOM: u32 = tag!(b"ahom"); // Ahom
583    const TAG_HLUW: u32 = tag!(b"hluw"); // Anatolian Hieroglyphs
584    const TAG_ARAB: u32 = tag!(b"arab"); // Arabic
585    const TAG_ARMN: u32 = tag!(b"armn"); // Armenian
586    const TAG_AVST: u32 = tag!(b"avst"); // Avestan
587    const TAG_BALI: u32 = tag!(b"bali"); // Balinese
588    const TAG_BAMU: u32 = tag!(b"bamu"); // Bamum
589    const TAG_BASS: u32 = tag!(b"bass"); // Bassa Vah
590    const TAG_BATK: u32 = tag!(b"batk"); // Batak
591    const TAG_BENG: u32 = tag!(b"beng"); // Bengali
592    const TAG_BNG2: u32 = tag!(b"bng2"); // Bengali v.2
593    const TAG_BHKS: u32 = tag!(b"bhks"); // Bhaiksuki
594    const TAG_BOPO: u32 = tag!(b"bopo"); // Bopomofo
595    const TAG_BRAH: u32 = tag!(b"brah"); // Brahmi
596    const TAG_BRAI: u32 = tag!(b"brai"); // Braille
597    const TAG_BUGI: u32 = tag!(b"bugi"); // Buginese
598    const TAG_BUHD: u32 = tag!(b"buhd"); // Buhid
599    const TAG_BYZM: u32 = tag!(b"byzm"); // Byzantine Music
600    const TAG_CANS: u32 = tag!(b"cans"); // Canadian Syllabics
601    const TAG_CARI: u32 = tag!(b"cari"); // Carian
602    const TAG_AGHB: u32 = tag!(b"aghb"); // Caucasian Albanian
603    const TAG_CAKM: u32 = tag!(b"cakm"); // Chakma
604    const TAG_CHAM: u32 = tag!(b"cham"); // Cham
605    const TAG_CHER: u32 = tag!(b"cher"); // Cherokee
606    const TAG_CHRS: u32 = tag!(b"chrs"); // Chorasmian
607    const TAG_HANI: u32 = tag!(b"hani"); // CJK Ideographic
608    const TAG_COPT: u32 = tag!(b"copt"); // Coptic
609    const TAG_CPRT: u32 = tag!(b"cprt"); // Cypriot Syllabary
610    const TAG_CYRL: u32 = tag!(b"cyrl"); // Cyrillic
611    const TAG_DFLT: u32 = tag!(b"DFLT"); // Default
612    const TAG_DSRT: u32 = tag!(b"dsrt"); // Deseret
613    const TAG_DEVA: u32 = tag!(b"deva"); // Devanagari
614    const TAG_DEV2: u32 = tag!(b"dev2"); // Devanagari v.2
615    const TAG_DIAK: u32 = tag!(b"diak"); // Dives Akuru
616    const TAG_DOGR: u32 = tag!(b"dogr"); // Dogra
617    const TAG_DUPL: u32 = tag!(b"dupl"); // Duployan
618    const TAG_EGYP: u32 = tag!(b"egyp"); // Egyptian Hieroglyphs
619    const TAG_ELBA: u32 = tag!(b"elba"); // Elbasan
620    const TAG_ELYM: u32 = tag!(b"elym"); // Elymaic
621    const TAG_ETHI: u32 = tag!(b"ethi"); // Ethiopic
622    const TAG_GEOR: u32 = tag!(b"geor"); // Georgian
623    const TAG_GLAG: u32 = tag!(b"glag"); // Glagolitic
624    const TAG_GOTH: u32 = tag!(b"goth"); // Gothic
625    const TAG_GRAN: u32 = tag!(b"gran"); // Grantha
626    const TAG_GREK: u32 = tag!(b"grek"); // Greek
627    const TAG_GUJR: u32 = tag!(b"gujr"); // Gujarati
628    const TAG_GJR2: u32 = tag!(b"gjr2"); // Gujarati v.2
629    const TAG_GONG: u32 = tag!(b"gong"); // Gunjala Gondi
630    const TAG_GURU: u32 = tag!(b"guru"); // Gurmukhi
631    const TAG_GUR2: u32 = tag!(b"gur2"); // Gurmukhi v.2
632    const TAG_HANG: u32 = tag!(b"hang"); // Hangul
633    const TAG_JAMO: u32 = tag!(b"jamo"); // Hangul Jamo
634    const TAG_ROHG: u32 = tag!(b"rohg"); // Hanifi Rohingya
635    const TAG_HANO: u32 = tag!(b"hano"); // Hanunoo
636    const TAG_HATR: u32 = tag!(b"hatr"); // Hatran
637    const TAG_HEBR: u32 = tag!(b"hebr"); // Hebrew
638    const TAG_HIRG: u32 = tag!(b"kana"); // Hiragana
639    const TAG_ARMI: u32 = tag!(b"armi"); // Imperial Aramaic
640    const TAG_PHLI: u32 = tag!(b"phli"); // Inscriptional Pahlavi
641    const TAG_PRTI: u32 = tag!(b"prti"); // Inscriptional Parthian
642    const TAG_JAVA: u32 = tag!(b"java"); // Javanese
643    const TAG_KTHI: u32 = tag!(b"kthi"); // Kaithi
644    const TAG_KNDA: u32 = tag!(b"knda"); // Kannada
645    const TAG_KND2: u32 = tag!(b"knd2"); // Kannada v.2
646    const TAG_KANA: u32 = tag!(b"kana"); // Katakana
647    const TAG_KALI: u32 = tag!(b"kali"); // Kayah Li
648    const TAG_KHAR: u32 = tag!(b"khar"); // Kharosthi
649    const TAG_KITS: u32 = tag!(b"kits"); // Khitan Small Script
650    const TAG_KHMR: u32 = tag!(b"khmr"); // Khmer
651    const TAG_KHOJ: u32 = tag!(b"khoj"); // Khojki
652    const TAG_SIND: u32 = tag!(b"sind"); // Khudawadi
653    const TAG_LAO: u32 = tag!(b"lao "); // Lao
654    const TAG_LATN: u32 = tag!(b"latn"); // Latin
655    const TAG_LEPC: u32 = tag!(b"lepc"); // Lepcha
656    const TAG_LIMB: u32 = tag!(b"limb"); // Limbu
657    const TAG_LINA: u32 = tag!(b"lina"); // Linear A
658    const TAG_LINB: u32 = tag!(b"linb"); // Linear B
659    const TAG_LISU: u32 = tag!(b"lisu"); // Lisu (Fraser)
660    const TAG_LYCI: u32 = tag!(b"lyci"); // Lycian
661    const TAG_LYDI: u32 = tag!(b"lydi"); // Lydian
662    const TAG_MAHJ: u32 = tag!(b"mahj"); // Mahajani
663    const TAG_MAKA: u32 = tag!(b"maka"); // Makasar
664    const TAG_MLYM: u32 = tag!(b"mlym"); // Malayalam
665    const TAG_MLM2: u32 = tag!(b"mlm2"); // Malayalam v.2
666    const TAG_MAND: u32 = tag!(b"mand"); // Mandaic, Mandaean
667    const TAG_MANI: u32 = tag!(b"mani"); // Manichaean
668    const TAG_MARC: u32 = tag!(b"marc"); // Marchen
669    const TAG_GONM: u32 = tag!(b"gonm"); // Masaram Gondi
670    const TAG_MATH: u32 = tag!(b"math"); // Mathematical Alphanumeric Symbols
671    const TAG_MEDF: u32 = tag!(b"medf"); // Medefaidrin (Oberi Okaime, Oberi kaim)
672    const TAG_MTEI: u32 = tag!(b"mtei"); // Meitei Mayek (Meithei, Meetei)
673    const TAG_MEND: u32 = tag!(b"mend"); // Mende Kikakui
674    const TAG_MERC: u32 = tag!(b"merc"); // Meroitic Cursive
675    const TAG_MERO: u32 = tag!(b"mero"); // Meroitic Hieroglyphs
676    const TAG_PLRD: u32 = tag!(b"plrd"); // Miao
677    const TAG_MODI: u32 = tag!(b"modi"); // Modi
678    const TAG_MONG: u32 = tag!(b"mong"); // Mongolian
679    const TAG_MROO: u32 = tag!(b"mroo"); // Mro
680    const TAG_MULT: u32 = tag!(b"mult"); // Multani
681    const TAG_MUSC: u32 = tag!(b"musc"); // Musical Symbols
682    const TAG_MYMR: u32 = tag!(b"mymr"); // Myanmar
683    const TAG_MYM2: u32 = tag!(b"mym2"); // Myanmar v.2
684    const TAG_NBAT: u32 = tag!(b"nbat"); // Nabataean
685    const TAG_NAND: u32 = tag!(b"nand"); // Nandinagari
686    const TAG_NEWA: u32 = tag!(b"newa"); // Newa
687    const TAG_TALU: u32 = tag!(b"talu"); // New Tai Lue
688    const TAG_NKO: u32 = tag!(b"nko "); // N'Ko
689    const TAG_NSHU: u32 = tag!(b"nshu"); // NĂ¼shu
690    const TAG_HMNP: u32 = tag!(b"hmnp"); // Nyiakeng Puachue Hmong
691    const TAG_ORYA: u32 = tag!(b"orya"); // Odia (formerly Oriya)
692    const TAG_ORY2: u32 = tag!(b"ory2"); // Odia v.2 (formerly Oriya v.2)
693    const TAG_OGAM: u32 = tag!(b"ogam"); // Ogham
694    const TAG_OLCK: u32 = tag!(b"olck"); // Ol Chiki
695    const TAG_ITAL: u32 = tag!(b"ital"); // Old Italic
696    const TAG_HUNG: u32 = tag!(b"hung"); // Old Hungarian
697    const TAG_NARB: u32 = tag!(b"narb"); // Old North Arabian
698    const TAG_PERM: u32 = tag!(b"perm"); // Old Permic
699    const TAG_XPEO: u32 = tag!(b"xpeo"); // Old Persian Cuneiform
700    const TAG_SOGO: u32 = tag!(b"sogo"); // Old Sogdian
701    const TAG_SARB: u32 = tag!(b"sarb"); // Old South Arabian
702    const TAG_ORKH: u32 = tag!(b"orkh"); // Old Turkic, Orkhon Runic
703    const TAG_OSGE: u32 = tag!(b"osge"); // Osage
704    const TAG_OSMA: u32 = tag!(b"osma"); // Osmanya
705    const TAG_HMNG: u32 = tag!(b"hmng"); // Pahawh Hmong
706    const TAG_PALM: u32 = tag!(b"palm"); // Palmyrene
707    const TAG_PAUC: u32 = tag!(b"pauc"); // Pau Cin Hau
708    const TAG_PHAG: u32 = tag!(b"phag"); // Phags-pa
709    const TAG_PHNX: u32 = tag!(b"phnx"); // Phoenician
710    const TAG_PHLP: u32 = tag!(b"phlp"); // Psalter Pahlavi
711    const TAG_RJNG: u32 = tag!(b"rjng"); // Rejang
712    const TAG_RUNR: u32 = tag!(b"runr"); // Runic
713    const TAG_SAMR: u32 = tag!(b"samr"); // Samaritan
714    const TAG_SAUR: u32 = tag!(b"saur"); // Saurashtra
715    const TAG_SHRD: u32 = tag!(b"shrd"); // Sharada
716    const TAG_SHAW: u32 = tag!(b"shaw"); // Shavian
717    const TAG_SIDD: u32 = tag!(b"sidd"); // Siddham
718    const TAG_SGNW: u32 = tag!(b"sgnw"); // Sign Writing
719    const TAG_SINH: u32 = tag!(b"sinh"); // Sinhala
720    const TAG_SOGD: u32 = tag!(b"sogd"); // Sogdian
721    const TAG_SORA: u32 = tag!(b"sora"); // Sora Sompeng
722    const TAG_SOYO: u32 = tag!(b"soyo"); // Soyombo
723    const TAG_XSUX: u32 = tag!(b"xsux"); // Sumero-Akkadian Cuneiform
724    const TAG_SUND: u32 = tag!(b"sund"); // Sundanese
725    const TAG_SYLO: u32 = tag!(b"sylo"); // Syloti Nagri
726    const TAG_SYRC: u32 = tag!(b"syrc"); // Syriac
727    const TAG_TGLG: u32 = tag!(b"tglg"); // Tagalog
728    const TAG_TAGB: u32 = tag!(b"tagb"); // Tagbanwa
729    const TAG_TALE: u32 = tag!(b"tale"); // Tai Le
730    const TAG_LANA: u32 = tag!(b"lana"); // Tai Tham (Lanna)
731    const TAG_TAVT: u32 = tag!(b"tavt"); // Tai Viet
732    const TAG_TAKR: u32 = tag!(b"takr"); // Takri
733    const TAG_TAML: u32 = tag!(b"taml"); // Tamil
734    const TAG_TML2: u32 = tag!(b"tml2"); // Tamil v.2
735    const TAG_TANG: u32 = tag!(b"tang"); // Tangut
736    const TAG_TELU: u32 = tag!(b"telu"); // Telugu
737    const TAG_TEL2: u32 = tag!(b"tel2"); // Telugu v.2
738    const TAG_THAA: u32 = tag!(b"thaa"); // Thaana
739    const TAG_THAI: u32 = tag!(b"thai"); // Thai
740    const TAG_TIBT: u32 = tag!(b"tibt"); // Tibetan
741    const TAG_TFNG: u32 = tag!(b"tfng"); // Tifinagh
742    const TAG_TIRH: u32 = tag!(b"tirh"); // Tirhuta
743    const TAG_UGAR: u32 = tag!(b"ugar"); // Ugaritic Cuneiform
744    const TAG_VAI: u32 = tag!(b"vai "); // Vai
745    const TAG_WCHO: u32 = tag!(b"wcho"); // Wancho
746    const TAG_WARA: u32 = tag!(b"wara"); // Warang Citi
747    const TAG_YEZI: u32 = tag!(b"yezi"); // Yezidi
748    const TAG_ZANB: u32 = tag!(b"zanb"); // Zanabazar Square
749                                         // missing: Yi
750
751    // auto-detect script + language from text (todo: performance!)
752
753    // let (lang, script) = whatlang::detect(text)
754    //     .map(|info| (info.lang(), info.script()))
755    //     .unwrap_or((Lang::Eng, Script::Latin));
756
757    let lang = None; // detecting the language is only necessary for special font features
758
759    // let lang = tag_mod::from_string(&lang.code().to_string().to_uppercase()).unwrap();
760
761    let script = match crate::text::script::detect_script(text).unwrap_or(Script::Latin) {
762        Script::Arabic => TAG_ARAB,
763        Script::Bengali => TAG_BENG,
764        Script::Cyrillic => TAG_CYRL,
765        Script::Devanagari => TAG_DEVA,
766        Script::Ethiopic => TAG_ETHI,
767        Script::Georgian => TAG_GEOR,
768        Script::Greek => TAG_GREK,
769        Script::Gujarati => TAG_GUJR,
770        Script::Gurmukhi => TAG_GUR2,
771        Script::Hangul => TAG_HANG,
772        Script::Hebrew => TAG_HEBR,
773        Script::Hiragana => TAG_HIRG, // NOTE: tag = 'kana', probably error
774        Script::Kannada => TAG_KND2,
775        Script::Katakana => TAG_KANA,
776        Script::Khmer => TAG_KHMR,
777        Script::Latin => TAG_LATN,
778        Script::Malayalam => TAG_MLYM,
779        Script::Mandarin => TAG_MAND,
780        Script::Myanmar => TAG_MYM2,
781        Script::Oriya => TAG_ORYA,
782        Script::Sinhala => TAG_SINH,
783        Script::Tamil => TAG_TAML,
784        Script::Telugu => TAG_TELU,
785        Script::Thai => TAG_THAI,
786    };
787
788    (script, lang)
789}
790
791// shape_word(text: &str, &font) -> TextBuffer
792// get_word_visual_width(word: &TextBuffer) ->
793// get_glyph_instances(infos: &GlyphInfos, positions: &GlyphPositions) -> PositionedGlyphBuffer
794
795fn shape<'a>(
796    font: &ParsedFont,
797    text: &[u32],
798    script: u32,
799    lang: Option<u32>,
800) -> Option<ShapedTextBufferUnsized> {
801    use core::convert::TryFrom;
802
803    use allsorts_subset_browser::{
804        gpos::apply as gpos_apply,
805        gsub::{apply as gsub_apply, FeatureMask, Features},
806    };
807
808    // Map glyphs
809    //
810    // We look ahead in the char stream for variation selectors. If one is found it is used for
811    // mapping the current glyph. When a variation selector is reached in the stream it is skipped
812    // as it was handled as part of the preceding character.
813    let mut chars_iter = text.iter().peekable();
814    let mut glyphs = Vec::with_capacity(text.len());
815
816    while let Some((ch, ch_as_char)) = chars_iter
817        .next()
818        .and_then(|c| Some((c, core::char::from_u32(*c)?)))
819    {
820        match allsorts_subset_browser::unicode::VariationSelector::try_from(ch_as_char) {
821            Ok(_) => {} // filter out variation selectors
822            Err(()) => {
823                let vs = chars_iter.peek().and_then(|&next| {
824                    allsorts_subset_browser::unicode::VariationSelector::try_from(
825                        core::char::from_u32(*next)?,
826                    )
827                    .ok()
828                });
829
830                let glyph_index = font.lookup_glyph_index(*ch).unwrap_or(0);
831                glyphs.push(make_raw_glyph(ch_as_char, glyph_index, vs));
832            }
833        }
834    }
835
836    const DOTTED_CIRCLE: u32 = '\u{25cc}' as u32;
837    let dotted_circle_index = font.lookup_glyph_index(DOTTED_CIRCLE).unwrap_or(0);
838
839    // Apply glyph substitution if table is present
840    if let Some(gsub) = &font.gsub_cache {
841        gsub_apply(
842            dotted_circle_index,
843            gsub,
844            font.opt_gdef_table.as_ref().map(|f| Rc::as_ref(f)),
845            script,
846            lang,
847            &Features::Mask(FeatureMask::empty()),
848            None, // TODO: variable fonts?
849            font.num_glyphs,
850            &mut glyphs,
851        )
852        .ok()?;
853    }
854
855    // Apply glyph positioning if table is present
856
857    let kerning = true;
858    let mut infos = allsorts_subset_browser::gpos::Info::init_from_glyphs(
859        font.opt_gdef_table.as_ref().map(|f| Rc::as_ref(f)),
860        glyphs,
861    );
862
863    if let Some(gpos) = &font.gpos_cache {
864        gpos_apply(
865            gpos,
866            font.opt_gdef_table.as_ref().map(|f| Rc::as_ref(f)),
867            kerning,
868            &Features::Mask(FeatureMask::all()),
869            None, // TODO: variable fonts?
870            script,
871            lang,
872            &mut infos,
873        )
874        .ok()?;
875    }
876
877    // calculate the horizontal advance for each char
878    let infos = infos
879        .iter()
880        .filter_map(|info| {
881            let glyph_index = info.glyph.glyph_index;
882            let adv_x = font.get_horizontal_advance(glyph_index);
883            let (size_x, size_y) = font.get_glyph_size(glyph_index)?;
884            let advance = Advance {
885                advance_x: adv_x,
886                size_x,
887                size_y,
888                kerning: info.kerning,
889            };
890            let info = translate_info(&info, advance);
891            Some(info)
892        })
893        .collect();
894
895    Some(ShapedTextBufferUnsized { infos })
896}
897
898#[inline]
899fn translate_info(i: &allsorts_subset_browser::gpos::Info, size: Advance) -> GlyphInfo {
900    GlyphInfo {
901        glyph: translate_raw_glyph(&i.glyph),
902        size,
903        kerning: i.kerning,
904        placement: translate_placement(&i.placement),
905    }
906}
907
908fn make_raw_glyph(
909    ch: char,
910    glyph_index: u16,
911    variation: Option<allsorts_subset_browser::unicode::VariationSelector>,
912) -> allsorts_subset_browser::gsub::RawGlyph<()> {
913    allsorts_subset_browser::gsub::RawGlyph {
914        unicodes: tiny_vec![[char; 1] => ch],
915        glyph_index,
916        liga_component_pos: 0,
917        glyph_origin: allsorts_subset_browser::gsub::GlyphOrigin::Char(ch),
918        flags: RawGlyphFlags::empty(),
919        extra_data: (),
920        variation,
921    }
922}
923
924#[inline]
925fn translate_raw_glyph(rg: &allsorts_subset_browser::gsub::RawGlyph<()>) -> RawGlyph {
926    RawGlyph {
927        unicode_codepoint: rg.unicodes.get(0).map(|s| (*s) as u32).into(),
928        glyph_index: rg.glyph_index,
929        liga_component_pos: rg.liga_component_pos,
930        glyph_origin: translate_glyph_origin(&rg.glyph_origin),
931        small_caps: rg.small_caps(),
932        multi_subst_dup: rg.multi_subst_dup(),
933        is_vert_alt: rg.is_vert_alt(),
934        fake_bold: rg.fake_bold(),
935        fake_italic: rg.fake_italic(),
936        variation: rg
937            .variation
938            .as_ref()
939            .map(translate_variation_selector)
940            .into(),
941    }
942}
943
944#[inline]
945const fn translate_glyph_origin(g: &allsorts_subset_browser::gsub::GlyphOrigin) -> GlyphOrigin {
946    use allsorts_subset_browser::gsub::GlyphOrigin::*;
947    match g {
948        Char(c) => GlyphOrigin::Char(*c),
949        Direct => GlyphOrigin::Direct,
950    }
951}
952
953#[inline]
954const fn translate_placement(p: &allsorts_subset_browser::gpos::Placement) -> Placement {
955    use allsorts_subset_browser::gpos::Placement::*;
956    use azul_core::app_resources::{
957        CursiveAnchorPlacement, MarkAnchorPlacement, PlacementDistance,
958    };
959
960    match p {
961        None => Placement::None,
962        Distance(x, y) => Placement::Distance(PlacementDistance { x: *x, y: *y }),
963        MarkAnchor(i, a1, a2) => Placement::MarkAnchor(MarkAnchorPlacement {
964            base_glyph_index: *i,
965            base_glyph_anchor: translate_anchor(a1),
966            mark_anchor: translate_anchor(a2),
967        }),
968        MarkOverprint(i) => Placement::MarkOverprint(*i),
969        CursiveAnchor(i, b, a1, a2) => Placement::CursiveAnchor(CursiveAnchorPlacement {
970            exit_glyph_index: *i,
971            right_to_left: *b,
972            exit_glyph_anchor: translate_anchor(a1),
973            entry_glyph_anchor: translate_anchor(a2),
974        }),
975    }
976}
977
978const fn translate_variation_selector(
979    v: &allsorts_subset_browser::unicode::VariationSelector,
980) -> VariationSelector {
981    use allsorts_subset_browser::unicode::VariationSelector::*;
982    match v {
983        VS01 => VariationSelector::VS01,
984        VS02 => VariationSelector::VS02,
985        VS03 => VariationSelector::VS03,
986        VS15 => VariationSelector::VS15,
987        VS16 => VariationSelector::VS16,
988    }
989}
990
991#[inline]
992const fn translate_anchor(anchor: &allsorts_subset_browser::layout::Anchor) -> Anchor {
993    Anchor {
994        x: anchor.x,
995        y: anchor.y,
996    }
997}