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