mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-09 03:41:59 +00:00
Merge PR #6661: fix(print): preserve WinAnsi characters in PDF labels
contributors/people.toml: both sides' new rows kept. crates/engine/src/print_cmds/tests.rs: both sides' appended tests kept whole.
This commit is contained in:
commit
e752bb2ec8
3 files changed
+70
-5
No files matched your search
@@ -191,3 +191,6 @@ sync_github_name = true
|
||||
|
||||
[people.ha1des]
|
||||
sync_github_name = true
|
||||
|
||||
[people.aqibmohd271]
|
||||
sync_github_name = true
|
||||
@@ -289,16 +289,51 @@ pub struct PrintPage {
|
||||
pub emulsion_down: bool,
|
||||
}
|
||||
|
||||
// ISO 32000-1, Annex D.2: the standard Helvetica font below uses WinAnsiEncoding,
|
||||
// not UTF-8. Escape non-ASCII bytes as three octal digits so the content stays ASCII
|
||||
// and a following digit cannot become part of the escape. Other scripts still need
|
||||
// an embedded font; retain the existing '?' fallback for unsupported characters.
|
||||
fn pdf_string(s: &str) -> String {
|
||||
let mut o = String::from("(");
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'(' | ')' | '\\' => {
|
||||
let byte = match c {
|
||||
'\u{20}'..='\u{7e}' | '\u{a0}'..='\u{ff}' => c as u8,
|
||||
'€' => 0x80,
|
||||
'‚' => 0x82,
|
||||
'ƒ' => 0x83,
|
||||
'„' => 0x84,
|
||||
'…' => 0x85,
|
||||
'†' => 0x86,
|
||||
'‡' => 0x87,
|
||||
'ˆ' => 0x88,
|
||||
'‰' => 0x89,
|
||||
'Š' => 0x8a,
|
||||
'‹' => 0x8b,
|
||||
'Œ' => 0x8c,
|
||||
'Ž' => 0x8e,
|
||||
'‘' => 0x91,
|
||||
'’' => 0x92,
|
||||
'“' => 0x93,
|
||||
'”' => 0x94,
|
||||
'•' => 0x95,
|
||||
'–' => 0x96,
|
||||
'—' => 0x97,
|
||||
'˜' => 0x98,
|
||||
'™' => 0x99,
|
||||
'š' => 0x9a,
|
||||
'›' => 0x9b,
|
||||
'œ' => 0x9c,
|
||||
'ž' => 0x9e,
|
||||
'Ÿ' => 0x9f,
|
||||
_ => b'?',
|
||||
};
|
||||
match byte {
|
||||
b'(' | b')' | b'\\' => {
|
||||
o.push('\\');
|
||||
o.push(c);
|
||||
o.push(char::from(byte));
|
||||
}
|
||||
c if c.is_ascii() && !c.is_ascii_control() => o.push(c),
|
||||
_ => o.push('?'),
|
||||
0x80..=0xff => o.push_str(&format!("\\{byte:03o}")),
|
||||
_ => o.push(char::from(byte)),
|
||||
}
|
||||
}
|
||||
o.push(')');
|
||||
|
||||
@@ -940,3 +940,30 @@ fn bad_bleed_is_an_error_not_a_panic() {
|
||||
assert!(matches!(r, Err(EngineError::BadParams { .. })), "bleed {bleed}: {r:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_strings_keep_winansi_accents_and_punctuation() {
|
||||
assert_eq!(pdf_string("Café – résumé"), r"(Caf\351 \226 r\351sum\351)");
|
||||
assert_eq!(pdf_string("€ ‘quote’ “double” • Œœ Šš Žž Ÿ"), r"(\200 \221quote\222 \223double\224 \225 \214\234 \212\232 \216\236 \237)");
|
||||
assert_eq!(pdf_string("é7"), r"(\3517)", "octal escapes must have three digits before a digit");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_strings_keep_pdf_delimiters_safe_and_fallback_bounded() {
|
||||
assert_eq!(pdf_string(r"a(b)\c"), r"(a\(b\)\\c)");
|
||||
assert_eq!(pdf_string("\n\r\t\0\u{7f}\u{81}中😀"), "(????????)");
|
||||
assert_eq!(pdf_string(""), "()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn printed_pdf_uses_winansi_for_labels_and_descriptions() {
|
||||
let mut page = page_of(3, vec![255; 6], false);
|
||||
page.label = Some("Café – résumé.psd".into());
|
||||
page.description = Some("Price: €20 (proof)".into());
|
||||
let pdf = print_pdf(&page);
|
||||
assert!(String::from_utf8_lossy(&pdf).contains("/Encoding /WinAnsiEncoding"));
|
||||
let content = pdf_content(&pdf);
|
||||
assert!(content.contains(r"(Caf\351 \226 r\351sum\351.psd) Tj"), "{content}");
|
||||
assert!(content.contains(r"(Price: \20020 \(proof\)) Tj"), "{content}");
|
||||
assert!(content.is_ascii(), "encoded bytes are escaped, never UTF-8 text operands");
|
||||
}
|
||||
Reference in new issue
Block a user