typst / pdf-writer

A step-by-step PDF writer.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 readers
  • output-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?

same-resource-in-different-pages

output-illustrator.pdf

@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>().