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::font_face::FontFaceRule;
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>.
64fn compute_used_font_features(
65    options: &ShapingOptions,
66    font_face_rule: Option<&FontFaceRule>,
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.descriptors.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.value() as u32,
88            )
89        }
90    }
91
92    // Step 10. Font features implied by the value of the font-variant property,
93    // the related font-variant subproperties and any other CSS property that uses
94    // OpenType features (e.g. the font-kerning property) are applied.
95    if options.ligatures == FontVariantLigatures::NONE {
96        add_feature(LIGA, 0);
97        add_feature(CLIG, 0);
98        add_feature(DLIG, 0);
99        add_feature(HLIG, 0);
100        add_feature(CALT, 0);
101    } else {
102        if options
103            .ligatures
104            .contains(FontVariantLigatures::COMMON_LIGATURES)
105        {
106            add_feature(LIGA, 1);
107            add_feature(CLIG, 1);
108        } else if options
109            .ligatures
110            .contains(FontVariantLigatures::NO_COMMON_LIGATURES)
111        {
112            add_feature(LIGA, 0);
113            add_feature(CLIG, 0);
114        }
115
116        if options
117            .ligatures
118            .contains(FontVariantLigatures::DISCRETIONARY_LIGATURES)
119        {
120            add_feature(DLIG, 1);
121        } else if options
122            .ligatures
123            .contains(FontVariantLigatures::NO_DISCRETIONARY_LIGATURES)
124        {
125            add_feature(DLIG, 0);
126        }
127
128        if options
129            .ligatures
130            .contains(FontVariantLigatures::HISTORICAL_LIGATURES)
131        {
132            add_feature(HLIG, 1);
133        } else if options
134            .ligatures
135            .contains(FontVariantLigatures::NO_HISTORICAL_LIGATURES)
136        {
137            add_feature(HLIG, 0);
138        }
139
140        if options.ligatures.contains(FontVariantLigatures::CONTEXTUAL) {
141            add_feature(CALT, 1);
142        } else if options
143            .ligatures
144            .contains(FontVariantLigatures::NO_CONTEXTUAL)
145        {
146            add_feature(CALT, 0);
147        }
148    }
149
150    if options.numeric != FontVariantNumeric::NORMAL {
151        if options.numeric.contains(FontVariantNumeric::LINING_NUMS) {
152            add_feature(LNUM, 1);
153        } else if options.numeric.contains(FontVariantNumeric::OLDSTYLE_NUMS) {
154            add_feature(ONUM, 1);
155        }
156        if options
157            .numeric
158            .contains(FontVariantNumeric::PROPORTIONAL_NUMS)
159        {
160            add_feature(PNUM, 1);
161        } else if options.numeric.contains(FontVariantNumeric::TABULAR_NUMS) {
162            add_feature(TNUM, 1);
163        }
164        if options
165            .numeric
166            .contains(FontVariantNumeric::DIAGONAL_FRACTIONS)
167        {
168            add_feature(FRAC, 1);
169        } else if options
170            .numeric
171            .contains(FontVariantNumeric::STACKED_FRACTIONS)
172        {
173            add_feature(AFRC, 1);
174        }
175        if options.numeric.contains(FontVariantNumeric::ORDINAL) {
176            add_feature(ORDN, 1);
177        }
178        if options.numeric.contains(FontVariantNumeric::SLASHED_ZERO) {
179            add_feature(ZERO, 1);
180        }
181    }
182
183    if options.east_asian != FontVariantEastAsian::NORMAL {
184        if options.east_asian.contains(FontVariantEastAsian::JIS78) {
185            add_feature(JP78, 1);
186        } else if options.east_asian.contains(FontVariantEastAsian::JIS83) {
187            add_feature(JP83, 1);
188        } else if options.east_asian.contains(FontVariantEastAsian::JIS90) {
189            add_feature(JP90, 1);
190        } else if options.east_asian.contains(FontVariantEastAsian::JIS04) {
191            add_feature(JP04, 1);
192        } else if options
193            .east_asian
194            .contains(FontVariantEastAsian::SIMPLIFIED)
195        {
196            add_feature(SMPL, 1);
197        } else if options
198            .east_asian
199            .contains(FontVariantEastAsian::TRADITIONAL)
200        {
201            add_feature(TRAD, 1);
202        }
203
204        if options
205            .east_asian
206            .contains(FontVariantEastAsian::FULL_WIDTH)
207        {
208            add_feature(FWID, 1);
209        } else if options
210            .east_asian
211            .contains(FontVariantEastAsian::PROPORTIONAL_WIDTH)
212        {
213            add_feature(PWID, 1);
214        }
215
216        if options.east_asian.contains(FontVariantEastAsian::RUBY) {
217            add_feature(RUBY, 1);
218        }
219    }
220
221    match options.position {
222        FontVariantPosition::Normal => {},
223        FontVariantPosition::Sub => add_feature(SUBS, 1),
224        FontVariantPosition::Super => add_feature(SUPS, 1),
225    }
226
227    if options
228        .flags
229        .contains(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
230    {
231        add_feature(KERN, 0);
232    }
233
234    // Step 13. Font features implied by the value of font-feature-settings property are applied.
235    for feature_setting in options.feature_settings.0.iter() {
236        add_feature(
237            Tag::from_u32(feature_setting.tag.0),
238            feature_setting.value as u32,
239        )
240    }
241
242    features.into_iter()
243}