1use 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 skrifa::attribute::Weight;
23use style::Zero;
24use webrender_api::{FontInstanceFlags, FontVariation};
25
26use super::library_handle::FreeTypeLibraryHandle;
27use crate::FontData;
28use crate::font::{FontMetrics, FontTableMethods, FractionalPixel, PlatformFontMethods};
29use crate::glyph::GlyphId;
30use crate::platform::freetype::freetype_face::FreeTypeFace;
31
32const SEMI_BOLD_U16: u16 = Weight::SEMI_BOLD.value() as u16;
33
34fn fixed_26_dot_6_to_float(fixed: FT_F26Dot6) -> f64 {
36 fixed as f64 / 64.0
37}
38
39#[derive(Debug)]
40pub struct FontTable {
41 data: FreeTypeFaceTableProviderData,
42 tag: Tag,
43}
44
45impl FontTableMethods for FontTable {
46 fn buffer(&self) -> &[u8] {
47 let font_ref = self.data.font_ref().expect("Font checked before creating");
48 let table_data = font_ref
49 .table_data(self.tag)
50 .expect("Table existence checked before creating");
51 table_data.as_bytes()
52 }
53}
54
55#[derive(Debug)]
56pub struct PlatformFont {
57 face: ReentrantMutex<FreeTypeFace>,
58 requested_face_size: Au,
59 actual_face_size: Au,
60 variations: Vec<FontVariation>,
61 synthetic_bold: bool,
62
63 table_provider_data: FreeTypeFaceTableProviderData,
65}
66
67impl PlatformFontMethods for PlatformFont {
68 fn new_from_data(
69 _font_identifier: FontIdentifier,
70 font_data: &FontData,
71 requested_size: Option<Au>,
72 variations: &[FontVariation],
73 synthetic_bold: bool,
74 ) -> Result<PlatformFont, &'static str> {
75 let library = FreeTypeLibraryHandle::get().lock();
76 let data: &[u8] = font_data.as_ref();
77 let face = FreeTypeFace::new_from_memory(&library, data)?;
78
79 let normalized_variations = face.set_variations_for_font(variations, &library)?;
80
81 let (requested_face_size, actual_face_size) = match requested_size {
82 Some(requested_size) => (requested_size, face.set_size(requested_size)?),
83 None => (Au::zero(), Au::zero()),
84 };
85
86 let table_provider_data = FreeTypeFaceTableProviderData::Web(font_data.clone());
87
88 let synthetic_bold = table_provider_data.should_apply_synthetic_bold(synthetic_bold);
89
90 Ok(PlatformFont {
91 face: ReentrantMutex::new(face),
92 requested_face_size,
93 actual_face_size,
94 table_provider_data,
95 variations: normalized_variations,
96 synthetic_bold,
97 })
98 }
99
100 fn new_from_local_font_identifier(
101 font_identifier: LocalFontIdentifier,
102 requested_size: Option<Au>,
103 variations: &[FontVariation],
104 synthetic_bold: bool,
105 ) -> Result<PlatformFont, &'static str> {
106 let library = FreeTypeLibraryHandle::get().lock();
107 let filename = CString::new(&*font_identifier.path).expect("filename contains NUL byte!");
108
109 let face = FreeTypeFace::new_from_file(
110 &library,
111 &filename,
112 font_identifier.face_index_for_freetype(),
113 )?;
114
115 let normalized_variations = face.set_variations_for_font(variations, &library)?;
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 Ok(memory_mapped_font_data) =
123 File::open(&*font_identifier.path).and_then(|file| unsafe { Mmap::map(&file) })
124 else {
125 return Err("Could not memory map");
126 };
127
128 let table_provider_data = FreeTypeFaceTableProviderData::Local(
129 Arc::new(memory_mapped_font_data),
130 font_identifier.index(),
131 );
132
133 let synthetic_bold = table_provider_data.should_apply_synthetic_bold(synthetic_bold);
134
135 Ok(PlatformFont {
136 face: ReentrantMutex::new(face),
137 requested_face_size,
138 actual_face_size,
139 table_provider_data,
140 variations: normalized_variations,
141 synthetic_bold,
142 })
143 }
144
145 fn descriptor(&self) -> FontTemplateDescriptor {
146 let Ok(font_ref) = self.table_provider_data.font_ref() else {
147 return FontTemplateDescriptor::default();
148 };
149 let Ok(os2) = font_ref.os2() else {
150 return FontTemplateDescriptor::default();
151 };
152 Self::descriptor_from_os2_table(&os2)
153 }
154
155 fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
156 let face = self.face.lock();
157
158 unsafe {
159 let idx = FT_Get_Char_Index(face.as_ptr(), codepoint as FT_ULong);
160 if idx != 0 as FT_UInt {
161 Some(idx as GlyphId)
162 } else {
163 debug!(
164 "Invalid codepoint: U+{:04X} ('{}')",
165 codepoint as u32, codepoint
166 );
167 None
168 }
169 }
170 }
171
172 fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
173 let face = self.face.lock();
174
175 let mut delta = FT_Vector { x: 0, y: 0 };
176 unsafe {
177 FT_Get_Kerning(
178 face.as_ptr(),
179 first_glyph,
180 second_glyph,
181 FT_KERNING_DEFAULT,
182 &mut delta,
183 );
184 }
185 fixed_26_dot_6_to_float(delta.x) * self.unscalable_font_metrics_scale()
186 }
187
188 fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
189 let face = self.face.lock();
190
191 let load_flags = face.glyph_load_flags();
192 let result = unsafe { FT_Load_Glyph(face.as_ptr(), glyph as FT_UInt, load_flags) };
193 if 0 != result {
194 debug!("Unable to load glyph {}. reason: {:?}", glyph, result);
195 return None;
196 }
197
198 let void_glyph = face.as_ref().glyph;
199 let slot: FT_GlyphSlot = void_glyph;
200 assert!(!slot.is_null());
201
202 if self.synthetic_bold {
203 mozilla_glyphslot_embolden_less(slot);
204 }
205
206 let advance = unsafe { (*slot).metrics.horiAdvance };
207 Some(fixed_26_dot_6_to_float(advance) * self.unscalable_font_metrics_scale())
208 }
209
210 fn metrics(&self) -> FontMetrics {
211 let face = self.face.lock();
212 let font_ref = self.table_provider_data.font_ref();
213
214 let freetype_size: &FT_SizeRec = unsafe { &*face.as_ref().size };
216 let freetype_metrics: &FT_Size_Metrics = &(freetype_size).metrics;
217
218 let mut max_advance;
219 let mut max_ascent;
220 let mut max_descent;
221 let mut line_height;
222 let mut y_scale = 0.0;
223 let mut em_height;
224 if face.scalable() {
225 y_scale = freetype_metrics.y_scale as f64 / 65535.0 / 64.0;
233
234 max_advance = (face.as_ref().max_advance_width as f64) * y_scale;
235 max_ascent = (face.as_ref().ascender as f64) * y_scale;
236 max_descent = -(face.as_ref().descender as f64) * y_scale;
237 line_height = (face.as_ref().height as f64) * y_scale;
238 em_height = (face.as_ref().units_per_EM as f64) * y_scale;
239 } else {
240 max_advance = fixed_26_dot_6_to_float(freetype_metrics.max_advance);
241 max_ascent = fixed_26_dot_6_to_float(freetype_metrics.ascender);
242 max_descent = -fixed_26_dot_6_to_float(freetype_metrics.descender);
243 line_height = fixed_26_dot_6_to_float(freetype_metrics.height);
244
245 em_height = freetype_metrics.y_ppem as f64;
246 if let Ok(head) = font_ref.clone().and_then(|font_ref| font_ref.head()) {
251 if face.color() {
255 em_height = self.requested_face_size.to_f64_px();
256 let adjust_scale = em_height / (freetype_metrics.y_ppem as f64);
257 max_advance *= adjust_scale;
258 max_descent *= adjust_scale;
259 max_ascent *= adjust_scale;
260 line_height *= adjust_scale;
261 }
262 y_scale = em_height / head.units_per_em() as f64;
263 }
264 }
265
266 let leading = line_height - (max_ascent + max_descent);
273
274 let underline_size = face.as_ref().underline_thickness as f64 * y_scale;
275 let underline_offset = face.as_ref().underline_position as f64 * y_scale + 0.5;
276
277 let mut strikeout_size = underline_size;
280 let mut strikeout_offset = em_height * 409.0 / 2048.0 + 0.5 * strikeout_size;
281
282 let mut x_height = 0.5 * em_height;
286 let mut average_advance = 0.0;
287
288 if let Ok(os2) = font_ref.and_then(|font_ref| font_ref.os2()) {
289 let y_strikeout_size = os2.y_strikeout_size();
290 let y_strikeout_position = os2.y_strikeout_position();
291 if !y_strikeout_size.is_zero() && !y_strikeout_position.is_zero() {
292 strikeout_size = y_strikeout_size as f64 * y_scale;
293 strikeout_offset = y_strikeout_position as f64 * y_scale;
294 }
295
296 let sx_height = os2.sx_height().unwrap_or(0);
297 if !sx_height.is_zero() {
298 x_height = sx_height as f64 * y_scale;
299 }
300
301 let x_average_char_width = os2.x_avg_char_width();
302 if !x_average_char_width.is_zero() {
303 average_advance = x_average_char_width as f64 * y_scale;
304 }
305 }
306
307 if average_advance.is_zero() {
308 average_advance = self
309 .glyph_index('0')
310 .and_then(|idx| self.glyph_h_advance(idx))
311 .map_or(max_advance, |advance| advance * y_scale);
312 }
313
314 let zero_horizontal_advance = self
315 .glyph_index('0')
316 .and_then(|idx| self.glyph_h_advance(idx))
317 .map(Au::from_f64_px);
318 let ic_horizontal_advance = self
319 .glyph_index('\u{6C34}')
320 .and_then(|idx| self.glyph_h_advance(idx))
321 .map(Au::from_f64_px);
322 let space_advance = self
323 .glyph_index(' ')
324 .and_then(|idx| self.glyph_h_advance(idx))
325 .unwrap_or(average_advance);
326
327 FontMetrics {
328 underline_size: Au::from_f64_px(underline_size),
329 underline_offset: Au::from_f64_px(underline_offset),
330 strikeout_size: Au::from_f64_px(strikeout_size),
331 strikeout_offset: Au::from_f64_px(strikeout_offset),
332 leading: Au::from_f64_px(leading),
333 x_height: Au::from_f64_px(x_height),
334 em_size: Au::from_f64_px(em_height),
335 ascent: Au::from_f64_px(max_ascent),
336 descent: Au::from_f64_px(max_descent),
337 max_advance: Au::from_f64_px(max_advance),
338 average_advance: Au::from_f64_px(average_advance),
339 line_gap: Au::from_f64_px(line_height),
340 zero_horizontal_advance,
341 ic_horizontal_advance,
342 space_advance: Au::from_f64_px(space_advance),
343 }
344 }
345
346 fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
347 let font_ref = self.table_provider_data.font_ref().ok()?;
348 let _table_data = font_ref.table_data(tag)?;
349 Some(FontTable {
350 data: self.table_provider_data.clone(),
351 tag,
352 })
353 }
354
355 fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
356 let face = self.face.lock();
357
358 let load_flags = FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING;
359 let result = unsafe { FT_Load_Glyph(face.as_ptr(), glyph_id as FT_UInt, load_flags) };
360 if 0 != result {
361 debug!("Unable to load glyph {}. reason: {:?}", glyph_id, result);
362 return Rect::default();
363 }
364
365 let metrics = unsafe { &(*face.as_ref().glyph).metrics };
366
367 Rect::new(
368 Point2D::new(
369 metrics.horiBearingX as f32,
370 (metrics.horiBearingY - metrics.height) as f32,
371 ),
372 Size2D::new(metrics.width as f32, metrics.height as f32),
373 ) * (1. / 64.)
374 }
375
376 fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
377 let mut flags = FontInstanceFlags::EMBEDDED_BITMAPS;
381
382 if self.synthetic_bold {
385 flags |= FontInstanceFlags::SYNTHETIC_BOLD;
386 }
387
388 flags
389 }
390
391 fn variations(&self) -> &[FontVariation] {
392 &self.variations
393 }
394}
395
396impl PlatformFont {
397 fn unscalable_font_metrics_scale(&self) -> f64 {
401 self.requested_face_size.to_f64_px() / self.actual_face_size.to_f64_px()
402 }
403}
404
405#[derive(Clone)]
406enum FreeTypeFaceTableProviderData {
407 Web(FontData),
408 Local(Arc<Mmap>, u32),
409}
410
411impl FreeTypeFaceTableProviderData {
412 fn font_ref(&self) -> Result<FontRef<'_>, ReadError> {
413 match self {
414 Self::Web(ipc_shared_memory) => FontRef::new(ipc_shared_memory.as_ref()),
415 Self::Local(mmap, index) => FontRef::from_index(mmap, *index),
416 }
417 }
418
419 fn should_apply_synthetic_bold(&self, synthetic_bold: bool) -> bool {
420 let face_is_bold = self
423 .font_ref()
424 .and_then(|font_ref| font_ref.os2())
425 .is_ok_and(|table| table.us_weight_class() >= SEMI_BOLD_U16);
426 let is_variable_font = self
427 .font_ref()
428 .and_then(|font_ref| font_ref.fvar())
429 .is_ok_and(|table| table.axis_count() > 0);
430 !face_is_bold && !is_variable_font && synthetic_bold
431 }
432}
433
434impl std::fmt::Debug for FreeTypeFaceTableProviderData {
435 fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436 Ok(())
437 }
438}
439
440fn mozilla_glyphslot_embolden_less(slot: FT_GlyphSlot) {
446 use freetype_sys::{
447 FT_GLYPH_FORMAT_OUTLINE, FT_GlyphSlot_Embolden, FT_Long, FT_MulFix, FT_Outline_Embolden,
448 };
449
450 if slot.is_null() {
451 return;
452 }
453
454 let slot_ = unsafe { &mut *slot };
455 let format = slot_.format;
456 if format != FT_GLYPH_FORMAT_OUTLINE {
457 unsafe { FT_GlyphSlot_Embolden(slot) };
459 return;
460 }
461
462 let face_ = unsafe { &*slot_.face };
463
464 let size_ = unsafe { &*face_.size };
467 let strength = unsafe { FT_MulFix(face_.units_per_EM as FT_Long, size_.metrics.y_scale) / 48 };
468 unsafe { FT_Outline_Embolden(&raw mut slot_.outline, strength) };
469
470 if slot_.advance.x != 0 {
472 slot_.advance.x += strength;
473 }
474 if slot_.advance.y != 0 {
475 slot_.advance.y += strength;
476 }
477 slot_.metrics.width += strength;
478 slot_.metrics.height += strength;
479 slot_.metrics.horiAdvance += strength;
480 slot_.metrics.vertAdvance += strength;
481 slot_.metrics.horiBearingY += strength;
482}