blitz-pdf 0.1.5 → 0.1.7

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 (60) hide show
  1. package/README.md +61 -9
  2. package/THIRD_PARTY_NOTICES.md +20 -0
  3. package/dist/types.d.ts +2 -1
  4. package/dist/types.d.ts.map +1 -1
  5. package/licenses/Noto-OFL.txt +94 -0
  6. package/licenses/NotoCJK-OFL.txt +92 -0
  7. package/licenses/NotoEmoji-OFL.txt +93 -0
  8. package/licenses/Unifont-OFL.txt +90 -0
  9. package/licenses/font-sources.json +106 -0
  10. package/package.json +1 -1
  11. package/src/types.ts +2 -1
  12. package/vendor/README.md +43 -0
  13. package/vendor/blitz-paint/Cargo.toml +50 -0
  14. package/vendor/blitz-paint/LICENSE-APACHE +176 -0
  15. package/vendor/blitz-paint/LICENSE-MIT +23 -0
  16. package/vendor/blitz-paint/src/color.rs +66 -0
  17. package/vendor/blitz-paint/src/debug_overlay.rs +168 -0
  18. package/vendor/blitz-paint/src/filters.rs +40 -0
  19. package/vendor/blitz-paint/src/gradient.rs +536 -0
  20. package/vendor/blitz-paint/src/kurbo_css/css_box.rs +847 -0
  21. package/vendor/blitz-paint/src/kurbo_css/mod.rs +77 -0
  22. package/vendor/blitz-paint/src/kurbo_css/non_uniform_radii.rs +60 -0
  23. package/vendor/blitz-paint/src/layers.rs +95 -0
  24. package/vendor/blitz-paint/src/lib.rs +190 -0
  25. package/vendor/blitz-paint/src/render/background.rs +968 -0
  26. package/vendor/blitz-paint/src/render/border.rs +631 -0
  27. package/vendor/blitz-paint/src/render/box_shadow.rs +157 -0
  28. package/vendor/blitz-paint/src/render/clip_path.rs +492 -0
  29. package/vendor/blitz-paint/src/render/form_controls.rs +103 -0
  30. package/vendor/blitz-paint/src/render/mask.rs +144 -0
  31. package/vendor/blitz-paint/src/render.rs +1351 -0
  32. package/vendor/blitz-paint/src/sizing.rs +63 -0
  33. package/vendor/blitz-paint/src/text.rs +747 -0
  34. package/vendor/fontique/Cargo.toml +194 -0
  35. package/vendor/fontique/LICENSE-APACHE +201 -0
  36. package/vendor/fontique/LICENSE-MIT +25 -0
  37. package/vendor/fontique/README.md +54 -0
  38. package/vendor/fontique/examples/generic_families.rs +37 -0
  39. package/vendor/fontique/src/attributes.rs +42 -0
  40. package/vendor/fontique/src/backend/android.rs +218 -0
  41. package/vendor/fontique/src/backend/coretext.rs +206 -0
  42. package/vendor/fontique/src/backend/dwrite.rs +395 -0
  43. package/vendor/fontique/src/backend/fontconfig.rs +844 -0
  44. package/vendor/fontique/src/backend/mod.rs +75 -0
  45. package/vendor/fontique/src/charmap.rs +127 -0
  46. package/vendor/fontique/src/collection/mod.rs +1016 -0
  47. package/vendor/fontique/src/collection/query.rs +392 -0
  48. package/vendor/fontique/src/fallback.rs +293 -0
  49. package/vendor/fontique/src/family.rs +111 -0
  50. package/vendor/fontique/src/family_name.rs +153 -0
  51. package/vendor/fontique/src/font.rs +562 -0
  52. package/vendor/fontique/src/generic.rs +36 -0
  53. package/vendor/fontique/src/impl_fontconfig.rs +159 -0
  54. package/vendor/fontique/src/lib.rs +87 -0
  55. package/vendor/fontique/src/matching.rs +483 -0
  56. package/vendor/fontique/src/scan.rs +312 -0
  57. package/vendor/fontique/src/script.rs +271 -0
  58. package/vendor/fontique/src/source.rs +108 -0
  59. package/vendor/fontique/src/source_cache.rs +254 -0
  60. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,562 @@
