Commit Graph
462 Commits
Author SHA1 Message Date
BFlat 94432bf2d0 Merge pull request #7729 from dotCooCoo/fix/pdf-image-profile-to-destination
PDF export: CMYK and other RGB destinations convert tagged images from their profile
2026-11-15 11:36:32 +07:00
dotCooCoo 1a4dd4f800 PDF export: limit the own-profile rule to destinations other than sRGB
An sRGB destination converts tagged gray and RGB images to sRGB even when the
document's own RGB profile is sRGB, so the statement that the document's own
profile as destination keeps tagged images applies only to other RGB profiles.
2026-11-14 18:39:38 -08:00
dotCooCoo f55e42343e PDF export: state that the document's own RGB profile as destination keeps tagged images
With Convert to Destination and an RGB destination that is the document's
own RGB profile, RGB colors and RGB images keep their numbers, so an image
that carries an ICC profile keeps it. The export contract and progress line
now say so, and the test checks it.
2026-11-14 18:19:33 -08:00
dotCooCoo 3cd57bca02 Merge feat/pdf-srgb-destination-converts-images into fix/pdf-image-profile-to-destination
# Conflicts:
#	crates/pdf/src/export/images.rs
#	crates/pdf/src/images.rs
#	docs/mcp.md
#	docs/target-app-parity.md
2026-11-14 18:11:47 -08:00
dotCooCoo 18e5e393d2 PDF export: warn when a tagged image can't be decoded for sRGB conversion
With Convert to Destination and an sRGB destination, a gray or RGB image
with an ICC profile whose pixels can't be decoded (a header past the
decoding allowance) was kept with its profile and no warning. It now
reports that the image is not converted. When the image is left out of
the file as well, only the warning that it is missing is given.
2026-11-14 17:57:51 -08:00
dotCooCoo 5956076b9f PDF export: CMYK and other RGB destinations convert tagged images from their profile
With Output › Convert to Destination and a CMYK destination, or an RGB
destination other than sRGB that images are converted to, an RGB image
that carries an ICC profile had its numbers read as sRGB, so a Display
P3 image came out with the wrong colors. Such an image is now converted
from its profile to sRGB with the color settings' rendering intent, then
to the destination.

- An untagged image converts from sRGB as before.
- A gray image keeps its profile, as gray is not converted.
- A profile the color engine can't read leaves the numbers read as sRGB,
  with a warning.
- Preserve Numbers is unchanged.

docs/mcp.md, the document.exportPdf params text and the progress doc
describe the behavior.
2026-11-14 17:44:50 -08:00
dotCooCoo 7d205368e8 PDF export: Convert to Destination converts tagged images to sRGB
With Output > Convert to Destination and an sRGB destination, a gray or
RGB image that carries an ICC profile (a Display P3 TIFF, a PNG or JPEG
with an embedded profile) is converted from that profile to sRGB with
the color settings' rendering intent and written without the profile.
Before, it kept its own numbers and profile.

- A 16-bit image that is not resampled or recompressed stays at 16 bits
  a sample from PDF 1.5 on.
- A JPEG kept as JPEG whose samples the conversion leaves unchanged (an
  sRGB profile) keeps its JPEG data; other JPEGs are encoded again.
- A black-and-white image whose converted levels are gray is compressed
  as grayscale.
- A profile the color engine can't read is kept, with a warning.
- Without conversion, and with Preserve Numbers, images keep their
  profile as before.

