Skip to main content

fonts/shapers/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5mod harfbuzz;
6
7use app_units::Au;
8use euclid::default::Point2D;
9pub(crate) use harfbuzz::Shaper;
10use read_fonts::types::Tag;
11use rustc_hash::FxHashMap;
12use style::computed_values::font_variant_position::T as FontVariantPosition;
13use style::properties::generated::font_face::Descriptors as FontFaceRuleDescriptors;
14use style::values::computed::{FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric};
15
16use crate::{
17    AFRC, CALT, CLIG, DLIG, FRAC, FWID, GlyphId, HLIG, JP04, JP78, JP83, JP90, KERN, LIGA, LNUM,
18    ONUM, ORDN, PNUM, PWID, RUBY, SMPL, SUBS, SUPS, ShapingFlags, ShapingOptions, TNUM, TRAD, ZERO,
19};
20
21/// Utility function to convert a `unicode_script::Script` enum into the corresponding `c_uint` tag that
22/// harfbuzz uses to represent unicode scipts.
23fn unicode_script_to_iso15924_tag(script: unicode_script::Script) -> u32 {
24    let bytes: [u8; 4] = match script {
25        unicode_script::Script::Unknown => *b"Zzzz",
26        _ => {
27            let short_name = script.short_name();
28            short_name.as_bytes().try_into().unwrap()
29        },
30    };
31
32    u32::from_be_bytes(bytes)
33}
34
35#[derive(Debug)]
36pub(crate) struct ShapedGlyph {
37    /// The actual glyph to render for this [`ShapedGlyph`].
38    pub glyph_id: GlyphId,
39    /// The original byte offset in the input buffer of the character that this
40    /// glyph belongs to. More than one glyph can share the same character and
41    /// one character can produce multiple glyphs.
42    pub string_byte_offset: usize,
43    /// The advance the direction of the writing mode that this glyph needs.
44    pub advance: Au,
45    /// An offset that should be applied when rendering this glyph.
46    pub offset: Option<Point2D<Au>>,
47}
48
49/// Holds the results of shaping. Abstracts over HarfBuzz and HarfRust which return data in very similar
50/// form but with different types
51pub(crate) trait GlyphShapingResult {
52    /// The number of shaped glyphs
53    fn len(&self) -> usize;
54    /// Whether or not the result is right-to-left.
55    fn is_rtl(&self) -> bool;
56    /// An iterator of the shaped glyphs of this data.
57    fn iter(&self) -> impl Iterator<Item = ShapedGlyph>;
58}
59
60/// Determine which OpenType features are applied for the font.
61///
62/// The order of precedence for resolving font-specific font feature properties is specified in
63/// <https://drafts.csswg.org/css-fonts-4/#apply-font-matching-variations>.
64pub(crate) fn compute_used_font_features(
65    options: &ShapingOptions,
66    font_face_rule: Option<&FontFaceRuleDescriptors>,
67) -> impl Iterator<Item = (Tag, u32)> {
68    let mut features = FxHashMap::default();
69
70    let mut add_feature = |tag, value| {
71        features.entry(tag).insert_entry(value);
72    };
73
74    // Step 1. Font features enabled by default are applied, including features required
75    // for a given script. See § 7.1 Default features for a description of these.
76    add_feature(LIGA, 1);
77    add_feature(CLIG, 1);
78
79    // Step 7. If the font is defined via an @font-face rule, the font features implied
80    // by the font-feature-settings descriptor in the @font-face rule are applied.
81    if let Some(font_feature_settings) =
82        font_face_rule.and_then(|rule| rule.font_feature_settings.as_ref())
83    {
84        for feature_setting in font_feature_settings.0.iter() {
85            add_feature(
86                Tag::from_u32(feature_setting.tag.0),
87                feature_setting.value.resolve().expect(
88                    "The value is enforced to be resolvable at parse time \
89                    (see FontFeatureSettings::parse_for_font_face_rule).",
90                ) as u32,
91            )
92        }
93    }
94
95    // Step 10. Font features implied by the value of the font-variant property,
96    // the related font-variant subproperties and any other CSS property that uses
97    // OpenType features (e.g. the font-kerning property) are applied.
98    if options.ligatures == FontVariantLigatures::NONE {
99        add_feature(LIGA, 0);
100        add_feature(CLIG, 0);
101        add_feature(DLIG, 0);
102        add_feature(HLIG, 0);
103        add_feature(CALT, 0);
104    } else {
105        if options
106            .ligatures
107            .contains(FontVariantLigatures::COMMON_LIGATURES)
108        {
109            add_feature(LIGA, 1);
110            add_feature(CLIG, 1);
111        } else if options
112            .ligatures
113            .contains(FontVariantLigatures::NO_COMMON_LIGATURES)
114        {
115            add_feature(LIGA, 0);
116            add_feature(CLIG, 0);
117        }
118
119        if options
120            .ligatures
121            .contains(FontVariantLigatures::DISCRETIONARY_LIGATURES)
122        {
123            add_feature(DLIG, 1);
124        } else if options
125            .ligatures
126            .contains(FontVariantLigatures::NO_DISCRETIONARY_LIGATURES)
127        {
128            add_feature(DLIG, 0);
129        }
130
131        if options
132            .ligatures
133            .contains(FontVariantLigatures::HISTORICAL_LIGATURES)
134        {
135            add_feature(HLIG, 1);
136        } else if options
137            .ligatures
138            .contains(FontVariantLigatures::NO_HISTORICAL_LIGATURES)
139        {
140            add_feature(HLIG, 0);
141        }
142
143        if options.ligatures.contains(FontVariantLigatures::CONTEXTUAL) {
144            add_feature(CALT, 1);
145        } else if options
146            .ligatures
147            .contains(FontVariantLigatures::NO_CONTEXTUAL)
148        {
149            add_feature(CALT, 0);
150        }
151    }
152
153    if options.numeric != FontVariantNumeric::NORMAL {
154        if options.numeric.contains(FontVariantNumeric::LINING_NUMS) {
155            add_feature(LNUM, 1);
156        } else if options.numeric.contains(FontVariantNumeric::OLDSTYLE_NUMS) {
157            add_feature(ONUM, 1);
158        }
159        if options
160            .numeric
161            .contains(FontVariantNumeric::PROPORTIONAL_NUMS)
162        {
163            add_feature(PNUM, 1);
164        } else if options.numeric.contains(FontVariantNumeric::TABULAR_NUMS) {
165            add_feature(TNUM, 1);
166        }
167        if options
168            .numeric
169            .contains(FontVariantNumeric::DIAGONAL_FRACTIONS)
170        {
171            add_feature(FRAC, 1);
172        } else if options
173            .numeric
174            .contains(FontVariantNumeric::STACKED_FRACTIONS)
175        {
176            add_feature(AFRC, 1);
177        }
178        if options.numeric.contains(FontVariantNumeric::ORDINAL) {
179            add_feature(ORDN, 1);
180        }
181        if options.numeric.contains(FontVariantNumeric::SLASHED_ZERO) {
182            add_feature(ZERO, 1);
183        }
184    }
185
186    if options.east_asian != FontVariantEastAsian::NORMAL {
187        if options.east_asian.contains(FontVariantEastAsian::JIS78) {
188            add_feature(JP78, 1);
189        } else if options.east_asian.contains(FontVariantEastAsian::JIS83) {
190            add_feature(JP83, 1);
191        } else if options.east_asian.contains(FontVariantEastAsian::JIS90) {
192            add_feature(JP90, 1);
193        } else if options.east_asian.contains(FontVariantEastAsian::JIS04) {
194            add_feature(JP04, 1);
195        } else if options
196            .east_asian
197            .contains(FontVariantEastAsian::SIMPLIFIED)
198        {
199            add_feature(SMPL, 1);
200        } else if options
201            .east_asian
202            .contains(FontVariantEastAsian::TRADITIONAL)
203        {
204            add_feature(TRAD, 1);
205        }
206
207        if options
208            .east_asian
209            .contains(FontVariantEastAsian::FULL_WIDTH)
210        {
211            add_feature(FWID, 1);
212        } else if options
213            .east_asian
214            .contains(FontVariantEastAsian::PROPORTIONAL_WIDTH)
215        {
216            add_feature(PWID, 1);
217        }
218
219        if options.east_asian.contains(FontVariantEastAsian::RUBY) {
220            add_feature(RUBY, 1);
221        }
222    }
223
224    match options.position {
225        FontVariantPosition::Normal => {},
226        FontVariantPosition::Sub => add_feature(SUBS, 1),
227        FontVariantPosition::Super => add_feature(SUPS, 1),
228    }
229
230    if options
231        .flags
232        .contains(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
233    {
234        add_feature(KERN, 0);
235    }
236
237    // Step 13. Font features implied by the value of font-feature-settings property are applied.
238    for feature_setting in options.feature_settings.0.iter() {
239        add_feature(
240            Tag::from_u32(feature_setting.tag.0),
241            feature_setting.value as u32,
242        )
243    }
244
245    features.into_iter()
246}