Type: tests compare opened documents with and without kept shaping plans

A new test binary opens a document of 13 type objects with the plans a
font face keeps and again while every shaped segment makes its own plan.
The document, its cached bounds and baselines included, and the first
frame's pixels must be the same. `test_fonts::keep_shape_plans` switches
the plans off; it exists only in tests and with the `test-fonts` feature.

The engine's open test now builds objects of one script and direction
that differ in kerning and language, and lays each one out in a font
database of its own, so a plan used for the wrong features fails its
comparison. It no longer sets Arabic, which could fall back to a system
font in one database and not the other.
This commit is contained in:
dotCooCoo
2026-11-07 05:14:59 +07:00
committed by bflatastic
parent 25b42b2246
commit 77d5f8bbea
4 changed files with 157 additions and 8 deletions
+14 -8
View File
@@ -503,23 +503,29 @@ fn a_file_read_for_opening_has_its_links_read_and_its_type_laid_out() {
}
/// Opening lays out each type object with the plans the objects before it left on the font's
/// face: its bounds and baselines are those of a layout in a font database that holds no plans.
/// face: its bounds and baselines are those of a layout in a font database of its own, which holds
/// no plans. The objects share a script and direction and differ in features and language. Only
/// bundled Latin glyphs are set, which every font database holds.
#[test]
fn type_read_for_opening_has_the_layout_of_a_font_database_with_no_plans() {
let dir = Folder::new("read-for-open-plans");
let mut s = session();
for (i, text) in ["AVATAR", "To Wa", "123 ...", "office", "\u{645}\u{631}\u{62d}\u{628}\u{627}"].iter().enumerate() {
s.execute("text.create", &json!({"x": 20, "y": 40 + i * 20, "text": text})).unwrap();
let style = vectorcraft_doc::CharStyle { size: 100.0, ..Default::default() };
let manual = vectorcraft_doc::CharStyle { kerning: Some(0.0), ..style.clone() };
let turkish = vectorcraft_doc::CharStyle { language: Some("tr".into()), ..style.clone() };
let mut doc = Document::new(800.0, 600.0);
let layer = doc.layers[0].id;
for (text, style) in [("AVAV", &style), ("AVAV", &manual), ("office", &turkish), ("To Wa", &style), ("To Wa", &manual)] {
let t = vectorcraft_doc::TextObject::point(vectorcraft_geom::Point::new(20.0, 120.0), text, style.clone());
let id = doc.alloc_id();
doc.insert(Some(layer), usize::MAX, vectorcraft_doc::Node::new(id, NodeKind::Text(Box::new(t)))).unwrap();
}
let doc_path = dir.file("type.vectorcraft");
save(&mut s, &doc_path);
let bytes = std::fs::read(&doc_path).unwrap();
let bytes = vectorcraft_format::save_file(&doc);
let file = cmd::fileio::read_for_open(&doc_path, &bytes, Some(doc_path.clone()), &Value::Null, false, false).unwrap();
let new_db = vectorcraft_text::FontDb::with_font_dirs(vec![]);
let mut seen = 0;
file.document().walk(|node| {
if let NodeKind::Text(t) = &node.kind {
let fresh = vectorcraft_text::layout(&new_db, t);
let fresh = vectorcraft_text::layout(&vectorcraft_text::FontDb::with_font_dirs(vec![]), t);
assert_eq!(t.cached_bounds, Some(fresh.bounds), "{:?}", t.plain_text());
assert_eq!(t.cached_baselines, fresh.baselines(), "{:?}", t.plain_text());
seen += 1;
+123
View File
@@ -0,0 +1,123 @@
//! A document of type opened with the shaping plans font faces keep, compared with the same file
//! opened while every shaped segment makes a plan of its own: the nodes and the first frame's
//! pixels are the same. Its own test binary: the switch for kept plans applies to the whole process.
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::sync::{Mutex, MutexGuard, Once};
use serde_json::Value;
use vectorcraft_doc::{CharStyle, Document, Node, NodeKind, TextObject, TextRun};
use vectorcraft_engine::Session;
use vectorcraft_engine::cmd::fileio;
use vectorcraft_geom::{Affine, Point};
use vectorcraft_render::{RenderOptions, Renderer};
use vectorcraft_text::FontDb;
use vectorcraft_text::test_fonts::{VARIABLE_FAMILY, keep_shape_plans, variable_font};
const WIDTH: u32 = 900;
const HEIGHT: u32 = 1300;
/// Taken by each test: the switch for kept plans applies to the whole process.
static SWITCH: Mutex<()> = Mutex::new(());
fn switch() -> MutexGuard<'static, ()> {
SWITCH.lock().unwrap_or_else(|e| e.into_inner())
}
fn style() -> CharStyle {
CharStyle { size: 60.0, ..CharStyle::default() }
}
/// A saved document of type objects whose shaping differs in script, direction, language, features
/// and variable font instance, the object in the plain style first.
fn file() -> Vec<u8> {
static ADD_FONT: Once = Once::new();
ADD_FONT.call_once(|| {
FontDb::global().add_font(variable_font().unwrap());
});
let manual = CharStyle { kerning: Some(0.0), ..style() };
let variable = |name: &str| CharStyle { font_family: VARIABLE_FAMILY.into(), font_style: name.into(), ..style() };
let mut mixed = TextObject::point(Point::ZERO, "AVAV", style());
mixed.runs = vec![TextRun::new("AV", style()), TextRun::new("AV", manual.clone())];
let mut vertical = TextObject::point(Point::ZERO, "AB\u{65e5}\u{672c}", style());
vertical.vertical = true;
let objects = [
TextObject::point(Point::ZERO, "AVATAR office", style()),
TextObject::point(Point::ZERO, "To Wa fi", style()),
TextObject::point(Point::ZERO, "123 ... 456", style()),
TextObject::point(Point::ZERO, "AVAV", manual),
mixed,
TextObject::point(Point::ZERO, "fi office", CharStyle { language: Some("tr".into()), ..style() }),
TextObject::point(Point::ZERO, "AVAV", CharStyle { optical_kerning: true, ..style() }),
TextObject::point(Point::ZERO, "Small Caps", CharStyle { small_caps: Some(70.0), ..style() }),
TextObject::point(Point::ZERO, "\u{645}\u{631}\u{62d}\u{628}\u{627}", style()),
TextObject::point(Point::ZERO, "\u{5e9}\u{5dc}\u{5d5}\u{5dd}", style()),
TextObject::point(Point::ZERO, "lIn lIn", variable("Regular")),
TextObject::point(Point::ZERO, "lIn lIn", variable("Bold")),
vertical,
];
let mut doc = Document::new(WIDTH as f64, HEIGHT as f64);
let layer = doc.layers[0].id;
let count = objects.len();
for (i, mut t) in objects.into_iter().enumerate() {
// Rows down the page; the vertical object, last, stands in the right margin.
t.xf = Affine::translate(if i + 1 == count { (800.0, 60.0) } else { (20.0, 90.0 * (i + 1) as f64) });
let id = doc.alloc_id();
doc.insert(Some(layer), usize::MAX, Node::new(id, NodeKind::Text(Box::new(t)))).unwrap();
}
vectorcraft_format::save_file(&doc)
}
/// The session `bytes` opened in, as `document.open` reads a file and then opens it.
fn open(bytes: &[u8]) -> Session {
let name = "plans.vectorcraft";
let mut s = Session::new();
let read = fileio::read_for_open(name, bytes, Some(name.into()), &Value::Null, false, false).unwrap();
fileio::open_read(&mut s, read).unwrap();
s
}
fn first_frame(doc: &Document) -> Vec<u8> {
Renderer::new().render(doc, WIDTH, HEIGHT, Affine::IDENTITY, &RenderOptions::default()).pixels
}
#[test]
fn an_opened_document_has_the_nodes_of_one_laid_out_with_plans_of_their_own() {
let _switch = switch();
let bytes = file();
keep_shape_plans(false);
let own = open(&bytes);
keep_shape_plans(true);
// The second open shapes every object with plans a face kept.
open(&bytes);
let kept = open(&bytes);
let (own, kept) = (own.doc().unwrap(), kept.doc().unwrap());
let mut laid_out = 0;
kept.doc.walk(|node| {
if let NodeKind::Text(t) = &node.kind {
assert!(t.cached_bounds.is_some(), "{:?}", t.plain_text());
laid_out += 1;
}
});
assert_eq!(laid_out, 13);
assert_eq!(kept.doc.layers, own.doc.layers);
assert!(*kept.doc == *own.doc, "the whole document is the same");
}
#[test]
fn an_opened_document_draws_the_first_frame_of_one_shaped_with_plans_of_its_own() {
let _switch = switch();
let bytes = file();
let opened = open(&bytes);
let doc = &opened.doc().unwrap().doc;
keep_shape_plans(false);
let own = first_frame(doc);
keep_shape_plans(true);
// Lays out each object with plans a face kept: the first frame lays type out again.
first_frame(doc);
let kept = first_frame(doc);
assert_ne!(kept, first_frame(&Document::new(WIDTH as f64, HEIGHT as f64)), "the frame shows the type");
let differing = kept.iter().zip(&own).filter(|(a, b)| a != b).count();
assert_eq!((kept.len(), differing), (own.len(), 0), "bytes of the frame that differ");
}
+4
View File
@@ -244,6 +244,10 @@ impl FontFace {
language: Option<&harfrust::Language>,
features: &[harfrust::Feature],
) -> Arc<harfrust::ShapePlan> {
#[cfg(any(test, feature = "test-fonts"))]
if !crate::test_fonts::keeps_shape_plans() {
return Arc::new(harfrust::ShapePlan::new(shaper, direction, script, language, features));
}
let key = harfrust::ShapePlanKey::new(script, direction).language(language).instance(self.instance.as_ref()).features(features);
let kept = |plans: &[Arc<harfrust::ShapePlan>]| plans.iter().rev().find(|p| key.matches(p)).cloned();
if let Some(plan) = kept(&self.plans.lock().unwrap_or_else(|e| e.into_inner())) {
+16
View File
@@ -1,6 +1,8 @@
//! Fonts built in code for tests, so no font file is committed: a small variable font (#296).
//! Compiled for this crate's tests and, with the `test-fonts` feature, for other crates' tests.
use std::sync::atomic::{AtomicBool, Ordering};
use skrifa::instance::{LocationRef, Size};
use skrifa::raw::TableProvider;
use skrifa::raw::tables::glyf::Glyph;
@@ -10,6 +12,20 @@ use write_fonts::tables::fvar::{AxisInstanceArrays, Fvar, InstanceRecord, Variat
use write_fonts::tables::gvar::{GlyphDelta, GlyphDeltas, GlyphVariations, Gvar, Tent};
use write_fonts::types::{F2Dot14, Fixed, NameId, Tag};
/// Whether faces use and keep shaping plans ([`keep_shape_plans`]).
static KEEP_SHAPE_PLANS: AtomicBool = AtomicBool::new(true);
/// Sets whether font faces use and keep shaping plans, for the whole process. When `keep` is false,
/// every shaped segment makes a plan of its own, so a test can compare what kept plans produce with
/// what its own plan produces. Plans a face already holds stay and are used again when it is true.
pub fn keep_shape_plans(keep: bool) {
KEEP_SHAPE_PLANS.store(keep, Ordering::SeqCst);
}
pub(crate) fn keeps_shape_plans() -> bool {
KEEP_SHAPE_PLANS.load(Ordering::SeqCst)
}
/// The family of [`variable_font`].
pub const VARIABLE_FAMILY: &str = "Varitest Sans";