in src/font.rs [159:358]
fn parse_buffer_with_index(buffer: &[u8], index: u32) -> Result<Vec<VariationData>, Error> {
use ttf_parser::{name_id, Fixed, VariationAxis};
let face = Face::parse(buffer, index)?;
let axes: Vec<VariationAxis> = face
.tables()
.fvar
.map(|v| v.axes.into_iter())
.into_iter()
.flatten()
.collect::<Vec<_>>();
// get fvar if any
let mut instances: IndexMap<Vec<OrderedFloat<f32>>, Vec<FvarInstance>> = IndexMap::new();
if let (Some(_), Some(name_table)) = (face.tables().fvar, face.tables().name) {
// currently ttf-parser is missing `fvar`'s instance records, we parse them
// directly from `RawFace`
let data: &[u8] = face
.raw_face()
.table(ttf_parser::Tag::from_bytes(b"fvar"))
.unwrap();
let mut raw = &*data;
let _version = raw.read_u32::<BigEndian>()?;
let axis_offset = raw.read_u16::<BigEndian>()?;
let _ = raw.read_u16::<BigEndian>()?;
let axis_count = raw.read_u16::<BigEndian>()?;
let axis_size = raw.read_u16::<BigEndian>()?;
let instance_count = raw.read_u16::<BigEndian>()?;
let instance_size = raw.read_u16::<BigEndian>()?;
let data = &data[(axis_offset as usize + (axis_count as usize * axis_size as usize))..];
for i in 0..instance_count {
let mut raw = &data[(i as usize * instance_size as usize)..];
let sub_family_name_id = raw.read_u16::<BigEndian>()?;
let _ = raw.read_u16::<BigEndian>()?;
let coords = (0..axis_count)
.map(|_| {
use ttf_parser::FromData;
let mut v = [0_u8; 4];
raw.read_exact(&mut v)
.map(|_| OrderedFloat(Fixed::parse(&v).unwrap().0))
})
.collect::<Result<Vec<_>, _>>()?;
let postscript_name_id = if raw.is_empty() {
None
} else {
Some(raw.read_u16::<BigEndian>()?)
};
let sub_family = name_table
.names
.into_iter()
.find(|name| name.name_id == sub_family_name_id)
.and_then(|name| {
Some(Name {
id: name.name_id,
name: name.to_string().or_else(|| {
// try to force unicode encoding
Some(std::str::from_utf8(name.name).ok()?.to_string())
})?,
language_id: name.language_id,
})
});
let postscript = name_table
.names
.into_iter()
.find(|name| Some(name.name_id) == postscript_name_id)
.and_then(|name| {
Some(Name {
id: name.name_id,
name: name.to_string().or_else(|| {
// try to force unicode encoding
Some(std::str::from_utf8(name.name).ok()?.to_string())
})?,
language_id: name.language_id,
})
});
if let (Some(sub_family), Some(postscript)) = (sub_family, postscript) {
instances.entry(coords).or_default().push(FvarInstance {
sub_family,
postscript,
})
}
}
}
let instances = instances
.into_iter()
.map(|(coords, names)| {
return (
coords.into_iter().map(|v| Fixed(v.0)).collect::<Vec<_>>(),
names,
);
})
.collect::<Vec<_>>();
let mut style_names = vec![];
let names = face
.names()
.into_iter()
.filter_map(|name| {
let id = name.name_id;
let mut name_str = name.to_string().or_else(|| {
// try to force unicode encoding
Some(std::str::from_utf8(name.name).ok()?.to_string())
})?;
if id == name_id::TYPOGRAPHIC_SUBFAMILY {
style_names.push(Name {
id,
name: name_str.clone(),
language_id: name.language_id,
});
}
if id == name_id::FAMILY
|| id == name_id::FULL_NAME
|| id == name_id::POST_SCRIPT_NAME
|| id == name_id::TYPOGRAPHIC_FAMILY
{
if id == name_id::POST_SCRIPT_NAME {
name_str = name_str.replace(" ", "-");
}
Some(Name {
id,
name: name_str,
language_id: name.language_id,
})
} else {
None
}
})
.collect::<Vec<_>>();
if names.is_empty() {
return Err(Error::EmptyName);
}
// Select a good name
let ascii_name = names
.iter()
.map(|item| &item.name)
.filter(|name| name.is_ascii() && name.len() > 3)
.min_by(|n1, n2| match n1.len().cmp(&n2.len()) {
std::cmp::Ordering::Equal => n1
.chars()
.filter(|c| *c == '-')
.count()
.cmp(&n2.chars().filter(|c| *c == '-').count()),
ordering @ _ => ordering,
})
.cloned()
.map(|name| {
if name.starts_with(".") {
(&name[1..]).to_string()
} else {
name
}
});
let mut results = vec![];
let key = FontKey {
weight: Some(face.weight().to_number()),
italic: Some(face.is_italic()),
stretch: Some(face.width().to_number()),
family: ascii_name.clone().unwrap_or_else(|| names[0].name.clone()),
variations: vec![],
};
for (coords, variation_names) in instances {
let mut key = key.clone();
let width_axis_index = axes
.iter()
.position(|axis| axis.tag == ttf_parser::Tag::from_bytes(b"wdth"));
let weight_axis_index = axes
.iter()
.position(|axis| axis.tag == ttf_parser::Tag::from_bytes(b"wght"));
if let Some(value) = width_axis_index.and_then(|i| coords.get(i)) {
// mapping wdth to usWidthClass, ref: https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_wdth
key.stretch = Some(((value.0 / 100.0) * 5.0).round().min(1.0).max(9.0) as u16);
}
if let Some(value) = weight_axis_index.and_then(|i| coords.get(i)) {
key.weight = Some(value.0 as u16);
}
for (coord, axis) in coords.iter().zip(axes.iter()) {
key.variations
.push((String::from_utf8(axis.tag.to_bytes().to_vec())?, coord.0));
}
results.push(VariationData {
key,
names: names.clone(),
style_names: style_names.clone(),
variation_names,
index,
});
}
if results.is_empty() {
// this is not a variable font, add normal font data
results.push(VariationData {
names,
key,
variation_names: vec![],
style_names,
index,
})
}
Ok(results)
}