blitz-pdf 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +6 -6
  2. package/THIRD_PARTY_NOTICES.md +5 -2
  3. package/docs/layout-regressions.md +65 -0
  4. package/docs/pagination-regressions.md +7 -6
  5. package/package.json +1 -1
  6. package/vendor/README.md +35 -0
  7. package/vendor/blitz-dom/Cargo.toml +113 -0
  8. package/vendor/blitz-dom/LICENSE-APACHE +176 -0
  9. package/vendor/blitz-dom/LICENSE-MIT +23 -0
  10. package/vendor/blitz-dom/assets/default.css +1142 -0
  11. package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
  12. package/vendor/blitz-dom/src/accessibility.rs +255 -0
  13. package/vendor/blitz-dom/src/config.rs +97 -0
  14. package/vendor/blitz-dom/src/cssom.rs +971 -0
  15. package/vendor/blitz-dom/src/debug.rs +206 -0
  16. package/vendor/blitz-dom/src/document.rs +3395 -0
  17. package/vendor/blitz-dom/src/events/driver.rs +427 -0
  18. package/vendor/blitz-dom/src/events/focus.rs +40 -0
  19. package/vendor/blitz-dom/src/events/ime.rs +31 -0
  20. package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
  21. package/vendor/blitz-dom/src/events/mod.rs +315 -0
  22. package/vendor/blitz-dom/src/events/pointer.rs +849 -0
  23. package/vendor/blitz-dom/src/font_metrics.rs +177 -0
  24. package/vendor/blitz-dom/src/form.rs +463 -0
  25. package/vendor/blitz-dom/src/html.rs +38 -0
  26. package/vendor/blitz-dom/src/iframe.rs +186 -0
  27. package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
  28. package/vendor/blitz-dom/src/layout/damage.rs +642 -0
  29. package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
  30. package/vendor/blitz-dom/src/layout/list.rs +237 -0
  31. package/vendor/blitz-dom/src/layout/mod.rs +969 -0
  32. package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
  33. package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
  34. package/vendor/blitz-dom/src/layout/table.rs +967 -0
  35. package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
  36. package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
  37. package/vendor/blitz-dom/src/lib.rs +160 -0
  38. package/vendor/blitz-dom/src/mutator.rs +1818 -0
  39. package/vendor/blitz-dom/src/net.rs +649 -0
  40. package/vendor/blitz-dom/src/node/attributes.rs +60 -0
  41. package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
  42. package/vendor/blitz-dom/src/node/element.rs +1106 -0
  43. package/vendor/blitz-dom/src/node/mod.rs +30 -0
  44. package/vendor/blitz-dom/src/node/node.rs +2036 -0
  45. package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
  46. package/vendor/blitz-dom/src/node/serialize.rs +225 -0
  47. package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
  48. package/vendor/blitz-dom/src/node/svg.rs +265 -0
  49. package/vendor/blitz-dom/src/node/text.rs +804 -0
  50. package/vendor/blitz-dom/src/query_selector.rs +275 -0
  51. package/vendor/blitz-dom/src/resolve.rs +474 -0
  52. package/vendor/blitz-dom/src/resolved_style.rs +797 -0
  53. package/vendor/blitz-dom/src/scrolling.rs +780 -0
  54. package/vendor/blitz-dom/src/selection.rs +122 -0
  55. package/vendor/blitz-dom/src/stylo.rs +1518 -0
  56. package/vendor/blitz-dom/src/stylo_device.rs +88 -0
  57. package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
  58. package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
  59. package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
  60. package/vendor/blitz-dom/src/traversal.rs +600 -0
  61. package/vendor/blitz-dom/src/tree.rs +136 -0
  62. package/vendor/blitz-dom/src/url.rs +63 -0
  63. package/vendor/blitz-dom/src/util.rs +256 -0
  64. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,655 @@
