Skip to content

Write PDF/X-4 when PDFX is '4', keeping the transparency, layers and colour fonts PDF/X-1a strips - #324

Open
jakejackson1 wants to merge 3 commits into
gravitypdffrom
feature/pdfx4-support
Open

jakejackson1 wants to merge 3 commits into
gravitypdffrom
feature/pdfx4-support

Conversation

@jakejackson1

Copy link
Copy Markdown
Member

Summary

PDFX now takes '4' for PDF/X-4 (ISO 15930-7). true and '1a' still give PDF/X-1a:2003, and any other string is refused. Under PDF/X-4, mPDF:

  • writes PDF 1.6. If PDFX is set after the header has been written, the catalog carries /Version /1.6 instead.
  • writes the pdfxid XMP schema, pdf:Trapped, xmpMM:VersionID, xmpMM:RenditionClass and xmpMM:InstanceID.
  • always embeds an output-intent profile. With no ICCProfile it uses the bundled SWOP2006_Coated3v2.icc, the IDEAlliance CMYK printer profile from the ICC registry. The profile is redistributable but may not be modified; its licence is in data/iccprofiles/NOTICE.md.
  • keeps opacity, watermarks, rgba() colours (as CMYK plus alpha), PNG alpha (/SMask) and layers. Each layer configuration is named.
  • blends each page in the colour space of the output intent: /Group … /CS /DeviceCMYK, or RGB/Gray when the intent is RGB/Gray.

Some rules apply to every PDF/X version, so they also change PDF/X-1a output:

  • JavaScript is removed.
  • Active form fields are removed.
  • File attachments are removed, as is any link or annotation inside the BleedBox (or the TrimBox when there is no bleed).
  • Images are no longer written with /Interpolate true.

Under PDFXauto = false each removal becomes a warning, and the document is refused.

What changed compared with jakejackson1#7

  • Colour emoji are drawn in colour under PDF/X-4. Fix the font-package script mappings #7 left ColorFormats::inColor() off for all PDF/X, but PDF/X-4 permits transparency. The catch is that PDF/X-4 allows DeviceRGB only when the output intent is RGB. So with a CMYK intent, the RGB in colour glyphs is written in an ICC-based sRGB colour space:
    • glyph fills and strokes set it by name through a new GlyphResources::rgb();
    • shadings and glyph images reference it;
    • it is written once, by BaseWriter::calibratedRgb().
    • With an RGB intent, glyphs stay in DeviceRGB.
  • Raster images keep their colour and transparency. Fix the font-package script mappings #7 converted RGB PNG/JPEG to CMYK with GD. Here, untagged RGB and palette images are tagged sRGB, the same way as the colour glyphs, and images with an embedded ICC profile keep it. This also allows BMP under PDF/X-4. CSS and vector colours are still converted to CMYK, as for PDF/X-1a. Only a CMYK JPEG is converted, to RGB, and only for an RGB intent.
  • Colours with transparency keep their alpha. Fix the font-package script mappings #7 converted them to CMYK without it.
  • The blending colour space is a device space. Fix the font-package script mappings #7 reserved an object id for an ICC-based blending space and forward-referenced it. Here the page group uses the device space that matches the output intent, so that mechanism is gone.
  • Things not carried over:
    • Visibility (printonly/screenonly/hidden) stays refused. It depends on /AS usage apps, and print-only content would not print without them.
    • The shared Info/XMP timestamp was not carried over: the fork's BaseWriter::date() already gives one date for both.
    • The ExtGState /TR//HT guard was dropped, since nothing in mPDF writes those keys.
    • The external preflight test hook and the acceptance HTML fixture were dropped.
    • Form JavaScript and action guards were not needed, because the fields are removed before they are written.
  • Smaller API. PDFX is no longer rewritten to a boolean in the constructor. isPdfx4() and isPdfx1a() read it, so setting PDFX = '4' after construction still works, as it already does for colour fonts under PDF/A. The page box geometry is not refactored; printedArea() works out the BleedBox/TrimBox on its own. This keeps the change clear of the shared PDF/A/PDF/X code the PDF/UA port also touches.

Testing

  • New tests/Mpdf/PdfX4Test.php (24 tests). It covers:
    • version parsing, header and catalog version;
    • the bundled profile embedded with /N 4, and the profile's prtr/CMYK header;
    • XMP and Info identification, including that PDF/X-1a is unchanged;
    • JavaScript, link, form and interpolation removal, and a link outside the BleedBox being kept;
    • opacity, rgba, watermark, PNG alpha and palette images in sRGB, and layers;
    • the page blending space for CMYK and RGB intents;
    • RGB→CMYK against RGB passthrough;
    • CBDT and COLR emoji in sRGB, and in DeviceRGB for an RGB intent;
    • refusal of encryption.
  • composer test passes (3151 tests, 5 skips). The skips were already there: generator/ICU checks. The snapshot group passes (85).
  • composer cs is clean. PHPStan 2.2 reports no errors, with no baseline change.
  • I have not validated the output against the PDF/X-4 standard. veraPDF has no PDF/X profile, and no Acrobat or pdfToolbox preflight was available. I did render PDF/X-4, PDF/X-1a and PDF/X-4 (RGB intent) documents with Ghostscript: emoji and translucent PNGs draw in colour under PDF/X-4, and flatten as before under PDF/X-1a. pdfinfo reports the PDF/X-4 document as PDF subtype: PDF/X-4.

Open questions

  • The SWOP profile is 2.7 MB and barely compresses, so every PDF/X-4 document that doesn't set ICCProfile grows by about 2.2 MB. Should we keep this default, or require ICCProfile for PDF/X-4?
  • In strict mode, PDF/X-1a documents that contain links, active forms or JavaScript now throw, and in auto mode those items are removed. This is right by the standard, but it changes behaviour for existing PDF/X-1a users.
  • PDF/X also expects a document title (dc:title). mPDF does not enforce this, for either version.

🤖 Generated with Claude Code

jakejackson1 and others added 3 commits September 22, 2026 18:13
…colour fonts PDF/X-1a strips

PDFX now takes '4' for PDF/X-4 (ISO 15930-7) beside true or '1a' for
PDF/X-1a:2003, which is unchanged apart from what PDF/X forbids of every
version: JavaScript, active form fields, and annotations within the
BleedBox or TrimBox are now removed, and images are no longer
interpolated.

PDF/X-4 is written as PDF 1.6 with the pdfxid metadata, and embeds its
output intent's profile: the SWOP2006 Coated #3 profile from the ICC
registry where ICCProfile names none. It keeps opacity, watermarks,
colours with transparency, PNG alpha and layers. Colour fonts are drawn
in colour, and pages are blended in the output intent's colour space.
DeviceRGB is permitted only against an RGB output intent, so otherwise
RGB images and colour glyphs are written in an ICC-based sRGB colour
space, while RGB colours are converted to CMYK as for PDF/X-1a.

Ported from jakejackson1#7.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
isPdfx1a() replaces the repeated "PDF/X but not PDF/X-4" test, the
colour restrictors hand an alpha colour under PDF/X-4 to the plain one,
and PdfX4Test reads objects through PageStreams.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant