mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-14 06:22:24 +00:00
pdf inspection: count pending text clips and drop page content after reading (#566)
* pdf inspection: count pending text clips and drop page content after reading - hayro-interpret: state_bytes counts the pending text-clip outlines a saved state copies, so MAX_SAVED_STATE_BYTES bounds them too. - plate-test structure: read each page through a ContentReader into a buffer dropped after the page, instead of page.operations(), which keeps every page's decoded content for as long as the document. * address review: cap page content read at the 256 MiB decode limit * fix the saved-state test: start the clip outline with a MoveTo
This commit is contained in:
1 parent
32589246be
commit
7f0f352d21
2 files changed
+28
-3
No files matched your search
@@ -69,6 +69,9 @@ pub struct ExtGState {
|
||||
pub smask_tr: bool,
|
||||
}
|
||||
|
||||
/// The most decoded content bytes read per page (the former `page.operations()` decode limit).
|
||||
const MAX_PAGE_CONTENT: usize = 256 * 1024 * 1024;
|
||||
|
||||
/// Summarize `pdf`. Malformed objects are skipped rather than reported.
|
||||
pub fn summarize(pdf: &[u8]) -> Result<Summary, Error> {
|
||||
let pdf = Pdf::new(Arc::new(pdf.to_vec())).map_err(|e| Error::Read(format!("{e:?}")))?;
|
||||
@@ -85,8 +88,11 @@ pub fn summarize(pdf: &[u8]) -> Result<Summary, Error> {
|
||||
if let Some(group) = raw.get::<Dict<'_>>(b"Group".as_slice()) {
|
||||
walk.group(&format!("page {}", index + 1), &group);
|
||||
}
|
||||
let mut ops = page.operations();
|
||||
walk.operators(&mut ops);
|
||||
// Read and dropped page by page: `page.operations()` keeps every page's decoded content
|
||||
// for as long as the document, so pages sharing one stream would each hold a copy.
|
||||
let mut content = Vec::new();
|
||||
page.content_reader().read_into(&mut content, MAX_PAGE_CONTENT);
|
||||
walk.operators(&mut UntypedIter::new(&content));
|
||||
let res = page.resources();
|
||||
walk.resources(&Resources {
|
||||
ext_g_states: res.ext_g_states.clone(),
|
||||
|
||||
+20
-1
@@ -193,7 +193,8 @@ const MAX_SAVED_STATE_BYTES: usize = 64 << 20;
|
||||
|
||||
/// Roughly what a saved copy of `state` holds.
|
||||
/// PdfCraft patch (51): the sums saturate, so a huge dash array or clip list counts as over the
|
||||
/// bound rather than wrapping on a 32-bit target.
|
||||
/// bound rather than wrapping on a 32-bit target. The pending text-clip outlines (text render
|
||||
/// modes 4–7) are copied with the state too, so they count.
|
||||
fn state_bytes(state: &State<'_>) -> usize {
|
||||
size_of::<State<'_>>()
|
||||
.saturating_add(
|
||||
@@ -205,6 +206,7 @@ fn state_bytes(state: &State<'_>) -> usize {
|
||||
.saturating_mul(size_of::<f32>()),
|
||||
)
|
||||
.saturating_add(state.clips.len().saturating_mul(size_of::<ClipType>()))
|
||||
.saturating_add(state.text_state.clip_paths.elements().len().saturating_mul(size_of::<PathEl>()))
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
@@ -690,6 +692,23 @@ mod saved_state_tests {
|
||||
assert_eq!(ctx.saved_bytes, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_text_clip_outlines_count_toward_the_saved_bytes() {
|
||||
let pdf = pdf();
|
||||
let cache = InterpreterCache::new();
|
||||
let mut ctx = Context::new(Affine::IDENTITY, Rect::new(0.0, 0.0, 100.0, 100.0), &cache, pdf.xref(), InterpreterSettings::default());
|
||||
let empty = state_bytes(ctx.get());
|
||||
ctx.get_mut().text_state.clip_paths.move_to((0.0, 0.0));
|
||||
for i in 0..100_000 {
|
||||
ctx.get_mut().text_state.clip_paths.line_to((f64::from(i), 1.0));
|
||||
}
|
||||
assert!(state_bytes(ctx.get()) >= empty + 100_000 * size_of::<PathEl>());
|
||||
for _ in 0..20_000 {
|
||||
ctx.save_state();
|
||||
}
|
||||
assert!(ctx.saved_bytes <= MAX_SAVED_STATE_BYTES);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_overlong_dash_array_strokes_solid() {
|
||||
let max = crate::types::MAX_DASH_ENTRIES;
|
||||
|
||||
Reference in new issue
Block a user