IccProfile::to_srgb_pixels converts a run of pixels in one pass.
Docs: docs/mcp.md, document.exportPdf params, target-app-parity.
2026-11-14 16:59:18 -08:00
dotCooCoo 094971e3ac Type on a path: type on an open path can start before the path
Type on an open path can now start before the path's start: its start
is stored below 0, the span runs from there, and the characters whose
middle falls before the path's start are not drawn, as SVG 2 lays out
text on a path (https://www.w3.org/TR/SVG2/text.html#TextLayoutPath).
The text keeps every character, and the start bracket stays at the
path's start. Past the path's end, text that does not fit is overflow,
as for other type on a path. Type that starts on the path draws every
glyph as before.

- SVG import keeps a <textPath> start before an open path's start
  (text-anchor end or middle, or a negative startOffset) instead of
  starting the text at the path's start, which drew every character.
- SVG export writes it as a negative startOffset.
- PDF export writes only the characters drawn on the path.
- The legacy .ai and .ai editing data writers write the type from the
  path's start with the type attributes warning.
- type.pathOptions takes a start below 0 for type on an open path; on a
  closed path it is the path's start, as before. A flip starts the type
  at its start bracket. Dragging the center bracket slides it from where
  it starts, and no further before the path.
- A click at the path's start places the caret by the first drawn
  character, and the caret in the hidden characters stands at the
  path's start. The highlight of substituted fonts and glyphs leaves
  them out.
- Release Type on a Path keeps the first drawn glyph where it was
  drawn, with the hidden characters before it.
- The Touch Type tool shows no box for a hidden character, and
  text.touchType returns an error for one. A character with a drawn
  glyph is changed by that glyph.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-14 15:44:12 -08:00
BFlat 4fffbce639 Merge pull request #7614 from dotCooCoo/fix/export-cull-brushed-path-art
PDF, EPS and print: a brushed path just off the page whose brush paints onto it is written
2026-11-15 06:17:15 +07:00
BFlat 03cca98963 Merge pull request #7673 from dotCooCoo/fix/pdf-layers-long-page-symbol-art
Export: hidden PDF layers place symbol art effects when a long page raises the version
2026-11-15 06:16:33 +07:00
dotCooCoo 9fb5abfbc6 Export: hidden PDF layers place symbol art effects when a long page raises the version
A PDF/A-2b export at PDF 1.4 with Create PDF Layers and a page longer
than 14,400 pt is written as PDF 1.6, which has PDF layers. The export
decided which layers place their instances' symbol art before it raised
the version, so a hidden layer, written as a PDF layer that is off, kept
its instances' art without the art's live effects.

The version a long page needs is now settled before the document is
baked. The pages come from the artboards, which baking leaves as they
are.
2026-11-14 13:28:34 -08:00
dotCooCoo 5afe6b3b73 PDF export: a PNG or JPEG profile cut short is left out too
The check before a PNG or JPEG is embedded as it is read the profile's
color space only from a profile of at least 128 bytes, so a gray or RGB
profile cut short after its color space reached the PDF writer. The
color space is now read from bytes 16..20 of any profile that holds
them, and valid_icc rejects the short profile.
2026-11-14 13:12:25 -08:00
dotCooCoo 8e6c948010 PDF export: a JPEG written without its profile keeps its JPEG data
In PDF/X-1a, a tagged gray JPEG that the Compression settings keep as
JPEG was decoded and written with Flate. It is now written with its
JPEG data unchanged, in DeviceGray without its profile.

A PNG or JPEG embedded as it is now has its gray or RGB profile checked
with valid_icc, like the images the recoder writes: a malformed profile
is left out, the JPEG keeping its JPEG data and the PNG decoded.
2026-11-14 12:56:47 -08:00
dotCooCoo af47bc5c3b PDF export: a malformed gray or RGB profile is left out of an image
A gray or RGB image is tagged with the ICC profile it carries only when
the profile passes the checks a CMYK image's profile already passes: at
most 16 MiB, the size its header declares, the acsp signature, an input,
display, output or color space profile class, and a tag table that lies
within it. Otherwise the image is written in DeviceGray or DeviceRGB, kept
as it is, resampled or compressed with JPEG.
2026-11-14 12:33:08 -08:00
dotCooCoo 4ee24a8bbe Merge main into fix/export-cull-brushed-path-art 2026-11-14 12:29:41 -08:00
dotCooCoo aa3e74384b PDF: PDF/X-1a test covers a tagged 16-bit gray TIFF
The PDF/X-1a case of tagged_tiffs_keep_their_icc_profile also exports a
16-bit gray TIFF with a gray profile, kept as it is, resampled and
compressed with JPEG, and checks each written file with the PDF/X-1a
check as well as its image color space.
2026-11-14 12:11:04 -08:00
dotCooCoo 9317ee0653 PDF: 16-bit TIFF test checks the written samples
The tagged TIFF test now compares the decoded image data with the source
for a 16-bit RGB TIFF whose high and low sample bytes differ: big-endian
16-bit samples at PDF 1.7, and the samples reduced to 8 bits at PDF 1.4.
A byte-swapped or truncated stream no longer passes.
2026-11-14 11:43:26 -08:00
bflatastic 79572cbf6c Revert "Cut over roxmltree, png, tiff, psd, and trace codecs to craft-libs (#7638)"
This reverts commit bada8e1710. #7638 merged with its gate failing: besides six unformatted
files, render and trace don't compile against craft-libs e2dc681 (u64::from(usize) in
render/src/paint.rs, a private craft-libs function, and a call with one argument too many in
trace/src/opaque.rs), so main's CI fails. The tree is that of 0adbdce6cb, whose CI passed.
2026-11-15 02:26:07 +07:00
Dylan M. Taylor bada8e1710 Cut over roxmltree, png, tiff, psd, and trace codecs to craft-libs (#7638) 2026-11-14 13:49:50 -05:00
dotCooCoo 6b9564c60d PDF: PDF/X-1a test covers a tagged gray JPEG
The PDF/X-1a case of tagged_tiffs_keep_their_icc_profile also exports a
gray JPEG with a gray profile, kept as it is, resampled and compressed
with JPEG, and checks that it is written in DeviceGray without the
profile. The comments on decoded_image, recode and the test say that
PDF/X-1a allows no ICC-based color spaces.
2026-11-14 10:48:45 -08:00
dotCooCoo 588e8e0bb0 Merge main into fix/pdf-tiff-keeps-icc 2026-11-14 09:48:14 -08:00
BFlat e76f8d2c88 Merge pull request #7501 from dotCooCoo/fix/export-bakes-symbol-art-effects
Export: live effects in symbol art apply in PDF, EPS, print and DXF
2026-11-15 00:41:46 +07:00
BFlat 63129de08a Merge pull request #7595 from dotCooCoo/fix/image-scaling-alpha
Images: scaled-down images keep their color beside clear pixels
2026-11-15 00:41:27 +07:00
dotCooCoo f8c5d2b6c0 Merge main into fix/pdf-tiff-keeps-icc
# Conflicts:
#	crates/pdf/src/images.rs
2026-11-14 07:31:28 -08:00
dotCooCoo a40f00ece9 Merge main into fix/export-cull-brushed-path-art 2026-11-14 07:25:25 -08:00
BFlat b541d97a06 Merge pull request #7586 from dotCooCoo/fix/blend-spot-stop-plates
Blend: steps between gradients linked to the same spot swatch keep its process plates
2026-11-14 21:54:47 +07:00
BFlat 8863c41560 Merge pull request #7581 from dotCooCoo/fix/pdf-cmyk-image-keeps-icc
PDF export: CMYK images that keep their numbers carry their ICC profile
2026-11-14 21:53:33 +07:00
BFlat 508a0b0180 Merge pull request #7597 from dotCooCoo/fix/separations-spot-stop-plates
Print: a spot gradient stop prints its process plates on the separations
2026-11-14 21:51:47 +07:00
dotCooCoo e45c6f518e PDF export: PDF/X-1a writes tagged gray images in DeviceGray
PDF/X-1a allows device colors only, but a gray TIFF that carried a gray
ICC profile kept it when no color conversion applied, and the export
then failed its PDF/X check. A gray or RGB PNG or JPEG with a profile
went the same way through the PDF writer. In PDF/X-1a, images are now
written without the profile they carry; the other standards and plain
exports keep it.
2026-11-14 06:02:55 -08:00
dotCooCoo 80854eb4b8 Merge main into fix/export-bakes-symbol-art-effects 2026-11-14 06:00:09 -08:00
dotCooCoo e086cf6d83 PDF, EPS and print: brush art painting onto the page keeps its path 2026-11-14 05:51:22 -08:00
dotCooCoo 786dcc5581 Images: scaled-down images keep their color beside clear pixels
The image crate's filtered resize filters each channel on its own, which
is correct only for premultiplied pixels, and its thumbnail averages each
channel on its own too. Placed images, linked-image previews, MCP
captures and previews, metafile bitmaps, PDF downsampling and library
thumbnails passed straight RGBA to them, so the color under clear pixels
(usually black) bled into the opaque pixels beside them and darkened
cut-out edges.

vectorcraft_doc::resample scales such images with each pixel's color
weighted by its alpha: filtered resampling premultiplies the pixels in
place and divides them back after, and averaging takes the area-weighted
mean of the pixels each new one covers. Opaque and alpha-free images are
scaled as before. PDF export also multiplies the inks of a CMYK image
with alpha by that alpha before resampling them, and divides them by the
resampled alpha after.
2026-11-14 05:07:39 -08:00
dotCooCoo 6ab46b5fd8 Print: a spot gradient stop prints its process plates on the separations
A gradient stop linked to a spot ink that also carries process plates
(a DeviceN stop opened from a PDF) printed only its spot tint: print
separations and Separations Preview left its Cyan, Magenta, Yellow and
Black plates blank. They now print the stop's plates beside the tint.
With spots printed as process, each process plate prints the stop's
plate layered with the spot tint's process equivalent, as a separated
image's spot plates layer. Plates beside no spot link, or beside
Registration, still print nothing of their own.

The color visitor also skipped a freeform gradient's points, so print
plates kept their display colors and Document Color Mode left them
unconverted. The points are now visited as unlinked colors.
2026-11-14 04:46:10 -08:00
dotCooCoo 3a6b016253 Blend: steps between gradients linked to the same spot swatch keep its process plates
A blend between two gradients whose matching stops are tints of one spot
swatch kept the link and interpolated the tint, but every step's stop lost
the process inks (C, M, Y, K) printed with the spot ink, so PDF export wrote
the steps as the spot ink alone.

- Matching stops linked to one swatch interpolate each process ink with the
  tint; a stop without process inks counts as none of each, and the ends
  keep their own.
- Gradients with different stop counts keep the inks at every resampled
  stop, interpolated with the midpoint-adjusted fraction between two stops
  of one swatch.
- A solid blended with a gradient brings no process inks of its own: its
  stand-in gradient no longer copies the other gradient's.

Stops of different swatches, or linked and unlinked stops, still blend into
unlinked process colors without plates.
2026-11-14 04:25:55 -08:00
dotCooCoo efb31aa51a PDF and EPS export: gradients name only the spot inks of stops that color something
A gradient's Separation or DeviceN space, and EPS's %%DocumentCustomColors,
named the spot ink of every linked stop. A stop out of order whose
neighboring stretches have no width, a stop at a tint of 0 without process
plates, and in PDF a stop whose stretches are fully transparent print no
ink, but their spot inks were still declared, so separations got an empty
plate.

- vectorcraft-color: Gradient::drawn_stops (the rule Document Info uses,
  moved here) and Gradient::reached_stops (every stop taken as opaque).
- PDF: Separation and DeviceN shadings, the Perceptual spot shadings and
  the spot-image warning name only inks of drawn stops at a tint above 0
  or with process plates; other stops are written with no ink, so their
  process inks and RGB or Lab colors no longer shape the space either.
- EPS: the same with reached stops, since EPS writes stops opaque; a
  transparent spot stop keeps its ink.

Output is unchanged when every stop colors part of the gradient and every
spot stop has a tint above 0 or process plates.
2026-11-14 04:11:40 -08:00
dotCooCoo b3fd92ccdc PDF export: CMYK images that keep their numbers carry their ICC profile
A CMYK TIFF written again, a CMYK JPEG that the Compression settings
resample or compress again, and a CMYK image written with its mask as
its soft mask lost the CMYK profile the image embeds. Such an image is
now written in an ICCBased space holding that profile, byte for byte,
when output.profiles is all or taggedSource, and in PDF/A-2b, PDF/X-3
and PDF/X-4 unless profiles is destination. Pixels the Output settings
convert are never tagged with it.

A CMYK JPEG embedded as it is now follows the same rule instead of
always carrying its own ICC_PROFILE segments: with profiles none it is
DeviceCMYK, and PDF/X-1a export no longer refuses a CMYK JPEG that
carries a profile.

A profile tags an image only when it is at most 16 MiB and its declared
size, acsp signature, device class, CMYK color space and tag table are
well formed. Tagged CMYK documents blend transparency in the document's
CMYK profile space, not in an image's own; without that space they
blend in DeviceCMYK, or keep their RGB space in PDF/A.
2026-11-14 03:59:32 -08:00
dotCooCoo 134b8b33a1 Export: placed symbol art keeps its instance's link
An instance whose symbol art has live effects is replaced in the export by a group holding the baked art. That group now takes the instance's attributes as well, so a PDF export keeps the instance's URL as a link annotation over the art where its effects draw it.
2026-11-14 02:59:02 -08:00
dotCooCoo b59ec11b32 Export: symbol art placement skips instances the export leaves out
Hidden instances, instances in hidden groups, on template layers, and
on hidden layers or layers that do not print when the exporter leaves
those out are no longer placed for PDF, print, EPS and DXF. Their art
was baked and counted toward the 16,000,000-object limit for placed
symbol art, so it could leave a visible instance written without its
effects.

bake_document_placing_symbols takes a WrittenLayers value: PDF passes
the layers its settings write, EPS writes layers that do not print but
not hidden ones, and DXF writes both. Print removes the layers that do
not print before baking.
2026-11-14 02:45:49 -08:00
dotCooCoo 7e66a990bc PDF export: a stored gray or RGB image keeps its ICC profile
A gray or RGB TIFF that reached PDF export as a TIFF, for example in a
native document or from a script, was decoded to 8-bit RGBA and written
as DeviceRGB, which dropped the ICC profile of its tag 34675, its 16-bit
samples and its gray color space. It is now written in an ICCBased color
space holding that profile, as a tagged PNG is: gray stays gray, 16-bit
samples stay 16-bit from PDF 1.5 on, and the alpha becomes a soft mask
only when some pixel is not opaque.

A gray or RGB image that the Compression settings resample or compress
with JPEG keeps its source profile too, a PNG included. Pixels that the
Output settings convert are written without it, and so is a profile whose
color space differs from the samples'. CMYK images are unchanged.
2026-11-14 02:42:43 -08:00
dotCooCoo 5695823937 Export: placed symbol art keeps its instance's stain
When PDF, print, EPS and DXF export place a symbol's art to bake its
effects, the art now carries the stain the instance's fill gives it, as
it did before the art was placed. The art is stained first and its
effects apply to the stained colors.
2026-11-14 02:06:18 -08:00
dotCooCoo 79e719dcdd Merge main into fix/export-bakes-symbol-art-effects 2026-11-14 01:53:06 -08:00
BFlat ddfa381c91 Merge pull request #7273 from dotCooCoo/fix/knockout-groups-flat-masks
PDF/SVG export: knockout groups nest no deeper with more elements
2026-11-14 16:51:33 +07:00
dotCooCoo 4aba706e1a Merge main into fix/export-bakes-symbol-art-effects 2026-11-13 22:43:58 -08:00
BFlat 0ca2952093 Merge pull request #7363 from dotCooCoo/fix/stained-instance-export
Symbols: a stained instance keeps its stain in SVG, PDF and the other exports
2026-11-14 13:42:39 +07:00
dotCooCoo a98b800761 Export: live effects in symbol art apply in PDF, EPS, print and DXF
PDF, print, EPS and DXF wrote a symbol instance by placing its symbol's
art as stored, so live effects inside that art were missing: a square
moved 100 pt by a Transform effect stayed in place in the file while the
canvas drew it moved.

bake_document_placing_symbols replaces each instance of a symbol whose
art (or the art of a symbol nested in it) needs baking by that art
placed as the instance places it, then baked. The art is placed before
its effects apply, as on the canvas, so an instance that scales its art
does not scale what its effects do. A dynamic instance's own paint
changes apply before baking. Nesting stops at 32 levels and the placed
art at 16,000,000 objects per export; past them an instance stays an
instance. DXF places 9-slice cells unclipped, as its scene does. The
symbols' own art, SVG and the .ai editing data are unchanged, and HPGL
already baked each placed instance.
2026-11-13 22:31:48 -08:00
dotCooCoo b1cf6f810b PDF import: a knockout group in a form of its own opacity reads back as one group
PDF export writes a translucent knockout group as a form with its opacity around the /K group. PDF import now reads that pair back as one knockout group with the opacity, blend mode and isolation, instead of a translucent group holding a knockout group.
2026-11-13 20:13:02 -08:00
dotCooCoo f383ce1475 Merge main into fix/knockout-groups-flat-masks 2026-11-13 19:09:32 -08:00
ruchirguitarandruchirguitar 30eb14d7dc PDF import: outlined Indic glyphs keep their own stroke and transparency (#5235) (#6957)
* PDF import: outlined Indic glyphs keep their own stroke and transparency (#5235)

Follow-up to #6906, for two review findings on how a line kept as
outlines is joined into paths:

- Glyphs were joined when their stroke widths matched; dash, cap, join
  and miter limit were ignored, so a dashed glyph took the stroke of the
  solid one before it. They join only when the whole stroke matches.
- Translucent glyphs were joined into one fill, where overlapping marks
  paint once instead of compositing as the file draws them. Only opaque
  glyphs join.

Also records the fix in the PDF row of docs/target-app-parity.md and in
docs/roadmap.md, as AGENTS.md asks.

Tests: outlined_glyphs_stroked_differently_stay_apart and
outlined_translucent_glyphs_stay_apart (both fail without the change).

* PDF import: outlined Indic glyphs under a soft mask stay apart; check kept geometry (#5235)

Review follow-ups on #6957 and #6906:

- Glyphs kept as outlines under a soft mask were joined into one path,
  so the mask was applied once to all of them where the file applies it
  to each glyph. They now join only when no soft mask is active.
- The fallback tests counted outline nodes only. A new test compares the
  kept outlines (element count and bounding box) with the same file
  opened as outlines, so a dropped or moved glyph fails it.

Tests: outlined_glyphs_under_a_soft_mask_stay_apart (fails without the
change) and kept_glyphs_are_where_the_file_draws_them.

* PDF import: mask outlined Indic glyphs one by one under a soft mask (#5235)

Review follow-up on #6957. Keeping glyphs out of one shared path was not
enough: each path still went into the soft mask's queue and
flush_masked wrapped them all in one masked group, so a mask that is not
constant was applied once to every glyph, where overlapping glyphs
composite one by one in the file.

With a soft mask active the queued art is now drained before the first
glyph and after each one, so every glyph gets a mask of its own.

Test: outlined_glyphs_under_a_varying_soft_mask_are_masked_one_by_one
checks that each outlined glyph carries its own mask (0 of 3 without
the change).

* #6957: clarify soft-mask exception in glyph grouping docs

---------

Co-authored-by: ruchirguitar <info@jeevantirth.org>
2026-11-14 03:26:20 +01:00
dotCooCoo de8ac51502 Symbols: a stained instance keeps its stain in SVG, PDF and the other exports
A symbol instance colored with the Symbol Stainer lost its stain in
export whenever the canvas's stained art was not used:

- An instance with geometry effects was reshaped for export from the
  symbol's art with only its dynamic changes, so SVG, PDF, EPS, .ai,
  DXF, EMF/WMF and HPGL wrote the unstained colors. A color adjustment
  on an instance recolored the unstained art the same way.
- PDF, EPS, legacy .ai, DXF and EMF/WMF wrote an instance without
  effects from the unstained art.

tint_node, stain and instance_art now live in
vectorcraft_doc::symbol_overrides, which both the effects and the
exporters can use; vectorcraft_brush re-exports them. Baking, color
adjustments and the writers above now use instance_art: dynamic changes
first, then the stain.
2026-11-13 17:24:06 -08:00
dotCooCoo 2ee29aefc6 Merge main into fix/knockout-groups-flat-masks 2026-11-13 16:59:50 -08:00