1
+ // Copyright 2024 the Parley Authors
2
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
3
+
4
+ //! Model for a font.
5
+
6
+ use crate::CharmapIndex;
7
+
8
+ use super::source::{SourceInfo, SourceKind};
9
+ use super::{Blob, source_cache::SourceCache};
10
+ use crate::{FontStyle, FontWeight, FontWidth};
11
+ use core::fmt;
12
+ use read_fonts::{FontRef, TableProvider as _, types::Tag};
13
+ use smallvec::SmallVec;
14
+
15
+ type AxisVec = SmallVec<[AxisInfo; 1]>;
16
+
17
+ /// Representation of a single font in a family.
18
+ #[derive(Clone, Debug)]
19
+ pub struct FontInfo {
20
+ source: SourceInfo,
21
+ index: u32,
22
+ width: FontWidth,
23
+ style: FontStyle,
24
+ weight: FontWeight,
25
+ axes: AxisVec,
26
+ attr_axes: u8,
27
+ charmap_index: CharmapIndex,
28
+ }
29
+
30
+ impl FontInfo {
31
+ /// Creates a new font object from the given source and index.
32
+ pub fn from_source(source: SourceInfo, index: u32) -> Option<Self> {
33
+ match &source.kind {
34
+ #[cfg(feature = "std")]
35
+ SourceKind::Path(path) => {
36
+ let file = std::fs::File::open(&**path).ok()?;
37
+ let mapped = unsafe { memmap2::Mmap::map(&file).ok()? };
38
+ let font = FontRef::from_index(&mapped, index).ok()?;
39
+ Self::from_font_ref(&font, source.clone(), index)
40
+ }
41
+ SourceKind::Memory(memory) => {
42
+ let font = FontRef::from_index(memory.as_ref(), index).ok()?;
43
+ Self::from_font_ref(&font, source.clone(), index)
44
+ }
45
+ }
46
+ }
47
+
48
+ /// Returns an object describing how to locate the data containing this
49
+ /// font.
50
+ pub fn source(&self) -> &SourceInfo {
51
+ &self.source
52
+ }
53
+
54
+ /// Returns the index of the font in a collection.
55
+ pub fn index(&self) -> u32 {
56
+ self.index
57
+ }
58
+
59
+ /// Attempts to load the font, optionally from a source cache.
60
+ pub fn load(&self, source_cache: Option<&mut SourceCache>) -> Option<Blob<u8>> {
61
+ if let Some(source_cache) = source_cache {
62
+ source_cache.get(&self.source)
63
+ } else {
64
+ match &self.source.kind {
65
+ SourceKind::Memory(blob) => Some(blob.clone()),
66
+ #[cfg(feature = "std")]
67
+ SourceKind::Path(path) => super::source_cache::load_blob(path),
68
+ }
69
+ }
70
+ }
71
+
72
+ /// Returns the visual width of the font-- a relative change from the normal
73
+ /// aspect ratio, typically in the range `0.5` to `2.0`.
74
+ pub fn width(&self) -> FontWidth {
75
+ self.width
76
+ }
77
+
78
+ /// Returns the visual style or 'slope' of the font.
79
+ pub fn style(&self) -> FontStyle {
80
+ self.style
81
+ }
82
+
83
+ /// Returns the visual weight class of the font, typically on a scale
84
+ /// from `1.0` to `1000.0`.
85
+ pub fn weight(&self) -> FontWeight {
86
+ self.weight
87
+ }
88
+
89
+ /// Returns synthesis suggestions for this font with the given attributes.
90
+ pub fn synthesis(&self, width: FontWidth, style: FontStyle, weight: FontWeight) -> Synthesis {
91
+ let mut synth = Synthesis::default();
92
+ let mut len = 0_usize;
93
+ if self.has_width_axis() && self.width != width {
94
+ synth.vars[len] = (Tag::new(b"wdth"), width.percentage());
95
+ len += 1;
96
+ }
97
+ // Imagine a caller requests weight 400, but the `wght` axis defaults to
98
+ // 100. Unless we synthesize `wght=400`, shaping will use weight 100. So,
99
+ // when the axis default and requested weight are not the same, synthesize
100
+ // the requested weight for correct shaping.
101
+ //
102
+ // The requested weight and axis default can differ when:
103
+ // 1. A `FontInfoOverride` overrides font weight.
104
+ // 2. A non-conformant font specifies different weights in its OS/2 and `fvar` tables.
105
+ // - See: <https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_wght#additional-information>
106
+ if let Some(weight_axis) = self.axes.iter().find(|axis| axis.tag == Tag::new(b"wght")) {
107
+ if weight_axis.default != weight.value() {
108
+ synth.vars[len] = (Tag::new(b"wght"), weight.value());
109
+ len += 1;
110
+ }
111
+ } else if weight.value() > self.weight.value() + 200.0 {
112
+ synth.embolden = true;
113
+ }
114
+ if self.style != style {
115
+ match style {
116
+ FontStyle::Normal => {}
117
+ FontStyle::Italic => {
118
+ if self.style == FontStyle::Normal {
119
+ if self.has_italic_axis() {
120
+ synth.vars[len] = (Tag::new(b"ital"), 1.0);
121
+ len += 1;
122
+ } else if self.has_slant_axis() {
123
+ synth.vars[len] = (Tag::new(b"slnt"), 14.0);
124
+ len += 1;
125
+ } else {
126
+ synth.skew = 14;
127
+ }
128
+ }
129
+ }
130
+ FontStyle::Oblique(angle) => {
131
+ if self.style == FontStyle::Normal {
132
+ let degrees = angle.unwrap_or(14.0);
133
+ if self.has_slant_axis() {
134
+ synth.vars[len] = (Tag::new(b"slnt"), degrees);
135
+ len += 1;
136
+ } else if self.has_italic_axis() && degrees > 0. {
137
+ synth.vars[len] = (Tag::new(b"ital"), 1.0);
138
+ len += 1;
139
+ } else {
140
+ synth.skew = degrees as i8;
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+ synth.len = len as u8;
147
+ synth
148
+ }
149
+
150
+ /// Returns the variation [axes] for the font.
151
+ ///
152
+ /// [axes]: crate::AxisInfo
153
+ pub fn axes(&self) -> &[AxisInfo] {
154
+ &self.axes
155
+ }
156
+
157
+ /// Returns `true` if the font has a `wght` [axis].
158
+ ///
159
+ /// This is a quicker check than scanning the [axes].
160
+ ///
161
+ /// [axes]: Self::axes
162
+ /// [axis]: crate::AxisInfo
163
+ pub fn has_weight_axis(&self) -> bool {
164
+ self.attr_axes & WEIGHT_AXIS != 0
165
+ }
166
+
167
+ /// Returns `true` if the font has a `wdth` [axis].
168
+ ///
169
+ /// This is a quicker check than scanning the [axes].
170
+ ///
171
+ /// [axes]: Self::axes
172
+ /// [axis]: crate::AxisInfo
173
+ pub fn has_width_axis(&self) -> bool {
174
+ self.attr_axes & WIDTH_AXIS != 0
175
+ }
176
+
177
+ /// Returns `true` if the font has a `slnt` [axis].
178
+ ///
179
+ /// This is a quicker check than scanning the [axes].
180
+ ///
181
+ /// [axes]: Self::axes
182
+ /// [axis]: crate::AxisInfo
183
+ pub fn has_slant_axis(&self) -> bool {
184
+ self.attr_axes & SLANT_AXIS != 0
185
+ }
186
+
187
+ /// Returns `true` if the font has an `ital` [axis].
188
+ ///
189
+ /// This is a quicker check than scanning the [axes].
190
+ ///
191
+ /// [axes]: Self::axes
192
+ /// [axis]: crate::AxisInfo
193
+ pub fn has_italic_axis(&self) -> bool {
194
+ self.attr_axes & ITALIC_AXIS != 0
195
+ }
196
+
197
+ /// Returns `true` if the font as an `opsz` [axis].
198
+ ///
199
+ /// This is a quicker check than scanning the [axes].
200
+ ///
201
+ /// [axes]: Self::axes
202
+ /// [axis]: crate::AxisInfo
203
+ pub fn has_optical_size_axis(&self) -> bool {
204
+ self.attr_axes & OPTICAL_SIZE_AXIS != 0
205
+ }
206
+
207
+ /// Returns the index used for constructing a [Charmap] for this font.
208
+ ///
209
+ /// [Charmap]: crate::Charmap
210
+ pub fn charmap_index(&self) -> CharmapIndex {
211
+ self.charmap_index
212
+ }
213
+ }
214
+
215
+ impl FontInfo {
216
+ pub(crate) fn from_font_ref(
217
+ font: &FontRef<'_>,
218
+ source: SourceInfo,
219
+ index: u32,
220
+ ) -> Option<Self> {
221
+ // It's probably not useful to retain fonts that don't have
222
+ // a valid cmap so just bail here if we fail.
223
+ let charmap_index = CharmapIndex::new(font)?;
224
+ let (width, style, weight) = read_attributes(font);
225
+ let (axes, attr_axes) = if let Ok(fvar_axes) = font.fvar().and_then(|fvar| fvar.axes()) {
226
+ let mut axes = SmallVec::<[AxisInfo; 1]>::with_capacity(fvar_axes.len());
227
+ let mut attrs_axes = 0_u8;
228
+ for fvar_axis in fvar_axes {
229
+ let axis = AxisInfo {
230
+ tag: fvar_axis.axis_tag(),
231
+ min: fvar_axis.min_value().to_f32(),
232
+ max: fvar_axis.max_value().to_f32(),
233
+ default: fvar_axis.default_value().to_f32(),
234
+ };
235
+ axes.push(axis);
236
+ match &axis.tag.to_be_bytes() {
237
+ b"wght" => attrs_axes |= WEIGHT_AXIS,
238
+ b"wdth" => attrs_axes |= WIDTH_AXIS,
239
+ b"slnt" => attrs_axes |= SLANT_AXIS,
240
+ b"ital" => attrs_axes |= ITALIC_AXIS,
241
+ b"opsz" => attrs_axes |= OPTICAL_SIZE_AXIS,
242
+ _ => {}
243
+ }
244
+ }
245
+ (axes, attrs_axes)
246
+ } else {
247
+ (SmallVec::default(), 0)
248
+ };
249
+ Some(Self {
250
+ source,
251
+ index,
252
+ width,
253
+ style,
254
+ weight,
255
+ axes,
256
+ attr_axes,
257
+ charmap_index,
258
+ })
259
+ }
260
+
261
+ #[allow(unused)]
262
+ pub(crate) fn maybe_override_attributes(
263
+ &mut self,
264
+ width: FontWidth,
265
+ style: FontStyle,
266
+ weight: FontWeight,
267
+ ) {
268
+ if self.width == FontWidth::default() {
269
+ self.width = width;
270
+ }
271
+ if self.style == FontStyle::default() {
272
+ self.style = style;
273
+ }
274
+ if self.weight == FontWeight::default() {
275
+ self.weight = weight;
276
+ }
277
+ }
278
+
279
+ pub(crate) fn apply_override(&mut self, info_override: &FontInfoOverride<'_>) {
280
+ if let Some(width) = info_override.width {
281
+ self.width = width;
282
+ }
283
+ if let Some(style) = info_override.style {
284
+ self.style = style;
285
+ }
286
+ if let Some(weight) = info_override.weight {
287
+ self.weight = weight;
288
+ }
289
+ if let Some(axes) = info_override.axes {
290
+ // This is O(n^2) but no font should have enough axes for that to
291
+ // matter (read-fonts does the same thing)
292
+ for (tag, value) in axes {
293
+ if let Some(axis) = self.axes.iter_mut().find(|axis| axis.tag == *tag) {
294
+ axis.default = *value;
295
+ }
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ const WEIGHT_AXIS: u8 = 0x01;
302
+ const WIDTH_AXIS: u8 = 0x02;
303
+ const SLANT_AXIS: u8 = 0x04;
304
+ const ITALIC_AXIS: u8 = 0x08;
305
+ const OPTICAL_SIZE_AXIS: u8 = 0x10;
306
+
307
+ /// An axis of variation for a variable font.
308
+ ///
309
+ /// Instances of this can be obtained from [`FontInfo::axes`].
310
+ ///
311
+ /// These give the [`Tag`] and range of valid values for a given font
312
+ /// variation. In `parley`, these values are used to create a
313
+ /// `FontVariation`.
314
+ ///
315
+ /// OpenType defines some common axes:
316
+ ///
317
+ /// * [Italic](https://fonts.google.com/knowledge/glossary/italic_axis) or `ital`
318
+ /// * [Optical Size](https://fonts.google.com/knowledge/glossary/optical_size_axis) or `opsz`
319
+ /// * [Slant](https://fonts.google.com/knowledge/glossary/slant_axis) or `slnt`
320
+ /// * [FontWeight](https://fonts.google.com/knowledge/glossary/weight_axis) or `wght`
321
+ /// * [Width](https://fonts.google.com/knowledge/glossary/width_axis) or `wdth`
322
+ ///
323
+ /// For a broader explanation of this, see
324
+ /// [Axis in Variable Fonts](https://fonts.google.com/knowledge/glossary/axis_in_variable_fonts)
325
+ /// from Google Fonts.
326
+ #[derive(Copy, Clone, Default, Debug)]
327
+ pub struct AxisInfo {
328
+ /// The tag that identifies the axis.
329
+ pub tag: Tag,
330
+ /// The inclusive minimum value of the axis.
331
+ pub min: f32,
332
+ /// The inclusive maximum value of the axis.
333
+ pub max: f32,
334
+ /// The default value of the axis.
335
+ pub default: f32,
336
+ }
337
+
338
+ /// Suggestions for synthesizing a set of font attributes for a given
339
+ /// font.
340
+ ///
341
+ /// Instances of this can be obtained from [`FontInfo::synthesis`]
342
+ /// as well as [`QueryFont::synthesis`].
343
+ ///
344
+ /// [`QueryFont::synthesis`]: crate::QueryFont::synthesis
345
+ #[derive(Copy, Clone, Default, PartialEq)]
346
+ pub struct Synthesis {
347
+ vars: [(Tag, f32); 3],
348
+ len: u8,
349
+ embolden: bool,
350
+ skew: i8,
351
+ }
352
+
353
+ impl Synthesis {
354
+ /// Returns `true` if any synthesis suggestions are available.
355
+ pub fn any(&self) -> bool {
356
+ self.len != 0 || self.embolden || self.skew != 0
357
+ }
358
+
359
+ /// Returns the variation settings that should be applied to match the
360
+ /// requested attributes.
361
+ ///
362
+ /// When using `parley`, these can be used to create `FontVariation`
363
+ /// settings.
364
+ pub fn variation_settings(&self) -> &[(Tag, f32)] {
365
+ &self.vars[..self.len as usize]
366
+ }
367
+
368
+ /// Returns `true` if the scaler should apply a faux bold.
369
+ pub fn embolden(&self) -> bool {
370
+ self.embolden
371
+ }
372
+
373
+ /// Returns a skew angle for faux italic/oblique, if requested.
374
+ pub fn skew(&self) -> Option<f32> {
375
+ if self.skew != 0 {
376
+ Some(self.skew as f32)
377
+ } else {
378
+ None
379
+ }
380
+ }
381
+ }
382
+
383
+ #[allow(clippy::missing_fields_in_debug)]
384
+ impl fmt::Debug for Synthesis {
385
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386
+ f.debug_struct("Synthesis")
387
+ .field("vars", &self.variation_settings())
388
+ .field("embolden", &self.embolden)
389
+ .field("skew", &self.skew)
390
+ .finish()
391
+ }
392
+ }
393
+
394
+ fn read_attributes(font: &FontRef<'_>) -> (FontWidth, FontStyle, FontWeight) {
395
+ use read_fonts::{
396
+ TableProvider,
397
+ tables::{
398
+ head::{Head, MacStyle},
399
+ os2::{Os2, SelectionFlags},
400
+ post::Post,
401
+ },
402
+ };
403
+
404
+ fn width_from_width_class(width_class: u16) -> FontWidth {
405
+ FontWidth::from_ratio(match width_class {
406
+ 0..=1 => 0.5,
407
+ 2 => 0.625,
408
+ 3 => 0.75,
409
+ 4 => 0.875,
410
+ 5 => 1.0,
411
+ 6 => 1.125,
412
+ 7 => 1.25,
413
+ 8 => 1.5,
414
+ _ => 2.0,
415
+ })
416
+ }
417
+
418
+ fn from_os2_post(os2: Os2<'_>, post: Option<Post<'_>>) -> (FontWidth, FontStyle, FontWeight) {
419
+ let width = width_from_width_class(os2.us_width_class());
420
+ // Bits 1 and 9 of the fsSelection field signify italic and
421
+ // oblique, respectively.
422
+ // See: <https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection>
423
+ let fs_selection = os2.fs_selection();
424
+ let style = if fs_selection.contains(SelectionFlags::ITALIC) {
425
+ FontStyle::Italic
426
+ } else if fs_selection.contains(SelectionFlags::OBLIQUE) {
427
+ let angle = post.map(|post| post.italic_angle().to_f64() as f32);
428
+ FontStyle::Oblique(angle)
429
+ } else {
430
+ FontStyle::Normal
431
+ };
432
+ // The usFontWeightClass field is specified with a 1-1000 range, but
433
+ // we don't clamp here because variable fonts could potentially
434
+ // have a value outside of that range.
435
+ // See <https://learn.microsoft.com/en-us/typography/opentype/spec/os2#usweightclass>
436
+ let weight = FontWeight::new(os2.us_weight_class() as f32);
437
+ (width, style, weight)
438
+ }
439
+
440
+ fn from_head(head: Head<'_>) -> (FontWidth, FontStyle, FontWeight) {
441
+ let mac_style = head.mac_style();
442
+ let style = if mac_style.contains(MacStyle::ITALIC) {
443
+ FontStyle::Italic
444
+ } else {
445
+ FontStyle::default()
446
+ };
447
+ let weight = if mac_style.contains(MacStyle::BOLD) {
448
+ FontWeight::BOLD
449
+ } else {
450
+ FontWeight::default()
451
+ };
452
+ (FontWidth::default(), style, weight)
453
+ }
454
+
455
+ if let Ok(os2) = font.os2() {
456
+ // Prefer values from the OS/2 table if it exists. We also use
457
+ // the post table to extract the angle for oblique styles.
458
+ from_os2_post(os2, font.post().ok())
459
+ } else if let Ok(head) = font.head() {
460
+ // Otherwise, fall back to the macStyle field of the head table.
461
+ from_head(head)
462
+ } else {
463
+ (
464
+ FontWidth::default(),
465
+ FontStyle::Normal,
466
+ FontWeight::default(),
467
+ )
468
+ }
469
+ }
470
+
471
+ /// Helper for specifying aspects of a font's metadata to be overridden when the
472
+ /// font is registered. Helpful when implementing a `@font-face`-like API, which
473
+ /// allows those defining the fonts to specify certain font properties manually.
474
+ #[derive(Copy, Clone, Debug, Default)]
475
+ pub struct FontInfoOverride<'a> {
476
+ /// Font family name to be used instead of the one specified in the font
477
+ /// itself.
478
+ pub family_name: Option<&'a str>,
479
+ /// Font width to be used instead of the one specified in the font itself.
480
+ pub width: Option<FontWidth>,
481
+ /// Font's visual style / "slope" to be used instead of the one specified in
482
+ /// the font itself.
483
+ pub style: Option<FontStyle>,
484
+ /// Font weight to be used instead of the one specified in the font itself.
485
+ pub weight: Option<FontWeight>,
486
+ /// Default values for the font's variation axes. Axes not included within
487
+ /// the font will be ignored.
488
+ pub axes: Option<&'a [(Tag, f32)]>,
489
+ }
490
+
491
+ #[cfg(test)]
492
+ mod tests {
493
+ use alloc::sync::Arc;
494
+
495
+ use super::*;
496
+ use crate::SourceId;
497
+
498
+ const ROBOTO: &[u8] =
499
+ include_bytes!("../../parley_dev/assets/fonts/roboto_fonts/Roboto-Regular.ttf");
500
+ const ROBOTO_FLEX: &[u8] =
501
+ include_bytes!("../../parley_dev/assets/fonts/roboto_fonts/RobotoFlex-VariableFont.ttf");
502
+
503
+ fn font_info(data: &[u8]) -> FontInfo {
504
+ let source = SourceInfo::new(
505
+ SourceId::new(),
506
+ SourceKind::Memory(Blob::new(Arc::new(data.to_vec()))),
507
+ );
508
+ FontInfo::from_source(source, 0).unwrap()
509
+ }
510
+
511
+ #[test]
512
+ fn variable_weight_synthesis_uses_axis_default() {
513
+ let mut font = font_info(ROBOTO_FLEX);
514
+ let axes = [(Tag::new(b"wght"), FontWeight::THIN.value())];
515
+ font.apply_override(&FontInfoOverride {
516
+ axes: Some(&axes),
517
+ ..Default::default()
518
+ });
519
+
520
+ // Model a face whose declared weight is 400 but whose `wght` axis
521
+ // defaults to 100.
522
+ assert_eq!(font.weight(), FontWeight::NORMAL);
523
+ assert_eq!(
524
+ font.axes()
525
+ .iter()
526
+ .find(|axis| axis.tag == Tag::new(b"wght"))
527
+ .unwrap()
528
+ .default,
529
+ FontWeight::THIN.value()
530
+ );
531
+
532
+ let synthesis = font.synthesis(font.width(), font.style(), FontWeight::NORMAL);
533
+ assert_eq!(
534
+ synthesis.variation_settings(),
535
+ &[(Tag::new(b"wght"), FontWeight::NORMAL.value())]
536
+ );
537
+ assert!(!synthesis.embolden());
538
+
539
+ let synthesis = font.synthesis(font.width(), font.style(), FontWeight::THIN);
540
+ assert!(!synthesis.any());
541
+ }
542
+
543
+ #[test]
544
+ fn static_weight_synthesis_requires_more_than_200_weight_increase() {
545
+ let font = font_info(ROBOTO);
546
+
547
+ let synthesis = font.synthesis(
548
+ font.width(),
549
+ font.style(),
550
+ FontWeight::new(font.weight().value() + 1.0),
551
+ );
552
+ assert!(!synthesis.any());
553
+
554
+ let synthesis = font.synthesis(
555
+ font.width(),
556
+ font.style(),
557
+ FontWeight::new(font.weight().value() + 201.0),
558
+ );
559
+ assert!(synthesis.embolden());
560
+ assert!(synthesis.variation_settings().is_empty());
561
+ }
562
+ }
@@ -0,0 +1,36 @@
1
+ // Copyright 2024 the Parley Authors
2
+ // SPDX-License-Identifier: Apache-2.0 OR MIT
3
+
4
+ //! Generic font families.
5
+
6
+ use super::FamilyId;
7
+ use crate::GenericFamily;
8
+ use smallvec::SmallVec;
9
+
10
+ type FamilyVec = SmallVec<[FamilyId; 2]>;
11
+ const COUNT: usize = GenericFamily::MAX_VALUE as usize + 1;
12
+
13
+ /// Maps generic families to family identifiers.
14
+ #[derive(Clone, Default, Debug)]
15
+ pub struct GenericFamilyMap {
16
+ map: [FamilyVec; COUNT],
17
+ }
18
+
19
+ impl GenericFamilyMap {
20
+ /// Returns the associated family identifiers for the given generic family.
21
+ pub fn get(&self, generic: GenericFamily) -> &[FamilyId] {
22
+ &self.map[generic as usize]
23
+ }
24
+
25
+ /// Sets the associated family identifiers for the given generic family.
26
+ pub fn set(&mut self, generic: GenericFamily, families: impl Iterator<Item = FamilyId>) {
27
+ let map = &mut self.map[generic as usize];
28
+ map.clear();
29
+ map.extend(families);
30
+ }
31
+
32
+ /// Appends the family identifiers to the list for the given generic family.
33
+ pub fn append(&mut self, generic: GenericFamily, families: impl Iterator<Item = FamilyId>) {
34
+ self.map[generic as usize].extend(families);
35
+ }
36
+ }