From 5f265960add754e547dfe15ac85271e03b7061c2 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Sun, 8 Nov 2026 23:44:21 +0700 Subject: [PATCH] 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. --- crates/edit/src/tests.rs | 32 ++++++++++++++++++++++++++ crates/edit/src/text_regions/styled.rs | 12 +++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/crates/edit/src/tests.rs b/crates/edit/src/tests.rs index 97127db949..4b3b6633e4 100644 --- a/crates/edit/src/tests.rs +++ b/crates/edit/src/tests.rs @@ -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> 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())]); diff --git a/crates/edit/src/text_regions/styled.rs b/crates/edit/src/text_regions/styled.rs index 133944d941..a17e4d8a5d 100644 --- a/crates/edit/src/text_regions/styled.rs +++ b/crates/edit/src/text_regions/styled.rs @@ -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)