Skip to content

Commit 3962956

Browse files
authored
Fix loading raw data from .ttc files on macos (servo#38753)
# Objective Ensure that functionality which uses the raw font data (such as rendering text to canvas) works correctly on macOS when the specified font is a system font that lives in an OpenType Collection (`.ttc`) file. ## Changes made - The `read_data_from_file` in each backend now returns a `index: u32` in addition to `data: Vec<u8>` - The `data` field on the `Font` type has been renamed to `raw` and the `data` method on the `Font` type has been renamed to `raw_font`. This allows the index to be cached as computing is moderately expensive on macOS (on the order of 100 microseconds). - Both of the above now store/return a `struct RawFont` instead of a `FontData` where `RawFont` is defined as `struct RawFont { data: FontData, index: u32 }`. - The users of the `data` method have been updated to use the cached index from `data` rather than calling `.index()` each time. --------- Signed-off-by: Nico Burns <[email protected]>
1 parent 3225d19 commit 3962956

File tree

13 files changed

+164
-76
lines changed

13 files changed

+164
-76
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/canvas/peniko_conversions.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ use style::color::AbsoluteColor;
99
use crate::backend::Convert;
1010
use crate::canvas_data::Filter;
1111

12+
impl Convert<peniko::Font> for fonts::FontDataAndIndex {
13+
fn convert(self) -> peniko::Font {
14+
use std::sync::Arc;
15+
peniko::Font::new(peniko::Blob::new(Arc::new(self.data)), self.index)
16+
}
17+
}
18+
1219
impl Convert<kurbo::Join> for LineJoinStyle {
1320
fn convert(self) -> kurbo::Join {
1421
match self {

components/canvas/raqote_backend.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,11 @@ impl GenericDrawTarget for raqote::DrawTarget {
333333
SHARED_FONT_CACHE.with(|font_cache| {
334334
let identifier = template.identifier();
335335
if !font_cache.borrow().contains_key(&identifier) {
336-
let data = std::sync::Arc::new(run.font.data().as_ref().to_vec());
337-
let Ok(font) = Font::from_bytes(data, identifier.index()) else {
336+
let Ok(font_data_and_index) = run.font.font_data_and_index() else {
337+
return;
338+
};
339+
let data = std::sync::Arc::new(font_data_and_index.data.as_ref().to_vec());
340+
let Ok(font) = Font::from_bytes(data, font_data_and_index.index) else {
338341
return;
339342
};
340343
font_cache.borrow_mut().insert(identifier.clone(), font);

components/canvas/vello_backend.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -402,13 +402,11 @@ impl GenericDrawTarget for VelloDrawTarget {
402402
SHARED_FONT_CACHE.with(|font_cache| {
403403
let identifier = template.identifier();
404404
if !font_cache.borrow().contains_key(&identifier) {
405-
font_cache.borrow_mut().insert(
406-
identifier.clone(),
407-
peniko::Font::new(
408-
peniko::Blob::from(run.font.data().as_ref().to_vec()),
409-
identifier.index(),
410-
),
411-
);
405+
let Ok(font) = run.font.font_data_and_index() else {
406+
return;
407+
};
408+
let font = font.clone().convert();
409+
font_cache.borrow_mut().insert(identifier.clone(), font);
412410
}
413411

414412
let font_cache = font_cache.borrow();

components/canvas/vello_cpu_backend.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,13 +305,11 @@ impl GenericDrawTarget for VelloCPUDrawTarget {
305305
SHARED_FONT_CACHE.with(|font_cache| {
306306
let identifier = template.identifier();
307307
if !font_cache.borrow().contains_key(&identifier) {
308-
font_cache.borrow_mut().insert(
309-
identifier.clone(),
310-
peniko::Font::new(
311-
peniko::Blob::from(run.font.data().as_ref().to_vec()),
312-
identifier.index(),
313-
),
314-
);
308+
let Ok(font) = run.font.font_data_and_index() else {
309+
return;
310+
};
311+
let font = font.clone().convert();
312+
font_cache.borrow_mut().insert(identifier.clone(), font);
315313
}
316314

317315
let font_cache = font_cache.borrow();

components/fonts/font.rs

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ use crate::platform::font::{FontTable, PlatformFont};
3333
pub use crate::platform::font_list::fallback_font_families;
3434
use crate::{
3535
ByteIndex, EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData,
36-
FontIdentifier, FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods, GlyphData,
37-
GlyphId, GlyphStore, LocalFontIdentifier, Shaper,
36+
FontDataAndIndex, FontDataError, FontIdentifier, FontTemplateDescriptor, FontTemplateRef,
37+
FontTemplateRefMethods, GlyphData, GlyphId, GlyphStore, LocalFontIdentifier, Shaper,
3838
};
3939

4040
#[macro_export]
@@ -254,8 +254,9 @@ pub struct Font {
254254
pub metrics: FontMetrics,
255255
pub descriptor: FontDescriptor,
256256

257-
/// The data for this font. This might be uninitialized for system fonts.
258-
data: OnceLock<FontData>,
257+
/// The data for this font. And the index of the font within the data (in case it's a TTC)
258+
/// This might be uninitialized for system fonts.
259+
data_and_index: OnceLock<FontDataAndIndex>,
259260

260261
shaper: OnceLock<Shaper>,
261262
cached_shape_data: RwLock<CachedShapeData>,
@@ -313,7 +314,9 @@ impl Font {
313314
template,
314315
metrics,
315316
descriptor,
316-
data: data.map(OnceLock::from).unwrap_or_default(),
317+
data_and_index: data
318+
.map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
319+
.unwrap_or_default(),
317320
shaper: OnceLock::new(),
318321
cached_shape_data: Default::default(),
319322
font_instance_key: Default::default(),
@@ -348,17 +351,20 @@ impl Font {
348351

349352
/// Return the data for this `Font`. Note that this is currently highly inefficient for system
350353
/// fonts and should not be used except in legacy canvas code.
351-
pub fn data(&self) -> &FontData {
352-
self.data.get_or_init(|| {
353-
let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
354-
unreachable!("All web fonts should already have initialized data");
355-
};
356-
FontData::from_bytes(
357-
&local_font_identifier
358-
.read_data_from_file()
359-
.unwrap_or_default(),
360-
)
361-
})
354+
pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
355+
if let Some(data_and_index) = self.data_and_index.get() {
356+
return Ok(data_and_index);
357+
}
358+
359+
let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
360+
unreachable!("All web fonts should already have initialized data");
361+
};
362+
let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
363+
return Err(FontDataError::FailedToLoad);
364+
};
365+
366+
let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
367+
Ok(data_and_index)
362368
}
363369

364370
pub fn variations(&self) -> &[FontVariation] {

components/fonts/lib.rs

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,17 @@ pub mod platform;
1414
mod shaper;
1515
mod system_font_service;
1616

17-
use std::sync::Arc;
18-
1917
pub use font::*;
2018
pub use font_context::*;
2119
pub use font_store::*;
2220
pub use font_template::*;
21+
pub use fonts_traits::*;
2322
pub use glyph::*;
24-
use ipc_channel::ipc::IpcSharedMemory;
25-
use malloc_size_of_derive::MallocSizeOf;
2623
pub use platform::LocalFontIdentifier;
2724
pub use shaper::*;
2825
pub use system_font_service::*;
2926
use unicode_properties::{EmojiStatus, UnicodeEmoji, emoji};
3027

31-
/// A data structure to store data for fonts. Data is stored internally in an
32-
/// [`IpcSharedMemory`] handle, so that it can be send without serialization
33-
/// across IPC channels.
34-
#[derive(Clone, MallocSizeOf)]
35-
pub struct FontData(#[conditional_malloc_size_of] pub(crate) Arc<IpcSharedMemory>);
36-
37-
impl FontData {
38-
pub fn from_bytes(bytes: &[u8]) -> Self {
39-
Self(Arc::new(IpcSharedMemory::from_bytes(bytes)))
40-
}
41-
42-
pub(crate) fn as_ipc_shared_memory(&self) -> Arc<IpcSharedMemory> {
43-
self.0.clone()
44-
}
45-
}
46-
47-
impl AsRef<[u8]> for FontData {
48-
fn as_ref(&self) -> &[u8] {
49-
&self.0
50-
}
51-
}
52-
5328
/// Whether or not font fallback selection prefers the emoji or text representation
5429
/// of a character. If `None` then either presentation is acceptable.
5530
#[derive(Clone, Copy, Debug, PartialEq)]

components/fonts/platform/freetype/font.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ enum FreeTypeFaceTableProviderData {
410410
impl FreeTypeFaceTableProviderData {
411411
fn font_ref(&self) -> Result<FontRef<'_>, ReadError> {
412412
match self {
413-
Self::Web(ipc_shared_memory) => FontRef::new(&ipc_shared_memory.0),
413+
Self::Web(ipc_shared_memory) => FontRef::new(ipc_shared_memory.as_ref()),
414414
Self::Local(mmap, index) => FontRef::from_index(mmap, *index),
415415
}
416416
}

components/fonts/platform/freetype/mod.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ use serde::{Deserialize, Serialize};
1313
use style::Atom;
1414
use webrender_api::NativeFontHandle;
1515

16+
use crate::{FontData, FontDataAndIndex};
17+
1618
pub mod font;
1719
mod freetype_face;
1820

@@ -57,9 +59,14 @@ impl LocalFontIdentifier {
5759
}
5860
}
5961

60-
pub(crate) fn read_data_from_file(&self) -> Option<Vec<u8>> {
62+
pub(crate) fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
6163
let file = File::open(Path::new(&*self.path)).ok()?;
6264
let mmap = unsafe { Mmap::map(&file).ok()? };
63-
Some(mmap[..].to_vec())
65+
let data = FontData::from_bytes(&mmap);
66+
67+
Some(FontDataAndIndex {
68+
data,
69+
index: self.variation_index as u32,
70+
})
6471
}
6572
}

components/fonts/platform/macos/font_list.rs

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ use std::fs::File;
66
use std::path::Path;
77

88
use base::text::{UnicodeBlock, UnicodeBlockMethod, unicode_plane};
9-
use log::debug;
9+
use log::{debug, warn};
1010
use malloc_size_of_derive::MallocSizeOf;
1111
use memmap2::Mmap;
12+
use read_fonts::types::NameId;
13+
use read_fonts::{FileRef, TableProvider as _};
1214
use serde::{Deserialize, Serialize};
1315
use style::Atom;
1416
use style::values::computed::font::GenericFontFamily;
@@ -18,8 +20,8 @@ use webrender_api::NativeFontHandle;
1820
use crate::platform::add_noto_fallback_families;
1921
use crate::platform::font::CoreTextFontTraitsMapping;
2022
use crate::{
21-
EmojiPresentationPreference, FallbackFontSelectionOptions, FontIdentifier, FontTemplate,
22-
FontTemplateDescriptor, LowercaseFontFamilyName,
23+
EmojiPresentationPreference, FallbackFontSelectionOptions, FontData, FontDataAndIndex,
24+
FontIdentifier, FontTemplate, FontTemplateDescriptor, LowercaseFontFamilyName,
2325
};
2426

2527
/// An identifier for a local font on a MacOS system. These values comes from the CoreText
@@ -43,16 +45,61 @@ impl LocalFontIdentifier {
4345
0
4446
}
4547

46-
pub(crate) fn read_data_from_file(&self) -> Option<Vec<u8>> {
47-
// TODO: This is incorrect, if the font file is a TTC (collection) with more than
48-
// one font. In that case we either need to reconstruct the pertinent tables into
49-
// a bundle of font data (expensive) or make sure that the value returned by
50-
// `index()` above is correct. The latter is potentially tricky as macOS might not
51-
// do an accurate mapping between the PostScript name that it gives us and what is
52-
// listed in the font.
48+
pub(crate) fn font_data_and_index(&self) -> Option<FontDataAndIndex> {
5349
let file = File::open(Path::new(&*self.path)).ok()?;
5450
let mmap = unsafe { Mmap::map(&file).ok()? };
55-
Some(mmap[..].to_vec())
51+
52+
// Determine index
53+
let file_ref = FileRef::new(mmap.as_ref()).ok()?;
54+
let index = ttc_index_from_postscript_name(file_ref, &self.postscript_name);
55+
56+
Some(FontDataAndIndex {
57+
data: FontData::from_bytes(&mmap),
58+
index,
59+
})
60+
}
61+
}
62+
63+
/// CoreText font enumeration gives us a Postscript name rather than an index.
64+
/// This functions maps from a Postscript name to an index.
65+
///
66+
/// This mapping works for single-font files and for simple TTC files, but may not work in all cases.
67+
/// We are not 100% sure which cases (if any) will not work. But we suspect that variable fonts may cause
68+
/// issues due to the Postscript names corresponding to instances not being straightforward, and the possibility
69+
/// that CoreText may return a non-standard in that scenerio.
70+
fn ttc_index_from_postscript_name(font_file: FileRef<'_>, postscript_name: &str) -> u32 {
71+
match font_file {
72+
// File only contains one font: simply return 0
73+
FileRef::Font(_) => 0,
74+
// File is a collection: iterate through each font in the collection and check
75+
// whether the name matches
76+
FileRef::Collection(collection) => {
77+
for i in 0..collection.len() {
78+
let font = collection.get(i).unwrap();
79+
let name_table = font.name().unwrap();
80+
if name_table
81+
.name_record()
82+
.iter()
83+
.filter(|record| record.name_id() == NameId::POSTSCRIPT_NAME)
84+
.any(|record| {
85+
record
86+
.string(name_table.string_data())
87+
.unwrap()
88+
.chars()
89+
.eq(postscript_name.chars())
90+
})
91+
{
92+
return i;
93+
}
94+
}
95+
96+
// If we fail to find a font, just use the first font in the file.
97+
warn!(
98+
"Font with postscript_name {} not found in collection",
99+
postscript_name
100+
);
101+
0
102+
},
56103
}
57104
}
58105

0 commit comments

Comments
 (0)