PDF with ICC based color space defined in the document's resource directory crashes CorelDRAW
adriaanmeuris opened this issue · comments
I've implemented the ICC example, which works fine in all readers (Acrobat, Illustrator, Preview, CorelDRAW, ...).
As I'd like to reuse the profile across pages, I moved it to the resource dict of the document as opposed to the resource dict of the page. This generates a working file that works fine in all readers except CorelDRAW: the program seems to crash with a segfault when parsing the color space:
0 CrlPDFImport.dylib 0x365a893e8 CPDFColorSpaceResource::~CPDFColorSpaceResource() + 40
1 CrlPDFImport.dylib 0x365ad7e78 CPDFPageResources::CleanUp() + 260
2 CrlPDFImport.dylib 0x365ad7fac CPDFPageResources::~CPDFPageResources() + 32
3 CrlPDFImport.dylib 0x365abae54 CPDFPage::~CPDFPage() + 76
4 CrlPDFImport.dylib 0x365ada994 CPDFPagesTree::~CPDFPagesTree() + 92
5 CrlPDFImport.dylib 0x365a7d4d0 CPDFCatalog::~CPDFCatalog() + 76
6 CrlPDFImport.dylib 0x365a900a0 CPDFDocument::~CPDFDocument() + 304
Please see the following screen recording to illustrate the issue:
Screen.Recording.2024-06-27.at.15.38.57.mp4
Here's the adapted implementation which creates 2 PDF-files:
output-page-resources.pdf
: works fine in all readersoutput-document-resources
: works fine in all readers except CorelDRAW
//! This example shows how to use ICC-based color spaces.
use pdf_writer::writers::ColorSpace;
use pdf_writer::{Content, Finish, Name, Pdf, Rect, Ref};
fn create_pdf_with_page_resources() -> std::io::Result<()> {
let mut pdf = Pdf::new();
let catalog_id = Ref::new(1);
let page_tree_id = Ref::new(2);
let page_id = Ref::new(3);
let content_id = Ref::new(4);
let icc_id = Ref::new(5);
// Setup catalog and pages
pdf.catalog(catalog_id).pages(page_tree_id);
// Set up the page tree. For more details see `hello.rs`.
pdf.pages(page_tree_id).kids([page_id]).count(1);
// Create an A4 page.
let mut page = pdf.page(page_id);
page.media_box(Rect::new(0.0, 0.0, 595.0, 842.0));
page.parent(page_tree_id);
page.contents(content_id);
// Setup page resources
let color_space_name = Name(b"sRGB");
page.resources()
.color_spaces()
.insert(color_space_name)
.start::<ColorSpace>()
.icc_based(icc_id);
page.finish();
// Write the content stream with a green rectangle and a crescent with a red stroke.
let mut content = Content::new();
content.set_fill_color_space(color_space_name);
content.set_fill_cmyk(0.0, 1.0, 0.0, 0.0);
content.rect(108.0, 734.0, 100.0, 100.0);
content.fill_even_odd();
// Write the content stream.
pdf.stream(content_id, &content.finish());
// Read the ICC profile from a file.
let icc_data = std::fs::read("sRGB_v4.icc")?;
let mut icc_profile = pdf.icc_profile(icc_id, &icc_data);
icc_profile.n(4);
icc_profile.alternate().device_cmyk();
icc_profile.finish();
// Write the PDF
std::fs::write("output-page-resources.pdf", pdf.finish())
}
fn create_pdf_with_document_resources() -> std::io::Result<()> {
let mut pdf = Pdf::new();
let catalog_id = Ref::new(1);
let page_tree_id = Ref::new(2);
let page_id = Ref::new(3);
let content_id = Ref::new(4);
let icc_id = Ref::new(5);
// Setup catalog and pages
pdf.catalog(catalog_id).pages(page_tree_id);
// Set up the page tree. For more details see `hello.rs`.
let mut pages = pdf.pages(page_tree_id);
pages.kids([page_id]).count(1);
// Setup document resources
let color_space_name = Name(b"sRGB");
let mut resources = pages.resources();
let mut spaces = resources.color_spaces();
spaces
.insert(color_space_name)
.start::<ColorSpace>()
.icc_based(icc_id);
spaces.finish();
resources.finish();
pages.finish();
// Create an A4 page.
let mut page = pdf.page(page_id);
page.media_box(Rect::new(0.0, 0.0, 595.0, 842.0));
page.parent(page_tree_id);
page.contents(content_id);
page.finish();
// Write the content stream with a green rectangle and a crescent with a red stroke.
let mut content = Content::new();
content.set_fill_color_space(color_space_name);
content.set_fill_cmyk(0.0, 1.0, 0.0, 0.0);
content.rect(108.0, 734.0, 100.0, 100.0);
content.fill_even_odd();
// Write the content stream.
pdf.stream(content_id, &content.finish());
// Read the ICC profile from a file.
let icc_data = std::fs::read("sRGB_v4.icc")?;
let mut icc_profile = pdf.icc_profile(icc_id, &icc_data);
icc_profile.n(4);
icc_profile.alternate().device_cmyk();
icc_profile.finish();
// Write the PDF
std::fs::write("output-document-resources.pdf", pdf.finish())
}
fn main() {
create_pdf_with_page_resources().expect("Failed to create PDF with page resources");
create_pdf_with_document_resources().expect("Failed to create PDF with document resources");
}
Hey there, I do not have a CorelDRAW license, so I cannot troubleshoot the issue. Your use of the inheritable Resources dictionary on the page tree root is compliant with section 7.7.3.2. of the PDF specification, so this must be a CorelDRAW bug.
Thanks @reknih, it seems Illustrator always defines resoures at page level, even if they point to the same resource - which works in CorelDRAW. Would that be OK, following the spec?
@adriaanmeuris I recommend always defining resources at the page level. We also had problems with merging PDFs using document-level (or rather page-tree level) resource dicts in Apple Preview. However, you can write the /Resources
key in the page dictionary as an indirect reference and reuse that (though you'll need to drop down to the untyped layer aka page_writer.pair(Name(b"Resources"), my_ref)
).
@laurmaedje thanks for your suggestion, happy to try this approach. Can you indicate how to build a reusable resource dictionary that points to my_ref
? From my understanding, the Resources
struct can only be created by Pages::resources
or Page::resources
so I'm not sure where to start.
That's what I meant with dropping to the untyped layer. You can start writing anything at the top level like this: chunk.indirect(id).start::<Resources>()
.