diff --git a/crates/edit/src/tests.rs b/crates/edit/src/tests.rs index 95bf528b09..31982f271d 100644 --- a/crates/edit/src/tests.rs +++ b/crates/edit/src/tests.rs @@ -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 = 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. diff --git a/crates/edit/src/text_regions/styled.rs b/crates/edit/src/text_regions/styled.rs index 2ab50083df..deccc9c09e 100644 --- a/crates/edit/src/text_regions/styled.rs +++ b/crates/edit/src/text_regions/styled.rs @@ -112,6 +112,8 @@ struct LegacyFace { style: FontStyle, /// The font object the resource names, when it is given by reference. object: Option, + /// 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, 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!(