Commit Graph
35 Commits
Author SHA1 Message Date
BFlat 776e468e89 Merge pull request #6242 from annoybot/fix/pdf-gradient-forms-macos
M4: fix PDF gradient placement in transparency groups
2026-11-10 20:05:34 +07:00
Ahmed Gomaa 9b797c06d1 PDF export: letters with marks are written once in the text layer
A mark glyph's vertical translation started a new draw_glyphs batch, so
krilla received the cluster's source range again and mapped its
characters twice. Copying or searching the exported PDF then found
doubled letters for Arabic harakat in any font, and for every dotted
letter in fonts that draw dots as zero-advance mark glyphs (Noto Sans,
Naskh and Kufi Arabic).

Keep compatible glyphs on the same layout line in one batch and carry
their relative vertical placement as per-glyph y_offset; horizontal
placement is unchanged (next_x - x advances). Font, size, linear
transform, line/column, paint run, list marker and hyphen boundaries
still split batches. Krilla keeps owning cluster mappings and
ActualText. No importer change.

Regressions decode the content stream and ToUnicode maps: one batch per
layout line, no duplicated cluster assignment, one ActualText per
multi-glyph cluster, contours within 0.05 pt of outline export; mixed
scripts, vertical columns, path rotations and PDF/X variants covered.
The reader round-trip stays an ignored test until the importer consumes
ActualText (#6165).

Addresses #6309.
2026-11-09 18:13:14 +02:00
dotCooCoo 90bc821f3f PDF: dithered and Perceptual CMYK gradients stay CMYK
A dithered fill or a Perceptual linear or radial gradient whose stops
are all CMYK was written as an RGB image. It is now a CMYK image.
The stops keep their inks, an off-center midpoint is halfway between
its two stops' inks, and a Perceptual curve uses the ink mixes a
DeviceN shading uses. Dither's noise is added per ink and adds no ink
where the gradient has none.

The Output settings convert the inks as they do for any CMYK image.
A gradient with an RGB, gray or Lab stop, a stop linked to a spot
swatch, or a Perceptual curve of more than 4,096 stops is still written
as an RGB image.
2026-11-09 05:55:04 -08:00
Patrick Nadeau ac674accfd M4: fix PDF gradient placement in transparency groups
Move form transforms into page space so Quartz keeps gradients aligned with their artwork. Add macOS rendering regressions and round-trip coverage for transparency, masks, layers and overprint.

Update the fix for the shared craft-pdf writer and the split exporter modules. Preserve upstream mask, coordinate-limit and layer-box fixes, moving the layer's paper bounds into the form's new space too.

Verified all three Quartz regressions fail on upstream 65adb7b21 and all five gradient-form tests pass with the fix. The new translated-layer regression also fails without moving the layer's paper bounds.

Before the final rebase onto 782f213e4: 463 PDF tests passed (one existing ignored test), workspace fmt, PDF Clippy, wasm, pdf-deps, layers, assets and brands passed. The final rebase reconciles documentation only; the PDF crate is unchanged from that tested revision.

After the final rebase, workspace fmt and Clippy pass. Full cargo xtask ci timed out while compiling workspace tests, so the full test suite and remaining CI stages did not complete. The earlier macOS Clippy blocker has been fixed upstream.
2026-11-09 08:45:23 -05:00
BFlat 8eb3d4166a Merge pull request #2552 from dotCooCoo/feat/print-copies-per-sheet
Print: Copies per Sheet prints as many copies of each artboard as fit the sheet
2026-11-09 11:17:48 +08:00
dotCooCoo 134520402b PDF/print: import Vec2 in the export writer after merging main 2026-11-08 18:22:08 -08:00
dotCooCoo 967d77919f Print: every copy on a sheet draws the same symbol instances
Each copy on a sheet draws within an equal share of the symbol objects the
export has left (MAX_SYMBOL_NODES), so a document near the limit leaves out
the same instances in every copy instead of dropping them from the later
copies. The limit stays export-wide; one copy per page draws as before.
2026-11-08 18:22:08 -08:00
dotCooCoo b1236c469b Print: Copies per Sheet prints as many copies of each artboard as fit the sheet
File › Print printed each artboard on a sheet of its own. A 2 × 3.5 in
card on Letter printed once in the middle of the page, and Print had no
option to fill the sheet with copies.

The Print dialog's General section has Copies per Sheet (off by
default) and a Gutter. With it on, a sheet holds as many copies of the
artboard as fit the imageable area, each with its bleed and marks,
across then down, gutter pt apart. The card prints 4 × 3 on Letter,
and 3 × 3 with an 18 pt gutter. The copies are not turned, and the
placement places the whole grid as it places one copy. An artboard
that does not fit prints one copy per sheet with a warning, and more
than 1,000 copies on a sheet is an error. Tiling leaves the option
out. The PDF and PostScript writers draw the art and the marks once
per copy, the PDF TrimBox holds every copy, the preview draws each
copy, and print.preview lists them as `copies`. With the option off,
the layout lists no copies and the writers draw the page as before.

