blitz-pdf 0.1.0 → 0.1.2

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 (114) hide show
  1. package/README.md +300 -10
  2. package/THIRD_PARTY_NOTICES.md +4 -2
  3. package/dist/browser.d.ts +1 -1
  4. package/dist/browser.d.ts.map +1 -1
  5. package/dist/document-options.d.ts +36 -0
  6. package/dist/document-options.d.ts.map +1 -0
  7. package/dist/document-options.js +236 -0
  8. package/dist/document-options.js.map +1 -0
  9. package/dist/node.d.ts +1 -1
  10. package/dist/node.d.ts.map +1 -1
  11. package/dist/options.d.ts +35 -1
  12. package/dist/options.d.ts.map +1 -1
  13. package/dist/options.js +21 -14
  14. package/dist/options.js.map +1 -1
  15. package/dist/types.d.ts +71 -1
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/validation.d.ts +2 -0
  18. package/dist/validation.d.ts.map +1 -0
  19. package/dist/validation.js +7 -0
  20. package/dist/validation.js.map +1 -0
  21. package/examples/zugferd.mjs +41 -0
  22. package/licenses/RUST_DEPENDENCIES.txt +27 -0
  23. package/package.json +3 -2
  24. package/src/browser.ts +1 -1
  25. package/src/document-options.ts +218 -0
  26. package/src/node.ts +1 -1
  27. package/src/options.ts +18 -13
  28. package/src/types.ts +76 -1
  29. package/src/validation.ts +6 -0
  30. package/vendor/README.md +27 -2
  31. package/vendor/krilla/.cargo-ok +1 -0
  32. package/vendor/krilla/.cargo_vcs_info.json +6 -0
  33. package/vendor/krilla/Cargo.lock +1357 -0
  34. package/vendor/krilla/Cargo.toml +165 -0
  35. package/vendor/krilla/LICENSE_APACHE +202 -0
  36. package/vendor/krilla/LICENSE_MIT +21 -0
  37. package/vendor/krilla/README.md +156 -0
  38. package/vendor/krilla/examples/basic.rs +117 -0
  39. package/vendor/krilla/examples/empty_document.rs +25 -0
  40. package/vendor/krilla/examples/parley.rs +174 -0
  41. package/vendor/krilla/examples/simple_text.rs +118 -0
  42. package/vendor/krilla/examples/stream_builder.rs +93 -0
  43. package/vendor/krilla/icc/LICENSE +116 -0
  44. package/vendor/krilla/icc/README.md +1 -0
  45. package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
  46. package/vendor/krilla/icc/sGrey-v4.icc +0 -0
  47. package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
  48. package/vendor/krilla/icc/sRGB-v4.icc +0 -0
  49. package/vendor/krilla/src/chunk_container.rs +503 -0
  50. package/vendor/krilla/src/configure/PDF_A1.md +227 -0
  51. package/vendor/krilla/src/configure/PDF_A2.md +269 -0
  52. package/vendor/krilla/src/configure/PDF_A3.md +31 -0
  53. package/vendor/krilla/src/configure/PDF_A4.md +211 -0
  54. package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
  55. package/vendor/krilla/src/configure/README.md +23 -0
  56. package/vendor/krilla/src/configure/mod.rs +197 -0
  57. package/vendor/krilla/src/configure/validate.rs +1244 -0
  58. package/vendor/krilla/src/configure/version.rs +134 -0
  59. package/vendor/krilla/src/content.rs +1352 -0
  60. package/vendor/krilla/src/data.rs +49 -0
  61. package/vendor/krilla/src/document.rs +156 -0
  62. package/vendor/krilla/src/error.rs +170 -0
  63. package/vendor/krilla/src/geom.rs +341 -0
  64. package/vendor/krilla/src/graphics/blend.rs +46 -0
  65. package/vendor/krilla/src/graphics/color.rs +552 -0
  66. package/vendor/krilla/src/graphics/graphic.rs +30 -0
  67. package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
  68. package/vendor/krilla/src/graphics/icc.rs +218 -0
  69. package/vendor/krilla/src/graphics/image.rs +835 -0
  70. package/vendor/krilla/src/graphics/mask.rs +128 -0
  71. package/vendor/krilla/src/graphics/mod.rs +14 -0
  72. package/vendor/krilla/src/graphics/paint.rs +434 -0
  73. package/vendor/krilla/src/graphics/separation.rs +86 -0
  74. package/vendor/krilla/src/graphics/shading_function.rs +865 -0
  75. package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
  76. package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
  77. package/vendor/krilla/src/graphics/xobject.rs +150 -0
  78. package/vendor/krilla/src/interactive/action.rs +68 -0
  79. package/vendor/krilla/src/interactive/annotation.rs +283 -0
  80. package/vendor/krilla/src/interactive/destination.rs +289 -0
  81. package/vendor/krilla/src/interactive/mod.rs +3 -0
  82. package/vendor/krilla/src/interchange/embed.rs +240 -0
  83. package/vendor/krilla/src/interchange/metadata.rs +564 -0
  84. package/vendor/krilla/src/interchange/mod.rs +4 -0
  85. package/vendor/krilla/src/interchange/outline.rs +263 -0
  86. package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
  87. package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
  88. package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
  89. package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
  90. package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
  91. package/vendor/krilla/src/lib.rs +179 -0
  92. package/vendor/krilla/src/num.rs +26 -0
  93. package/vendor/krilla/src/page.rs +612 -0
  94. package/vendor/krilla/src/pdf.rs +313 -0
  95. package/vendor/krilla/src/resource.rs +436 -0
  96. package/vendor/krilla/src/serialize.rs +1101 -0
  97. package/vendor/krilla/src/stream.rs +368 -0
  98. package/vendor/krilla/src/surface.rs +664 -0
  99. package/vendor/krilla/src/text/cid.rs +488 -0
  100. package/vendor/krilla/src/text/font.rs +351 -0
  101. package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
  102. package/vendor/krilla/src/text/glyph/colr.rs +581 -0
  103. package/vendor/krilla/src/text/glyph/mod.rs +205 -0
  104. package/vendor/krilla/src/text/glyph/outline.rs +72 -0
  105. package/vendor/krilla/src/text/glyph/svg.rs +34 -0
  106. package/vendor/krilla/src/text/group.rs +463 -0
  107. package/vendor/krilla/src/text/mod.rs +232 -0
  108. package/vendor/krilla/src/text/shape.rs +102 -0
  109. package/vendor/krilla/src/text/type3.rs +514 -0
  110. package/vendor/krilla/src/util.rs +286 -0
  111. package/vendor/stylo/properties/longhands.toml +0 -1
  112. package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
  113. package/wasm/blitz_pdf.js +10 -5
  114. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,351 @@
