mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-15 03:36:51 -04:00
Fix #813: type can name an installed version of its font
A font library can hold two versions of one family and style (a new Montserrat-Bold.ttf beside an old montserrat.ttf). The font database kept only the first one loaded, so type set in the other couldn't be matched when a PDF or .ai was opened: it came in as outlines. Now every version loads (the first stays the one family and style resolve to), PDF import tries the other versions when the glyphs differ, and type set in a version the family and style alone wouldn't pick names it in CharStyle::font_version (left out when None, so documents without it are unchanged). Layout, shaping, the substituted-glyph highlight, Find Font, Package and Document Info follow it; changing the font clears it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b43e633329
commit
562e09707a
18 files changed
+279
-38
No files matched your search
@@ -460,8 +460,12 @@ fn matching_face(
|
||||
return Some(face.clone());
|
||||
}
|
||||
let db = vectorcraft_text::FontDb::global();
|
||||
let others =
|
||||
// Other installed versions of its family and style (a font library keeps old ones), then the
|
||||
// family's other styles.
|
||||
let versions = db.versions(face).into_iter().skip(1).take(16);
|
||||
let styles =
|
||||
db.styles(&face.family).into_iter().filter(|s| !s.eq_ignore_ascii_case(&face.style)).take(64).filter_map(|s| db.face(&face.family, &s));
|
||||
let others = versions.chain(styles);
|
||||
let mut best = own.map(|d| (d, face.clone()));
|
||||
for f in others {
|
||||
if let Some(d) = glyph_deviation(&f, text, o, vertical)
|
||||
@@ -636,6 +640,9 @@ struct Builder<'p> {
|
||||
/// Font (cache key) → the installed face that draws its glyphs, decided from its first glyph
|
||||
/// that can be compared ([`matching_face`]); `None`: its glyphs differ, so it stays outlines.
|
||||
matched: HashMap<u128, Option<Arc<vectorcraft_text::FontFace>>>,
|
||||
/// Per font: the installed version its type names, when it isn't the one its family and style
|
||||
/// resolve to (see `CharStyle::font_version`).
|
||||
versions: HashMap<u128, Option<String>>,
|
||||
/// The soft mask of the graphics state, the art drawn through it so far, and the masks read.
|
||||
mask: Option<Arc<MaskSpec>>,
|
||||
masked: Vec<Arc<Node>>,
|
||||
@@ -784,6 +791,7 @@ impl<'p> Builder<'p> {
|
||||
cid_text: HashMap::new(),
|
||||
font_chars: HashMap::new(),
|
||||
matched: HashMap::new(),
|
||||
versions: HashMap::new(),
|
||||
mask: None,
|
||||
masked: vec![],
|
||||
masks: HashMap::new(),
|
||||
@@ -1356,12 +1364,25 @@ impl<'p> Builder<'p> {
|
||||
};
|
||||
info = FontInfo { family: f.family.clone(), style: f.style.clone(), face: Some(f) };
|
||||
}
|
||||
// A version of the family and style that the two alone don't resolve to: the type names it.
|
||||
// Kept per font once its face is decided (until then each glyph asks).
|
||||
let named = |f: &Option<Arc<vectorcraft_text::FontFace>>| {
|
||||
let f = f.as_ref()?;
|
||||
let default = vectorcraft_text::FontDb::global().face(&f.family, &f.style)?;
|
||||
(default.version != f.version).then(|| f.version.clone())
|
||||
};
|
||||
let version = match self.versions.get(&key) {
|
||||
Some(v) => v.clone(),
|
||||
None if self.matched.contains_key(&key) => self.versions.entry(key).or_insert_with(|| named(&info.face)).clone(),
|
||||
None => named(&info.face),
|
||||
};
|
||||
let (paint, opacity) = self.paint(paint, stroke.is_some());
|
||||
let stroke = stroke.map(|p| (paint.clone(), p.line_width as f64 * scale));
|
||||
let look = Look {
|
||||
font: key,
|
||||
family: info.family,
|
||||
style: info.style,
|
||||
version,
|
||||
size: at.size,
|
||||
h_scale: at.h_scale,
|
||||
fill: stroke.is_none().then_some(paint),
|
||||
|
||||
@@ -75,6 +75,8 @@ pub(crate) struct Look {
|
||||
pub font: u128,
|
||||
pub family: String,
|
||||
pub style: String,
|
||||
/// The installed version of the font when it isn't the one family and style resolve to.
|
||||
pub version: Option<String>,
|
||||
pub size: f64,
|
||||
pub h_scale: f64,
|
||||
pub fill: Option<Paint>,
|
||||
@@ -97,6 +99,7 @@ impl Look {
|
||||
CharStyle {
|
||||
font_family: self.family.clone(),
|
||||
font_style: self.style.clone(),
|
||||
font_version: self.version.clone(),
|
||||
size: round(self.size),
|
||||
h_scale: round(self.h_scale),
|
||||
fill: self.fill.clone().unwrap_or(Paint::None),
|
||||
|
||||
@@ -260,6 +260,7 @@ fn a_single_upright_glyph_comes_in_as_vertical_type_and_takes_no_horizontal_glyp
|
||||
font: 1,
|
||||
family: "Source Sans 3".into(),
|
||||
style: "Regular".into(),
|
||||
version: None,
|
||||
size: 20.0,
|
||||
h_scale: 100.0,
|
||||
fill: Some(vectorcraft_color::Paint::solid(vectorcraft_color::Color::rgb(0.0, 0.0, 0.0))),
|
||||
@@ -282,6 +283,7 @@ fn letter_spaced_vertical_type_keeps_its_characters_together() {
|
||||
font: 1,
|
||||
family: "Source Sans 3".into(),
|
||||
style: "Regular".into(),
|
||||
version: None,
|
||||
size: 20.0,
|
||||
h_scale: 100.0,
|
||||
fill: Some(vectorcraft_color::Paint::solid(vectorcraft_color::Color::rgb(0.0, 0.0, 0.0))),
|
||||
|
||||
@@ -290,3 +290,26 @@ fn slanted_type_reopens_as_type_that_still_leans() {
|
||||
assert!(off < 0.5, "{turn} {lean}: {a:?} vs {b:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Type set in an installed font's older version (a library keeps both, one name) reopens as type
|
||||
/// in that version: the file's glyphs are matched to it rather than kept as outlines, and the type
|
||||
/// names the version, which the family and style alone don't pick.
|
||||
#[test]
|
||||
fn type_in_another_installed_version_reopens_in_that_version() {
|
||||
use vectorcraft_text::test_fonts::{TWIN_FAMILY, TWIN_VERSIONS, twin_font};
|
||||
// The newer version first: the one the family and style resolve to.
|
||||
FontDb::global().add_font(twin_font(true).unwrap());
|
||||
FontDb::global().add_font(twin_font(false).unwrap());
|
||||
for version in [None, Some(TWIN_VERSIONS[0])] {
|
||||
let st = CharStyle { font_family: TWIN_FAMILY.into(), font_version: version.map(Into::into), ..style(40.0) };
|
||||
let d = doc(vec![TextObject::point(Point::new(20.0, 80.0), "Hamburgefonstiv", st)]);
|
||||
let back = import_as(&pdf(&d, false).bytes, TextAs::Text);
|
||||
let mut found = vec![];
|
||||
back.walk(|n| {
|
||||
if let NodeKind::Text(t) = &n.kind {
|
||||
found.push((t.plain_text(), t.first_style().font_version.clone()));
|
||||
}
|
||||
});
|
||||
assert_eq!(found, [("Hamburgefonstiv".to_string(), version.map(String::from))], "{version:?}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user