Give an unencoded space in legacy Thai the source font's space fallback

A space that no subset of the face encodes was drawn as a quarter-em gap,
narrower than the text-space fallback the source font uses elsewhere.
Keep each subset's source space with it and use that for the gap.
This commit is contained in:
bflatastic committed 2026-11-08 23:51:51 +07:00
1 parent fbc2dc77a7
commit 5f356ade12
2 files changed
+26 -7

No files matched your search

+19
View File
@@ -13112,6 +13112,25 @@ fn editing_legacy_thai_shapes_a_run_across_subsets_of_the_same_face() {
assert_eq!(pdfcraft_cos::write_full(&doc, &Default::default()).unwrap(), before, "a refused edit changes nothing");
}
/// A space no subset of the face encodes is the gap the source font's text-space fallback gives
/// (`source_space`, a third of an em here), as wrapping measures it everywhere else.
#[test]
fn an_unencoded_space_in_legacy_thai_takes_the_source_space_fallback() {
let mut doc = text_page("BT /F1 16 Tf 50 650 Td <00d6> Tj ET");
let font = legacy_thai_font(&mut doc, "ABCDEF+THSarabunPSK", &legacy_thai_program(), LEGACY_THAI_UNICODE);
doc.set(pdfcraft_cos::ObjRef::new(5, 0), Object::Dict(font));
let style = BlockStyle { width: Some(400.0), ..Default::default() };
text::rewrite_block(&mut doc, 0, 0, Some("ก ก"), &style).unwrap();
// After the first ก (500 units), the second starts 330 units further: the gap.
let gaps: Vec<f64> = pdfcraft_content::parse(&page_content_bytes(&doc, 0))
.ops
.iter()
.filter(|op| op.is("TJ"))
.flat_map(|op| op.operands.first().and_then(Object::as_array).into_iter().flatten().filter_map(Object::as_f64))
.collect();
assert!(gaps.iter().any(|g| (g + 330.0).abs() < 0.01), "{gaps:?}");
}
/// Every resource name of one THSarabunPSK font object shares its program, decoded once. Distinct
/// subsets of the face are read within one budget for the edit: past it the edit is refused with
/// the reason, and the document is unchanged.
+7 -7
View File
@@ -112,6 +112,8 @@ struct LegacyFace {
style: FontStyle,
/// The font object the resource names, when it is given by reference.
object: Option<pdfcraft_cos::ObjRef>,
/// Its text-space fallback ([`source_space`]), in ems.
space: f64,
}
/// What one edit may decode, and so keep, of the font programs of its THSarabunPSK subsets
@@ -860,15 +862,13 @@ fn legacy_thai_font(
let Some(font) = doc.dict(resource) else { continue };
let object = resource.as_ref();
let face = match object.and_then(|o| cache.values().find(|face| face.object == Some(o))) {
Some(alias) => LegacyFace { glyphs: alias.glyphs.clone(), metrics: alias.metrics.clone(), style: alias.style, object },
Some(alias) => LegacyFace { glyphs: alias.glyphs.clone(), metrics: alias.metrics.clone(), object, ..*alias },
None => {
let metrics = Metrics::from_dict(doc, &font);
let glyphs = pdfcraft_fonts::pdf::LegacyThaiFont::from_pdf(doc, &font, source_space(doc, fonts, &metrics), budget)
.ok()
.flatten()
.map(std::rc::Rc::new);
let space = source_space(doc, fonts, &metrics);
let glyphs = pdfcraft_fonts::pdf::LegacyThaiFont::from_pdf(doc, &font, space, budget).ok().flatten().map(std::rc::Rc::new);
let style = FontStyle::of(doc, &font, &metrics);
LegacyFace { glyphs, metrics, style, object }
LegacyFace { glyphs, metrics, style, object, space }
}
};
cache.insert(name, face);
@@ -996,7 +996,7 @@ fn shape_existing_thai(cache: &HashMap<String, LegacyFace>, primary: &str, text:
let code = f.metrics.encode(" ")?;
Some(f.metrics.width(f.metrics.codes(&code).first()?.0))
})
.unwrap_or(0.25);
.unwrap_or(first.space);
(primary, Vec::new(), width)
} else {
return Err(EditError::Invalid(format!(