use super::*;
use crate::pdf::font::tests::{CffOptions, collection_font, otf};
struct ChangingFont {
bytes: Vec<u8>,
starts: usize,
}
impl RangedSource for ChangingFont {
fn size(&self) -> u64 {
self.bytes.len() as u64
}
fn read_at(&mut self, offset: u64, out: &mut [u8]) -> Result<usize> {
if offset == 0 {
self.starts += 1;
}
let start = offset as usize;
let count = out.len().min(self.bytes.len().saturating_sub(start));
out[..count].copy_from_slice(&self.bytes[start..start + count]);
if self.starts >= 3 && count != 0 && start + count == self.bytes.len() {
out[count - 1] ^= 1;
}
Ok(count)
}
}
#[test]
fn changed_complete_font_program_prevents_finishing_document() {
let mut bytes = drawing_font();
bytes.push(0);
let mut source = ChangingFont { bytes, starts: 0 };
let limits = Limits {
io_chunk_bytes: 31,
..Limits::default()
};
let mut font = OpenTypeFont::read(&mut source, 0, &limits, &NEVER).unwrap();
let mut sink = Sink::default();
let mut document = PdfDocument::new(&mut sink, &limits, &NEVER).unwrap();
let handle = document.add_font(&font).unwrap();
let fonts = [&handle];
let mut content = document.begin_content_page(page(), &fonts, &[]).unwrap();
content.glyph(0, 'A', matrix(10.0)).unwrap();
content.finish().unwrap();
let error = document
.embed_font_with_embedding(&handle, &mut font, FontEmbedding::Full)
.unwrap_err();
assert_eq!(error.reason, CHANGED);
assert!(document.image_page_failed);
assert!(document.finish().is_err());
}
#[test]
fn full_program_preserves_original_gids_and_independent_semantic_text() {
for collection in [false, true] {
for mapped in [false, true] {
let bytes = if collection {
collection_font()
} else {
drawing_font()
};
let mut source = &bytes[..];
let limits = Limits {
io_chunk_bytes: 31,
..Limits::default()
};
let mut font = OpenTypeFont::read(&mut source, 0, &limits, &NEVER).unwrap();
let gid = font.glyph('δΈ').unwrap().id;
let mut sink = Sink::default();
let mut document = PdfDocument::new(&mut sink, &limits, &NEVER).unwrap();
let handle = if mapped {
document.add_mapped_font(&font)
} else {
document.add_font(&font)
}
.unwrap();
let fonts = [&handle];
let mut content = document.begin_content_page(page(), &fonts, &[]).unwrap();
if mapped {
content.mapped_glyph(0, 'δΈ', 'A', matrix(10.0), 0).unwrap();
content
.mapped_glyph(0, 'δΈ', 'π', matrix(40.0), 0)
.unwrap();
} else {
content.glyph(0, 'δΈ', matrix(10.0)).unwrap();
}
content.finish().unwrap();
document
.embed_font_with_embedding(&handle, &mut font, FontEmbedding::Full)
.unwrap();
let report = document.finish().unwrap();
assert!(report.input_bytes_read > 0);
let text = pdf_text(&sink.bytes);
assert!(text.contains("/BaseFont /CajFixture /"));
assert!(!text.contains("+CajFixture"));
let program = inflated_stream(&sink.bytes, b"/Length1 ");
if !collection {
assert_eq!(program, bytes);
}
let face = xberg_ttf_parser::Face::parse(&program, 0).unwrap();
assert_eq!(face.number_of_glyphs(), 3);
assert_eq!(face.glyph_index('δΈ').unwrap().0, gid);
let mapping = text
.split("/CIDToGIDMap ")
.nth(1)
.unwrap()
.split(' ')
.next()
.unwrap();
let map = inflated_stream(&sink.bytes, format!("\n{mapping} 0 obj").as_bytes());
if mapped {
assert_eq!(map, [0, 0, 0, gid as u8, 0, gid as u8]);
let cmap = text
.split("/ToUnicode ")
.nth(1)
.unwrap()
.split(' ')
.next()
.unwrap();
let unicode = inflated_stream(&sink.bytes, format!("\n{cmap} 0 obj").as_bytes());
let unicode = String::from_utf8(unicode).unwrap();
assert!(unicode.contains("<0001> <0041>"));
assert!(unicode.contains("<0002> <D83DDE00>"));
} else {
assert_eq!(&map[2 * 'δΈ' as usize..], gid.to_be_bytes());
}
if let Some(root) = std::env::var_os("CAJ2PDF_FONT_TEST_OUTPUT") {
let root = std::path::PathBuf::from(root);
std::fs::create_dir_all(&root).unwrap();
let path = root.join(format!(
"complete-collection-{collection}-mapped-{mapped}.pdf"
));
std::fs::write(path, &sink.bytes).unwrap();
}
}
}
}
#[test]
fn full_option_keeps_unused_resources_small_and_refuses_used_cff_before_writing() {
for cff in [false, true] {
for drawn in [false, true] {
let bytes = if cff {
otf(&CffOptions::default())
} else {
drawing_font()
};
let mut source = &bytes[..];
let limits = Limits::default();
let mut font = OpenTypeFont::read(&mut source, 0, &limits, &NEVER).unwrap();
let mut sink = Sink::default();
let mut document = PdfDocument::new(&mut sink, &limits, &NEVER).unwrap();
let handle = document.add_font(&font).unwrap();
let fonts = [&handle];
let mut content = document.begin_content_page(page(), &fonts, &[]).unwrap();
if drawn {
content.glyph(0, 'δΈ', matrix(10.0)).unwrap();
}
content.finish().unwrap();
let before = document.writer.position();
let result =
document.embed_font_with_embedding(&handle, &mut font, FontEmbedding::Full);
if cff && drawn {
assert_eq!(
result.unwrap_err().reason,
"complete font embedding requires TrueType outlines"
);
assert_eq!(document.writer.position(), before);
assert!(!document.image_page_failed);
document.embed_font(&handle, &mut font).unwrap();
} else {
result.unwrap();
}
document.finish().unwrap();
if !cff && !drawn {
let program = inflated_stream(&sink.bytes, b"/Length1 ");
let face = xberg_ttf_parser::Face::parse(&program, 0).unwrap();
assert_eq!(face.number_of_glyphs(), 1);
}
}
}
}
#[test]
fn full_font_read_failure_can_retry_but_output_failure_poisons_document() {
let mut source = FontSource::new();
let limits = Limits::default();
let mut font = OpenTypeFont::read(&mut source, 0, &limits, &NEVER).unwrap();
let mut sink = Sink::default();
let failed = sink.fail_now.clone();
let mut document = PdfDocument::new(&mut sink, &limits, &NEVER).unwrap();
let handle = document.add_font(&font).unwrap();
let fonts = [&handle];
let mut content = document.begin_content_page(page(), &fonts, &[]).unwrap();
content.glyph(0, 'A', matrix(10.0)).unwrap();
content.finish().unwrap();
font.source.fail = true;
let before = document.writer.position();
assert!(
document
.embed_font_with_embedding(&handle, &mut font, FontEmbedding::Full)
.is_err()
);
assert_eq!(document.writer.position(), before);
assert!(!document.image_page_failed);
font.source.fail = false;
failed.set(true);
assert!(
document
.embed_font_with_embedding(&handle, &mut font, FontEmbedding::Full)
.is_err()
);
assert!(document.image_page_failed);
failed.set(false);
assert!(
document
.embed_font_with_embedding(&handle, &mut font, FontEmbedding::Full)
.is_err()
);
assert!(document.finish().is_err());
}