fonts/platform/freetype/
font.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::ffi::CString;
6use std::fs::File;
7
8use app_units::Au;
9use euclid::default::{Point2D, Rect, Size2D};
10use fonts_traits::{FontIdentifier, FontTemplateDescriptor, LocalFontIdentifier};
11use freetype_sys::{
12    FT_F26Dot6, FT_Get_Char_Index, FT_Get_Kerning, FT_GlyphSlot, FT_HAS_MULTIPLE_MASTERS,
13    FT_KERNING_DEFAULT, FT_LOAD_DEFAULT, FT_LOAD_NO_HINTING, FT_Load_Glyph, FT_Size_Metrics,
14    FT_SizeRec, FT_UInt, FT_ULong, FT_Vector,
15};
16use log::debug;
17use memmap2::Mmap;
18use parking_lot::ReentrantMutex;
19use read_fonts::types::Tag;
20use read_fonts::{FontRef, ReadError, TableProvider};
21use servo_arc::Arc;
22use style::Zero;
23use webrender_api::{FontInstanceFlags, FontVariation};
24
25use super::library_handle::FreeTypeLibraryHandle;
26use crate::FontData;
27use crate::font::{FontMetrics, FontTableMethods, FractionalPixel, PlatformFontMethods};
28use crate::glyph::GlyphId;
29use crate::platform::freetype::freetype_face::FreeTypeFace;
30
31/// Convert FreeType-style 26.6 fixed point to an [`f64`].
32fn fixed_26_dot_6_to_float(fixed: FT_F26Dot6) -> f64 {
33    fixed as f64 / 64.0
34}
35
36#[derive(Debug)]
37pub struct FontTable {
38    data: FreeTypeFaceTableProviderData,
39    tag: Tag,
40}
41
42impl FontTableMethods for FontTable {
43    fn buffer(&self) -> &[u8] {
44        let font_ref = self.data.font_ref().expect("Font checked before creating");
45        let table_data = font_ref
46            .table_data(self.tag)
47            .expect("Table existence checked before creating");
48        table_data.as_bytes()
49    }
50}
51
52#[derive(Debug)]
53#[allow(unused)]
54pub struct PlatformFont {
55    face: ReentrantMutex<FreeTypeFace>,
56    requested_face_size: Au,
57    actual_face_size: Au,
58    variations: Vec<FontVariation>,
59    synthetic_bold: bool,
60
61    /// A member that allows using `skrifa` to read values from this font.
62    table_provider_data: FreeTypeFaceTableProviderData,
63}
64
65impl PlatformFontMethods for PlatformFont {
66    fn new_from_data(
67        _font_identifier: FontIdentifier,
68        font_data: &FontData,
69        requested_size: Option<Au>,
70        variations: &[FontVariation],
71        mut synthetic_bold: bool,
72    ) -> Result<PlatformFont, &'static str> {
73        let library = FreeTypeLibraryHandle::get().lock();
74        let data: &[u8] = font_data.as_ref();
75        let face = FreeTypeFace::new_from_memory(&library, data)?;
76
77        let normalized_variations = face.set_variations_for_font(variations, &library)?;
78
79        let (requested_face_size, actual_face_size) = match requested_size {
80            Some(requested_size) => (requested_size, face.set_size(requested_size)?),
81            None => (Au::zero(), Au::zero()),
82        };
83
84        // Variable fonts, where the font designer has provided one or more axes of
85        // variation do not count as font synthesis and their use is not affected by
86        // the font-synthesis property.
87        //
88        // <https://www.w3.org/TR/css-fonts-4/#font-synthesis-intro>
89        if FT_HAS_MULTIPLE_MASTERS(face.as_ptr()) {
90            synthetic_bold = false;
91        }
92
93        Ok(PlatformFont {
94            face: ReentrantMutex::new(face),
95            requested_face_size,
96            actual_face_size,
97            table_provider_data: FreeTypeFaceTableProviderData::Web(font_data.clone()),
98            variations: normalized_variations,
99            synthetic_bold,
100        })
101    }
102
103    fn new_from_local_font_identifier(
104        font_identifier: LocalFontIdentifier,
105        requested_size: Option<Au>,
106        variations: &[FontVariation],
107        mut synthetic_bold: bool,
108    ) -> Result<PlatformFont, &'static str> {
109        let library = FreeTypeLibraryHandle::get().lock();
110        let filename = CString::new(&*font_identifier.path).expect("filename contains NUL byte!");
111
112        let face = FreeTypeFace::new_from_file(
113            &library,
114            &filename,
115            font_identifier.face_index_for_freetype(),
116        )?;
117
118        let normalized_variations = face.set_variations_for_font(variations, &library)?;
119
120        let (requested_face_size, actual_face_size) = match requested_size {
121            Some(requested_size) => (requested_size, face.set_size(requested_size)?),
122            None => (Au::zero(), Au::zero()),
123        };
124
125        let Ok(memory_mapped_font_data) =
126            File::open(&*font_identifier.path).and_then(|file| unsafe { Mmap::map(&file) })
127        else {
128            return Err("Could not memory map");
129        };
130
131        // Variable fonts, where the font designer has provided one or more axes of
132        // variation do not count as font synthesis and their use is not affected by
133        // the font-synthesis property.
134        //
135        // <https://www.w3.org/TR/css-fonts-4/#font-synthesis-intro>
136        if FT_HAS_MULTIPLE_MASTERS(face.as_ptr()) {
137            synthetic_bold = false;
138        }
139
140        Ok(PlatformFont {
141            face: ReentrantMutex::new(face),
142            requested_face_size,
143            actual_face_size,
144            table_provider_data: FreeTypeFaceTableProviderData::Local(
145                Arc::new(memory_mapped_font_data),
146                font_identifier.index(),
147            ),
148            variations: normalized_variations,
149            synthetic_bold,
150        })
151    }
152
153    fn descriptor(&self) -> FontTemplateDescriptor {
154        let Ok(font_ref) = self.table_provider_data.font_ref() else {
155            return FontTemplateDescriptor::default();
156        };
157        let Ok(os2) = font_ref.os2() else {
158            return FontTemplateDescriptor::default();
159        };
160        Self::descriptor_from_os2_table(&os2)
161    }
162
163    fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
164        let face = self.face.lock();
165
166        unsafe {
167            let idx = FT_Get_Char_Index(face.as_ptr(), codepoint as FT_ULong);
168            if idx != 0 as FT_UInt {
169                Some(idx as GlyphId)
170            } else {
171                debug!(
172                    "Invalid codepoint: U+{:04X} ('{}')",
173                    codepoint as u32, codepoint
174                );
175                None
176            }
177        }
178    }
179
180    fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
181        let face = self.face.lock();
182
183        let mut delta = FT_Vector { x: 0, y: 0 };
184        unsafe {
185            FT_Get_Kerning(
186                face.as_ptr(),
187                first_glyph,
188                second_glyph,
189                FT_KERNING_DEFAULT,
190                &mut delta,
191            );
192        }
193        fixed_26_dot_6_to_float(delta.x) * self.unscalable_font_metrics_scale()
194    }
195
196    fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
197        let face = self.face.lock();
198
199        let load_flags = face.glyph_load_flags();
200        let result = unsafe { FT_Load_Glyph(face.as_ptr(), glyph as FT_UInt, load_flags) };
201        if 0 != result {
202            debug!("Unable to load glyph {}. reason: {:?}", glyph, result);
203            return None;
204        }
205
206        let void_glyph = face.as_ref().glyph;
207        let slot: FT_GlyphSlot = void_glyph;
208        assert!(!slot.is_null());
209
210        if self.synthetic_bold {
211            mozilla_glyphslot_embolden_less(slot);
212        }
213
214        let advance = unsafe { (*slot).metrics.horiAdvance };
215        Some(fixed_26_dot_6_to_float(advance) * self.unscalable_font_metrics_scale())
216    }
217
218    fn metrics(&self) -> FontMetrics {
219        let face = self.face.lock();
220        let font_ref = self.table_provider_data.font_ref();
221
222        // face.size is a *c_void in the bindings, presumably to avoid recursive structural types
223        let freetype_size: &FT_SizeRec = unsafe { &*face.as_ref().size };
224        let freetype_metrics: &FT_Size_Metrics = &(freetype_size).metrics;
225
226        let mut max_advance;
227        let mut max_ascent;
228        let mut max_descent;
229        let mut line_height;
230        let mut y_scale = 0.0;
231        let mut em_height;
232        if face.scalable() {
233            // Prefer FT_Size_Metrics::y_scale to y_ppem as y_ppem does not have subpixel accuracy.
234            //
235            // FT_Size_Metrics::y_scale is in 16.16 fixed point format.  Its (fractional) value is a
236            // factor that converts vertical metrics from design units to units of 1/64 pixels, so
237            // that the result may be interpreted as pixels in 26.6 fixed point format.
238            //
239            // This converts the value to a float without losing precision.
240            y_scale = freetype_metrics.y_scale as f64 / 65535.0 / 64.0;
241
242            max_advance = (face.as_ref().max_advance_width as f64) * y_scale;
243            max_ascent = (face.as_ref().ascender as f64) * y_scale;
244            max_descent = -(face.as_ref().descender as f64) * y_scale;
245            line_height = (face.as_ref().height as f64) * y_scale;
246            em_height = (face.as_ref().units_per_EM as f64) * y_scale;
247        } else {
248            max_advance = fixed_26_dot_6_to_float(freetype_metrics.max_advance);
249            max_ascent = fixed_26_dot_6_to_float(freetype_metrics.ascender);
250            max_descent = -fixed_26_dot_6_to_float(freetype_metrics.descender);
251            line_height = fixed_26_dot_6_to_float(freetype_metrics.height);
252
253            em_height = freetype_metrics.y_ppem as f64;
254            // FT_Face doc says units_per_EM and a bunch of following fields are "only relevant to
255            // scalable outlines". If it's an sfnt, we can get units_per_EM from the 'head' table
256            // instead; otherwise, we don't have a unitsPerEm value so we can't compute y_scale and
257            // x_scale.
258            if let Ok(head) = font_ref.clone().and_then(|font_ref| font_ref.head()) {
259                // Bug 1267909 - Even if the font is not explicitly scalable, if the face has color
260                // bitmaps, it should be treated as scalable and scaled to the desired size. Metrics
261                // based on y_ppem need to be rescaled for the adjusted size.
262                if face.color() {
263                    em_height = self.requested_face_size.to_f64_px();
264                    let adjust_scale = em_height / (freetype_metrics.y_ppem as f64);
265                    max_advance *= adjust_scale;
266                    max_descent *= adjust_scale;
267                    max_ascent *= adjust_scale;
268                    line_height *= adjust_scale;
269                }
270                y_scale = em_height / head.units_per_em() as f64;
271            }
272        }
273
274        // 'leading' is supposed to be the vertical distance between two baselines,
275        // reflected by the height attribute in freetype. On OS X (w/ CTFont),
276        // leading represents the distance between the bottom of a line descent to
277        // the top of the next line's ascent or: (line_height - ascent - descent),
278        // see http://stackoverflow.com/a/5635981 for CTFont implementation.
279        // Convert using a formula similar to what CTFont returns for consistency.
280        let leading = line_height - (max_ascent + max_descent);
281
282        let underline_size = face.as_ref().underline_thickness as f64 * y_scale;
283        let underline_offset = face.as_ref().underline_position as f64 * y_scale + 0.5;
284
285        // The default values for strikeout size and offset. Use OpenType spec's suggested position
286        // for Roman font as the default for offset.
287        let mut strikeout_size = underline_size;
288        let mut strikeout_offset = em_height * 409.0 / 2048.0 + 0.5 * strikeout_size;
289
290        // CSS 2.1, section 4.3.2 Lengths: "In the cases where it is
291        // impossible or impractical to determine the x-height, a value of
292        // 0.5em should be used."
293        let mut x_height = 0.5 * em_height;
294        let mut average_advance = 0.0;
295
296        if let Ok(os2) = font_ref.and_then(|font_ref| font_ref.os2()) {
297            let y_strikeout_size = os2.y_strikeout_size();
298            let y_strikeout_position = os2.y_strikeout_position();
299            if !y_strikeout_size.is_zero() && !y_strikeout_position.is_zero() {
300                strikeout_size = y_strikeout_size as f64 * y_scale;
301                strikeout_offset = y_strikeout_position as f64 * y_scale;
302            }
303
304            let sx_height = os2.sx_height().unwrap_or(0);
305            if !sx_height.is_zero() {
306                x_height = sx_height as f64 * y_scale;
307            }
308
309            let x_average_char_width = os2.x_avg_char_width();
310            if !x_average_char_width.is_zero() {
311                average_advance = x_average_char_width as f64 * y_scale;
312            }
313        }
314
315        if average_advance.is_zero() {
316            average_advance = self
317                .glyph_index('0')
318                .and_then(|idx| self.glyph_h_advance(idx))
319                .map_or(max_advance, |advance| advance * y_scale);
320        }
321
322        let zero_horizontal_advance = self
323            .glyph_index('0')
324            .and_then(|idx| self.glyph_h_advance(idx))
325            .map(Au::from_f64_px);
326        let ic_horizontal_advance = self
327            .glyph_index('\u{6C34}')
328            .and_then(|idx| self.glyph_h_advance(idx))
329            .map(Au::from_f64_px);
330        let space_advance = self
331            .glyph_index(' ')
332            .and_then(|idx| self.glyph_h_advance(idx))
333            .unwrap_or(average_advance);
334
335        FontMetrics {
336            underline_size: Au::from_f64_px(underline_size),
337            underline_offset: Au::from_f64_px(underline_offset),
338            strikeout_size: Au::from_f64_px(strikeout_size),
339            strikeout_offset: Au::from_f64_px(strikeout_offset),
340            leading: Au::from_f64_px(leading),
341            x_height: Au::from_f64_px(x_height),
342            em_size: Au::from_f64_px(em_height),
343            ascent: Au::from_f64_px(max_ascent),
344            descent: Au::from_f64_px(max_descent),
345            max_advance: Au::from_f64_px(max_advance),
346            average_advance: Au::from_f64_px(average_advance),
347            line_gap: Au::from_f64_px(line_height),
348            zero_horizontal_advance,
349            ic_horizontal_advance,
350            space_advance: Au::from_f64_px(space_advance),
351        }
352    }
353
354    fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
355        let font_ref = self.table_provider_data.font_ref().ok()?;
356        let _table_data = font_ref.table_data(tag)?;
357        Some(FontTable {
358            data: self.table_provider_data.clone(),
359            tag,
360        })
361    }
362
363    fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
364        let face = self.face.lock();
365
366        let load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
367        let result = unsafe { FT_Load_Glyph(face.as_ptr(), glyph_id as FT_UInt, load_flags) };
368        if 0 != result {
369            debug!("Unable to load glyph {}. reason: {:?}", glyph_id, result);
370            return Rect::default();
371        }
372
373        let metrics = unsafe { &(*face.as_ref().glyph).metrics };
374
375        Rect::new(
376            Point2D::new(
377                metrics.horiBearingX as f32,
378                (metrics.horiBearingY - metrics.height) as f32,
379            ),
380            Size2D::new(metrics.width as f32, metrics.height as f32),
381        ) * (1. / 64.)
382    }
383
384    fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
385        // On other platforms, we only pass this when we know that we are loading a font with
386        // color characters, but not passing this flag simply *prevents* WebRender from
387        // loading bitmaps. There's no harm to always passing it.
388        let mut flags = FontInstanceFlags::EMBEDDED_BITMAPS;
389
390        if self.synthetic_bold {
391            flags |= FontInstanceFlags::SYNTHETIC_BOLD;
392        }
393
394        flags
395    }
396
397    fn variations(&self) -> &[FontVariation] {
398        &self.variations
399    }
400}
401
402impl PlatformFont {
403    /// Find the scale to use for metrics of unscalable fonts. Unscalable fonts, those using bitmap
404    /// glyphs, are scaled after glyph rasterization. In order for metrics to match the final scaled
405    /// font, we need to scale them based on the final size and the actual font size.
406    fn unscalable_font_metrics_scale(&self) -> f64 {
407        self.requested_face_size.to_f64_px() / self.actual_face_size.to_f64_px()
408    }
409}
410
411#[derive(Clone)]
412enum FreeTypeFaceTableProviderData {
413    Web(FontData),
414    Local(Arc<Mmap>, u32),
415}
416
417impl FreeTypeFaceTableProviderData {
418    fn font_ref(&self) -> Result<FontRef<'_>, ReadError> {
419        match self {
420            Self::Web(ipc_shared_memory) => FontRef::new(ipc_shared_memory.as_ref()),
421            Self::Local(mmap, index) => FontRef::from_index(mmap, *index),
422        }
423    }
424}
425
426impl std::fmt::Debug for FreeTypeFaceTableProviderData {
427    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428        Ok(())
429    }
430}
431
432// This is copied from the webrender glyph rasterizer
433// https://github.com/servo/webrender/blob/c4bd5b47d8f5cd684334b445e67a1f945d106848/wr_glyph_rasterizer/src/platform/unix/font.rs#L115
434//
435// Custom version of FT_GlyphSlot_Embolden to be less aggressive with outline
436// fonts than the default implementation in FreeType.
437fn mozilla_glyphslot_embolden_less(slot: FT_GlyphSlot) {
438    use freetype_sys::{
439        FT_GLYPH_FORMAT_OUTLINE, FT_GlyphSlot_Embolden, FT_Long, FT_MulFix, FT_Outline_Embolden,
440    };
441
442    if slot.is_null() {
443        return;
444    }
445
446    let slot_ = unsafe { &mut *slot };
447    let format = slot_.format;
448    if format != FT_GLYPH_FORMAT_OUTLINE {
449        // For non-outline glyphs, just fall back to FreeType's function.
450        unsafe { FT_GlyphSlot_Embolden(slot) };
451        return;
452    }
453
454    let face_ = unsafe { &*slot_.face };
455
456    // FT_GlyphSlot_Embolden uses a divisor of 24 here; we'll be only half as
457    // bold.
458    let size_ = unsafe { &*face_.size };
459    let strength = unsafe { FT_MulFix(face_.units_per_EM as FT_Long, size_.metrics.y_scale) / 48 };
460    unsafe { FT_Outline_Embolden(&raw mut slot_.outline, strength) };
461
462    // Adjust metrics to suit the fattened glyph.
463    if slot_.advance.x != 0 {
464        slot_.advance.x += strength;
465    }
466    if slot_.advance.y != 0 {
467        slot_.advance.y += strength;
468    }
469    slot_.metrics.width += strength;
470    slot_.metrics.height += strength;
471    slot_.metrics.horiAdvance += strength;
472    slot_.metrics.vertAdvance += strength;
473    slot_.metrics.horiBearingY += strength;
474}