Docs: docs/mcp.md (Print, Printing), docs/target-app-parity.md.
2026-11-08 17:22:46 -08:00
dotCooCoo ac0bb3c4ce Gradients: freeform points that share a color model mix in that model
The canvas, the gradient chips and the SVG, PDF, EPS and EMF writers mixed
freeform points in display RGB, while Object > Expand and Create Gradient
Mesh mixed them in the model the points share. Points that are all CMYK,
Gray, Lab or RGB now mix in that model in those places too. Points in
different models mix as before: in display RGB, and on ink planes as the
inks of each point.

This changes how existing art looks:

- CMYK and Lab points. With the default Generic CMYK working space and no
  ink planes, halfway between C100 and K100 the canvas drew (17, 106, 139)
  and now draws (54, 118, 143).
- RGB points in a working RGB space other than sRGB mix in that space.
- On ink planes (a CMYK document that shows transparency), RGB and Lab
  points mix first and then separate.

Gray mixes keep their values, and CMYK and Gray points on ink planes draw
as before. RGB points in an sRGB working space draw as before except on
ink planes.

- PDF and EPS write a gradient whose points are all CMYK, or all Gray in a
  CMYK file, as a CMYK image. PDF's Output settings convert it as they
  convert any CMYK image.
- Darkening applies to the points before they mix.
- With Appearance of Black set to Rich, the canvas mixes a gradient with a
  pure CMYK black point in display RGB, and the point fades from RGB black.
2026-11-08 17:18:43 -08:00
dotCooCoo c94269ff72 Type: Composite Fonts… sets each class of character in a font of its own
Squashed from #6244 onto main.
2026-11-09 03:08:23 +07:00
dotCooCoo b50a6bcad4 PDF export: the link walk measures only linked objects and clipping groups
The walk that writes link annotations measured every object at every
level, on every export that can have links, and read the box only for
an object with a URL that opens and for a clipping group. It now
measures only those, so a document with no URLs or clipping groups is
not measured a second time. The link annotations are unchanged.

The output is byte for byte the same on a document of linked objects in
plain and clipping groups on and off the page, with default settings,
with uncompressed text and with PDF layers.
2026-11-07 05:15:08 +07:00
dotCooCoo a3045845c8 PDF export: members of a plain group or layer are measured once to leave out off-page art
The walk measured every object's visual box to leave out what is off
the page. A plain group or layer is measured as the union of its
members, and each member then measured itself again when it was
written, so an object in plain groups was measured once for each level
it sits at.

