Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src-tauri/src/pdf_engine/source_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ struct Flags {
no_tounicode: bool,
missing_font: bool,
ambiguous: bool,
subset_font: bool,
custom_encoding: bool,
masked: bool,
shared: bool,
geometry: bool,
Expand Down Expand Up @@ -256,6 +258,8 @@ struct TextInspect {
vertical: bool,
no_tounicode: bool,
ambiguous: bool,
subset_font: bool,
custom_encoding: bool,
width: f64,
font_id: ObjectId,
}
Expand Down Expand Up @@ -618,6 +622,8 @@ impl Walker<'_> {
no_tounicode: inspect.no_tounicode,
missing_font: inspect.missing_font,
ambiguous: inspect.ambiguous,
subset_font: inspect.subset_font,
custom_encoding: inspect.custom_encoding,
masked: gs.masked,
geometry: geom_unsafe,
..Flags::default()
Expand Down Expand Up @@ -798,6 +804,10 @@ fn pick_reason(flags: &Flags) -> (SourceCapability, Option<String>) {
Some("NO_TOUNICODE")
} else if flags.ambiguous {
Some("AMBIGUOUS_UNICODE")
} else if flags.subset_font {
Some("SUBSET_FONT")
} else if flags.custom_encoding {
Some("CUSTOM_ENCODING")
} else if flags.masked {
Some("MASKED_IMAGE")
} else if flags.shared {
Expand Down Expand Up @@ -1010,10 +1020,14 @@ fn inspect_text(
let mut vertical = false;
let mut no_tounicode = false;
let mut ambiguous = false;
let mut subset_font = false;
let mut custom_encoding = false;
let mut width_sum = 0.0;
if let Some(font) = dict {
type3 = is_type3(font);
vertical = is_vertical(doc, font);
subset_font = is_subset_font(doc, font);
custom_encoding = !is_cid_or_type0(font) && has_custom_encoding(doc, font);
if is_cid_or_type0(font) {
if tounicode_usable(doc, font) {
ambiguous = true;
Expand All @@ -1036,6 +1050,8 @@ fn inspect_text(
vertical,
no_tounicode,
ambiguous,
subset_font,
custom_encoding,
width: (width_sum + tj_adj) / 1000.0 * size,
font_id,
}
Expand All @@ -1046,6 +1062,32 @@ fn is_type3(font: &Dictionary) -> bool {
|| font.get(b"CharProcs").is_ok()
}

fn is_subset_font(doc: &Document, font: &Dictionary) -> bool {
let Ok(Object::Name(name)) = font.get_deref(b"BaseFont", doc) else {
return false;
};
name.len() > 7
&& name.get(6) == Some(&b'+')
&& name[..6].iter().all(u8::is_ascii_uppercase)
}

fn has_custom_encoding(doc: &Document, font: &Dictionary) -> bool {
if font.get(b"Encoding").is_err() {
return false;
}
let Ok(encoding) = font.get_deref(b"Encoding", doc) else {
return true;
};
!matches!(
encoding,
Object::Name(name)
if matches!(
name.as_slice(),
b"StandardEncoding" | b"WinAnsiEncoding" | b"MacRomanEncoding"
)
)
}

fn is_cid_or_type0(font: &Dictionary) -> bool {
match font.get(b"Subtype").ok().and_then(|o| o.as_name().ok()) {
Some(b"Type0") | Some(b"CIDFontType0") | Some(b"CIDFontType2") => return true,
Expand Down
92 changes: 91 additions & 1 deletion src-tauri/src/pdf_engine/source_content_integ.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const FILE_CAP_BYTES: u64 = 400 * 1024 * 1024;

const FROZEN_REASONS: &[&str] = &[
"MISSING_FONT",
"SUBSET_FONT",
"CUSTOM_ENCODING",
"NO_TOUNICODE",
"AMBIGUOUS_UNICODE",
"TYPE3",
Expand Down Expand Up @@ -784,6 +786,73 @@ fn classify_missing_font_is_not_supported() {
assert_eq!(reason_code(&hits[0]).as_deref(), Some("MISSING_FONT"));
}

#[test]
fn classify_subset_and_custom_encoded_fonts_are_not_supported() {
let scratch = Scratch::new("review-font-compatibility");

let mut subset = Dictionary::new();
subset.set("Type", "Font");
subset.set("Subtype", "Type1");
subset.set("BaseFont", "ABCDEF+Helvetica");
let subset_path = scratch.file("subset.pdf");
write_text_page_with_font(
&subset_path,
b"BT /F1 12 Tf 72 400 Td (Hi) Tj ET",
subset,
);
let subset_hits = classify(&subset_path, "REVIEW-SUBSET-FONT");
assert_unsupported(
first_of_kind(&subset_hits, "text", "REVIEW-SUBSET-FONT"),
"text",
"SUBSET_FONT",
"REVIEW-SUBSET-FONT",
);

let mut differences = Dictionary::new();
differences.set("Type", "Encoding");
differences.set("BaseEncoding", "WinAnsiEncoding");
differences.set(
"Differences",
vec![Object::Integer(72), Object::Name(b"customH".to_vec())],
);
let mut custom = Dictionary::new();
custom.set("Type", "Font");
custom.set("Subtype", "Type1");
custom.set("BaseFont", "Helvetica");
custom.set("Encoding", differences);
let custom_path = scratch.file("custom-encoding.pdf");
write_text_page_with_font(
&custom_path,
b"BT /F1 12 Tf 72 400 Td (Hi) Tj ET",
custom,
);
let custom_hits = classify(&custom_path, "REVIEW-CUSTOM-ENCODING");
assert_unsupported(
first_of_kind(&custom_hits, "text", "REVIEW-CUSTOM-ENCODING"),
"text",
"CUSTOM_ENCODING",
"REVIEW-CUSTOM-ENCODING",
);
}

#[test]
fn classify_named_standard_encoding_remains_supported() {
let scratch = Scratch::new("review-standard-encoding");
let path = scratch.file("win-ansi.pdf");
let mut font = Dictionary::new();
font.set("Type", "Font");
font.set("Subtype", "Type1");
font.set("BaseFont", "Helvetica");
font.set("Encoding", "WinAnsiEncoding");
write_text_page_with_font(&path, b"BT /F1 12 Tf 72 400 Td (Hi) Tj ET", font);
let hits = classify(&path, "REVIEW-STANDARD-ENCODING");
assert_supported_text_or_image(
first_of_kind(&hits, "text", "REVIEW-STANDARD-ENCODING"),
"text",
"REVIEW-STANDARD-ENCODING",
);
}

#[test]
fn classify_repeated_form_occurrences_have_unique_locators() {
let scratch = Scratch::new("review-repeated-form");
Expand Down Expand Up @@ -823,15 +892,36 @@ fn write_helvetica_page(path: &Path, content: &[u8]) {
}

fn write_helvetica_page_with_stream_dict(path: &Path, content: &[u8], stream_dict: Dictionary) {
let mut font = Dictionary::new();
font.set("Type", "Font");
font.set("Subtype", "Type1");
font.set("BaseFont", "Helvetica");
write_text_page_with_font_and_stream_dict(path, content, font, stream_dict);
}

fn write_text_page_with_font(path: &Path, content: &[u8], font: Dictionary) {
write_text_page_with_font_and_stream_dict(path, content, font, Dictionary::new());
}

fn write_text_page_with_font_and_stream_dict(
path: &Path,
content: &[u8],
font: Dictionary,
stream_dict: Dictionary,
) {
let mut doc = Document::with_version("1.7");
let pages_id = doc.new_object_id();
let content_id = doc.add_object(Object::Stream(Stream::new(stream_dict, content.to_vec())));
let mut fonts = Dictionary::new();
fonts.set("F1", Object::Dictionary(font));
let mut resources = Dictionary::new();
resources.set("Font", Object::Dictionary(fonts));
let mut page = Dictionary::new();
page.set("Type", "Page");
page.set("Parent", pages_id);
page.set("MediaBox", box_obj([0, 0, 612, 792]));
page.set("Contents", content_id);
page.set("Resources", Object::Dictionary(helvetica_resources()));
page.set("Resources", Object::Dictionary(resources));
let page_id = doc.add_object(Object::Dictionary(page));

let mut pages = Dictionary::new();
Expand Down
Loading