Skip to main content

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