Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92b0ca9c68 |
+107
-16
@@ -482,7 +482,11 @@ pub(crate) fn parse_cid_w_array(
|
|||||||
widths: &mut HashMap<u16, u16>,
|
widths: &mut HashMap<u16, u16>,
|
||||||
) {
|
) {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
let mut assigned = 0usize;
|
||||||
while i < w_array.len() {
|
while i < w_array.len() {
|
||||||
|
if assigned >= crate::tounicode::MAX_CID_W_EXPANSION {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let start_cid = match &w_array[i] {
|
let start_cid = match &w_array[i] {
|
||||||
Object::Integer(n) => *n as u16,
|
Object::Integer(n) => *n as u16,
|
||||||
Object::Real(n) => *n as u16,
|
Object::Real(n) => *n as u16,
|
||||||
@@ -501,12 +505,14 @@ pub(crate) fn parse_cid_w_array(
|
|||||||
Object::Array(arr) => {
|
Object::Array(arr) => {
|
||||||
// [c [w1 w2 ...]] — consecutive widths starting at c
|
// [c [w1 w2 ...]] — consecutive widths starting at c
|
||||||
for (j, w_obj) in arr.iter().enumerate() {
|
for (j, w_obj) in arr.iter().enumerate() {
|
||||||
let w = match w_obj {
|
if !assign_cid_width(
|
||||||
Object::Integer(n) => *n as u16,
|
widths,
|
||||||
Object::Real(n) => *n as u16,
|
start_cid.wrapping_add(j as u16),
|
||||||
_ => continue,
|
w_obj,
|
||||||
};
|
&mut assigned,
|
||||||
widths.insert(start_cid + j as u16, w);
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
@@ -514,12 +520,14 @@ pub(crate) fn parse_cid_w_array(
|
|||||||
// Could be a reference to an array
|
// Could be a reference to an array
|
||||||
if let Ok(Object::Array(arr)) = doc.get_object(*r) {
|
if let Ok(Object::Array(arr)) = doc.get_object(*r) {
|
||||||
for (j, w_obj) in arr.iter().enumerate() {
|
for (j, w_obj) in arr.iter().enumerate() {
|
||||||
let w = match w_obj {
|
if !assign_cid_width(
|
||||||
Object::Integer(n) => *n as u16,
|
widths,
|
||||||
Object::Real(n) => *n as u16,
|
start_cid.wrapping_add(j as u16),
|
||||||
_ => continue,
|
w_obj,
|
||||||
};
|
&mut assigned,
|
||||||
widths.insert(start_cid + j as u16, w);
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
} else {
|
} else {
|
||||||
@@ -542,8 +550,8 @@ pub(crate) fn parse_cid_w_array(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for cid in start_cid..=end {
|
if !assign_cid_width_range(widths, start_cid, end, w, &mut assigned) {
|
||||||
widths.insert(cid, w);
|
return;
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
@@ -561,8 +569,8 @@ pub(crate) fn parse_cid_w_array(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for cid in start_cid..=end {
|
if !assign_cid_width_range(widths, start_cid, end, w, &mut assigned) {
|
||||||
widths.insert(cid, w);
|
return;
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
@@ -573,6 +581,45 @@ pub(crate) fn parse_cid_w_array(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assign_cid_width(
|
||||||
|
widths: &mut HashMap<u16, u16>,
|
||||||
|
cid: u16,
|
||||||
|
w_obj: &Object,
|
||||||
|
assigned: &mut usize,
|
||||||
|
) -> bool {
|
||||||
|
let w = match w_obj {
|
||||||
|
Object::Integer(n) => *n as u16,
|
||||||
|
Object::Real(n) => *n as u16,
|
||||||
|
_ => return true,
|
||||||
|
};
|
||||||
|
if *assigned >= crate::tounicode::MAX_CID_W_EXPANSION {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
widths.insert(cid, w);
|
||||||
|
*assigned += 1;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_cid_width_range(
|
||||||
|
widths: &mut HashMap<u16, u16>,
|
||||||
|
start: u16,
|
||||||
|
end: u16,
|
||||||
|
w: u16,
|
||||||
|
assigned: &mut usize,
|
||||||
|
) -> bool {
|
||||||
|
if start > end {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for cid in start..=end {
|
||||||
|
if *assigned >= crate::tounicode::MAX_CID_W_EXPANSION {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
widths.insert(cid, w);
|
||||||
|
*assigned += 1;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute the width of a string in text space units,
|
/// Compute the width of a string in text space units,
|
||||||
/// given raw bytes and font width info.
|
/// given raw bytes and font width info.
|
||||||
/// Returns width in text space units (font_units * units_scale * font_size).
|
/// Returns width in text space units (font_units * units_scale * font_size).
|
||||||
@@ -2313,4 +2360,48 @@ end",
|
|||||||
// invalid CMap result — so it must not clear the gid flag.
|
// invalid CMap result — so it must not clear the gid flag.
|
||||||
assert!(gid_flagged(Some("<01> <FFFD>\n<02> <FFFD>")));
|
assert!(gid_flagged(Some("<01> <FFFD>\n<02> <FFFD>")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_cid_w_array_range_and_consecutive() {
|
||||||
|
use super::parse_cid_w_array;
|
||||||
|
use lopdf::{Document, Object};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
let doc = Document::new();
|
||||||
|
let mut widths = HashMap::new();
|
||||||
|
let w = vec![
|
||||||
|
Object::Integer(10),
|
||||||
|
Object::Integer(12),
|
||||||
|
Object::Integer(500),
|
||||||
|
Object::Integer(20),
|
||||||
|
Object::Array(vec![Object::Integer(100), Object::Integer(200)]),
|
||||||
|
];
|
||||||
|
parse_cid_w_array(&doc, &w, &mut widths);
|
||||||
|
assert_eq!(widths.get(&10), Some(&500));
|
||||||
|
assert_eq!(widths.get(&11), Some(&500));
|
||||||
|
assert_eq!(widths.get(&12), Some(&500));
|
||||||
|
assert_eq!(widths.get(&20), Some(&100));
|
||||||
|
assert_eq!(widths.get(&21), Some(&200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_cid_w_array_repeated_full_ranges_stay_bounded() {
|
||||||
|
use super::parse_cid_w_array;
|
||||||
|
use crate::tounicode::MAX_CID_W_EXPANSION;
|
||||||
|
use lopdf::{Document, Object};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
let doc = Document::new();
|
||||||
|
let mut widths = HashMap::new();
|
||||||
|
let mut w = Vec::new();
|
||||||
|
for _ in 0..5_000 {
|
||||||
|
w.push(Object::Integer(0));
|
||||||
|
w.push(Object::Integer(65535));
|
||||||
|
w.push(Object::Integer(500));
|
||||||
|
}
|
||||||
|
parse_cid_w_array(&doc, &w, &mut widths);
|
||||||
|
assert!(widths.len() <= MAX_CID_W_EXPANSION);
|
||||||
|
assert_eq!(widths.get(&0), Some(&500));
|
||||||
|
assert_eq!(widths.get(&65535), Some(&500));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+73
-12
@@ -1832,6 +1832,11 @@ fn merge_cmaps(mut base: ToUnicodeCMap, overlay: ToUnicodeCMap) -> ToUnicodeCMap
|
|||||||
base
|
base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upper bound on CID `/W` range expansion. The CID domain is 16-bit, so more
|
||||||
|
/// than 65,536 unique keys cannot exist; repeating full-width ranges must not
|
||||||
|
/// re-expand the same domain.
|
||||||
|
pub(crate) const MAX_CID_W_EXPANSION: usize = 65_536;
|
||||||
|
|
||||||
/// Check if a CIDFont's /W (widths) array contains CID values that look like
|
/// Check if a CIDFont's /W (widths) array contains CID values that look like
|
||||||
/// Unicode codepoints rather than low-value GIDs.
|
/// Unicode codepoints rather than low-value GIDs.
|
||||||
///
|
///
|
||||||
@@ -1843,20 +1848,23 @@ pub(crate) fn cid_values_look_like_unicode(cid_font_dict: &lopdf::Dictionary) ->
|
|||||||
_ => return false,
|
_ => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The /W array format: [cid [w1 w2 ...]] or [cid_start cid_end w]
|
// The /W array format: [cid [w1 w2 ...]] or [cid_start cid_end w].
|
||||||
// We extract all CID values (the first element of each group).
|
// Collect unique CIDs only: repeating a full-width range must not grow a
|
||||||
let mut cids: Vec<u16> = Vec::new();
|
// temporary vector (or the sort) with the range length on every copy.
|
||||||
|
let mut seen = HashSet::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < w_arr.len() {
|
while i < w_arr.len() && seen.len() < MAX_CID_W_EXPANSION {
|
||||||
if let Ok(cid) = w_arr[i].as_i64() {
|
if let Ok(cid) = w_arr[i].as_i64() {
|
||||||
cids.push(cid as u16);
|
let start = cid as u16;
|
||||||
// Skip the width data
|
|
||||||
if i + 1 < w_arr.len() {
|
if i + 1 < w_arr.len() {
|
||||||
match &w_arr[i + 1] {
|
match &w_arr[i + 1] {
|
||||||
Object::Array(widths) => {
|
Object::Array(widths) => {
|
||||||
// [cid [w1 w2 ...]] — CIDs are cid, cid+1, ..., cid+len-1
|
// [cid [w1 w2 ...]] — CIDs are cid, cid+1, ..., cid+len-1
|
||||||
for j in 1..widths.len() {
|
for j in 0..widths.len() {
|
||||||
cids.push((cid as u16).wrapping_add(j as u16));
|
if seen.len() >= MAX_CID_W_EXPANSION {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
seen.insert(start.wrapping_add(j as u16));
|
||||||
}
|
}
|
||||||
i += 2;
|
i += 2;
|
||||||
}
|
}
|
||||||
@@ -1864,9 +1872,7 @@ pub(crate) fn cid_values_look_like_unicode(cid_font_dict: &lopdf::Dictionary) ->
|
|||||||
// [cid_start cid_end w] — range of CIDs
|
// [cid_start cid_end w] — range of CIDs
|
||||||
if i + 2 < w_arr.len() {
|
if i + 2 < w_arr.len() {
|
||||||
if let Ok(cid_end) = w_arr[i + 1].as_i64() {
|
if let Ok(cid_end) = w_arr[i + 1].as_i64() {
|
||||||
for c in (cid as u16)..=(cid_end as u16) {
|
record_unique_cid_range(start, cid_end as u16, &mut seen);
|
||||||
cids.push(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
i += 3;
|
i += 3;
|
||||||
} else {
|
} else {
|
||||||
@@ -1875,6 +1881,7 @@ pub(crate) fn cid_values_look_like_unicode(cid_font_dict: &lopdf::Dictionary) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
seen.insert(start);
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1882,10 +1889,11 @@ pub(crate) fn cid_values_look_like_unicode(cid_font_dict: &lopdf::Dictionary) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cids.is_empty() {
|
if seen.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut cids: Vec<u16> = seen.into_iter().collect();
|
||||||
cids.sort_unstable();
|
cids.sort_unstable();
|
||||||
let median = cids[cids.len() / 2];
|
let median = cids[cids.len() / 2];
|
||||||
// Unicode text CIDs are typically >= 0x20 (space) with letters at 0x41+.
|
// Unicode text CIDs are typically >= 0x20 (space) with letters at 0x41+.
|
||||||
@@ -1894,6 +1902,18 @@ pub(crate) fn cid_values_look_like_unicode(cid_font_dict: &lopdf::Dictionary) ->
|
|||||||
median >= 0x41
|
median >= 0x41
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn record_unique_cid_range(start: u16, end: u16, seen: &mut HashSet<u16>) {
|
||||||
|
if start > end {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for cid in start..=end {
|
||||||
|
if seen.len() >= MAX_CID_W_EXPANSION {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seen.insert(cid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a ToUnicodeCMap from predefined CID→Unicode mapping based on CIDSystemInfo.
|
/// Build a ToUnicodeCMap from predefined CID→Unicode mapping based on CIDSystemInfo.
|
||||||
///
|
///
|
||||||
/// Supports Adobe-Korea1 (Korean) character collection. Can be extended for
|
/// Supports Adobe-Korea1 (Korean) character collection. Can be extended for
|
||||||
@@ -3298,4 +3318,45 @@ endbfrange
|
|||||||
"An indirect /Subtype naming CIDFontType2 must still reach the remap"
|
"An indirect /Subtype naming CIDFontType2 must still reach the remap"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cid_values_look_like_unicode_letter_range() {
|
||||||
|
let mut dict = lopdf::Dictionary::new();
|
||||||
|
dict.set(
|
||||||
|
"W",
|
||||||
|
Object::Array(vec![
|
||||||
|
Object::Integer(0x41),
|
||||||
|
Object::Integer(0x5A),
|
||||||
|
Object::Integer(500),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert!(cid_values_look_like_unicode(&dict));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cid_values_look_like_unicode_low_gids() {
|
||||||
|
let mut dict = lopdf::Dictionary::new();
|
||||||
|
dict.set(
|
||||||
|
"W",
|
||||||
|
Object::Array(vec![
|
||||||
|
Object::Integer(0),
|
||||||
|
Object::Array(vec![Object::Integer(500); 10]),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert!(!cid_values_look_like_unicode(&dict));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cid_values_look_like_unicode_repeated_full_ranges_stay_bounded() {
|
||||||
|
// Repeating `[0 65535 w]` must not materialize 65,536 CIDs per copy.
|
||||||
|
let mut w = Vec::new();
|
||||||
|
for _ in 0..5_000 {
|
||||||
|
w.push(Object::Integer(0));
|
||||||
|
w.push(Object::Integer(65535));
|
||||||
|
w.push(Object::Integer(500));
|
||||||
|
}
|
||||||
|
let mut dict = lopdf::Dictionary::new();
|
||||||
|
dict.set("W", Object::Array(w));
|
||||||
|
assert!(cid_values_look_like_unicode(&dict));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user