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