blitz-pdf 0.1.2 → 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.
- package/README.md +26 -0
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/docs/layout-regressions.md +65 -0
- package/docs/pagination-regressions.md +89 -0
- package/package.json +2 -1
- package/vendor/README.md +35 -0
- package/vendor/blitz-dom/Cargo.toml +113 -0
- package/vendor/blitz-dom/LICENSE-APACHE +176 -0
- package/vendor/blitz-dom/LICENSE-MIT +23 -0
- package/vendor/blitz-dom/assets/default.css +1142 -0
- package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
- package/vendor/blitz-dom/src/accessibility.rs +255 -0
- package/vendor/blitz-dom/src/config.rs +97 -0
- package/vendor/blitz-dom/src/cssom.rs +971 -0
- package/vendor/blitz-dom/src/debug.rs +206 -0
- package/vendor/blitz-dom/src/document.rs +3395 -0
- package/vendor/blitz-dom/src/events/driver.rs +427 -0
- package/vendor/blitz-dom/src/events/focus.rs +40 -0
- package/vendor/blitz-dom/src/events/ime.rs +31 -0
- package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
- package/vendor/blitz-dom/src/events/mod.rs +315 -0
- package/vendor/blitz-dom/src/events/pointer.rs +849 -0
- package/vendor/blitz-dom/src/font_metrics.rs +177 -0
- package/vendor/blitz-dom/src/form.rs +463 -0
- package/vendor/blitz-dom/src/html.rs +38 -0
- package/vendor/blitz-dom/src/iframe.rs +186 -0
- package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
- package/vendor/blitz-dom/src/layout/damage.rs +642 -0
- package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
- package/vendor/blitz-dom/src/layout/list.rs +237 -0
- package/vendor/blitz-dom/src/layout/mod.rs +969 -0
- package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
- package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
- package/vendor/blitz-dom/src/layout/table.rs +967 -0
- package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
- package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
- package/vendor/blitz-dom/src/lib.rs +160 -0
- package/vendor/blitz-dom/src/mutator.rs +1818 -0
- package/vendor/blitz-dom/src/net.rs +649 -0
- package/vendor/blitz-dom/src/node/attributes.rs +60 -0
- package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
- package/vendor/blitz-dom/src/node/element.rs +1106 -0
- package/vendor/blitz-dom/src/node/mod.rs +30 -0
- package/vendor/blitz-dom/src/node/node.rs +2036 -0
- package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
- package/vendor/blitz-dom/src/node/serialize.rs +225 -0
- package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
- package/vendor/blitz-dom/src/node/svg.rs +265 -0
- package/vendor/blitz-dom/src/node/text.rs +804 -0
- package/vendor/blitz-dom/src/query_selector.rs +275 -0
- package/vendor/blitz-dom/src/resolve.rs +474 -0
- package/vendor/blitz-dom/src/resolved_style.rs +797 -0
- package/vendor/blitz-dom/src/scrolling.rs +780 -0
- package/vendor/blitz-dom/src/selection.rs +122 -0
- package/vendor/blitz-dom/src/stylo.rs +1518 -0
- package/vendor/blitz-dom/src/stylo_device.rs +88 -0
- package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
- package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
- package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
- package/vendor/blitz-dom/src/traversal.rs +600 -0
- package/vendor/blitz-dom/src/tree.rs +136 -0
- package/vendor/blitz-dom/src/url.rs +63 -0
- package/vendor/blitz-dom/src/util.rs +256 -0
- 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
|
+
}
|