1use 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
33fn 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 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 variations: &[FontVariation],
72 synthetic_bold: bool,
73 ) -> Result<PlatformFont, &'static str> {
74 let library = FreeTypeLibraryHandle::get().lock();
75 let data = FontBackingStore::Web(font_data.clone());
76 let face = FreeTypeFace::new_from_memory(&library, data, 0)?;
77
78 let normalized_variations = face.set_variations_for_font(variations, &library)?;
79
80 let (requested_face_size, actual_face_size) = match requested_size {
81 Some(requested_size) => (requested_size, face.set_size(requested_size)?),
82 None => (Au::zero(), Au::zero()),
83 };
84
85 let table_provider_data = FreeTypeFaceTableProviderData::Web(font_data.clone());
86
87 let synthetic_bold = table_provider_data.should_apply_synthetic_bold(synthetic_bold);
88
89 Ok(PlatformFont {
90 face: ReentrantMutex::new(face),
91 requested_face_size,
92 actual_face_size,
93 table_provider_data,
94 variations: normalized_variations,
95 synthetic_bold,
96 })
97 }
98
99 fn new_from_local_font_identifier(
100 font_identifier: LocalFontIdentifier,
101 requested_size: Option<Au>,
102 variations: &[FontVariation],
103 synthetic_bold: bool,
104 ) -> Result<PlatformFont, &'static str> {
105 let library = FreeTypeLibraryHandle::get().lock();
106
107 let Ok(memory_mapped_font_data) = File::open(&*font_identifier.path)
108 .and_then(|file| unsafe { Mmap::map(&file) })
109 .map(Arc::new)
110 else {
111 return Err("Could not memory map font");
112 };
113
114 let face_index = font_identifier.face_index_for_freetype();
115 let face = FreeTypeFace::new_from_memory(
116 &library,
117 FontBackingStore::Local(memory_mapped_font_data.clone()),
118 face_index,
119 )?;
120
121 let normalized_variations = face.set_variations_for_font(variations, &library)?;
122
123 let (requested_face_size, actual_face_size) = match requested_size {
124 Some(requested_size) => (requested_size, face.set_size(requested_size)?),
125 None => (Au::zero(), Au::zero()),
126 };
127
128 let table_provider_data =
129 FreeTypeFaceTableProviderData::Local(memory_mapped_font_data, font_identifier.index());
130
131 let synthetic_bold = table_provider_data.should_apply_synthetic_bold(synthetic_bold);
132
133 Ok(PlatformFont {
134 face: ReentrantMutex::new(face),
135 requested_face_size,
136 actual_face_size,
137 table_provider_data,
138 variations: normalized_variations,
139 synthetic_bold,
140 })
141 }
142
143 fn descriptor(&self) -> FontTemplateDescriptor {
144 let Ok(font_ref) = self.table_provider_data.font_ref() else {
145 return FontTemplateDescriptor::default();
146 };
147 let Ok(os2) = font_ref.os2() else {
148 return FontTemplateDescriptor::default();
149 };
150 Self::descriptor_from_os2_table(&os2)
151 }
152
153 fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
154 let face = self.face.lock();
155
156 unsafe {
157 let idx = FT_Get_Char_Index(face.as_ptr(), codepoint as FT_ULong);
158 if idx != 0 as FT_UInt {
159 Some(idx as GlyphId)
160 } else {
161 debug!(
162 "Invalid codepoint: U+{:04X} ('{}')",
163 codepoint as u32, codepoint
164 );
165 None
166 }
167 }
168 }
169
170 fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
171 let face = self.face.lock();
172
173 let mut delta = FT_Vector { x: 0, y: 0 };
174 unsafe {
175 FT_Get_Kerning(
176 face.as_ptr(),
177 first_glyph,
178 second_glyph,
179 FT_KERNING_DEFAULT,
180 &mut delta,
181 );
182 }
183 fixed_26_dot_6_to_float(delta.x) * self.unscalable_font_metrics_scale()
184 }
185
186 fn glyph_h_advance(&self, glyph: GlyphId) -> Option<FractionalPixel> {
187 let face = self.face.lock();
188
189 let load_flags = face.glyph_load_flags();
190 let result = unsafe { FT_Load_Glyph(face.as_ptr(), glyph as FT_UInt, load_flags) };
191 if 0 != result {
192 debug!("Unable to load glyph {}. reason: {:?}", glyph, result);
193 return None;
194 }
195
196 let void_glyph = face.as_ref().glyph;
197 let slot: FT_GlyphSlot = void_glyph;
198 if void_glyph.is_null() {
199 return None;
200 }
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}