1
+ use std::fmt::{Debug, Formatter};
2
+ use std::hash::{Hash, Hasher};
3
+ use std::ops::Deref;
4
+ use std::sync::Arc;
5
+
6
+ use skrifa::instance::{Location, LocationRef, Size};
7
+ use skrifa::metrics::GlyphMetrics;
8
+ use skrifa::raw::types::NameId;
9
+ use skrifa::raw::TableProvider;
10
+ use skrifa::{FontRef, MetadataProvider, OutlineGlyphCollection};
11
+ use tiny_skia_path::FiniteF32;
12
+ use yoke::{Yoke, Yokeable};
13
+
14
+ use crate::geom::Rect;
15
+ use crate::text::GlyphId;
16
+ use crate::util::Prehashed;
17
+ use crate::Data;
18
+
19
+ /// An OpenType font. Can be a TrueType, OpenType font or a TrueType collection.
20
+ /// It holds a reference to the underlying data as well as some basic information
21
+ /// about the font.
22
+ ///
23
+ /// Cloning and hashing this type is cheap.
24
+ #[derive(Clone, Hash, Eq, PartialEq)]
25
+ pub struct Font(Arc<Prehashed<Repr>>);
26
+
27
+ impl Font {
28
+ /// Create a new font from some data. If you want to create a variable font at a specific
29
+ /// location, use [`Font::new_variable`] instead.
30
+ ///
31
+ /// The `index` indicates the index that should be
32
+ /// associated with this font for TrueType collections, otherwise this value should be
33
+ /// set to 0.
34
+ ///
35
+ /// Returns `None` if the index is invalid or the font couldn't be read.
36
+ pub fn new(data: Data, index: u32) -> Option<Self> {
37
+ Self::new_variable(data, index, &[])
38
+ }
39
+
40
+ /// Like [`Font::new`], creates a new font from some data, but allows you to specify
41
+ /// variation coordinates in case the font is variable.
42
+ pub fn new_variable(data: Data, index: u32, variation_coords: &[(Tag, f32)]) -> Option<Self> {
43
+ let font_info = FontInfo::new(data.as_ref(), index, variation_coords)?;
44
+
45
+ Font::new_with_info(data.clone(), Arc::new(font_info))
46
+ }
47
+
48
+ pub(crate) fn new_with_info(data: Data, font_info: Arc<FontInfo>) -> Option<Self> {
49
+ let yoke_data = YokeData {
50
+ data: data.0.clone(),
51
+ location: font_info.location.clone(),
52
+ };
53
+
54
+ let font_ref_yoke = Yoke::<FontRefYoke<'static>, Box<YokeData>>::attach_to_cart(
55
+ Box::new(yoke_data),
56
+ |data| {
57
+ let font_ref =
58
+ FontRef::from_index(data.data.as_ref().as_ref(), font_info.index).unwrap();
59
+ FontRefYoke {
60
+ font_ref: font_ref.clone(),
61
+ glyph_metrics: font_ref.glyph_metrics(Size::unscaled(), &data.location),
62
+ outline_glyphs: font_ref.outline_glyphs(),
63
+ }
64
+ },
65
+ );
66
+
67
+ Some(Font(Arc::new(Prehashed::new(Repr {
68
+ font_data: data,
69
+ font_ref_yoke,
70
+ font_info,
71
+ }))))
72
+ }
73
+
74
+ pub(crate) fn postscript_name(&self) -> Option<&str> {
75
+ self.0.font_info.postscript_name.as_deref()
76
+ }
77
+
78
+ /// Return the index of the font.
79
+ pub(crate) fn index(&self) -> u32 {
80
+ self.font_info().index
81
+ }
82
+
83
+ pub(crate) fn font_info(&self) -> Arc<FontInfo> {
84
+ self.0.font_info.clone()
85
+ }
86
+
87
+ pub(crate) fn variation_coordinates(&self) -> &[(Tag, FiniteF32)] {
88
+ &self.0.font_info.var_coords
89
+ }
90
+
91
+ pub(crate) fn cap_height(&self) -> Option<f32> {
92
+ self.0.font_info.cap_height.map(|n| n.get())
93
+ }
94
+
95
+ pub(crate) fn ascent(&self) -> f32 {
96
+ self.0.font_info.ascent.get()
97
+ }
98
+
99
+ pub(crate) fn weight(&self) -> f32 {
100
+ self.0.font_info.weight.get()
101
+ }
102
+
103
+ pub(crate) fn stretch(&self) -> f32 {
104
+ self.0.font_info.stretch.get()
105
+ }
106
+
107
+ pub(crate) fn descent(&self) -> f32 {
108
+ self.0.font_info.descent.get()
109
+ }
110
+
111
+ pub(crate) fn num_glyphs(&self) -> u32 {
112
+ self.0.font_info.num_glyphs
113
+ }
114
+
115
+ pub(crate) fn is_monospaced(&self) -> bool {
116
+ self.0.font_info.is_monospaced
117
+ }
118
+
119
+ pub(crate) fn italic_angle(&self) -> f32 {
120
+ self.0.font_info.italic_angle.get()
121
+ }
122
+
123
+ /// The units per em of the font.
124
+ pub fn units_per_em(&self) -> f32 {
125
+ self.0.font_info.units_per_em as f32
126
+ }
127
+
128
+ pub(crate) fn bbox(&self) -> Rect {
129
+ self.0.font_info.global_bbox
130
+ }
131
+
132
+ pub(crate) fn location_ref(&self) -> LocationRef<'_> {
133
+ (&self.0.font_info.location).into()
134
+ }
135
+
136
+ pub(crate) fn font_ref(&self) -> &FontRef<'_> {
137
+ &self.0.font_ref_yoke.get().font_ref
138
+ }
139
+
140
+ pub(crate) fn glyph_metrics(&self) -> &GlyphMetrics<'_> {
141
+ &self.0.font_ref_yoke.get().glyph_metrics
142
+ }
143
+
144
+ pub(crate) fn outline_glyphs(&self) -> &OutlineGlyphCollection<'_> {
145
+ &self.0.font_ref_yoke.get().outline_glyphs
146
+ }
147
+
148
+ pub(crate) fn font_data(&self) -> Data {
149
+ self.0.font_data.clone()
150
+ }
151
+
152
+ #[inline]
153
+ pub(crate) fn advance_width(&self, glyph_id: GlyphId) -> Option<f32> {
154
+ self.glyph_metrics().advance_width(glyph_id.to_skrifa())
155
+ }
156
+ }
157
+
158
+ /// A 4-byte OpenType tag.
159
+ #[derive(Copy, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
160
+ pub struct Tag([u8; 4]);
161
+
162
+ impl Tag {
163
+ /// Create a new tag.
164
+ pub fn new(tag: &[u8; 4]) -> Self {
165
+ Self(*tag)
166
+ }
167
+
168
+ /// Try to create a new tag from a string.
169
+ ///
170
+ /// Return `None` if the string is not 4 bytes in size.
171
+ pub fn try_from_str(s: &str) -> Option<Self> {
172
+ let tag: [u8; 4] = s.as_bytes().try_into().ok()?;
173
+
174
+ Some(Self(tag))
175
+ }
176
+
177
+ /// Return the value of the tag.
178
+ pub fn get(&self) -> &[u8; 4] {
179
+ &self.0
180
+ }
181
+ }
182
+
183
+ impl Debug for Font {
184
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
185
+ write!(f, "Font {{..}}")
186
+ }
187
+ }
188
+
189
+ struct YokeData {
190
+ data: Arc<dyn AsRef<[u8]> + Send + Sync>,
191
+ location: Location,
192
+ }
193
+
194
+ impl Deref for YokeData {
195
+ type Target = YokeData;
196
+
197
+ fn deref(&self) -> &Self::Target {
198
+ self
199
+ }
200
+ }
201
+
202
+ /// `FontInfo` holds basic information about the font which is necessary
203
+ /// to distinguish a `Font` object from others. The `Hash` implementation
204
+ /// of the `Font` struct solely depends on its `FontInfo` object. The reason
205
+ /// we do this is to avoid hashing the whole font, which can be dozens of megabytes.
206
+ /// Instead, we parse the most basic information as well as additional distinguishing
207
+ /// information, such as the font name and the checksum, and has this instead.
208
+ /// This is much faster, and since we also include the checksum, the odds of two
209
+ /// different fonts ending up with the same hash is pretty much zero.
210
+ #[derive(Debug, Hash, Eq, PartialEq)]
211
+ pub(crate) struct FontInfo {
212
+ index: u32,
213
+ checksum: u32,
214
+ data_len: usize,
215
+ // `location` is derived from `var_coords`, but we need to store `var_coords` it explicitly
216
+ // so we can later pass it to the subsetter.
217
+ var_coords: Vec<(Tag, FiniteF32)>,
218
+ location: Location,
219
+ units_per_em: u16,
220
+ global_bbox: Rect,
221
+ num_glyphs: u32,
222
+ postscript_name: Option<String>,
223
+ ascent: FiniteF32,
224
+ descent: FiniteF32,
225
+ cap_height: Option<FiniteF32>,
226
+ is_monospaced: bool,
227
+ italic_angle: FiniteF32,
228
+ weight: FiniteF32,
229
+ has_glyf: bool,
230
+ has_cff: bool,
231
+ has_cff2: bool,
232
+ stretch: FiniteF32,
233
+ }
234
+
235
+ struct Repr {
236
+ font_info: Arc<FontInfo>,
237
+ font_data: Data,
238
+ font_ref_yoke: Yoke<FontRefYoke<'static>, Box<YokeData>>,
239
+ }
240
+
241
+ impl Hash for Repr {
242
+ fn hash<H: Hasher>(&self, state: &mut H) {
243
+ // We assume that if the font info is distinct, the font itself is distinct as well. This
244
+ // doesn't have to be the case: while the font does have a checksum, it's "only" a
245
+ // u32. The proper way would be to hash the whole font data, but this is just too expensive.
246
+ // However, the odds of the checksum AND all font metrics (including font name) being the same
247
+ // with the font being different is diminishingly low.
248
+ self.font_info.hash(state);
249
+ }
250
+ }
251
+
252
+ impl FontInfo {
253
+ pub(crate) fn new(data: &[u8], index: u32, var_coords: &[(Tag, f32)]) -> Option<Self> {
254
+ let font_ref = FontRef::from_index(data, index).ok()?;
255
+ let location = font_ref.axes().location(
256
+ var_coords
257
+ .iter()
258
+ .map(|i| (skrifa::Tag::new(i.0.get()), i.1)),
259
+ );
260
+ let data_len = data.len();
261
+ let checksum = font_ref.head().ok()?.checksum_adjustment();
262
+ let num_glyphs = font_ref.glyph_names().num_glyphs();
263
+
264
+ let metrics = font_ref.metrics(Size::unscaled(), &location);
265
+ let os_2 = font_ref.os2().ok();
266
+ let ascent = FiniteF32::new(
267
+ os_2.as_ref()
268
+ .map(|s| s.s_typo_ascender() as f32)
269
+ .unwrap_or(metrics.ascent),
270
+ )?;
271
+ let descent = FiniteF32::new(
272
+ os_2.as_ref()
273
+ .map(|s| s.s_typo_descender() as f32)
274
+ .unwrap_or(metrics.descent),
275
+ )?;
276
+ let is_monospaced = metrics.is_monospace;
277
+ let cap_height = metrics.cap_height.map(|n| FiniteF32::new(n).unwrap());
278
+ let italic_angle = FiniteF32::new(metrics.italic_angle).unwrap();
279
+ let weight = FiniteF32::new(font_ref.attributes().weight.value())?;
280
+ let stretch = FiniteF32::new(font_ref.attributes().stretch.ratio())?;
281
+ let units_per_em = metrics.units_per_em;
282
+
283
+ let global_bbox = metrics
284
+ .bounds
285
+ .and_then(|b| Rect::from_ltrb(b.x_min, b.y_min, b.x_max, b.y_max))
286
+ .unwrap_or(Rect::from_xywh(
287
+ 0.0,
288
+ 0.0,
289
+ units_per_em as f32,
290
+ units_per_em as f32,
291
+ )?);
292
+
293
+ let postscript_name = {
294
+ if let Ok(name) = font_ref.name() {
295
+ name.name_record().iter().find_map(|n| {
296
+ if n.name_id.get() == NameId::POSTSCRIPT_NAME {
297
+ if let Ok(string) = n.string(name.string_data()) {
298
+ return Some(string.to_string());
299
+ }
300
+ }
301
+
302
+ None
303
+ })
304
+ } else {
305
+ None
306
+ }
307
+ };
308
+
309
+ let has_glyf = font_ref.glyf().is_ok();
310
+ let has_cff = font_ref.cff().is_ok();
311
+ let has_cff2 = font_ref.cff2().is_ok();
312
+
313
+ Some(FontInfo {
314
+ index,
315
+ data_len,
316
+ checksum,
317
+ var_coords: var_coords
318
+ .iter()
319
+ .map(|v| (v.0, FiniteF32::new(v.1).unwrap_or_default()))
320
+ .collect(),
321
+ location,
322
+ num_glyphs,
323
+ units_per_em,
324
+ postscript_name,
325
+ ascent,
326
+ cap_height,
327
+ has_glyf,
328
+ has_cff,
329
+ has_cff2,
330
+ descent,
331
+ is_monospaced,
332
+ weight,
333
+ stretch,
334
+ italic_angle,
335
+ global_bbox,
336
+ })
337
+ }
338
+
339
+ pub(crate) fn can_be_cid_font(&self) -> bool {
340
+ self.has_cff || self.has_glyf || self.has_cff2
341
+ }
342
+ }
343
+
344
+ /// A yoke so that we can attach a `FontRef` object to the corresponding `Font`,
345
+ /// without running into lifetime issues.
346
+ #[derive(Yokeable, Clone)]
347
+ struct FontRefYoke<'a> {
348
+ pub font_ref: FontRef<'a>,
349
+ pub glyph_metrics: GlyphMetrics<'a>,
350
+ pub outline_glyphs: OutlineGlyphCollection<'a>,
351
+ }
@@ -0,0 +1,81 @@
1
+ use skrifa::bitmap::{BitmapData, BitmapFormat, BitmapGlyph, Origin};
2
+ use skrifa::MetadataProvider;
3
+
4
+ use crate::geom::{Size, Transform};
5
+ use crate::graphics::image::Image;
6
+ use crate::surface::Surface;
7
+ use crate::text::Font;
8
+ use crate::text::GlyphId;
9
+
10
+ pub(crate) fn has_bitmap_data(font: &Font, glyph: GlyphId) -> bool {
11
+ // We only support PNG.
12
+ get_bitmap_glyph(font, glyph).is_some_and(|b| matches!(b.data, BitmapData::Png(_)))
13
+ }
14
+
15
+ pub(crate) fn get_bitmap_glyph(font: &Font, glyph: GlyphId) -> Option<BitmapGlyph<'_>> {
16
+ let bitmap_strikes = font.font_ref().bitmap_strikes();
17
+ bitmap_strikes.glyph_for_size(skrifa::instance::Size::unscaled(), glyph.to_skrifa())
18
+ }
19
+
20
+ /// Draw a bitmap-based glyph on a surface.
21
+ pub(crate) fn draw_glyph(font: Font, glyph: GlyphId, surface: &mut Surface) -> Option<()> {
22
+ let metrics = font
23
+ .font_ref()
24
+ .metrics(skrifa::instance::Size::unscaled(), font.location_ref());
25
+
26
+ let bitmap_strikes = font.font_ref().bitmap_strikes();
27
+ let bitmap_glyph =
28
+ bitmap_strikes.glyph_for_size(skrifa::instance::Size::unscaled(), glyph.to_skrifa())?;
29
+
30
+ let upem = metrics.units_per_em as f32;
31
+
32
+ match bitmap_glyph.data {
33
+ BitmapData::Png(data) => {
34
+ let image = Image::from_png(data.to_vec().into(), false).ok()?;
35
+ let size = Size::from_wh(image.size().0 as f32, image.size().1 as f32).unwrap();
36
+
37
+ // Adapted from vello.
38
+ let scale_factor = upem / (bitmap_glyph.ppem_y);
39
+ let mut transform =
40
+ Transform::from_translate(-bitmap_glyph.bearing_x, bitmap_glyph.bearing_y)
41
+ .pre_concat(Transform::from_scale(scale_factor, scale_factor))
42
+ .pre_concat(Transform::from_translate(
43
+ -bitmap_glyph.inner_bearing_x,
44
+ -bitmap_glyph.inner_bearing_y,
45
+ ));
46
+
47
+ transform = match bitmap_glyph.placement_origin {
48
+ Origin::TopLeft => transform,
49
+ Origin::BottomLeft => {
50
+ transform.pre_concat(Transform::from_translate(0.0, -(image.size().1 as f32)))
51
+ }
52
+ };
53
+
54
+ transform = if let Some(format) = bitmap_strikes.format() {
55
+ if format == BitmapFormat::Sbix {
56
+ // For unknown reasons, using Apple Color Emoji will lead to a vertical shift on MacOS, but this shift
57
+ // doesn't seem to be coming from the font and most likely is somehow hardcoded. On Windows,
58
+ // this shift will not be applied. However, if this shift is not applied the emojis are a bit
59
+ // too high up when being together with other text, so we try to imitate this.
60
+ // See also https://github.com/harfbuzz/harfbuzz/issues/2679#issuecomment-1345595425
61
+ // We approximate this vertical shift that seems to be produced by it.
62
+ // This value seems to be pretty close to what is happening on MacOS.
63
+ transform
64
+ .pre_concat(Transform::from_translate(0.0, 0.128 * upem / scale_factor))
65
+ } else {
66
+ transform
67
+ }
68
+ } else {
69
+ transform
70
+ };
71
+
72
+ surface.push_transform(&transform);
73
+ surface.draw_image(image, size);
74
+ surface.pop();
75
+
76
+ Some(())
77
+ }
78
+ BitmapData::Bgra(_) => None,
79
+ BitmapData::Mask(_) => None,
80
+ }
81
+ }