1mod 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, CWSH, DLIG, FRAC, FWID, GlyphId, HIST, HLIG, JP04, JP78, JP83, JP90, KERN,
18 LIGA, LNUM, NALT, ONUM, ORDN, ORNM, PNUM, PWID, RUBY, SALT, SMPL, SUBS, SUPS, SWSH,
19 ShapingFlags, ShapingOptions, TNUM, TRAD, ZERO,
20};
21
22const HIGHEST_DEFINED_STYLESET_INDEX: u32 = 20;
27
28const HIGHEST_DEFINED_CHARACTER_VARIANT_INDEX: u32 = 99;
34
35fn unicode_script_to_iso15924_tag(script: unicode_script::Script) -> u32 {
38 let bytes: [u8; 4] = match script {
39 unicode_script::Script::Unknown => *b"Zzzz",
40 _ => {
41 let short_name = script.short_name();
42 short_name.as_bytes().try_into().unwrap()
43 },
44 };
45
46 u32::from_be_bytes(bytes)
47}
48
49#[derive(Debug)]
50pub(crate) struct ShapedGlyph {
51 pub glyph_id: GlyphId,
53 pub string_byte_offset: usize,
57 pub advance: Au,
59 pub offset: Option<Point2D<Au>>,
61}
62
63pub(crate) trait GlyphShapingResult {
66 fn len(&self) -> usize;
68 fn is_rtl(&self) -> bool;
70 fn iter(&self) -> impl Iterator<Item = ShapedGlyph>;
72}
73
74pub(crate) fn compute_used_font_features(
79 options: &ShapingOptions,
80 font_face_rule: Option<&FontFaceRuleDescriptors>,
81) -> impl Iterator<Item = (Tag, u32)> {
82 let mut features = FxHashMap::default();
83
84 let mut add_feature = |tag, value| {
85 features.entry(tag).insert_entry(value);
86 };
87
88 add_feature(LIGA, 1);
91 add_feature(CLIG, 1);
92
93 if let Some(font_feature_settings) =
96 font_face_rule.and_then(|rule| rule.font_feature_settings.as_ref())
97 {
98 for feature_setting in font_feature_settings.0.iter() {
99 add_feature(
100 Tag::from_u32(feature_setting.tag.0),
101 feature_setting.value.resolve().expect(
102 "The value is enforced to be resolvable at parse time \
103 (see FontFeatureSettings::parse_for_font_face_rule).",
104 ) as u32,
105 )
106 }
107 }
108
109 if options.ligatures == FontVariantLigatures::NONE {
113 add_feature(LIGA, 0);
114 add_feature(CLIG, 0);
115 add_feature(DLIG, 0);
116 add_feature(HLIG, 0);
117 add_feature(CALT, 0);
118 } else {
119 if options
120 .ligatures
121 .contains(FontVariantLigatures::COMMON_LIGATURES)
122 {
123 add_feature(LIGA, 1);
124 add_feature(CLIG, 1);
125 } else if options
126 .ligatures
127 .contains(FontVariantLigatures::NO_COMMON_LIGATURES)
128 {
129 add_feature(LIGA, 0);
130 add_feature(CLIG, 0);
131 }
132
133 if options
134 .ligatures
135 .contains(FontVariantLigatures::DISCRETIONARY_LIGATURES)
136 {
137 add_feature(DLIG, 1);
138 } else if options
139 .ligatures
140 .contains(FontVariantLigatures::NO_DISCRETIONARY_LIGATURES)
141 {
142 add_feature(DLIG, 0);
143 }
144
145 if options
146 .ligatures
147 .contains(FontVariantLigatures::HISTORICAL_LIGATURES)
148 {
149 add_feature(HLIG, 1);
150 } else if options
151 .ligatures
152 .contains(FontVariantLigatures::NO_HISTORICAL_LIGATURES)
153 {
154 add_feature(HLIG, 0);
155 }
156
157 if options.ligatures.contains(FontVariantLigatures::CONTEXTUAL) {
158 add_feature(CALT, 1);
159 } else if options
160 .ligatures
161 .contains(FontVariantLigatures::NO_CONTEXTUAL)
162 {
163 add_feature(CALT, 0);
164 }
165 }
166
167 if options.numeric != FontVariantNumeric::NORMAL {
168 if options.numeric.contains(FontVariantNumeric::LINING_NUMS) {
169 add_feature(LNUM, 1);
170 } else if options.numeric.contains(FontVariantNumeric::OLDSTYLE_NUMS) {
171 add_feature(ONUM, 1);
172 }
173 if options
174 .numeric
175 .contains(FontVariantNumeric::PROPORTIONAL_NUMS)
176 {
177 add_feature(PNUM, 1);
178 } else if options.numeric.contains(FontVariantNumeric::TABULAR_NUMS) {
179 add_feature(TNUM, 1);
180 }
181 if options
182 .numeric
183 .contains(FontVariantNumeric::DIAGONAL_FRACTIONS)
184 {
185 add_feature(FRAC, 1);
186 } else if options
187 .numeric
188 .contains(FontVariantNumeric::STACKED_FRACTIONS)
189 {
190 add_feature(AFRC, 1);
191 }
192 if options.numeric.contains(FontVariantNumeric::ORDINAL) {
193 add_feature(ORDN, 1);
194 }
195 if options.numeric.contains(FontVariantNumeric::SLASHED_ZERO) {
196 add_feature(ZERO, 1);
197 }
198 }
199
200 if options.east_asian != FontVariantEastAsian::NORMAL {
201 if options.east_asian.contains(FontVariantEastAsian::JIS78) {
202 add_feature(JP78, 1);
203 } else if options.east_asian.contains(FontVariantEastAsian::JIS83) {
204 add_feature(JP83, 1);
205 } else if options.east_asian.contains(FontVariantEastAsian::JIS90) {
206 add_feature(JP90, 1);
207 } else if options.east_asian.contains(FontVariantEastAsian::JIS04) {
208 add_feature(JP04, 1);
209 } else if options
210 .east_asian
211 .contains(FontVariantEastAsian::SIMPLIFIED)
212 {
213 add_feature(SMPL, 1);
214 } else if options
215 .east_asian
216 .contains(FontVariantEastAsian::TRADITIONAL)
217 {
218 add_feature(TRAD, 1);
219 }
220
221 if options
222 .east_asian
223 .contains(FontVariantEastAsian::FULL_WIDTH)
224 {
225 add_feature(FWID, 1);
226 } else if options
227 .east_asian
228 .contains(FontVariantEastAsian::PROPORTIONAL_WIDTH)
229 {
230 add_feature(PWID, 1);
231 }
232
233 if options.east_asian.contains(FontVariantEastAsian::RUBY) {
234 add_feature(RUBY, 1);
235 }
236 }
237
238 match options.position {
239 FontVariantPosition::Normal => {},
240 FontVariantPosition::Sub => add_feature(SUBS, 1),
241 FontVariantPosition::Super => add_feature(SUPS, 1),
242 }
243
244 if options.alternates.historical_forms {
245 add_feature(HIST, 1);
246 }
247
248 if let Some(stylistic) = &options.alternates.stylistic {
249 add_feature(SALT, stylistic.0);
250 }
251
252 for styleset in &options.alternates.styleset {
253 if *styleset > HIGHEST_DEFINED_STYLESET_INDEX {
254 continue;
255 }
256
257 let tag = Tag::new(&[
258 b's',
259 b's',
260 b'0' + (*styleset / 10) as u8,
261 b'0' + (*styleset % 10) as u8,
262 ]);
263 add_feature(tag, 1);
264 }
265
266 for character_variant in &options.alternates.character_variant {
267 if character_variant.0 > HIGHEST_DEFINED_CHARACTER_VARIANT_INDEX {
268 continue;
269 }
270
271 let tag = Tag::new(&[
272 b'c',
273 b'v',
274 b'0' + (character_variant.0 / 10) as u8,
275 b'0' + (character_variant.0 % 10) as u8,
276 ]);
277 add_feature(tag, character_variant.1.unwrap_or(1));
278 }
279
280 if let Some(swash) = &options.alternates.swash {
281 add_feature(SWSH, swash.0);
282 add_feature(CWSH, swash.0);
283 }
284
285 if let Some(ornaments) = &options.alternates.ornaments {
286 add_feature(ORNM, ornaments.0);
287 }
288
289 if let Some(annotation) = &options.alternates.annotation {
290 add_feature(NALT, annotation.0);
291 }
292
293 if options
294 .flags
295 .contains(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
296 {
297 add_feature(KERN, 0);
298 }
299
300 for feature_setting in options.feature_settings.0.iter() {
302 add_feature(
303 Tag::from_u32(feature_setting.tag.0),
304 feature_setting.value as u32,
305 )
306 }
307
308 features.into_iter()
309}