mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-26 02:58:32 -06:00
fix(engine): list a page truncated for both forms and pictures once
This commit is contained in:
@@ -184,18 +184,18 @@ impl crate::Document {
|
||||
/// The pages as paragraphs and images (for Word, HTML and RTF export), including what
|
||||
/// form XObjects draw.
|
||||
pub fn export_pages(&self) -> Vec<pdfcraft_export::Page> {
|
||||
self.export_pages_checked(true).0
|
||||
self.export_pages_checked(true, MAX_EXPORT_IMAGE_BYTES).0
|
||||
}
|
||||
|
||||
/// [`Self::export_pages`], and the pages whose forms were left partly unread or whose
|
||||
/// pictures went past [`MAX_EXPORT_IMAGE_BYTES`] ([`OfficeExport::truncated_pages`]). Without
|
||||
/// pictures went past `image_budget` bytes ([`OfficeExport::truncated_pages`]). Without
|
||||
/// `images` no picture is prepared.
|
||||
fn export_pages_checked(&self, images: bool) -> (Vec<pdfcraft_export::Page>, Vec<usize>) {
|
||||
pub(crate) fn export_pages_checked(&self, images: bool, image_budget: usize) -> (Vec<pdfcraft_export::Page>, Vec<usize>) {
|
||||
let Some(cos) = self.editor.as_ref().map(|e| &e.cos) else { return (Vec::new(), Vec::new()) };
|
||||
let mut truncated = Vec::new();
|
||||
// Each image object is encoded once and shared by every place that draws it.
|
||||
let mut encoded: HashMap<ObjRef, PreparedImage> = HashMap::new();
|
||||
let mut image_bytes = MAX_EXPORT_IMAGE_BYTES;
|
||||
let mut image_bytes = image_budget;
|
||||
let pages = self
|
||||
.info
|
||||
.pages
|
||||
@@ -213,9 +213,7 @@ impl crate::Document {
|
||||
let display_width = f64::from(if info.rotation % 180 == 0 { info.width } else { info.height });
|
||||
let unit = if crop_width > 0.0 && crop_width.is_finite() && display_width.is_finite() { display_width / crop_width } else { 1.0 };
|
||||
let reading = pdfcraft_edit::reading_blocks_checked(cos, i).unwrap_or_default();
|
||||
if reading.truncated {
|
||||
truncated.push(i);
|
||||
}
|
||||
let forms_unread = reading.truncated;
|
||||
let blocks = reading
|
||||
.blocks
|
||||
.into_iter()
|
||||
@@ -255,10 +253,10 @@ impl crate::Document {
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
// The page colour (Edit a PDF ▸ Background, a producer's page fill) for formats that keep one.
|
||||
if over_budget {
|
||||
if forms_unread || over_budget {
|
||||
truncated.push(i);
|
||||
}
|
||||
// The page colour (Edit a PDF ▸ Background, a producer's page fill) for formats that keep one.
|
||||
let background = pdfcraft_edit::page_background(cos, i).ok().flatten();
|
||||
pdfcraft_export::Page { width: info.width as f64, height: info.height as f64, blocks, images, background }
|
||||
})
|
||||
@@ -275,7 +273,7 @@ impl crate::Document {
|
||||
/// ([`OfficeExport::truncated_pages`]).
|
||||
pub fn export_office_checked(&self, format: OfficeFormat) -> OfficeExport {
|
||||
// RTF leaves pictures out.
|
||||
let (pages, truncated_pages) = self.export_pages_checked(format != OfficeFormat::Rtf);
|
||||
let (pages, truncated_pages) = self.export_pages_checked(format != OfficeFormat::Rtf, MAX_EXPORT_IMAGE_BYTES);
|
||||
let title = self.info.title.clone().unwrap_or_else(|| self.name.trim_end_matches(".pdf").to_string());
|
||||
let bytes = match format {
|
||||
OfficeFormat::Docx => pdfcraft_export::docx(&pages, &title),
|
||||
|
||||
@@ -7283,3 +7283,47 @@ fn exporting_office_keeps_dotted_leader_entries_as_paragraphs() {
|
||||
let positions: Vec<_> = entries.iter().map(|entry| html.find(&format!("<p>{entry}</p>")).expect("one paragraph per entry")).collect();
|
||||
assert!(positions.windows(2).all(|pair| pair[0] < pair[1]), "reading order: {html}");
|
||||
}
|
||||
|
||||
/// A page whose forms go past the reading limit and whose picture goes past the image budget is
|
||||
/// listed once among the truncated pages.
|
||||
#[test]
|
||||
fn a_page_truncated_for_forms_and_pictures_is_listed_once() {
|
||||
let depth = compare::MAX_FORM_DEPTH + 1;
|
||||
let mut objs: Vec<String> = vec![
|
||||
"<< /Type /Catalog /Pages 2 0 R >>".into(),
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 200 200] >>".into(),
|
||||
String::new(),
|
||||
"<< /Type /XObject /Subtype /Image /Width 2 /Height 2 /ColorSpace /DeviceGray /BitsPerComponent 8 /Length 4 >>\nstream\n\0\x40\u{80}\u{ff}\nendstream".into(),
|
||||
"<< /Length 34 >>\nstream\nq 10 0 0 10 0 0 cm /Im Do Q /D1 Do\nendstream".into(),
|
||||
];
|
||||
// Forms D1..D{depth}, objects 6 onwards, each drawing the next.
|
||||
let xobjects: String = (1..=depth).map(|i| format!("/D{i} {} 0 R ", 5 + i)).collect();
|
||||
let resources = format!("<< /XObject << /Im 4 0 R {xobjects}>> >>");
|
||||
for i in 1..=depth {
|
||||
let body = if i == depth { "q Q".to_string() } else { format!("/D{} Do", i + 1) };
|
||||
objs.push(format!(
|
||||
"<< /Type /XObject /Subtype /Form /BBox [0 0 200 200] /Resources {resources} /Length {} >>\nstream\n{body}\nendstream",
|
||||
body.len()
|
||||
));
|
||||
}
|
||||
objs[2] = format!("<< /Type /Page /Parent 2 0 R /Contents 5 0 R /Resources {resources} >>");
|
||||
let mut bytes = b"%PDF-1.7\n".to_vec();
|
||||
let mut offsets = Vec::new();
|
||||
for (i, o) in objs.iter().enumerate() {
|
||||
offsets.push(bytes.len());
|
||||
bytes.extend_from_slice(format!("{} 0 obj\n{o}\nendobj\n", i + 1).as_bytes());
|
||||
}
|
||||
let xref = bytes.len();
|
||||
bytes.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objs.len() + 1).as_bytes());
|
||||
for o in offsets {
|
||||
bytes.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes());
|
||||
}
|
||||
bytes.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objs.len() + 1).as_bytes());
|
||||
let mut s = Session::new();
|
||||
let id = s.open("both.pdf", None, Arc::new(bytes), None).unwrap();
|
||||
let doc = s.get(id).unwrap();
|
||||
assert_eq!(doc.export_pages_checked(false, 0).1, [0], "the forms alone truncate the page");
|
||||
let (pages, truncated) = doc.export_pages_checked(true, 0);
|
||||
assert!(pages[0].images.is_empty(), "the picture is past the budget");
|
||||
assert_eq!(truncated, [0]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user