A gray stop rewritten into a CMYK space took K as 1 - gray in f32, so gray 0.96 wrote K 0.04000002 and the 8-bit gray 128 wrote 0.4980392 rather than 127/255. K is now subtracted from the shortest decimal that writes the gray, and an 8-bit gray n gives (255 - n) / 255. Patch 17, SOURCE.json and the ledger are updated.
28 KiB
Local Krilla 0.8.2 fork
This is the crates.io Krilla 0.8.2 package (MIT OR Apache-2.0), upstream commit
3ffdf0588cf98050aad6edba51ca70162e1fb5b5, with the patches listed below. Registry
bookkeeping files, Cargo.lock and the redundant original manifest are omitted. Upstream
MIT/Apache license texts are retained under ../../licenses/pdf-writer; the registry package
itself omits those texts. Its examples and four CC0 ICC profiles are retained without changes.
Patch ledger
| File | Patch | Touches |
|---|---|---|
patches/01-unwind-safe-drop.patch |
Unwind-safe drop | document.rs, error.rs, lib.rs, page.rs, serialize.rs, surface.rs |
patches/02-group-color-space.patch |
Blending color space | error.rs, graphics/{graphics_state,image,mask,tiling_pattern,xobject}.rs, lib.rs, page.rs, pdf.rs, resource.rs, serialize.rs, text/type3.rs |
patches/03-overprint.patch |
Overprint | configure/{PDF_A2.md,PDF_A4.md,validate.rs}, content.rs, graphics/{blend,graphics_state}.rs, surface.rs |
patches/04-knockout.patch |
Knockout groups | content.rs, graphics/{blend,xobject}.rs, surface.rs |
patches/05-soft-mask-backdrop.patch |
Soft-mask backdrop and transfer function | configure/{PDF_A2,PDF_A4}.md, graphics/mask.rs |
patches/06-device-n.patch |
DeviceN colors | configure/{PDF_A1.md,PDF_A2.md,PDF_A4.md,validate.rs}, graphics/{color,device_n,mod,paint}.rs, serialize.rs |
patches/07-device-n-images-shadings.patch |
DeviceN images and shadings | graphics/{image,paint,shading_function}.rs |
patches/08-print-output-intent.patch |
Print output intent and PDF/X markers | chunk_container.rs, configure/{PDF_A1.md,PDF_A2.md,PDF_A4.md,validate.rs}, interchange/metadata.rs, lib.rs, serialize.rs |
patches/09-rendition-class.patch |
Master rendition in XMP | chunk_container.rs |
patches/10-mesh-gradients.patch |
Mesh gradients | content.rs, error.rs, graphics/{mask,paint,shading_function}.rs, serialize.rs, surface.rs |
patches/11-png-decode-cap.patch |
PNG decode cap | graphics/image.rs |
patches/12-optional-content.patch |
Optional content | chunk_container.rs, configure/{PDF_A1,PDF_A2,PDF_A4,PDF_UA1}.md, configure/validate.rs, content.rs, error.rs, graphics/{mod,optional_content}.rs, lib.rs, resource.rs, serialize.rs, surface.rs |
patches/13-annotation-bounds.patch |
Annotation and destination bounds | error.rs, interactive/{annotation,destination}.rs, page.rs, serialize.rs |
patches/14-devicen-pdf-version.patch |
DeviceN attributes by PDF version | graphics/device_n.rs |
patches/15-pdfx-conformance.patch |
PDF/X conformance key | serialize.rs |
patches/16-modification-date.patch |
Modification date | interchange/metadata.rs |
patches/17-color-precision.patch |
Color precision | graphics/{color,shading_function}.rs |
Each patch applies with patch -p1 from this directory, in numeric order, to the pristine
crate. SOURCE.json records, for every retained file, the upstream hash (upstream_sha256)
and the hash after all patches (sha256), plus the hash of the .crate download
(crate_sha256).
Every change to src/ updates its patch file, this ledger and SOURCE.json in the same
commit. To take a new Krilla release: unpack the crates.io package, apply the patches in
order, drop the ones the release makes unnecessary, regenerate SOURCE.json, and rerun the
craft-pdf and plate-test suites.
01: unwind-safe drop
The patch changes two destructors: Surface::drop and Page::drop return early
when std::thread::panicking() is true. Drawing can panic with an unfinished
transform, clip, isolated group or tagged section. Finalizing that state during
unwind can panic a second time and abort before an outer catch can recover.
Discarding the unfinished state permits native unwind recovery. Cleanup marks the
serialization context poisoned until it completes successfully, preserving any prior
poison. Document::finish returns KrillaError::Poisoned after failed cleanup, including
explicit surface finalization, even if a caller retains the writer after a narrow
catch. A caller must still discard other captured app state after a panic. Normal
finalization, including checks for unbalanced state, remains unchanged.
Public marker constants, CRAFT_UNWIND_SAFE_DROP and CRAFT_UNWIND_POISONING, let craft-pdf reject an accidentally
unpatched dependency at compile time. Consumers must copy the Krilla root patch
from the craft-pdf README because Cargo ignores dependency workspaces' patches.
The shared writer's native subprocess regression covers the previously aborting cleanup and a successful fresh export afterward. Native and WASM export tests cover the unchanged successful path. WASM aborts, aborting panic configurations and process-wide allocation failures remain outside unwind recovery.
02: blending color space
Upstream gives every transparency group an RGB /CS and writes no page group, so
transparency over CMYK content blends in RGB and comes back with all four process inks
(K50 over C100 separates as roughly C84 M34 Y33 K37 instead of C50 K50).
SerializeSettings::group_color_space chooses the blending space. GroupColorSpace::Rgb,
the default, writes the same groups and page dictionaries as upstream. With
GroupColorSpace::Cmyk:
- Content groups (
draw_masked,draw_opacified,draw_isolated,Graphic) write no/CSand inherit the page group. - A page that uses transparency gets
/Group <</S/Transparency/CS …>>: DeviceCMYK, or thecmyk_profileICC space whenno_device_csis set. Opaque pages get no page group, so they stay valid for PDF/X-1a and X-3. - Soft-mask source groups have no parent, so they name their own space: DeviceCMYK, or
sGrey when
no_device_csis set. Through a CMYK profile K 100% is not black, so an ICC CMYK mask group would give the default black backdrop a luminosity above 0, and the masked object would show where the mask content paints nothing. The XObjectReprcarries aGroupRole(content or mask source) for this. - Pages placed as XObjects (
draw_pdf_page) get an isolated group without/CS; the RGB color space is not registered. The source page's own group is dropped, as before; krilla #410 (unreleased, needs hayro-write 0.8) keeps it. no_device_cswithout acmyk_profilefails atDocument::finishwithKrillaError::MissingGroupCmykProfile.
Which pages blend is tracked by object reference, because cached objects are serialized once: transparent graphics states (alpha below 1, a blend mode other than Normal, a soft mask), group XObjects, images with alpha and embedded pages are recorded when written, and so are forms, tiling patterns and Type 3 fonts whose resources include one of these. A page blends when its resources name a recorded object.
Tests: crates/pdf/tests/print_color.rs, and the plate-test scenes S06, S07, S08, S10,
S15 and D04.
03: overprint
Upstream writes no overprint entries, so separated output knocks out every plate under an object; a [Black] rect over C100 prints K100 instead of C100 K100.
Surface::push_overprint(Overprint { fill, stroke, nonzero }) pushes overprint settings
like push_blend_mode: no group, popped with Surface::pop. Paths, text and images drawn
in between get an ExtGState with /op (fill), /OP (stroke) and /OPM (1 when nonzero,
otherwise 0). All three entries are always written, so an inner push replaces an outer one
completely and a RIP never inherits OPM 1 from an enclosing state. Groups pushed inside
inherit the settings through the Do that paints them. Overprint is not a field of Fill
or Stroke, because apps build those as struct literals.
The ExtGState Repr carries overprint_fill, overprint_stroke and overprint_mode;
empty, combine and serialize handle them like the other entries. Overprint is
not transparency: it marks no page as blending.
Validation: ISO 19005-2 and 19005-4 (6.2.4.2) forbid OPM 1 when an ICCBased CMYK color
space is used for a side that overprints. Validated PDF/A export writes all CMYK as
ICCBased, and the graphics state doesn't know the colors painted under it, so OPM 1 with
/op or /OP true under no_device_cs registers ValidationError::OverprintModeIccCmyk,
which PDF/A-2, A-3 and A-4 prohibit. PDF/A-1 and PDF/UA allow it. ISO 32000-1 (8.6.7)
applies OPM 1 to DeviceCMYK only, so under no_device_cs it has no effect in a PDF 1.7
reader; Overprint::nonzero says so.
Tests: crates/pdf/tests/print_color.rs, and the plate-test scenes S01, S03, S04, S12,
S16, D06 and D10.
04: knockout groups
Upstream can only push isolated groups, so objects overlapping inside a group always composite with each other; a knockout group of a 50% cyan and a 50% magenta circle over yellow separates as C25 M50 Y25 where they overlap instead of M50 Y50.
Surface::push_group(GroupOptions { isolated, knockout }) pushes a group with /I and
/K set independently; push_isolated is shorthand for isolated alone. A knockout
group is a transparency group even when it is not isolated, so it gets patch 02's /CS
rules: krilla's RGB /CS under GroupColorSpace::Rgb, none under Cmyk, and it marks
its page as blending. A group with neither attribute is a plain form XObject, as before.
The XObject Repr carries knockout; XObject::group builds a content group from
GroupOptions.
Tests: crates/pdf/tests/print_color.rs, and the plate-test scene S09.
05: soft-mask backdrop and transfer function
Upstream writes soft masks with /S and /G only. A luminosity mask then has the default
black backdrop, so the masked object is hidden wherever the mask content paints nothing, and
there is no transfer curve.
Mask::with_backdrop(color)writes/BCon luminosity masks, in the mask group's color space: RGB underGroupColorSpace::Rgb(DeviceRGB or sRGB); underGroupColorSpace::Cmyk, four components (DeviceCMYK), or one (sGrey) withno_device_cs. Colors from another space are converted with the device formulas; luma becomes equal RGB components or black ink only. On an alpha mask, where/BChas no meaning, the call returns the mask unchanged.Mask::with_transfer(TransferFunction)writes/TR:/Identity, or a Type 0 sampled function (domain and range 0 to 1, 16-bit samples, linear interpolation) forTransferFunction::Sampled. An empty curve is written as/Identity; one sample is repeated to span the domain.
Standards: PDF/A-2 and PDF/A-4 6.2.5 forbid TR in graphics states (veraPDF rules test
PDExtGState only); nothing restricts the soft-mask dictionary's /TR, so no validation
error is added and the PDF/A notes say so. PDF/X (ISO 15930-7) forbids graphics-state transfer
functions; whether it or a PDF/X-4 preflight also flags a soft-mask /TR could not be checked
against the standard's text.
Tests: crates/pdf/tests/print_color.rs, and the plate-test scenes S11, M1, M2 and D08.
06: DeviceN colors
Upstream has only Separation as a special color space, so a color that sets several inks at once (C60 K40 without touching M and Y, or a process ink with a spot) has to be written as CMYK, which knocks out or replaces the other plates.
color::device_n adds SpecialColor::DeviceN:
DeviceNSpace::new(colorants)takes an ordered list ofColorant::Process(Process)andColorant::Spot { name, alternate: cmyk::Color }. It returnsNonefor an empty list, more than 32 colorants, a repeated name, or a spot namedAll,Noneor like a process ink.device_n::Color::new(tints, space)takes one tint per colorant.- The space is written as
[/DeviceN [names] alternate tint attributes]and cached like a Separation space. The alternate is DeviceCMYK, or thecmyk_profileICC space withno_device_cs. - The tint transform is a Type 4 (PostScript calculator) function. Each colorant
contributes its tint times its CMYK alternate (process inks: 1 on their own component);
contributions combine as
1 - (1 - a)(1 - b)…, so process tints pass through unchanged. It registersValidationError::ContainsPostScript, which krilla's PDF/A tables forbid for PDF/A-1, -2 and -3. - From PDF 1.6, a space with spots gets an attributes dictionary whose
/Colorantsmaps each spot to its Separation space. When spots and process inks mix, it is/Subtype /NChannelwith/Process << /ColorSpace alternate /Components [/Cyan /Magenta /Yellow /Black] >>. Patch 14 changes which of these each PDF version gets. - Validation: a spot's CMYK alternate counts as its Separation fallback, so one colorant
name keeps one alternate across Separation and DeviceN spaces
(
ValidationError::InconsistentSeparationFallback)./Alland/Noneare Separation-only and checked as before. The PDF/A ledgers state that DeviceN fails export under PDF/A-1, -2 and -3 and is written under PDF/A-4. Color::to_regular(annotations, tags) gives the alternate CMYK;Paint::as_rgbgivesNone, as for CMYK.
Images and shadings use the same DeviceNSpace (patch 07).
Tests: crates/pdf/tests/print_color.rs (device_n_*), and the plate-test scene N1.
07: DeviceN images and shadings
Upstream images are RGB, gray or CMYK, so a duotone or a spot-channel image can't name its inks. Gradient stops must share one color space: a stop that mixes a spot with process inks, or two spots, has no space to go in, and stops from different spaces fail an assertion while writing.
ImageColorspace::DeviceNforCustomImage, with the space from the newCustomImage::device_n_space(defaultNone): one sample per colorant, in the space's colorant order, 0 = no ink.Image::from_customrefuses a DeviceN image without a space. The image's/ColorSpaceis the cached DeviceN space from patch 06; an ICC profile is ignored.ImageColorspacestaysCopy, so the new variant breaks only exhaustive matches.- Gradient stops (linear, radial, sweep) in different ink-based spaces are rewritten into
one space when the shading function is created. Ink-based: CMYK, gray (as black), a
Separation with a named colorant, DeviceN. A process colorant takes part when a stop has
it at a nonzero tint or a DeviceN stop names it; a spot takes part when a stop names it.
- One spot and no process colorant: a Separation shading in the spot's own space. A spot to paper (CMYK 0 0 0 0, white, or tint 0) stays a Separation shading.
- Process colorants only (gray to CMYK, for example): DeviceCMYK.
- Anything else: a DeviceN shading, process colorants in CMYK order, then spots in the order the stops name them; the function has one output per colorant. A spot whose fallback isn't CMYK can't join a DeviceN space.
- Stops that already share a space, and mixes with RGB,
AllorNone, are written as upstream does.
- PostScript (sweep) gradient functions get one
/Rangepair per output component; upstream always wrote three.
The conversion lives in shading_function::common_color_space, which takes a list of
colors, so other shading types (such as mesh gradients) can call it for their colors.
Tests: crates/pdf/tests/print_color.rs (device_n_image_*, device_n_shading_*), and
the plate-test scene S14.
08: print output intent and PDF/X markers
Upstream writes an output intent only for PDF/A, always sRGB, and has no /Trapped or PDF/X
version. DesignCraft and VectorCraft added these by rewriting the serialized bytes.
SerializeSettings::output_intent: Option<OutputIntent>writes an/OutputIntentsentry with/S /GTS_PDFX: the CMYK profile as/DestOutputProfile,/OutputConditionIdentifier, and optional/OutputCondition,/RegistryNameand/Info. With a PDF/A validator, theGTS_PDFA1entry names the same profile object and condition instead of sRGB, because PDF/A requires every output intent to share one destination profile.SerializeSettings::trapped: Option<Trapped>writes/Trappedto the document information dictionary andpdf:Trappedto XMP,Unknownincluded (the Adobe PDF schema allows it, and PDF/A-1 requires the two to match).SerializeSettings::pdfx_version: Option<String>writes/GTS_PDFXVersionto the dictionary andpdfxid:GTS_PDFXVersionto XMP. PDF/A-1, -2 and -3 require an extension schema for the XMP property, which this patch does not write, so the setting registersValidationError::PdfxVersionWithoutExtensionSchema, which they prohibit. PDF/A-4 has no extension schema requirement and allows it.- The dictionary entries follow the dictionary's PDF 2.0 deprecation: from PDF 2.0 only XMP
carries them. The dictionary is written without
Metadatawhen it has print markers. - All three default to
None, which keeps upstream's output. - The PDF/A ledgers state the two output intents and the CMYK destination profile; krilla checks only that the profile has four components.
krilla does not check PDF/X conformance; callers do. CRAFT_PRINT_COLOR marks the fork's
print color patches (02 to 08) so craft-pdf fails to build against an unpatched Krilla.
Tests: crates/pdf/tests/print_color.rs and the plate-test print preset over every scene.
09: master rendition in XMP
From storytold/craft-libs#183 by Hamza (@Hamza0101). Upstream writes
xmpMM:RenditionClass as proof on every document, which tells a prepress system that a
print deliverable is a proof copy. XMP Part 2 (Media Management) defines default as the
master rendition, so the patch writes default. Every document's XMP differs from
upstream's output in this value, whatever the other settings.
10: mesh gradients
From storytold/craft-libs#184 by @fastmikey99.
Krilla 0.8.2 paints only flat color, linear, radial and sweep gradients and tiling patterns, so
an app writing a gradient mesh had to cut each patch into flat-color pieces: a VectorCraft
document holding an Illustrator file's 22,850 mesh patches became a 197 MB PDF with stepped
blends. The patch adds paint::MeshGradient (and MeshPatch): Coons patches with a color and
an opacity at each corner, written as one PDF Type 6 shading (ISO 32000-1, 8.7.4.5.7) through
pdf-writer's StreamShading, so the viewer or RIP blends them at its own resolution. Each patch
is an 8-bit flag (no shared edge), its twelve points as 32-bit coordinates over the mesh's
bounds (Decode) and its four corner colors at 8 bits a component, Flate-compressed. It goes
through the same path as the other gradients (GradientProperties::MeshGradient): a shading
pattern for the fill, and for corner opacities below 1 a luminosity soft mask holding the same
mesh in gray (use_opacities), as cairo and Illustrator write transparent meshes.
The corner colors of every patch go through common_color_space from patch 07, the rule
gradient stops follow, before the shading is written: process colors alone become
DeviceCMYK, one spot alone a Separation space, and any other mix of inks a DeviceN space with
the process colorants in CMYK order, then the spots. A mesh mixing CMYK and a spot is a
DeviceN mesh with one component per colorant at each corner. Colors the rule can't bring into
one space (RGB with CMYK, All or None with anything else) would be written with another
space's component count, so the document fails with KrillaError::MixedMeshColorSpaces
instead. The soft mask is written in gray from the opacities and is unaffected.
Tests: crates/pdf/tests/print_color.rs (mesh_*, rgb_mesh_*) and the plate-test scene S17.
11: PNG decode cap
A PNG whose header implies more than 1 GiB of decoded output is refused by
Image::from_png and by the deferred decoder, and the decoded buffer is reserved
fallibly, so a small file (or a bitmap glyph in a font) cannot request an allocation that
ends the process. The color and alpha buffers that PNG, GIF and WebP decoding split an
image into are reserved fallibly too, and a failure there is an image error.
Image::from_custom computes the expected channel lengths with checked usize arithmetic and
returns an error when they overflow or don't match the data, where upstream multiplied in
u32 and asserted while writing (32 DeviceN components at 16 bits overflow at about 67
megapixels).
12: optional content
Krilla 0.8.2 can't write optional content groups (layers), and it has no hook for raw content
operators or extra catalog and resource entries, so CADCraft's drawing layers can't be added
from outside. The new module graphics/optional_content.rs holds the feature; the other files
get small hooks.
Document::add_optional_content_group(name, visible) -> OcgIdwrites<< /Type /OCG /Name (…) >>once (a text string, so any Unicode name works).Surface::start_optional_content(OcgId)andSurface::end_optional_content(), both returningKrillaResult<()>, write/OC /ocN BDC…EMC, with/ocNin the current stream's/Propertiesresources (a new resource kind beside/ExtGState). Inside a group, opacity or mask, the entry goes in that form's resources.- Marks nest, and nest independently of tagging, whose single marked-content level is
unchanged. An end with nothing open, or one that would close a tagged section started inside
the mark, returns
KrillaError::UnbalancedOptionalContentand writes nothing. AnOcgIdindex the document doesn't have returnsKrillaError::UnknownOptionalContentGroup. Marks still open when their stream finishes (surface finish, or a pop of a group, opacity or mask), and a tagged section ended while a mark started inside it is open, are closed withEMCand makeDocument::finishfail withKrillaError::UnbalancedOptionalContent. Nothing panics. - When any group exists, the catalog gets
/OCProperties << /OCGs [all] /D << /Name (Layers) /Order [all] /OFF [hidden] >> >>, listing groups that no page uses too;/OFFis omitted when every group is shown.Document::set_optional_content_page_mode(true)adds/PageMode /UseOC, written only when groups exist. - Validation: adding a group registers
ValidationError::OptionalContent, which PDF/A-1 prohibits (6.1.13), and below PDF 1.5 alsoValidationError::RequiresNewerPdfVersion(VersionedFeature::OptionalContent), which PDF/UA-1 prohibits. Without a validator a PDF 1.4 document still gets the groups, as with krilla's other versioned features. The PDF/A-2, A-4 and UA-1 ledgers record that the one configuration has aName, anOrderlisting every group and noAS.
CRAFT_OPTIONAL_CONTENT marks the patch; craft-pdf requires it at compile time.
Tests: crates/pdf/tests/optional_content.rs.
13: annotation and destination bounds
Upstream panics on link geometry that has no finite place on the page, which aborts a WASM
app. Document::finish returns an error instead and writes nothing for the object:
| Input | Upstream | Error |
|---|---|---|
LinkAnnotation::new_with_quad_points with no quads, a non-finite point, or a bounding box whose width or height overflows |
assert! or unwrap |
InvalidAnnotationGeometry(location) |
A link rect or quad bounding box that overflows when flipped into PDF coordinates (page_height - y) |
unwrap |
InvalidAnnotationGeometry(location) |
| A media, crop, bleed, trim or art box that overflows the same way | unwrap |
InvalidPageBox(page_index) |
An XyzDestination page index past the last page |
panic! |
InvalidDestination(page_index) |
An XyzDestination point that maps to a non-finite coordinate |
writes inf or NaN |
InvalidDestination(page_index) |
The constructors keep their signatures: LinkAnnotation holds an Option<Rect> and the check
runs at serialization, before the annotation object starts. Refusing a single annotation
instead of skipping it keeps the indices of tagged annotations in the structure tree valid.
Tests: crates/pdf/tests/link_annotations.rs.
14: DeviceN attributes by PDF version
Patch 06 writes the DeviceN attributes dictionary only from PDF 1.6. A PDF/X-1a file is PDF
1.3 or 1.4, so its spots then had no /Colorants, and a file written at 1.6 or later and
relabeled 1.4 kept /Subtype /NChannel and /Process, which preflight tools flag in a 1.4
file.
ISO 32000-1, 8.6.6.5 (Table 71) marks the attributes dictionary and its /Colorants entry
PDF 1.3, and /Subtype, /Process and /MixingHints PDF 1.6; the PDF Reference for 1.3 and
1.4 lists /Colorants as the dictionary's only entry. The spec was checked from memory, not
against the text.
- Every version krilla writes (1.4 and later): a space with spots gets an attributes
dictionary with
/Colorants, each spot mapped to its Separation space. - From PDF 1.6, a space that mixes spots and process inks also gets
/Subtype /NChanneland/Process, as patch 06 writes them. - A space of process inks only gets no attributes, as before.
Tests: crates/pdf/tests/print_color.rs (device_n_attributes_follow_the_pdf_version) and
crates/prepress/tests/flatten_document.rs (pdf_x1a_output_is_pdf_14_without_nchannel).
15: PDF/X conformance key
Patch 08 writes only GTS_PDFXVersion. PDF/X-1a and X-3 also need GTS_PDFXConformance in
the document information dictionary: a PDF/X-1a:2003 file carries GTS_PDFXVersion (PDF/X-1:2003) and GTS_PDFXConformance (PDF/X-1a:2003) (ISO 15930-4). Later parts (X-4
and on) have no conformance key.
SerializeSettings::pdfx_conformance: Option<String>writes/GTS_PDFXConformanceto the dictionary, under the same PDF 2.0 rule as patch 08's entries. It defaults toNone.- There is no XMP property for it: the
pdfxidschema (ISO 15930-7) defines onlyGTS_PDFXVersion. A PDF 2.0 file therefore omits the key.
Tests: crates/pdf/tests/print_color.rs (trapped_and_pdfx_version_are_written_to_info_and_xmp)
and crates/prepress/tests/flatten_document.rs (pdf_x1a_output_is_pdf_14_without_nchannel).
16: modification date
Upstream has only a creation date and writes it as the modification date too (/ModDate and
xmp:ModifyDate), so a document rewritten from another, such as a flattened copy, can't keep
its source's modification date.
Metadata::modification_date(DateTime)sets the date written as/ModDateandxmp:ModifyDate. Without it, the creation date is written there, as upstream does.- The XMP history events keep the creation date.
Tests: crates/pdf/tests/writing.rs (writes_pages_metadata_and_selected_version_without_a_renderer,
the_creation_date_is_the_modification_date_when_none_is_given) and
crates/prepress/tests/flatten_document.rs
(a_document_flattens_page_by_page_and_keeps_boxes_metadata_and_shared_fonts).
17: color precision
Upstream stores gray, CMYK, Separation and DeviceN components as u8, so every ink is written
as a multiple of 1/255: C75 becomes 0.7490196 and C4 M4 Y4 K4 becomes 0.039215688.
cmyk::Color::new_f32,luma::Color::new_f32,separation::Color::new_f32anddevice_n::Color::new_f32take components from 0 to 1 and write them as given. Values outside 0 to 1 are clamped and NaN is 0.- The
u8constructors keep their signatures and writevalue / 255, as upstream does. - The colors store each component as an
f32whose bitsEqandHashcompare; -0 is stored as 0, so equal components compare equal.RegularColor::as_rgbrounds gray to the nearest 8-bit value. - Gradient stops that are rewritten into one DeviceCMYK, Separation or DeviceN space keep their components, and a DeviceN color's CMYK alternate (annotations, tags) is no longer rounded.
- A gray stop rewritten into a CMYK space becomes the black ink
1 - gray, subtracted from the shortest decimal that writes the gray, so gray 0.96 is K 0.04. An 8-bit graynis K(255 - n) / 255, ascmyk::Color::newwrites it. - Mesh gradients write their corner colors and opacities at 16 bits a component instead of 8.
Tests: crates/pdf/tests/print_color.rs (f32_colors_*, gray_stops_keep_their_exact_black_in_cmyk_shadings,
mesh_*, rgb_mesh_is_a_device_rgb_shading).