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