diff --git a/Cargo.toml b/Cargo.toml index 6350322..550d0f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ unicode-script = { version = "0.5.4", optional = true } woff2-patched = { version = "0.3.0", optional = true } png = { version = "0.17.13", optional = true } inflections = "1.1.1" +indexmap = "2.6.0" [target.'cfg(target_arch = "wasm32")'.dependencies] wit-bindgen-rt = { version = "0.24.0", optional = true } diff --git a/package.json b/package.json index c4f05a9..6a785f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fontkit-rs", - "version": "0.0.12", + "version": "0.0.13-beta.1", "description": "Toolkit used to load, match, measure, and render texts", "main": "index.js", "directories": { diff --git a/src/font.rs b/src/font.rs index ad03d29..1760ed7 100644 --- a/src/font.rs +++ b/src/font.rs @@ -1,10 +1,10 @@ use arc_swap::ArcSwap; #[cfg(feature = "parse")] use byteorder::{BigEndian, ReadBytesExt}; +#[cfg(feature = "parse")] +use indexmap::IndexMap; use ordered_float::OrderedFloat; use ouroboros::self_referencing; -#[cfg(feature = "parse")] -use std::collections::HashMap; use std::fmt; use std::hash::Hash; #[cfg(feature = "parse")] @@ -114,16 +114,6 @@ pub(super) struct FvarInstance { pub(super) postscript: Name, } -#[derive(Clone)] -enum Variant { - Index(u32), - Instance { - coords: Vec, - names: Vec, - axes: Vec, - }, -} - /// Returns whether a buffer is WOFF font data. pub fn is_woff(buf: &[u8]) -> bool { buf.len() > 4 && buf[0] == 0x77 && buf[1] == 0x4F && buf[2] == 0x46 && buf[3] == 0x46 @@ -154,6 +144,7 @@ pub fn is_otf(buf: &[u8]) -> bool { && buf[4] == 0x00 } +#[derive(Debug)] pub(crate) struct VariationData { pub key: FontKey, pub names: Vec, @@ -177,7 +168,7 @@ impl VariationData { .collect::>(); // get fvar if any - let mut instances: HashMap>, Vec> = HashMap::new(); + let mut instances: IndexMap>, Vec> = 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` @@ -334,12 +325,12 @@ impl VariationData { .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)) { - key.stretch = Some(value.0 as u16); + // 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); } - key.family = variation_names[0].postscript.name.clone(); for (coord, axis) in coords.iter().zip(axes.iter()) { key.variations .push((String::from_utf8(axis.tag.to_bytes().to_vec())?, coord.0)); @@ -473,6 +464,20 @@ impl Font { let mut buffer = Vec::new(); let mut file = std::fs::File::open(path)?; file.read_to_end(&mut buffer).unwrap(); + + #[cfg(feature = "woff2-patched")] + if is_woff2(&buffer) { + buffer = woff2_patched::convert_woff2_to_ttf(&mut buffer.as_slice())?; + } + #[cfg(feature = "parse")] + if is_woff(&buffer) { + use std::io::Cursor; + + let reader = Cursor::new(buffer); + let mut otf_buf = Cursor::new(Vec::new()); + crate::conv::woff::convert_woff_to_otf(reader, &mut otf_buf)?; + buffer = otf_buf.into_inner(); + } self.buffer.swap(Arc::new(buffer)); } Ok(()) @@ -488,9 +493,15 @@ impl Font { let filters = Filter::from_key(key); let mut queue = self.variants.iter().collect::>(); for filter in filters { - queue.retain(|v| v.fulfils(&filter)); - if queue.len() == 1 { + let mut q = queue.clone(); + q.retain(|v| v.fulfils(&filter)); + if q.len() == 1 { + queue = q; + break; + } else if q.is_empty() { break; + } else { + queue = q; } } let variant = queue[0]; @@ -500,7 +511,8 @@ impl Font { buffer, face_builder: |buf| Face::parse(buf, variant.index), } - .try_build()?; + .try_build() + .unwrap(); face.with_face_mut(|face| { for (coord, axis) in &variant.key.variations { face.set_variation(Tag::from_bytes_lossy(coord.as_bytes()), *axis); diff --git a/src/lib.rs b/src/lib.rs index 905bf61..68fbf0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,13 +130,25 @@ impl FontKit { } pub fn exact_match(&self, key: &font::FontKey) -> Option { - return self.fonts.get(key)?.face(key).ok(); + let face = self.query(key)?; + let mut patched_key = key.clone(); + if patched_key.weight.is_none() { + patched_key.weight = Some(400); + } + if patched_key.stretch.is_none() { + patched_key.stretch = Some(5); + } + if patched_key.italic.is_none() { + patched_key.italic = Some(false); + } + if face.key() == patched_key { + return Some(face); + } else { + return None; + } } pub fn query(&self, key: &font::FontKey) -> Option { - if let Some(result) = self.exact_match(key) { - return Some(result as _); - } let mut search_results = self .fonts .iter() @@ -163,6 +175,19 @@ impl FontKit { } None } + + pub fn keys(&self) -> Vec { + self.fonts + .iter() + .flat_map(|i| { + i.value() + .variants() + .iter() + .map(|i| i.key.clone()) + .collect::>() + }) + .collect() + } } #[cfg(feature = "parse")] @@ -206,7 +231,7 @@ fn load_font_from_path(path: impl AsRef) -> Option { } }; font.set_path(path.to_path_buf()); - // println!("{:?}", font.names); + // println!("{:?}", font.variants()); font.unload(); Some(font) } diff --git a/src/metrics.rs b/src/metrics.rs index bcee95a..da885d5 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -253,7 +253,7 @@ impl TextMetrics { } } - pub(crate) fn has_missing(&self) -> bool { + pub fn has_missing(&self) -> bool { self.positions .read() .map(|p| p.iter().any(|c| c.metrics.missing)) diff --git a/tests/basic.rs b/tests/basic.rs index 61ef32f..6f643e0 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -101,6 +101,6 @@ pub fn test_complex_text_wrap() -> Result<(), Error> { }); area.unwrap_text(); area.wrap_text(64.4)?; - assert_eq!(area.value_string(), "商家\n热卖\n1234\n567\n8"); + assert_eq!(area.value_string(), "商家\n热卖\n123\n456\n78"); Ok(()) }