Let only a same-style complete program keep the bundled Thai face out

A complete program of the source's family in another style, such as one
its descriptor marks bold, stopped the bundled Regular Thai outlines from
completing a Regular subset, and the text was then drawn in that bold
program. Count only a program in the source's style, as the subset
completion itself does.
This commit is contained in:
bflatastic committed 2026-11-08 23:51:51 +07:00
1 parent e8322a8fc2
commit 5f265960ad
2 files changed
+41 -3

No files matched your search

+32
View File
@@ -12692,6 +12692,38 @@ fn a_bold_thai_subset_is_not_completed_with_the_regular_bundled_face() {
assert_eq!(pdfcraft_cos::write_full(&doc, &Default::default()).unwrap(), before, "a refused edit changes nothing");
}
/// A complete program of the family in another style (bold, by its descriptor) can't complete a
/// Regular subset, so it doesn't keep the bundled Regular outlines from doing so: the text is not
/// drawn in the bold program instead.
#[test]
fn a_complete_program_in_another_style_does_not_keep_the_bundled_thai_face_out() {
if pdfcraft_fonts::document_thai_font().is_none() {
eprintln!(
"skipping a_complete_program_in_another_style_does_not_keep_the_bundled_thai_face_out: built without craft-fonts (set CRAFT_FONTS_DIR to run it)"
);
return;
}
let mut doc = text_page("BT /F1 16 Tf 50 700 Td (A) Tj ET");
let cmap =
doc.add(Object::Stream(Stream::flate(Dict::new(), b"1 begincodespacerange <00> <FF> endcodespacerange 1 beginbfchar <41> <0E19> endbfchar")));
doc.update_dict(pdfcraft_cos::ObjRef::new(5, 0), |f| {
f.set(b"BaseFont".to_vec(), Object::name("ABCDEF+NotoSansThai"));
f.set(b"ToUnicode".to_vec(), Object::Ref(cmap));
})
.unwrap();
let complete = with_legacy_thai_cmap(include_bytes!("../../../assets/fonts/Inter-Regular.ttf").to_vec());
let mut bold = legacy_thai_font(&mut doc, "NotoSansThai", &complete, LEGACY_THAI_UNICODE);
let Some(Object::Array(mut descendants)) = bold.get(b"DescendantFonts").cloned() else { panic!("fixture") };
let Some(Object::Dict(descendant)) = descendants.first_mut() else { panic!("fixture") };
let Some(Object::Dict(mut descriptor)) = descendant.get(b"FontDescriptor").cloned() else { panic!("fixture") };
descriptor.set(b"FontWeight".to_vec(), Object::Int(700));
descendant.set(b"FontDescriptor".to_vec(), Object::Dict(descriptor));
bold.set(b"DescendantFonts".to_vec(), Object::Array(descendants));
doc.set(pdfcraft_cos::ObjRef::new(6, 0), Object::Dict(bold));
let edited = text::rewrite_block(&mut doc, 0, 0, Some("ผู้ใช้"), &text::BlockStyle::default()).expect("completed with the bundled face");
assert_eq!(edited.substituted.as_deref(), pdfcraft_fonts::document_thai_font().map(|f| f.family));
}
#[test]
fn grouped_source_text_preparation_edits_original_operators_without_changing_shared_forms() {
let mut doc = page_with_forms("q 1 0 0 1 12 20 cm /Body Do Q", &[("Body".into(), "BT /F1 14 Tf 40 500 Td (Original body) Tj ET".into())]);
+9 -3
View File
@@ -1051,8 +1051,11 @@ fn matching_thai_outline(doc: &Document, fonts: &Dict, source: &TextLine, span:
// The bundled face is Regular and upright. A source its descriptor or name marks bold or
// italic keeps that style, so completing it with these outlines would be a silent change:
// it needs an explicit choice instead. (Variable-font instances often keep the family name.)
let style = fonts.get(source.font.as_bytes()).and_then(|f| doc.dict(f)).map(|f| FontStyle::of(doc, &f, &Metrics::from_dict(doc, &f)));
if style.map_or(source.bold || source.italic, |s| s.bold || s.italic) {
let style = fonts
.get(source.font.as_bytes())
.and_then(|f| doc.dict(f))
.map_or(FontStyle { bold: source.bold, italic: source.italic }, |f| FontStyle::of(doc, &f, &Metrics::from_dict(doc, &f)));
if style.bold || style.italic {
return false;
}
if !span.chars().any(|c| matches!(u32::from(c), 0x0e00..=0x0e7f))
@@ -1063,9 +1066,12 @@ fn matching_thai_outline(doc: &Document, fonts: &Dict, source: &TextLine, span:
}
// A registered complete matching program also completes the source subset.
// Do not replace it with bundled outlines merely because the original resource is a subset.
// Only one in the source's style does (`program_candidates`), so only that one counts here.
let family = pdfcraft_fonts::embedding::face_key(&source.base_font);
!fonts.iter().filter_map(|(_, font)| doc.dict(font)).any(|font| {
pdfcraft_fonts::embedding::face_key(&Metrics::from_dict(doc, &font).base_font) == family
let metrics = Metrics::from_dict(doc, &font);
pdfcraft_fonts::embedding::face_key(&metrics.base_font) == family
&& FontStyle::of(doc, &font, &metrics) == style
&& pdfcraft_fonts::pdf::embedded_sfnt(doc, &font).is_some_and(|bytes| {
pdfcraft_fonts::embedding::validate(&bytes).is_ok()
&& pdfcraft_fonts::source_shaping::has_thai_layout(&bytes)