Environment
- Datalogics.PDFL for .NET, APDFL 18.0.5PlusP1y (managed
Datalogics.PDFL.dll FileVersion 18.0.5.57, native DL180pdfl.dll FileVersion 18.0.5.19, ProductName "APDFL 18.0.5PlusP1y")
- Windows 11 x64, .NET 10
Summary
When a document contains an image XObject that is shared by multiple pages (one image object, referenced from each page's resources via a Form XObject — a common structure for scanned letterheads), applying a redaction to ONE page and then optimizing the document corrupts every other page:
Document.ApplyRedactions() mutates the shared image object in place — the redaction square is burned into the image pixels, so after a plain Save(SaveFlags.Full) the black box is visible on every page that draws that image, not just the redacted page.
- If, instead of a plain save, the still-open document is passed to
PDFOptimizer.Optimize(), the optimizer removes the XObject resource entries from every non-redacted page. The content streams still contain the /Fm0 Do operators, but the resources are gone, so viewers silently render nothing (MuPDF reports syntax error: cannot find XObject resource 'Fm0'). Backgrounds/photos disappear from all pages except the redacted one.
Neither step alone corrupts the file — see the matrix below.
Reproduction
Input: a 3-page PDF where each page's /Resources /XObject /Fm0 points at the same Form XObject, whose own resources contain one DCTDecode image (/Im0) drawn full-page. Pages also have a line of text. (Generator script for the exact file at the bottom; I can also send the 8 KB sample PDF on request.)
using Datalogics.PDFL;
using Library library = new();
using Document document = new("shared-image-3pages.pdf");
// one redaction on page 2 (0-based index 1), over the text line
using (Page page = document.GetPage(1))
{
using Quad quad = new(
new Point(100, 712), new Point(240, 712),
new Point(100, 696), new Point(240, 696));
using Color black = new(0, 0, 0);
using Redaction redaction = new(page, new List<Quad> { quad }, black) { FillNormal = true };
}
document.ApplyRedactions();
// variant A: plain save -> defect 1 (burn-in on all pages)
using (MemoryStream regular = new())
{
document.Save(SaveFlags.Full, regular);
File.WriteAllBytes("variantA.pdf", regular.ToArray());
}
// variant B: optimize the live document -> defect 2 (other pages lose the image)
using (MemoryStream optimized = new())
using (PDFOptimizer optimizer = new())
{
optimizer.Optimize(document, optimized);
File.WriteAllBytes("variantB.pdf", optimized.ToArray());
}
Observed results (all on 18.0.5PlusP1y)
| Scenario |
Result |
Redact p2 + ApplyRedactions + Save(SaveFlags.Full) |
Structurally valid, but the redaction square is burned into the shared image → box visible on pages 1 and 3 too (defect 1) |
No redaction + PDFOptimizer only |
Correct (images downsampled/recompressed as expected, all references valid) |
Redact p2 + ApplyRedactions + PDFOptimizer on the live Document |
Pages 1 and 3 lose their /XObject resource entries — content streams still call /Fm0 Do but the resource dictionaries are empty; the shared image survives only on page 2 (defect 2) |
Redact p2 + ApplyRedactions + Save(SaveFlags.Full) → reopen from bytes → PDFOptimizer |
Resources intact on all pages (workaround for defect 2; defect 1 burn-in still present) |
The same behavior reproduces on a real-world 26-page document (scanned letterhead shared by all pages): after redacting one page and optimizing, 25 of 26 pages ended up with dangling Do operators and no images.
Expected
- Redacting page N must not alter the rendered appearance of any other page. If the redaction area intersects an image object shared with other pages, the page being redacted should get its own (sanitized) copy — mutating the shared object leaks the redaction onto unrelated pages.
- The optimizer should never drop a resource that is still referenced by a page's content stream.
Workarounds we ship in the meantime
- Deep-copy the page to be redacted into a temporary
Document via InsertPages (this un-shares the XObjects), apply redactions there, and swap the result back with ReplacePages.
- Always
Save(SaveFlags.Full) + reopen before running PDFOptimizer on a document that had redactions applied in the same session.
Related release-note entries
The release notes list SF#46189 ("applying a Redaction could cause non-adjacent images to be removed"), SF#46980 ("form on multiple pages … holes punched through the form"), SF#46981 ("misplaced instances when an image was reused"), and SF#46629 ("optimizing images shared across different pages") as fixed in earlier releases — but the combination above still reproduces on 18.0.5PlusP1y.
Input file generator (Python, pikepdf + Pillow)
Script that produces the exact 3-page repro PDF
import io
import pikepdf
from pikepdf import Name
from PIL import Image as PILImage, ImageDraw
img = PILImage.new("RGB", (400, 566), "white")
d = ImageDraw.Draw(img)
d.rectangle([0, 0, 400, 80], fill=(230, 90, 30))
d.polygon([(400, 566), (400, 420), (260, 566)], fill=(230, 90, 30))
buf = io.BytesIO()
img.save(buf, "JPEG", quality=70)
pdf = pikepdf.new()
image = pikepdf.Stream(pdf, buf.getvalue())
image.Type = Name.XObject
image.Subtype = Name.Image
image.Width = 400
image.Height = 566
image.ColorSpace = Name.DeviceRGB
image.BitsPerComponent = 8
image.Filter = Name.DCTDecode
image = pdf.make_indirect(image)
form = pikepdf.Stream(pdf, b"q /GS0 gs 595 0 0 842 0 0 cm /Im0 Do Q")
form.Type = Name.XObject
form.Subtype = Name.Form
form.BBox = pikepdf.Array([0, 0, 595, 842])
form.Resources = pikepdf.Dictionary(
XObject=pikepdf.Dictionary(Im0=image),
ExtGState=pikepdf.Dictionary(GS0=pikepdf.Dictionary(Type=Name.ExtGState)),
)
form = pdf.make_indirect(form)
font = pdf.make_indirect(pikepdf.Dictionary(Type=Name.Font, Subtype=Name.Type1, BaseFont=Name.Helvetica))
for i in range(3):
text = f"Page {i + 1} confidential number 123456789".encode()
content = (
b"/Artifact <</Subtype /Watermark /Type /Pagination >>BDC q 1 0 0 1 0 0 cm /Fm0 Do Q EMC "
b"BT 0 0 0 rg /F1 12 Tf 100 700 Td (" + text + b") Tj ET"
)
page = pdf.make_indirect(
pikepdf.Dictionary(
Type=Name.Page,
MediaBox=pikepdf.Array([0, 0, 595, 842]),
Resources=pikepdf.Dictionary(
XObject=pikepdf.Dictionary(Fm0=form),
Font=pikepdf.Dictionary(F1=font),
ProcSet=pikepdf.Array([Name.PDF, Name.Text, Name.ImageC]),
),
Contents=pikepdf.Stream(pdf, content),
)
)
pdf.pages.append(pikepdf.Page(page))
pdf.save("shared-image-3pages.pdf")
Posting here since this repo is the public .NET channel — happy to move this to the support portal and provide the sample PDFs + broken outputs there if that is preferred.
Environment
Datalogics.PDFL.dllFileVersion 18.0.5.57, nativeDL180pdfl.dllFileVersion 18.0.5.19, ProductName "APDFL 18.0.5PlusP1y")Summary
When a document contains an image XObject that is shared by multiple pages (one image object, referenced from each page's resources via a Form XObject — a common structure for scanned letterheads), applying a redaction to ONE page and then optimizing the document corrupts every other page:
Document.ApplyRedactions()mutates the shared image object in place — the redaction square is burned into the image pixels, so after a plainSave(SaveFlags.Full)the black box is visible on every page that draws that image, not just the redacted page.PDFOptimizer.Optimize(), the optimizer removes the XObject resource entries from every non-redacted page. The content streams still contain the/Fm0 Dooperators, but the resources are gone, so viewers silently render nothing (MuPDF reportssyntax error: cannot find XObject resource 'Fm0'). Backgrounds/photos disappear from all pages except the redacted one.Neither step alone corrupts the file — see the matrix below.
Reproduction
Input: a 3-page PDF where each page's
/Resources /XObject /Fm0points at the same Form XObject, whose own resources contain one DCTDecode image (/Im0) drawn full-page. Pages also have a line of text. (Generator script for the exact file at the bottom; I can also send the 8 KB sample PDF on request.)Observed results (all on 18.0.5PlusP1y)
ApplyRedactions+Save(SaveFlags.Full)PDFOptimizeronlyApplyRedactions+PDFOptimizeron the liveDocument/XObjectresource entries — content streams still call/Fm0 Dobut the resource dictionaries are empty; the shared image survives only on page 2 (defect 2)ApplyRedactions+Save(SaveFlags.Full)→ reopen from bytes →PDFOptimizerThe same behavior reproduces on a real-world 26-page document (scanned letterhead shared by all pages): after redacting one page and optimizing, 25 of 26 pages ended up with dangling
Dooperators and no images.Expected
Workarounds we ship in the meantime
DocumentviaInsertPages(this un-shares the XObjects), apply redactions there, and swap the result back withReplacePages.Save(SaveFlags.Full)+ reopen before runningPDFOptimizeron a document that had redactions applied in the same session.Related release-note entries
The release notes list SF#46189 ("applying a Redaction could cause non-adjacent images to be removed"), SF#46980 ("form on multiple pages … holes punched through the form"), SF#46981 ("misplaced instances when an image was reused"), and SF#46629 ("optimizing images shared across different pages") as fixed in earlier releases — but the combination above still reproduces on 18.0.5PlusP1y.
Input file generator (Python, pikepdf + Pillow)
Script that produces the exact 3-page repro PDF
Posting here since this repo is the public .NET channel — happy to move this to the support portal and provide the sample PDFs + broken outputs there if that is preferred.