1
+ //! Conversion functions from Stylo types to Parley types
2
+ use blitz_traits::node_id::NodeId;
3
+ use std::borrow::Cow;
4
+
5
+ use style::values::computed::Length;
6
+
7
+ use crate::node::TextBrush;
8
+
9
+ // Module of type aliases so we can refer to stylo types with nicer names
10
+ pub(crate) mod stylo {
11
+ pub(crate) use style::computed_values::direction::T as Direction;
12
+ pub(crate) use style::computed_values::font_variant_caps::T as FontVariantCaps;
13
+ pub(crate) use style::computed_values::font_variant_position::T as FontVariantPosition;
14
+ pub(crate) use style::computed_values::text_wrap_mode::T as TextWrapMode;
15
+ pub(crate) use style::computed_values::unicode_bidi::T as UnicodeBidi;
16
+ pub(crate) use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
17
+ pub(crate) use style::properties::ComputedValues;
18
+ pub(crate) use style::properties::style_structs::Font;
19
+ pub(crate) use style::values::computed::AlignmentBaseline;
20
+ pub(crate) use style::values::computed::BaselineShift;
21
+ pub(crate) use style::values::computed::LineBreak;
22
+ pub(crate) use style::values::computed::OverflowWrap;
23
+ pub(crate) use style::values::computed::WordBreak;
24
+ pub(crate) use style::values::computed::font::FontFeatureSettings;
25
+ pub(crate) use style::values::computed::font::FontStyle;
26
+ pub(crate) use style::values::computed::font::FontVariantEastAsian;
27
+ pub(crate) use style::values::computed::font::FontVariantLigatures;
28
+ pub(crate) use style::values::computed::font::FontVariantNumeric;
29
+ pub(crate) use style::values::computed::font::FontVariationSettings;
30
+ pub(crate) use style::values::computed::font::FontWeight;
31
+ pub(crate) use style::values::computed::font::FontWidth;
32
+ pub(crate) use style::values::computed::font::GenericFontFamily;
33
+ pub(crate) use style::values::computed::font::LineHeight;
34
+ pub(crate) use style::values::computed::font::SingleFontFamily;
35
+ pub(crate) use style::values::generics::box_::BaselineShiftKeyword;
36
+ pub(crate) use style::values::specified::TextAlignKeyword;
37
+ pub(crate) use style::values::specified::TextAlignLast;
38
+ }
39
+
40
+ pub(crate) mod parley {
41
+ pub(crate) use parley::BaseDirection;
42
+ pub(crate) use parley::FontFeature;
43
+ pub(crate) use parley::FontVariation;
44
+ pub(crate) use parley::fontique::QueryFamily;
45
+ pub(crate) use parley::layout::Alignment;
46
+ pub(crate) use parley::setting::*;
47
+ pub(crate) use parley::style::*;
48
+ }
49
+
50
+ pub(crate) fn generic_font_family(input: stylo::GenericFontFamily) -> parley::GenericFamily {
51
+ match input {
52
+ stylo::GenericFontFamily::None => parley::GenericFamily::SansSerif,
53
+ stylo::GenericFontFamily::Serif => parley::GenericFamily::Serif,
54
+ stylo::GenericFontFamily::SansSerif => parley::GenericFamily::SansSerif,
55
+ stylo::GenericFontFamily::Monospace => parley::GenericFamily::Monospace,
56
+ stylo::GenericFontFamily::Cursive => parley::GenericFamily::Cursive,
57
+ stylo::GenericFontFamily::Fantasy => parley::GenericFamily::Fantasy,
58
+ stylo::GenericFontFamily::SystemUi => parley::GenericFamily::SystemUi,
59
+ }
60
+ }
61
+
62
+ pub(crate) fn query_font_family(input: &stylo::SingleFontFamily) -> parley::QueryFamily<'_> {
63
+ match input {
64
+ stylo::SingleFontFamily::FamilyName(name) => {
65
+ 'ret: {
66
+ let name = name.name.as_str();
67
+
68
+ // Legacy web compatibility
69
+ #[cfg(target_vendor = "apple")]
70
+ if name == "-apple-system" {
71
+ break 'ret parley::QueryFamily::Generic(parley::GenericFamily::SystemUi);
72
+ }
73
+ #[cfg(target_os = "macos")]
74
+ if name == "BlinkMacSystemFont" {
75
+ break 'ret parley::QueryFamily::Generic(parley::GenericFamily::SystemUi);
76
+ }
77
+
78
+ break 'ret parley::QueryFamily::Named(name);
79
+ }
80
+ }
81
+ stylo::SingleFontFamily::Generic(generic) => {
82
+ parley::QueryFamily::Generic(self::generic_font_family(*generic))
83
+ }
84
+ }
85
+ }
86
+
87
+ pub(crate) fn font_weight(input: stylo::FontWeight) -> parley::FontWeight {
88
+ parley::FontWeight::new(input.value())
89
+ }
90
+
91
+ pub(crate) fn font_width(input: stylo::FontWidth) -> parley::FontWidth {
92
+ parley::FontWidth::from_percentage(input.0.to_float())
93
+ }
94
+
95
+ pub(crate) fn font_style(input: stylo::FontStyle) -> parley::FontStyle {
96
+ match input {
97
+ stylo::FontStyle::NORMAL => parley::FontStyle::Normal,
98
+ stylo::FontStyle::ITALIC => parley::FontStyle::Italic,
99
+ val => parley::FontStyle::Oblique(Some(val.oblique_degrees())),
100
+ }
101
+ }
102
+
103
+ pub(crate) fn font_variations(input: &stylo::FontVariationSettings) -> Vec<parley::FontVariation> {
104
+ input
105
+ .0
106
+ .iter()
107
+ .map(|v| parley::FontVariation {
108
+ tag: parley::Tag::from_bytes(v.tag.0.to_be_bytes()),
109
+ value: v.value,
110
+ })
111
+ .collect()
112
+ }
113
+
114
+ #[inline]
115
+ fn feature(tag: &[u8; 4], value: u16) -> parley::FontFeature {
116
+ parley::FontFeature {
117
+ tag: parley::Tag::from_bytes(*tag),
118
+ value,
119
+ }
120
+ }
121
+
122
+ /// Convert the `font-feature-settings` property (low-level OpenType feature control)
123
+ /// into a list of Parley font features.
124
+ pub(crate) fn font_feature_settings(
125
+ input: &stylo::FontFeatureSettings,
126
+ out: &mut Vec<parley::FontFeature>,
127
+ ) {
128
+ out.extend(input.0.iter().map(|v| parley::FontFeature {
129
+ tag: parley::Tag::from_bytes(v.tag.0.to_be_bytes()),
130
+ value: v.value as u16,
131
+ }));
132
+ }
133
+
134
+ /// Map the `font-variant-ligatures` property to OpenType features.
135
+ pub(crate) fn font_variant_ligatures(
136
+ input: stylo::FontVariantLigatures,
137
+ out: &mut Vec<parley::FontFeature>,
138
+ ) {
139
+ use stylo::FontVariantLigatures as L;
140
+
141
+ // `none` disables all optional ligature and contextual features.
142
+ if input.contains(L::NONE) {
143
+ out.push(feature(b"liga", 0));
144
+ out.push(feature(b"clig", 0));
145
+ out.push(feature(b"dlig", 0));
146
+ out.push(feature(b"hlig", 0));
147
+ out.push(feature(b"calt", 0));
148
+ return;
149
+ }
150
+
151
+ if input.contains(L::COMMON_LIGATURES) {
152
+ out.push(feature(b"liga", 1));
153
+ out.push(feature(b"clig", 1));
154
+ } else if input.contains(L::NO_COMMON_LIGATURES) {
155
+ out.push(feature(b"liga", 0));
156
+ out.push(feature(b"clig", 0));
157
+ }
158
+ if input.contains(L::DISCRETIONARY_LIGATURES) {
159
+ out.push(feature(b"dlig", 1));
160
+ } else if input.contains(L::NO_DISCRETIONARY_LIGATURES) {
161
+ out.push(feature(b"dlig", 0));
162
+ }
163
+ if input.contains(L::HISTORICAL_LIGATURES) {
164
+ out.push(feature(b"hlig", 1));
165
+ } else if input.contains(L::NO_HISTORICAL_LIGATURES) {
166
+ out.push(feature(b"hlig", 0));
167
+ }
168
+ if input.contains(L::CONTEXTUAL) {
169
+ out.push(feature(b"calt", 1));
170
+ } else if input.contains(L::NO_CONTEXTUAL) {
171
+ out.push(feature(b"calt", 0));
172
+ }
173
+ }
174
+
175
+ /// Map the `font-variant-caps` property to OpenType features.
176
+ pub(crate) fn font_variant_caps(input: stylo::FontVariantCaps, out: &mut Vec<parley::FontFeature>) {
177
+ match input {
178
+ stylo::FontVariantCaps::Normal => {}
179
+ stylo::FontVariantCaps::SmallCaps => out.push(feature(b"smcp", 1)),
180
+ }
181
+ }
182
+
183
+ /// Map the `font-variant-position` property to OpenType features.
184
+ pub(crate) fn font_variant_position(
185
+ input: stylo::FontVariantPosition,
186
+ out: &mut Vec<parley::FontFeature>,
187
+ ) {
188
+ match input {
189
+ stylo::FontVariantPosition::Normal => {}
190
+ stylo::FontVariantPosition::Sub => out.push(feature(b"subs", 1)),
191
+ stylo::FontVariantPosition::Super => out.push(feature(b"sups", 1)),
192
+ }
193
+ }
194
+
195
+ /// Map the `font-variant-numeric` property to OpenType features.
196
+ pub(crate) fn font_variant_numeric(
197
+ input: stylo::FontVariantNumeric,
198
+ out: &mut Vec<parley::FontFeature>,
199
+ ) {
200
+ use stylo::FontVariantNumeric as N;
201
+
202
+ if input.contains(N::LINING_NUMS) {
203
+ out.push(feature(b"lnum", 1));
204
+ }
205
+ if input.contains(N::OLDSTYLE_NUMS) {
206
+ out.push(feature(b"onum", 1));
207
+ }
208
+ if input.contains(N::PROPORTIONAL_NUMS) {
209
+ out.push(feature(b"pnum", 1));
210
+ }
211
+ if input.contains(N::TABULAR_NUMS) {
212
+ out.push(feature(b"tnum", 1));
213
+ }
214
+ if input.contains(N::DIAGONAL_FRACTIONS) {
215
+ out.push(feature(b"frac", 1));
216
+ }
217
+ if input.contains(N::STACKED_FRACTIONS) {
218
+ out.push(feature(b"afrc", 1));
219
+ }
220
+ if input.contains(N::ORDINAL) {
221
+ out.push(feature(b"ordn", 1));
222
+ }
223
+ if input.contains(N::SLASHED_ZERO) {
224
+ out.push(feature(b"zero", 1));
225
+ }
226
+ }
227
+
228
+ /// Map the `font-variant-east-asian` property to OpenType features.
229
+ pub(crate) fn font_variant_east_asian(
230
+ input: stylo::FontVariantEastAsian,
231
+ out: &mut Vec<parley::FontFeature>,
232
+ ) {
233
+ use stylo::FontVariantEastAsian as E;
234
+
235
+ if input.contains(E::JIS78) {
236
+ out.push(feature(b"jp78", 1));
237
+ }
238
+ if input.contains(E::JIS83) {
239
+ out.push(feature(b"jp83", 1));
240
+ }
241
+ if input.contains(E::JIS90) {
242
+ out.push(feature(b"jp90", 1));
243
+ }
244
+ if input.contains(E::JIS04) {
245
+ out.push(feature(b"jp04", 1));
246
+ }
247
+ if input.contains(E::SIMPLIFIED) {
248
+ out.push(feature(b"smpl", 1));
249
+ }
250
+ if input.contains(E::TRADITIONAL) {
251
+ out.push(feature(b"trad", 1));
252
+ }
253
+ if input.contains(E::FULL_WIDTH) {
254
+ out.push(feature(b"fwid", 1));
255
+ }
256
+ if input.contains(E::PROPORTIONAL_WIDTH) {
257
+ out.push(feature(b"pwid", 1));
258
+ }
259
+ if input.contains(E::RUBY) {
260
+ out.push(feature(b"ruby", 1));
261
+ }
262
+ }
263
+
264
+ /// Build the full list of OpenType font features for a computed font style.
265
+ ///
266
+ /// Per the CSS Fonts feature precedence rules
267
+ /// (<https://drafts.csswg.org/css-fonts/#feature-precedence>), features implied by the
268
+ /// high-level `font-variant-*` properties are applied first, and the low-level
269
+ /// `font-feature-settings` property is applied last so that it takes precedence.
270
+ ///
271
+ /// Parley stably sorts the feature list by tag and HarfBuzz applies the last of any
272
+ /// duplicate tags, so appending `font-feature-settings` after the `font-variant-*`
273
+ /// features gives it the higher precedence required by the spec.
274
+ pub(crate) fn font_features(font_styles: &stylo::Font) -> Vec<parley::FontFeature> {
275
+ let mut features = Vec::new();
276
+
277
+ self::font_variant_ligatures(font_styles.font_variant_ligatures, &mut features);
278
+ self::font_variant_caps(font_styles.font_variant_caps, &mut features);
279
+ self::font_variant_numeric(font_styles.font_variant_numeric, &mut features);
280
+ self::font_variant_east_asian(font_styles.font_variant_east_asian, &mut features);
281
+ self::font_variant_position(font_styles.font_variant_position, &mut features);
282
+
283
+ self::font_feature_settings(&font_styles.font_feature_settings, &mut features);
284
+
285
+ features.sort_by_key(|feature| feature.tag);
286
+ features.dedup_by_key(|feature| feature.tag);
287
+
288
+ features
289
+ }
290
+
291
+ pub(crate) fn base_direction(
292
+ direction: stylo::Direction,
293
+ unicode_bidi: stylo::UnicodeBidi,
294
+ ) -> parley::BaseDirection {
295
+ if unicode_bidi == stylo::UnicodeBidi::Plaintext {
296
+ return parley::BaseDirection::Auto;
297
+ }
298
+ match direction {
299
+ stylo::Direction::Ltr => parley::BaseDirection::Ltr,
300
+ stylo::Direction::Rtl => parley::BaseDirection::Rtl,
301
+ }
302
+ }
303
+
304
+ pub(crate) fn text_align(input: stylo::TextAlignKeyword) -> parley::Alignment {
305
+ match input {
306
+ stylo::TextAlignKeyword::Start => parley::Alignment::Start,
307
+ stylo::TextAlignKeyword::Left => parley::Alignment::Left,
308
+ stylo::TextAlignKeyword::Right => parley::Alignment::Right,
309
+ stylo::TextAlignKeyword::Center => parley::Alignment::Center,
310
+ stylo::TextAlignKeyword::Justify => parley::Alignment::Justify,
311
+ stylo::TextAlignKeyword::End => parley::Alignment::End,
312
+ stylo::TextAlignKeyword::MozCenter => parley::Alignment::Center,
313
+ stylo::TextAlignKeyword::MozLeft => parley::Alignment::Left,
314
+ stylo::TextAlignKeyword::MozRight => parley::Alignment::Right,
315
+ }
316
+ }
317
+
318
+ /// Returns `None` for `text-align-last: auto`
319
+ pub(crate) fn text_align_last(input: stylo::TextAlignLast) -> Option<parley::Alignment> {
320
+ match input {
321
+ stylo::TextAlignLast::Auto => None,
322
+ stylo::TextAlignLast::Start => Some(parley::Alignment::Start),
323
+ stylo::TextAlignLast::End => Some(parley::Alignment::End),
324
+ stylo::TextAlignLast::Left => Some(parley::Alignment::Left),
325
+ stylo::TextAlignLast::Right => Some(parley::Alignment::Right),
326
+ stylo::TextAlignLast::Center => Some(parley::Alignment::Center),
327
+ stylo::TextAlignLast::Justify => Some(parley::Alignment::Justify),
328
+ }
329
+ }
330
+
331
+ pub(crate) fn text_wrap_mode(input: stylo::TextWrapMode) -> parley::TextWrapMode {
332
+ match input {
333
+ stylo::TextWrapMode::Wrap => parley::TextWrapMode::Wrap,
334
+ stylo::TextWrapMode::Nowrap => parley::TextWrapMode::NoWrap,
335
+ }
336
+ }
337
+
338
+ pub(crate) fn white_space_collapse(input: stylo::WhiteSpaceCollapse) -> parley::WhiteSpaceCollapse {
339
+ match input {
340
+ stylo::WhiteSpaceCollapse::Collapse => parley::WhiteSpaceCollapse::Collapse,
341
+ stylo::WhiteSpaceCollapse::Preserve => parley::WhiteSpaceCollapse::Preserve,
342
+
343
+ stylo::WhiteSpaceCollapse::PreserveBreaks => parley::WhiteSpaceCollapse::PreserveBreaks,
344
+ stylo::WhiteSpaceCollapse::BreakSpaces => parley::WhiteSpaceCollapse::BreakSpaces,
345
+ }
346
+ }
347
+
348
+ /// Map the css-inline-3 `alignment-baseline` and `baseline-shift` longhands (which Stylo
349
+ /// stores in place of the `vertical-align` shorthand) to Parley's `VerticalAlign`.
350
+ ///
351
+ /// Percentages are resolved against the element's own `line-height`.
352
+ pub(crate) fn vertical_align(style: &stylo::ComputedValues) -> parley::VerticalAlign {
353
+ let box_styles = style.get_box();
354
+ let alignment = match box_styles.clone_alignment_baseline() {
355
+ stylo::AlignmentBaseline::Baseline => parley::AlignmentBaseline::Baseline,
356
+ stylo::AlignmentBaseline::TextTop => parley::AlignmentBaseline::TextTop,
357
+ stylo::AlignmentBaseline::TextBottom => parley::AlignmentBaseline::TextBottom,
358
+ stylo::AlignmentBaseline::Middle => parley::AlignmentBaseline::Middle,
359
+ };
360
+ let shift = match box_styles.clone_baseline_shift() {
361
+ stylo::BaselineShift::Keyword(stylo::BaselineShiftKeyword::Sub) => {
362
+ parley::BaselineShift::Sub
363
+ }
364
+ stylo::BaselineShift::Keyword(stylo::BaselineShiftKeyword::Super) => {
365
+ parley::BaselineShift::Super
366
+ }
367
+ stylo::BaselineShift::Keyword(stylo::BaselineShiftKeyword::Top) => {
368
+ parley::BaselineShift::Top
369
+ }
370
+ stylo::BaselineShift::Keyword(stylo::BaselineShiftKeyword::Bottom) => {
371
+ parley::BaselineShift::Bottom
372
+ }
373
+ // TODO: `center` (align the aligned subtree's centre with the line box's centre) is not
374
+ // representable in Parley yet. Approximate it with `middle`.
375
+ stylo::BaselineShift::Keyword(stylo::BaselineShiftKeyword::Center) => {
376
+ return parley::VerticalAlign::MIDDLE;
377
+ }
378
+ stylo::BaselineShift::Length(lp) => {
379
+ let shift = if lp.has_percentage() {
380
+ let font_styles = style.get_font();
381
+ let font_size = font_styles.font_size.used_size.0.px();
382
+ let line_height = match font_styles.line_height {
383
+ // TODO: `normal` resolves against the first available font's metrics, which
384
+ // aren't available here; 1.2 is the usual approximation.
385
+ stylo::LineHeight::Normal => font_size * 1.2,
386
+ stylo::LineHeight::Number(num) => font_size * num.0,
387
+ stylo::LineHeight::Length(value) => value.0.px(),
388
+ };
389
+ lp.resolve(Length::new(line_height)).px()
390
+ } else {
391
+ lp.resolve(Length::new(0.0)).px()
392
+ };
393
+ parley::BaselineShift::Length(shift)
394
+ }
395
+ };
396
+ parley::VerticalAlign::new(alignment, shift)
397
+ }
398
+
399
+ pub(crate) fn style(
400
+ span_id: NodeId,
401
+ style: &stylo::ComputedValues,
402
+ ) -> parley::TextStyle<'static, 'static, TextBrush> {
403
+ let font_styles = style.get_font();
404
+ let itext_styles = style.get_inherited_text();
405
+
406
+ // Convert font size and line height
407
+ let font_size = font_styles.font_size.used_size.0.px();
408
+ let line_height = match font_styles.line_height {
409
+ stylo::LineHeight::Normal => parley::LineHeight::NORMAL,
410
+ stylo::LineHeight::Number(num) => parley::LineHeight::FontSizeRelative(num.0),
411
+ stylo::LineHeight::Length(value) => parley::LineHeight::Absolute(value.0.px()),
412
+ };
413
+ let vertical_align = self::vertical_align(style);
414
+
415
+ let letter_spacing = itext_styles
416
+ .letter_spacing
417
+ .0
418
+ .resolve(Length::new(font_size))
419
+ .px();
420
+
421
+ let word_spacing = itext_styles
422
+ .word_spacing
423
+ .resolve(Length::new(font_size))
424
+ .px();
425
+
426
+ // Convert Bold/Italic
427
+ let font_weight = self::font_weight(font_styles.font_weight);
428
+ let font_style = self::font_style(font_styles.font_style);
429
+ let font_width = self::font_width(font_styles.font_width);
430
+ let font_variations = self::font_variations(&font_styles.font_variation_settings);
431
+ let font_features = self::font_features(font_styles);
432
+
433
+ // Convert font family
434
+ let families: Vec<_> = font_styles
435
+ .font_family
436
+ .families
437
+ .list
438
+ .iter()
439
+ .map(|family| match family {
440
+ stylo::SingleFontFamily::FamilyName(name) => {
441
+ 'ret: {
442
+ let name = name.name.as_str();
443
+
444
+ // Legacy web compatibility
445
+ #[cfg(target_vendor = "apple")]
446
+ if name == "-apple-system" {
447
+ break 'ret parley::FontFamilyName::Generic(
448
+ parley::GenericFamily::SystemUi,
449
+ );
450
+ }
451
+ #[cfg(target_os = "macos")]
452
+ if name == "BlinkMacSystemFont" {
453
+ break 'ret parley::FontFamilyName::Generic(
454
+ parley::GenericFamily::SystemUi,
455
+ );
456
+ }
457
+
458
+ break 'ret parley::FontFamilyName::Named(Cow::Owned(name.to_string()));
459
+ }
460
+ }
461
+ stylo::SingleFontFamily::Generic(generic) => {
462
+ parley::FontFamilyName::Generic(self::generic_font_family(*generic))
463
+ }
464
+ })
465
+ .collect();
466
+
467
+ // Wrapping and breaking
468
+ let word_break = match itext_styles.word_break {
469
+ stylo::WordBreak::Normal => parley::WordBreak::Normal,
470
+ stylo::WordBreak::BreakAll => parley::WordBreak::BreakAll,
471
+ stylo::WordBreak::KeepAll => parley::WordBreak::KeepAll,
472
+ };
473
+ let line_break = match itext_styles.line_break {
474
+ stylo::LineBreak::Loose => parley::LineBreak::Loose,
475
+ stylo::LineBreak::Normal => parley::LineBreak::Normal,
476
+ stylo::LineBreak::Auto | stylo::LineBreak::Strict => parley::LineBreak::Strict,
477
+ stylo::LineBreak::Anywhere => parley::LineBreak::Anywhere,
478
+ };
479
+ let overflow_wrap = match itext_styles.overflow_wrap {
480
+ stylo::OverflowWrap::Normal => parley::OverflowWrap::Normal,
481
+ stylo::OverflowWrap::BreakWord => parley::OverflowWrap::BreakWord,
482
+ stylo::OverflowWrap::Anywhere => parley::OverflowWrap::Anywhere,
483
+ };
484
+ let text_wrap_mode = text_wrap_mode(itext_styles.text_wrap_mode);
485
+
486
+ parley::TextStyle {
487
+ // font_family: parley::FontFamily::Single(FontFamilyName::Generic(GenericFamily::SystemUi)),
488
+ font_family: parley::FontFamily::List(Cow::Owned(families)),
489
+ font_size,
490
+ font_width,
491
+ font_style,
492
+ font_weight,
493
+ font_variations: parley::FontVariations::List(Cow::Owned(font_variations)),
494
+ font_features: parley::FontFeatures::List(Cow::Owned(font_features)),
495
+ locale: parley::Language::parse(&font_styles._x_lang.0).ok(),
496
+ line_height,
497
+ vertical_align,
498
+ word_spacing,
499
+ letter_spacing,
500
+ text_wrap_mode,
501
+ white_space_collapse: white_space_collapse(itext_styles.white_space_collapse),
502
+ overflow_wrap,
503
+ word_break,
504
+ line_break,
505
+
506
+ // Contains NodeId
507
+ brush: TextBrush::from_id(span_id),
508
+
509
+ // We avoid sending these styles through Parley because they don't affect layout
510
+ // and handling them separately allows us to update them without rebuilding the Parley layout.
511
+ //
512
+ // Instead of setting them here we pass the NodeId in the `brush` field and use that to read these
513
+ // styles lazily when rendering.
514
+ has_underline: Default::default(),
515
+ underline_offset: Default::default(),
516
+ underline_size: Default::default(),
517
+ underline_brush: Default::default(),
518
+ has_strikethrough: Default::default(),
519
+ strikethrough_offset: Default::default(),
520
+ strikethrough_size: Default::default(),
521
+ strikethrough_brush: Default::default(),
522
+ }
523
+ }
524
+
525
+ #[cfg(test)]
526
+ mod tests {
527
+ use super::*;
528
+ use style::values::generics::font::{FeatureTagValue, FontSettings, FontTag};
529
+
530
+ /// Collect the (tag, value) pairs from a feature list for easy assertions.
531
+ fn pairs(features: &[parley::FontFeature]) -> Vec<([u8; 4], u16)> {
532
+ features
533
+ .iter()
534
+ .map(|f| (f.tag.to_bytes(), f.value))
535
+ .collect()
536
+ }
537
+
538
+ fn feature_settings(tags: &[(&[u8; 4], i32)]) -> stylo::FontFeatureSettings {
539
+ FontSettings(
540
+ tags.iter()
541
+ .map(|(tag, value)| FeatureTagValue {
542
+ tag: FontTag(u32::from_be_bytes(**tag)),
543
+ value: *value,
544
+ })
545
+ .collect::<Vec<_>>()
546
+ .into_boxed_slice(),
547
+ )
548
+ }
549
+
550
+ #[test]
551
+ fn ligatures_none_disables_all() {
552
+ let mut out = Vec::new();
553
+ font_variant_ligatures(stylo::FontVariantLigatures::NONE, &mut out);
554
+ assert_eq!(
555
+ pairs(&out),
556
+ vec![
557
+ (*b"liga", 0),
558
+ (*b"clig", 0),
559
+ (*b"dlig", 0),
560
+ (*b"hlig", 0),
561
+ (*b"calt", 0),
562
+ ]
563
+ );
564
+ }
565
+
566
+ #[test]
567
+ fn ligatures_normal_emits_nothing() {
568
+ let mut out = Vec::new();
569
+ font_variant_ligatures(stylo::FontVariantLigatures::NORMAL, &mut out);
570
+ assert!(out.is_empty());
571
+ }
572
+
573
+ #[test]
574
+ fn ligatures_mixed_toggles() {
575
+ let mut out = Vec::new();
576
+ let input = stylo::FontVariantLigatures::COMMON_LIGATURES
577
+ | stylo::FontVariantLigatures::NO_DISCRETIONARY_LIGATURES
578
+ | stylo::FontVariantLigatures::HISTORICAL_LIGATURES;
579
+ font_variant_ligatures(input, &mut out);
580
+ assert_eq!(
581
+ pairs(&out),
582
+ vec![(*b"liga", 1), (*b"clig", 1), (*b"dlig", 0), (*b"hlig", 1)]
583
+ );
584
+ }
585
+
586
+ #[test]
587
+ fn caps_small_caps() {
588
+ let mut out = Vec::new();
589
+ font_variant_caps(stylo::FontVariantCaps::SmallCaps, &mut out);
590
+ assert_eq!(pairs(&out), vec![(*b"smcp", 1)]);
591
+
592
+ let mut out = Vec::new();
593
+ font_variant_caps(stylo::FontVariantCaps::Normal, &mut out);
594
+ assert!(out.is_empty());
595
+ }
596
+
597
+ #[test]
598
+ fn position_sub_and_super() {
599
+ let mut out = Vec::new();
600
+ font_variant_position(stylo::FontVariantPosition::Sub, &mut out);
601
+ assert_eq!(pairs(&out), vec![(*b"subs", 1)]);
602
+
603
+ let mut out = Vec::new();
604
+ font_variant_position(stylo::FontVariantPosition::Super, &mut out);
605
+ assert_eq!(pairs(&out), vec![(*b"sups", 1)]);
606
+ }
607
+
608
+ #[test]
609
+ fn numeric_features() {
610
+ let mut out = Vec::new();
611
+ let input = stylo::FontVariantNumeric::OLDSTYLE_NUMS
612
+ | stylo::FontVariantNumeric::TABULAR_NUMS
613
+ | stylo::FontVariantNumeric::SLASHED_ZERO;
614
+ font_variant_numeric(input, &mut out);
615
+ assert_eq!(
616
+ pairs(&out),
617
+ vec![(*b"onum", 1), (*b"tnum", 1), (*b"zero", 1)]
618
+ );
619
+ }
620
+
621
+ #[test]
622
+ fn east_asian_features() {
623
+ let mut out = Vec::new();
624
+ let input =
625
+ stylo::FontVariantEastAsian::JIS04 | stylo::FontVariantEastAsian::PROPORTIONAL_WIDTH;
626
+ font_variant_east_asian(input, &mut out);
627
+ assert_eq!(pairs(&out), vec![(*b"jp04", 1), (*b"pwid", 1)]);
628
+ }
629
+
630
+ #[test]
631
+ fn feature_settings_map_directly() {
632
+ let mut out = Vec::new();
633
+ font_feature_settings(&feature_settings(&[(b"smcp", 1), (b"tnum", 0)]), &mut out);
634
+ assert_eq!(pairs(&out), vec![(*b"smcp", 1), (*b"tnum", 0)]);
635
+ }
636
+
637
+ /// `font-feature-settings` has a higher precedence than the `font-variant-*`
638
+ /// properties, so its entries must be appended *after* the variant-derived ones.
639
+ /// Parley stably sorts by tag and HarfBuzz applies the last of any duplicate tags,
640
+ /// so the trailing `font-feature-settings` value wins.
641
+ #[test]
642
+ fn feature_settings_override_variant_features() {
643
+ let mut out = Vec::new();
644
+ // font-variant-numeric: tabular-nums => tnum=1
645
+ font_variant_numeric(stylo::FontVariantNumeric::TABULAR_NUMS, &mut out);
646
+ // font-feature-settings: "tnum" 0 (should override the variant)
647
+ font_feature_settings(&feature_settings(&[(b"tnum", 0)]), &mut out);
648
+
649
+ let flat = pairs(&out);
650
+ // Both are present, and the feature-settings value comes last.
651
+ assert_eq!(flat, vec![(*b"tnum", 1), (*b"tnum", 0)]);
652
+ let last_tnum = flat.iter().rev().find(|(tag, _)| tag == b"tnum").unwrap();
653
+ assert_eq!(last_tnum.1, 0);
654
+ }
655
+ }