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.
- package/README.md +61 -9
- package/THIRD_PARTY_NOTICES.md +20 -0
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/licenses/Noto-OFL.txt +94 -0
- package/licenses/NotoCJK-OFL.txt +92 -0
- package/licenses/NotoEmoji-OFL.txt +93 -0
- package/licenses/Unifont-OFL.txt +90 -0
- package/licenses/font-sources.json +106 -0
- package/package.json +1 -1
- package/src/types.ts +2 -1
- package/vendor/README.md +43 -0
- package/vendor/blitz-paint/Cargo.toml +50 -0
- package/vendor/blitz-paint/LICENSE-APACHE +176 -0
- package/vendor/blitz-paint/LICENSE-MIT +23 -0
- package/vendor/blitz-paint/src/color.rs +66 -0
- package/vendor/blitz-paint/src/debug_overlay.rs +168 -0
- package/vendor/blitz-paint/src/filters.rs +40 -0
- package/vendor/blitz-paint/src/gradient.rs +536 -0
- package/vendor/blitz-paint/src/kurbo_css/css_box.rs +847 -0
- package/vendor/blitz-paint/src/kurbo_css/mod.rs +77 -0
- package/vendor/blitz-paint/src/kurbo_css/non_uniform_radii.rs +60 -0
- package/vendor/blitz-paint/src/layers.rs +95 -0
- package/vendor/blitz-paint/src/lib.rs +190 -0
- package/vendor/blitz-paint/src/render/background.rs +968 -0
- package/vendor/blitz-paint/src/render/border.rs +631 -0
- package/vendor/blitz-paint/src/render/box_shadow.rs +157 -0
- package/vendor/blitz-paint/src/render/clip_path.rs +492 -0
- package/vendor/blitz-paint/src/render/form_controls.rs +103 -0
- package/vendor/blitz-paint/src/render/mask.rs +144 -0
- package/vendor/blitz-paint/src/render.rs +1351 -0
- package/vendor/blitz-paint/src/sizing.rs +63 -0
- package/vendor/blitz-paint/src/text.rs +747 -0
- package/vendor/fontique/Cargo.toml +194 -0
- package/vendor/fontique/LICENSE-APACHE +201 -0
- package/vendor/fontique/LICENSE-MIT +25 -0
- package/vendor/fontique/README.md +54 -0
- package/vendor/fontique/examples/generic_families.rs +37 -0
- package/vendor/fontique/src/attributes.rs +42 -0
- package/vendor/fontique/src/backend/android.rs +218 -0
- package/vendor/fontique/src/backend/coretext.rs +206 -0
- package/vendor/fontique/src/backend/dwrite.rs +395 -0
- package/vendor/fontique/src/backend/fontconfig.rs +844 -0
- package/vendor/fontique/src/backend/mod.rs +75 -0
- package/vendor/fontique/src/charmap.rs +127 -0
- package/vendor/fontique/src/collection/mod.rs +1016 -0
- package/vendor/fontique/src/collection/query.rs +392 -0
- package/vendor/fontique/src/fallback.rs +293 -0
- package/vendor/fontique/src/family.rs +111 -0
- package/vendor/fontique/src/family_name.rs +153 -0
- package/vendor/fontique/src/font.rs +562 -0
- package/vendor/fontique/src/generic.rs +36 -0
- package/vendor/fontique/src/impl_fontconfig.rs +159 -0
- package/vendor/fontique/src/lib.rs +87 -0
- package/vendor/fontique/src/matching.rs +483 -0
- package/vendor/fontique/src/scan.rs +312 -0
- package/vendor/fontique/src/script.rs +271 -0
- package/vendor/fontique/src/source.rs +108 -0
- package/vendor/fontique/src/source_cache.rs +254 -0
- 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
|
+
}
|