Measuring a plain group or layer now keeps its members' boxes in a
tree, and each member uses its box when it is written. A container
written whatever its box is (a PDF layer's group, placed symbol art) is
not measured. A knockout group's elements are measured for the group's
box and again for the list of elements on the page, and that second box
is used for the element and for its mask. Objects in a live container
(Blend, Envelope, Repeat, Intertwine) are still measured with the
container and again after it is expanded. Node::visual_of_members holds
the formula that Node::visual also uses, so the boxes and every decision
are unchanged.

The output is byte for byte the same on one open path of 200,000
anchors, 20,000 rectangles in 200 groups of 100, and 5,000 point type
objects, and on a document of plain, see-through, clipping and knockout
groups on and off the page, each exported with default settings, with
uncompressed text and with PDF layers.
2026-11-07 05:15:07 +07:00
Don Omondi dbbe8a9298 PDF and .ai export: objects' URLs become clickable links (#5785)
Each visible object with a URL (Attributes panel) is written as a Link
annotation with a URI action over its bounds on the page, trimmed to its
clipping groups. Hidden objects, non-printing layers and printed sheets get
none; PDF/X files leave them out with a warning. URIs are percent-encoded
to 7-bit ASCII.
2026-11-06 15:48:02 +07:00
dotCooCoo 84904020e8 Images: a colorized image's missing-link placeholder shows in its own colors
A linked 1-bit image mask from a .ai file keeps its fill color as a
colorize paint, so the image takes the color once its file is relinked.
Until then the image shows the missing-link placeholder, and the canvas
and PDF export colorized that placeholder too: its light tile turned
nearly transparent and its red cross took the fill color.

ImageBlob::takes_colorize is false for a preview whose pixels are not
grayscale. While it is false, the canvas draws the image and PDF export
writes it as it is, printed separations separate it as an uncolorized
image, and transparency flattening (EPS, PDF 1.3 and PDF/X-1a export,
Flatten Transparency) does not count its paint as transparency. The
paint stays on the image, and a grayscale preview is still colorized.
SVG and EPS export already write only grayscale images colorized.
2026-11-05 23:25:05 +07:00
Don Omondi ce4f16369e PDF import: a DeviceN gradient of a spot ink over process plates keeps the ink and the plates, and PDF export writes it as DeviceN again (#3235) 2026-11-05 09:09:53 +07:00
dotCooCoo dd41552c30 PDF export: warn about spot stops in freeform gradients without points
A freeform gradient whose field has no points of its own takes its
colors from the stops, so spot-linked stops in it are written in
process colors too. The export now warns in that case, as it does for
the other gradients drawn as images.
2026-11-04 20:36:11 +07:00
dotCooCoo 52d180910d PDF export: spot inks named All or None print as spot inks
In a Separation color space, the colorant name All marks every plate and
None marks no plate (ISO 32000-1:2008, 8.6.6.4). A spot swatch with one of
those names was written under its own name, so its paint printed on every
plate or on none.

PDF export now writes such an ink as All Spot or None Spot, numbered
All Spot 2, All Spot 3 and on when a spot swatch already has that name,
the same rule EPS export uses. The new name is used for fills and strokes,
tint shadings, DeviceN gradients, colorized images and the Lab alternates.
Registration still writes All.
2026-11-02 05:04:46 -08:00
dotCooCoo b30f7ad34c Fix #3118: PDF export writes CMYK inks, gray and spot tints as the document holds them
Squashed from #5080 onto main.
2026-11-02 15:22:38 +07:00
dotCooCoo 83f2bfe9d6 Symbols: a scaled instance of a symbol with 9-slice guides keeps its corners and edges unstretched
Squashed from #5079 onto main.
2026-11-02 15:22:07 +07:00
Brandon Thomas cb700213c6 Merge pull request #5191 from dotCooCoo/fix/issue-2181
Fix #2181: PDF layer forms get real boxes and lose their off-page marks
2026-11-02 00:34:31 -05:00
dotCooCoo d5849d6553 Fix #2181: PDF layer forms get real boxes and lose their off-page marks
With Create PDF layers on, every layer's form XObject had
/BBox [-32767 -32767 32767 32767] and started with an empty clip at
x = -32000 that only marked the form while the file was written. Other
apps read that as art about 23 m across, and the reporters could not move
or place the imported layers.

- The marks of layer, overprint and invisible-text forms are removed when
  the file is finished.
- A layer's form gets a box round its art and the page's paper, in whole
  units. Since the box still covers the page, PDF import skips it as a
  clip, so sublayers still import as sublayers.
2026-11-01 16:41:20 -08:00
dotCooCoo 589c56da22 PDF export: a white stop linked to a process swatch keeps a spot gradient on its ink
A gradient of one spot ink's tints and paper white is written as a
Separation shading in that ink, with each white stop at 0% of it. A
white stop linked to a swatch that is not a spot color (a global White
swatch, for example) did not count as paper white. In an RGB document
the spot ink was then dropped: a Classic gradient was written in the
stops' own colors, and a Perceptual one was exported as an image in
process colors.

Such a stop now counts as paper white in the Separation and DeviceN
paths and in the checks that pick them, as an unlinked white stop does.
A stop linked to a white spot color is still a stop of that ink.
2026-11-01 16:28:40 -08:00
dotCooCoo 38bb213917 PDF export: a Perceptual gradient that mixes spot and process inks keeps its inks
A Perceptual linear or radial gradient that mixes spot colors with each
other or with CMYK or gray stops was written as an image in process
colors. It is now written as a DeviceN shading of its inks, with the
stops on its Perceptual curve: at each one, the inks of the two stops on
either side are mixed at the weight where the two colors mixed in
display RGB have the OKLab lightness of the color on the curve.

- Gradient::ink_mixes gives the curve's stops as weights between stops,
  under the same 4,096-stop limit as ink_tints.
- Perceptual gradients that mix a spot color with RGB or Lab stops,
  dithered fills and freeform gradients stay images, and the warning
  now names those cases.
2026-11-01 11:09:09 +07:00
dotCooCoo ac896cfe3d PDF export: a Perceptual gradient of one spot ink's tints keeps its ink
A Perceptual linear or radial gradient was always written as an image,
so a gradient of spot ink tints lost its plate, and no warning named the
spot ink.

PDF export now writes a Perceptual gradient whose stops are tints of one
spot color (and paper white) as a Separation shading in that ink. Its
tints follow the gradient's curve: at each stop the renderer places on
the curve, the tint with the OKLab lightness of the color there.

Dithered fills, freeform gradients and Perceptual gradients that mix a
spot color with other colors are still images, and the export now warns
when such a gradient has stops linked to a spot swatch that its spot
colors are written in process colors.
2026-11-01 08:32:54 +07:00
dotCooCoo ba16468710 PDF export: a gradient that mixes a spot color with process colors keeps the spot ink
A linear or radial gradient whose stops mix a spot color with process
colors, or two spot colors, was written in process colors with a
warning. It is now written as a DeviceN shading of the process inks
its stops use and its spot inks, so each spot ink separates onto its
own plate. An off-center midpoint becomes a stop with the inks halfway
between its two stops. A gradient that mixes a spot color with RGB or
Lab colors is still written without the spot ink, with a warning that
says so.
2026-11-01 04:55:11 +07:00
bflatastic f22caa6108 Fix #4938: Dynamic symbols keep per-instance appearance linked to the symbol 2026-11-01 04:53:38 +07:00
dotCooCoo fa89546397 PDF export: leave out an object whose opacity mask lost an image
An unclipped opacity mask is drawn on a white backdrop. When an image in
the mask art could not be written (a colorized image whose color channel
is over the size limit, an image that can't be decoded, or one placed
beyond the coordinates a file holds), the backdrop stayed white and the
masked object showed in full where the image should hide it.

The exporter now records an image left out while it writes mask art,
and leaves the masked object out instead of applying the incomplete
mask, with a warning. A failure in a nested mask makes the outer mask
incomplete too.
2026-10-31 05:42:51 +07:00
dotCooCoo 7d706ffa26 PDF: colorized images that can't be exported are cached as missing
colorized_image returned before its cache insertion when the image was
missing, left out or undecodable, or when its color channel was larger
than MAX_RASTER_ALLOC. Every placement of such an image then decoded it
and computed its coverage again. Both cases now cache None for the key,
as load_image does.
2026-10-31 05:42:41 +07:00
dotCooCoo 16db8b6a1a PDF export: bound a colorized image's color channel
A colorized image's color channel is the paint's samples repeated once a
pixel, and nothing bounded its size: an image colorized in Registration in
a document with many spot swatches could allocate without limit, and an
overflowing length panicked inside repeat. The length is now computed with
checked arithmetic and refused above MAX_RASTER_ALLOC, the image's own
decode budget, and the export leaves such an image out with the existing
"an image couldn't be decoded" warning instead of drawing it uncolored.
2026-10-30 23:43:57 +07:00
Don Omondi 43db0c00bc PDF export: gradient meshes as smooth Type 6 patch shadings
PDF export (and the PDF-compatible .ai it writes) cut each gradient mesh
patch into 8 x 8 flat-coloured quads. Viewers showed colour bands and
hairline seams between them, and files with many meshes became very
large and slow to write.

An opaque mesh is now written as one Type 6 (Coons patch) shading
(ISO 32000-1, 8.7.4.5.7) filling its outline, through the PDF writer's
MeshGradient: each patch blends its corner colours bilinearly, as the
canvas draws it. Colours go through page_color as other fills do, and a
mesh mixing RGB with other models goes through RGB. A mesh with
see-through points, an invalid one, or one past the coordinate range
keeps the quads.
2026-10-30 08:15:20 +07:00
dotCooCoo 1538733954 Images: a fill color colorizes grayscale and 1-bit images
Squashed from #4431 onto main.
2026-10-30 00:15:57 +07:00
dotCooCoo acc5e53c91 Envelopes: a pixelated CMYK image with its own grid mask is written without /Interpolate
PDF export writes a CMYK image whose opacity mask is a gray image on its
own pixel grid with the mask as the image's soft mask. That path always
asked viewers to smooth the image, so a pixelated piece of an envelope
with Anti-Alias off was still written with /Interpolate when it had such
a mask. It now takes the pixelated flag as images without a mask do, and
caches the smoothed and pixelated versions apart.
2026-10-29 11:07:58 +07:00
dotCooCoo ab69d8f6d9 Envelopes: Anti-Alias and Preserve Shape Using change how distorted images draw
Envelope Options stored Anti-Alias and Preserve Shape Using, but nothing that
draws an image read them: a distorted image was always smoothed and always
cut into clip groups.

- With Anti-Alias off, each piece of a distorted image is marked pixelated
  (saved as `pixelated`). The renderer draws it with nearest-neighbor
  sampling and without mips, SVG export writes `image-rendering: pixelated`,
  and PDF export writes it without /Interpolate.
- With Preserve Shape Using Transparency, each piece is the image with a
  white opacity mask in the piece's shape instead of a clip group. The
  renderer paints an image whose mask is one plain white path straight into
  that path, as it does for an image in a clip group, when the path lies
  inside the image's frame.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:07:57 +07:00
Dylan M. Taylor 317d270823 Clarify PDF import stages and module dependencies 2026-10-28 05:02:46 +00:00
aiongg 882cb848b3 refactor(pdf): move export.rs items to export/{entry,exporter,images,nodes,paint,text,writer}.rs 2026-10-26 21:45:33 +00:00