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_KERNING_DEFAULT,
13    FT_LOAD_DEFAULT, FT_LOAD_NO_HINTING, FT_Load_Glyph, FT_Size_Metrics, FT_SizeRec, FT_UInt,
14    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
60    /// A member that allows using `skrifa` to read values from this font.
61    table_provider_data: FreeTypeFaceTableProviderData,
62}
63
64impl PlatformFontMethods for PlatformFont {
65    fn new_from_data(
66        _font_identifier: FontIdentifier,
67        font_data: &FontData,
68        requested_size: Option<Au>,
69        variations: &[FontVariation],
70    ) -> Result<PlatformFont, &'static str> {
71        let library = FreeTypeLibraryHandle::get().lock();
72        let data: &[u8] = font_data.as_ref();
73        let face = FreeTypeFace::new_from_memory(&library, data)?;
74
75        let normalized_variations = face.set_variations_for_font(variations, &library)?;
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        Ok(PlatformFont {
83            face: ReentrantMutex::new(face),
84            requested_face_size,
85            actual_face_size,
86            table_provider_data: FreeTypeFaceTableProviderData::Web(font_data.clone()),
87            variations: normalized_variations,
88        })
89    }
90
91    fn new_from_local_font_identifier(
92        font_identifier: LocalFontIdentifier,
93        requested_size: Option<Au>,
94        variations: &[FontVariation],
95    ) -> Result<PlatformFont, &'static str> {
96        let library = FreeTypeLibraryHandle::get().lock();
97        let filename = CString::new(&*font_identifier.path).expect("filename contains NUL byte!");
98
99        let face = FreeTypeFace::new_from_file(&library, &filename, font_identifier.index())?;
100
101        let normalized_variations = face.set_variations_for_font(variations, &library)?;
102
103        let (requested_face_size, actual_face_size) = match requested_size {
104            Some(requested_size) => (requested_size, face.set_size(requested_size)?),
105            None => (Au::zero(), Au::zero()),
106        };
107
108        let Ok(memory_mapped_font_data) =
109            File::open(&*font_identifier.path).and_then(|file| unsafe { Mmap::map(&file) })
110        else {
111            return Err("Could not memory map");
112        };
113
114        Ok(PlatformFont {
115            face: ReentrantMutex::new(face),
116            requested_face_size,
117            actual_face_size,
118            table_provider_data: FreeTypeFaceTableProviderData::Local(
119                Arc::new(memory_mapped_font_data),
120                font_identifier.index(),
121            ),
122            variations: normalized_variations,
123        })
124    }
125
126    fn descriptor(&self) -> FontTemplateDescriptor {
127        let Ok(font_ref) = self.table_provider_data.font_ref() else {
128            return FontTemplateDescriptor::default();
129        };
130        let Ok(os2) = font_ref.os2() else {
131            return FontTemplateDescriptor::default();
132        };
133        Self::descriptor_from_os2_table(&os2)
134    }
135
136    fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
137        let face = self.face.lock();
138
139        unsafe {
140            let idx = FT_Get_Char_Index(face.as_ptr(), codepoint as FT_ULong);
141            if idx != 0 as FT_UInt {
142                Some(idx as GlyphId)
143            } else {
144                debug!(
145                    "Invalid codepoint: U+{:04X} ('{}')",
146                    codepoint as u32, codepoint
147                );
148                None
149            }
150        }
151    }
152
153    fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
154        let face = self.face.lock();
155
156        let mut delta = FT_Vector { x: 0, y: 0 };
157        unsafe {
158            FT_Get_Kerning(
159                face.as_ptr(),
160                first_glyph,
161                second_glyph,
162                FT_KERNING_DEFAULT,
163                &mut delta,
164            );
165        }
166        fixed_26_dot_6_to_float(delta.x) * self.unscalable_font_metrics_scale()
167    }
168
169    fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
170        let face = self.face.lock();
171
172        let load_flags = face.glyph_load_flags();
173        let result = unsafe { FT_Load_Glyph(face.as_ptr(), glyph as FT_UInt, load_flags) };
174        if 0 != result {
175            debug!("Unable to load glyph {}. reason: {:?}", glyph, result);
176            return None;
177        }
178
179        let void_glyph = face.as_ref().glyph;
180        let slot: FT_GlyphSlot = void_glyph;
181        assert!(!slot.is_null());
182
183        let advance = unsafe { (*slot).metrics.horiAdvance };
184        Some(fixed_26_dot_6_to_float(advance) * self.unscalable_font_metrics_scale())
185    }
186
187    fn metrics(&self) -> FontMetrics {
188        let face = self.face.lock();
189        let font_ref = self.table_provider_data.font_ref();
190
191        // face.size is a *c_void in the bindings, presumably to avoid recursive structural types
192        let freetype_size: &FT_SizeRec = unsafe { &*face.as_ref().size };
193        let freetype_metrics: &FT_Size_Metrics = &(freetype_size).metrics;
194
195        let mut max_advance;
196        let mut max_ascent;
197        let mut max_descent;
198        let mut line_height;
199        let mut y_scale = 0.0;
200        let mut em_height;
201        if face.scalable() {
202            // Prefer FT_Size_Metrics::y_scale to y_ppem as y_ppem does not have subpixel accuracy.
203            //
204            // FT_Size_Metrics::y_scale is in 16.16 fixed point format.  Its (fractional) value is a
205            // factor that converts vertical metrics from design units to units of 1/64 pixels, so
206            // that the result may be interpreted as pixels in 26.6 fixed point format.
207            //
208            // This converts the value to a float without losing precision.
209            y_scale = freetype_metrics.y_scale as f64 / 65535.0 / 64.0;
210
211            max_advance = (face.as_ref().max_advance_width as f64) * y_scale;
212            max_ascent = (face.as_ref().ascender as f64) * y_scale;
213            max_descent = -(face.as_ref().descender as f64) * y_scale;
214            line_height = (face.as_ref().height as f64) * y_scale;
215            em_height = (face.as_ref().units_per_EM as f64) * y_scale;
216        } else {
217            max_advance = fixed_26_dot_6_to_float(freetype_metrics.max_advance);
218            max_ascent = fixed_26_dot_6_to_float(freetype_metrics.ascender);
219            max_descent = -fixed_26_dot_6_to_float(freetype_metrics.descender);
220            line_height = fixed_26_dot_6_to_float(freetype_metrics.height);
221
222            em_height = freetype_metrics.y_ppem as f64;
223            // FT_Face doc says units_per_EM and a bunch of following fields are "only relevant to
224            // scalable outlines". If it's an sfnt, we can get units_per_EM from the 'head' table
225            // instead; otherwise, we don't have a unitsPerEm value so we can't compute y_scale and
226            // x_scale.
227            if let Ok(head) = font_ref.clone().and_then(|font_ref| font_ref.head()) {
228                // Bug 1267909 - Even if the font is not explicitly scalable, if the face has color
229                // bitmaps, it should be treated as scalable and scaled to the desired size. Metrics
230                // based on y_ppem need to be rescaled for the adjusted size.
231                if face.color() {
232                    em_height = self.requested_face_size.to_f64_px();
233                    let adjust_scale = em_height / (freetype_metrics.y_ppem as f64);
234                    max_advance *= adjust_scale;
235                    max_descent *= adjust_scale;
236                    max_ascent *= adjust_scale;
237                    line_height *= adjust_scale;
238                }
239                y_scale = em_height / head.units_per_em() as f64;
240            }
241        }
242
243        // 'leading' is supposed to be the vertical distance between two baselines,
244        // reflected by the height attribute in freetype. On OS X (w/ CTFont),
245        // leading represents the distance between the bottom of a line descent to
246        // the top of the next line's ascent or: (line_height - ascent - descent),
247        // see http://stackoverflow.com/a/5635981 for CTFont implementation.
248        // Convert using a formula similar to what CTFont returns for consistency.
249        let leading = line_height - (max_ascent + max_descent);
250
251        let underline_size = face.as_ref().underline_thickness as f64 * y_scale;
252        let underline_offset = face.as_ref().underline_position as f64 * y_scale + 0.5;
253
254        // The default values for strikeout size and offset. Use OpenType spec's suggested position
255        // for Roman font as the default for offset.
256        let mut strikeout_size = underline_size;
257        let mut strikeout_offset = em_height * 409.0 / 2048.0 + 0.5 * strikeout_size;
258
259        // CSS 2.1, section 4.3.2 Lengths: "In the cases where it is
260        // impossible or impractical to determine the x-height, a value of
261        // 0.5em should be used."
262        let mut x_height = 0.5 * em_height;
263        let mut average_advance = 0.0;
264
265        if let Ok(os2) = font_ref.and_then(|font_ref| font_ref.os2()) {
266            let y_strikeout_size = os2.y_strikeout_size();
267            let y_strikeout_position = os2.y_strikeout_position();
268            if !y_strikeout_size.is_zero() && !y_strikeout_position.is_zero() {
269                strikeout_size = y_strikeout_size as f64 * y_scale;
270                strikeout_offset = y_strikeout_position as f64 * y_scale;
271            }
272
273            let sx_height = os2.sx_height().unwrap_or(0);
274            if !sx_height.is_zero() {
275                x_height = sx_height as f64 * y_scale;
276            }
277
278            let x_average_char_width = os2.x_avg_char_width();
279            if !x_average_char_width.is_zero() {
280                average_advance = x_average_char_width as f64 * y_scale;
281            }
282        }
283
284        if average_advance.is_zero() {
285            average_advance = self
286                .glyph_index('0')
287                .and_then(|idx| self.glyph_h_advance(idx))
288                .map_or(max_advance, |advance| advance * y_scale);
289        }
290
291        let zero_horizontal_advance = self
292            .glyph_index('0')
293            .and_then(|idx| self.glyph_h_advance(idx))
294            .map(Au::from_f64_px);
295        let ic_horizontal_advance = self
296            .glyph_index('\u{6C34}')
297            .and_then(|idx| self.glyph_h_advance(idx))
298            .map(Au::from_f64_px);
299        let space_advance = self
300            .glyph_index(' ')
301            .and_then(|idx| self.glyph_h_advance(idx))
302            .unwrap_or(average_advance);
303
304        FontMetrics {
305            underline_size: Au::from_f64_px(underline_size),
306            underline_offset: Au::from_f64_px(underline_offset),
307            strikeout_size: Au::from_f64_px(strikeout_size),
308            strikeout_offset: Au::from_f64_px(strikeout_offset),
309            leading: Au::from_f64_px(leading),
310            x_height: Au::from_f64_px(x_height),
311            em_size: Au::from_f64_px(em_height),
312            ascent: Au::from_f64_px(max_ascent),
313            descent: Au::from_f64_px(max_descent),
314            max_advance: Au::from_f64_px(max_advance),
315            average_advance: Au::from_f64_px(average_advance),
316            line_gap: Au::from_f64_px(line_height),
317            zero_horizontal_advance,
318            ic_horizontal_advance,
319            space_advance: Au::from_f64_px(space_advance),
320        }
321    }
322
323    fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
324        let font_ref = self.table_provider_data.font_ref().ok()?;
325        let _table_data = font_ref.table_data(tag)?;
326        Some(FontTable {
327            data: self.table_provider_data.clone(),
328            tag,
329        })
330    }
331
332    fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
333        let face = self.face.lock();
334
335        let load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
336        let result = unsafe { FT_Load_Glyph(face.as_ptr(), glyph_id as FT_UInt, load_flags) };
337        if 0 != result {
338            debug!("Unable to load glyph {}. reason: {:?}", glyph_id, result);
339            return Rect::default();
340        }
341
342        let metrics = unsafe { &(*face.as_ref().glyph).metrics };
343
344        Rect::new(
345            Point2D::new(
346                metrics.horiBearingX as f32,
347                (metrics.horiBearingY - metrics.height) as f32,
348            ),
349            Size2D::new(metrics.width as f32, metrics.height as f32),
350        ) * (1. / 64.)
351    }
352
353    fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
354        // On other platforms, we only pass this when we know that we are loading a font with
355        // color characters, but not passing this flag simply *prevents* WebRender from
356        // loading bitmaps. There's no harm to always passing it.
357        FontInstanceFlags::EMBEDDED_BITMAPS
358    }
359
360    fn variations(&self) -> &[FontVariation] {
361        &self.variations
362    }
363}
364
365impl PlatformFont {
366    /// Find the scale to use for metrics of unscalable fonts. Unscalable fonts, those using bitmap
367    /// glyphs, are scaled after glyph rasterization. In order for metrics to match the final scaled
368    /// font, we need to scale them based on the final size and the actual font size.
369    fn unscalable_font_metrics_scale(&self) -> f64 {
370        self.requested_face_size.to_f64_px() / self.actual_face_size.to_f64_px()
371    }
372}
373
374#[derive(Clone)]
375enum FreeTypeFaceTableProviderData {
376    Web(FontData),
377    Local(Arc<Mmap>, u32),
378}
379
380impl FreeTypeFaceTableProviderData {
381    fn font_ref(&self) -> Result<FontRef<'_>, ReadError> {
382        match self {
383            Self::Web(ipc_shared_memory) => FontRef::new(ipc_shared_memory.as_ref()),
384            Self::Local(mmap, index) => FontRef::from_index(mmap, *index),
385        }
386    }
387}
388
389impl std::fmt::Debug for FreeTypeFaceTableProviderData {
390    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391        Ok(())
392    }
393}