mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-24 10:08:00 -06:00
Merge PR #4380: M2: a non-embedded CIDFont's fallback request carries its descendant's font descriptor
Conflicts: vendor/README.md: hayro-interpret row keeps patches (55) to (57) and #4380's (58), with their test lists.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
//! The request a font resolver gets for a CIDFont the document doesn't embed carries the style
|
||||
//! of the descendant's font descriptor (ISO 32000-2 §9.7.4.1, §9.7.6.1, §9.8). Contributor-original
|
||||
//! synthetic fixtures.
|
||||
use hayro::hayro_interpret::InterpreterSettings;
|
||||
use hayro::hayro_interpret::font::{FallbackFontQuery, FontQuery};
|
||||
use hayro::hayro_interpret::hayro_cmap::CidFamily;
|
||||
use hayro::hayro_syntax::Pdf;
|
||||
use hayro::{RenderCache, RenderSettings, render};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// A one-page document drawing two characters in a Type 0 font whose descendant is a
|
||||
/// non-embedded CIDFontType0 with the given entries after `/CIDSystemInfo`. The name says
|
||||
/// nothing about the style and matches no standard font.
|
||||
fn fixture(descendant_extra: &str, descriptor: Option<&str>) -> Vec<u8> {
|
||||
let mut objects = vec![
|
||||
"<< /Type /Catalog /Pages 2 0 R >>".to_string(),
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".into(),
|
||||
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 100] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>".into(),
|
||||
stream("", "BT /F1 24 Tf 10 40 Td <11b30ed4> Tj ET"),
|
||||
"<< /Type /Font /Subtype /Type0 /BaseFont /CraftCJK-W3 /Encoding /Identity-H /DescendantFonts [6 0 R] >>".into(),
|
||||
format!(
|
||||
"<< /Type /Font /Subtype /CIDFontType0 /BaseFont /CraftCJK-W3 \
|
||||
/CIDSystemInfo << /Registry (Adobe) /Ordering (GB1) /Supplement 2 >> {descendant_extra} >>"
|
||||
),
|
||||
];
|
||||
if let Some(descriptor) = descriptor {
|
||||
objects.push(descriptor.to_string());
|
||||
}
|
||||
pdf(&objects)
|
||||
}
|
||||
|
||||
/// The fallback queries the resolver received while drawing the page.
|
||||
fn fallback_queries(bytes: Vec<u8>) -> Vec<FallbackFontQuery> {
|
||||
let seen = Arc::new(Mutex::new(Vec::new()));
|
||||
let sink = seen.clone();
|
||||
let settings = InterpreterSettings {
|
||||
font_resolver: Arc::new(move |query| {
|
||||
if let FontQuery::Fallback(f) = query {
|
||||
sink.lock().unwrap().push(f.clone());
|
||||
}
|
||||
None
|
||||
}),
|
||||
..InterpreterSettings::default()
|
||||
};
|
||||
let pdf = Pdf::new(bytes).unwrap();
|
||||
let pages = pdf.pages();
|
||||
let page = pages.first().unwrap();
|
||||
let cache = RenderCache::new();
|
||||
render(page, &cache, &settings, &RenderSettings::default());
|
||||
seen.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_cid_font_fallback_request_carries_its_descendants_style() {
|
||||
// Flags: Serif (bit 2), Nonsymbolic (bit 6), Italic (bit 7) = 2 + 32 + 64.
|
||||
let descriptor = "<< /Type /FontDescriptor /FontName /CraftCJK-W3 /Flags 98 /FontWeight 700 \
|
||||
/FontBBox [0 -120 1000 880] /ItalicAngle -12 /Ascent 880 /Descent -120 /CapHeight 700 /StemV 120 >>";
|
||||
let queries = fallback_queries(fixture("/FontDescriptor 7 0 R", Some(descriptor)));
|
||||
let query = queries.first().expect("the resolver is asked for a fallback");
|
||||
assert_eq!(query.post_script_name.as_deref(), Some("CraftCJK-W3"));
|
||||
assert!(query.character_collection.as_ref().is_some_and(|cc| cc.family == CidFamily::AdobeGB1), "{query:?}");
|
||||
assert!(query.is_serif, "Serif flag of the descendant's descriptor: {query:?}");
|
||||
assert!(query.is_italic, "Italic flag of the descendant's descriptor: {query:?}");
|
||||
assert_eq!(query.font_weight, 700, "/FontWeight of the descendant's descriptor: {query:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_cid_font_without_a_descriptor_asks_for_the_default_style() {
|
||||
let queries = fallback_queries(fixture("", None));
|
||||
let query = queries.first().expect("the resolver is asked for a fallback");
|
||||
assert_eq!(query.post_script_name.as_deref(), Some("CraftCJK-W3"));
|
||||
assert!(!query.is_serif && !query.is_italic && !query.is_bold, "{query:?}");
|
||||
assert_eq!(query.font_weight, 400);
|
||||
}
|
||||
|
||||
fn pdf(objects: &[String]) -> Vec<u8> {
|
||||
let mut bytes = b"%PDF-1.7\n".to_vec();
|
||||
let mut offsets = Vec::new();
|
||||
for (i, object) in objects.iter().enumerate() {
|
||||
offsets.push(bytes.len());
|
||||
bytes.extend_from_slice(format!("{} 0 obj\n{object}\nendobj\n", i + 1).as_bytes());
|
||||
}
|
||||
let xref = bytes.len();
|
||||
bytes.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objects.len() + 1).as_bytes());
|
||||
for offset in offsets {
|
||||
bytes.extend_from_slice(format!("{offset:010} 00000 n \n").as_bytes());
|
||||
}
|
||||
bytes.extend_from_slice(format!("trailer << /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objects.len() + 1).as_bytes());
|
||||
bytes
|
||||
}
|
||||
|
||||
fn stream(dict: &str, body: &str) -> String {
|
||||
format!("<< {dict} /Length {} >>\nstream\n{body}\nendstream", body.len())
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
//! The crate's integration tests, linked into one test binary.
|
||||
|
||||
mod cid_fallback_query;
|
||||
mod inline_image_ei;
|
||||
mod jbig2_invariants;
|
||||
mod named_form_properties;
|
||||
|
||||
@@ -783,7 +783,7 @@ status = "partial"
|
||||
commands = []
|
||||
tools = []
|
||||
evidence = ["crates/render/src/raster.rs::huge_cid_width_ranges_terminate", "crates/render/src/raster.rs::non_embedded_japanese_cid_fonts_draw_with_a_craft_fonts_face", "crates/render/src/raster.rs::mincho_substitute_covers_half_width_katakana", "crates/render/src/text.rs::identity_to_unicode_names_give_text", "crates/fonts/src/pdf.rs::identity_to_unicode_names_read_codes_as_unicode"]
|
||||
notes = "CID-to-Unicode tables are M2.4; a non-embedded Adobe-Japan1 font is drawn in a craft-fonts Japanese face (Mincho or Gothic by name; BIZ UDMincho for Mincho, which covers half-width katakana) when built with craft-fonts; non-embedded GB1, CNS1 and Korea1 fonts still draw nothing (no allowed face); a `/ToUnicode` given as the name `/Identity-H` or `/Identity-V` (WSCAD) reads codes as Unicode for selection, search and editing, without control characters"
|
||||
notes = "CID-to-Unicode tables are M2.4; a non-embedded Adobe-Japan1 font is drawn in a craft-fonts Japanese face (Mincho or Gothic by name, else by the descendant font descriptor's Serif flag; bold Gothic for a Bold name or a `/FontWeight` of 600 or more; BIZ UDMincho for Mincho, which covers half-width katakana) when built with craft-fonts; non-embedded GB1, CNS1 and Korea1 fonts still draw nothing (no allowed face); a `/ToUnicode` given as the name `/Identity-H` or `/Identity-V` (WSCAD) reads codes as Unicode for selection, search and editing, without control characters"
|
||||
|
||||
[[feature]]
|
||||
id = "view.render-image-codecs"
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+3
-1
@@ -86,7 +86,9 @@ impl Type0Font {
|
||||
if let Some((standard, _)) = select_standard_font(dict, &font_descriptor) {
|
||||
(FontQuery::Standard(standard), true)
|
||||
} else {
|
||||
let mut query = FallbackFontQuery::new(dict);
|
||||
// PdfCraft patch: the style comes from the descendant's descriptor.
|
||||
let mut query =
|
||||
FallbackFontQuery::with_descriptor(dict, Some(font_descriptor.clone()));
|
||||
query.character_collection = character_collection.clone();
|
||||
|
||||
warn!(
|
||||
|
||||
+8
-1
@@ -564,6 +564,13 @@ pub struct FallbackFontQuery {
|
||||
|
||||
impl FallbackFontQuery {
|
||||
pub(crate) fn new(dict: &Dict<'_>) -> Self {
|
||||
Self::with_descriptor(dict, dict.get::<Dict<'_>>(FONT_DESC))
|
||||
}
|
||||
|
||||
/// PdfCraft patch: the query for the font `dict` names, with its style read from
|
||||
/// `descriptor`. A Type 0 font has no font descriptor of its own; its descendant CIDFont
|
||||
/// has it (ISO 32000-2 §9.7.4.1, §9.7.6.1).
|
||||
pub(crate) fn with_descriptor(dict: &Dict<'_>, descriptor: Option<Dict<'_>>) -> Self {
|
||||
let post_script_name = dict
|
||||
.get::<Name<'_>>(BASE_FONT)
|
||||
.map(|n| strip_subset_prefix(n.as_str()).to_string());
|
||||
@@ -573,7 +580,7 @@ impl FallbackFontQuery {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(descriptor) = dict.get::<Dict<'_>>(FONT_DESC) {
|
||||
if let Some(descriptor) = descriptor {
|
||||
data.font_name = dict
|
||||
.get::<Name<'_>>(FONT_NAME)
|
||||
.map(|n| strip_subset_prefix(n.as_str()).to_string());
|
||||
|
||||
Reference in New Issue
Block a user