Commit Graph
193 Commits
Author SHA1 Message Date
Brian TorresandClaude Opus 5.5 ac7f0eebf8 PDF import and export: keep invisible text (rendering mode 3) as type (#2675)
Text drawn in rendering mode 3 (3 Tr), which is how a scanned page carries
its recognized OCR text, was left out on open (since #2988, with a warning),
so a scanned PDF opened and saved again lost its searchable text.

Import: an invisible glyph now comes in as type with neither fill nor
stroke. It skips the glyph-outline checks that guard how visible type looks,
and it joins only other invisible text on a line (Look::takes no longer
lets it into a stroke-only run). INVISIBLE_TEXT_NOTE now only reports text
that can't be type (text opened as outlines, Type 3 fonts), and
tests_reader_compat::invisible_text_is_reported_when_left_out expects the
invisible line as unpainted type when text opens as type.

Export and print: characters with no fill and no stroke are written as real
text inside a form marked Mark::Invisible; forms::finish sets that form's
text rendering mode to 3, so it draws nothing but stays selectable and
searchable (PDF/X-1a and PDF/A-2b too). The writer has no invisible mode of
its own, so this reuses the marked-form mechanism used for layers and
overprint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 04:43:11 +07:00
00xJ0 8d123b19ca Test: check which text the PDF import keeps beside invisible text (#2675 review)
The visible line's text comes in as type and the invisible line's doesn't; as type or outlines,
all the art kept lies on the visible line's baseline, none on the invisible one's.
2026-10-19 22:19:50 +07:00
00xJ0 4aa4b0e70b PDF import: say when invisible text is left out (part of #2675)
Text in rendering mode 3 (invisible: a scanned page's searchable OCR layer) was dropped with no
warning, so a scanned PDF opened and saved again lost its searchable text without a hint. The
import now reports INVISIBLE_TEXT_NOTE when it leaves such text out, for type and outlines alike.

Keeping the text needs a kind of type that is kept but not drawn, and the PDF writer (krilla, in
craft-libs) to write it with `3 Tr`; its text API has fill, stroke and fill-and-stroke only.
2026-10-19 22:19:50 +07:00
Hamza 1c80b23745 Exports: SOURCE_DATE_EPOCH stands in for the clock, so output repeats
PNG, PDF, EPS, legacy `.ai`, packaged zips and saved `.vectorcraft` files
record the wall-clock time they were made in, so the same commands run twice
gave different bytes although the pixels matched (#2880):

  PDF   /CreationDate and /ModDate   D:20261018155816 vs D:20261018155817
  PNG   the `Creation Time` tEXt entry
  .vectorcraft  one byte of the stored date
  SVG   carries no time, and already repeated

`metadata::output_time` is the time a file records: `SOURCE_DATE_EPOCH` when it
holds a non-negative whole number of seconds, else the clock, as the
reproducible-builds specification defines it. A date a command is given
(`file.new {created}`, a save's `modified`) still wins over the variable, and a
blank, negative or non-numeric value is ignored so that an export is never
refused over the environment.

`metadata::now_unix` keeps the real clock and now has exactly two callers, both
of which must not be pinnable from the environment: the recovery heartbeat, and
the seed PDF encryption draws on.
2026-10-19 00:37:55 +07:00
dotCooCoo f0c2d8c890 PDF and .ai import: editing data with a UTF-16 description is checked against its pages
A PDF or PDF-compatible .ai saved with Preserve Editing carries the
native document as an embedded file, with a hash of its pages at the
end of the file's /Desc. The import read /Desc with from_utf8_lossy and
looked for "hash ". /Desc is a PDF text string, which may be written as
UTF-16BE with a byte order mark (ISO 32000-1 7.9.2.2). Written that
way it holds no UTF-8 "hash ", so the import found no hash, took the
editing data as intact and reopened the native document with no
warning, even after another app had changed the pages.

/Desc is now read with text_string, the importer's decoder for text
strings, so the hash is found in UTF-16BE, UTF-8 and PDFDocEncoding.
Changed pages open as artwork with the "changed in another app"
warning. The embedded file's /UF and /F names are decoded the same
way, so editing data named only in UTF-16BE is found.
A /Desc without a hash is still trusted, as before.
2026-10-19 00:03:51 +07:00
dotCooCoo aa9cf83fd2 PDF export: PDF and .ai files keep the document's creation date
The engine built the PDF options without a creation date, so the
writer dated every PDF and PDF-compatible .ai file with the time it
was written. A document created on 2020-01-01 (File › File Info
shows the date) exported as a PDF with today's date in /CreationDate
and xmp:CreateDate. SVG export with the metadata option already
writes the document's own date.

The options now carry the document's File Info created date and the
time of writing as the modification date. A document without a
creation date is dated with the time of writing, as before, so PDF/X
files still get their required date. The PDF writer writes one date
for both, so the export then sets /ModDate, xmp:ModifyDate and the
PDF/A history dates to the time of writing; /CreationDate and
xmp:CreateDate keep the document's date. The page information mark
and the embedded editing data's modification date keep the time of
writing.

Docs: docs/mcp.md (File Info), docs/target-app-parity.md.
2026-10-18 12:36:27 +07:00
dotCooCoo df7daff3e4 PDF export: an image that can't be decoded is left out with a note instead of failing the export
When the PDF writer keeps a PNG, GIF or WebP image as it is, it accepts
the image once its header reads, and it reports pixel data that it
can't decode only when it finishes the file. An image whose pixel data
was cut short or damaged therefore failed the whole export with "PDF
writer error: Image(...)", and nothing was written. This happened in
Save as PDF, .ai saves, Save a Copy, Print, Export for Screens and
native files with PDF compatibility, and with On Copy: PDF turned on,
Copy left the PDF off the clipboard.

When the writer fails on an image's pixel data, the export now runs
again with that image left out. Its warnings hold the note that raster
exports give, once: "an image couldn't be decoded and is missing from
the picture". This note replaces the writer's "image '<key>' could not
be decoded and was skipped", which named the image by an internal key.
An export without such an image runs once, as before. A .ai file keeps
the image in its editing data and reopens with it.

Docs: docs/mcp.md (PDF options).
2026-10-18 12:34:27 +07:00
dotCooCoo ffc12babf2 PDF import: PDF and .ai layer names without a byte order mark are read as PDFDocEncoding
The layers of PDF and PDF-compatible .ai files come from their
optional content groups, whose /Name is a PDF text string. The import
read a name without a byte order mark as Latin-1. Such a string is
PDFDocEncoding (ISO 32000-1 Annex D, Table D.2), which differs from
Latin-1 at 0x18 to 0x1F and 0x80 to 0xA0. A group named
(Art\205final) opened as a layer named "Art", U+0085, "final" instead
of "Art–final", and curly double quotes (0x8D, 0x8E) and the euro sign
(0xA0) became control characters and a no-break space.

text_string now reads such a string with the PDFDocEncoding table that
File Info already used for the document information dictionary. The
table moves from file_info.rs to import_scan.rs, and File Info reads
the information dictionary's strings with text_string.
2026-10-18 12:15:31 +07:00
dotCooCoo 830cefcaf6 PDF import: an XMP packet nested more than 256 deep is skipped instead of overflowing the stack
When a PDF, PDF-compatible .ai or .ait opens, crates/pdf/src/file_info.rs
reads its File Info from the XMP packet with roxmltree, which parses
nested elements recursively and has no limit on their depth. A packet
with elements nested 10,000 deep (70 KB) overflowed a 1 MB stack, and
one nested 100,000 deep (700 KB) overflowed an 8 MB stack. A stack
overflow aborts the app.

File Info now counts how deep the packet's elements nest before roxmltree
parses it, and leaves out a packet nested more than 256 deep, as it
leaves out a packet that is not XML. The document information
dictionary then gives the File Info.
2026-10-18 12:15:31 +07:00
NetterSilverandClaude Haiku 5.5 fe3e8f65a5 Import PDF 0 w strokes as hairlines instead of dropping them
Fixes #2674

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:38:50 +07:00
dotCooCoo 4712fb2c2f Character: variable fonts get a slider for each axis
VectorCraft listed a variable font's named instances as styles, but
type could not be set between or past them. The reference app's
Character panel has a Variable Font control that adjusts a variable
font's axes, such as Weight, Width and Slant.

Type now keeps Variable Font settings by axis tag
(CharStyle::font_axes, saved as fontAxes only when set). text.setFormat
and text.setRangeStyle take fontAxes: each value is clamped to its
axis's range, a tag the font has no axis for is left out, and null
goes back to the style's own setting. Another font or style clears
them, including one from a character style that does not set them. A
character style captured from type sets them, with {} for none.
text.fontList {family, style} lists a font's axes.

Layout, outlines, the substituted-glyph highlight and PDF export use
the font instanced at the settings, and neighboring runs at different
settings are shaped apart, as runs in different fonts are. SVG export
writes them as font-variation-settings, the wght setting as the weight
and the wdth setting as font-stretch, and embeds the font instanced
there; SVG import reads font-variation-settings back. Legacy .ai export
warns that the settings are left out.

The Character panel shows a Variable Font button for a variable font.
It opens a slider and a field for each axis the font does not hide,
labeled with the font's name for the axis, and screen readers get each
slider under that name. A drag is one undo step. Reset Panel leaves
the settings, as it leaves the font and style.

Missing Glyph Protection now keeps the settings and the font version
on the characters that keep their font; before, it kept only the
family and style. A character style applied or redefined with another
font now clears the old font's version, as text.setStyle does, unless
the style sets one. SVG export with embedded fonts warns when faces of
one weight, style and width share characters, which browsers then draw
in one of the faces.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/type-parity.md.
2026-10-17 19:38:46 +07:00
dotCooCoo 1651dadf41 .ai import: type from the layers opens without the PDF part's outlines warnings
A PDF-compatible .ai read through its editing data kept the PDF part's
warnings that text was kept as outlines ("text was converted to
outlines", the U+FFFD warning and the warning on glyphs that differ
from the installed font). The layers take from the page only text
nodes and the stroked paths around them, so filled outlined glyphs
don't reach the document, and type made from the text document holds
every character.

- The warnings are dropped when the document holds no glyphs kept as
  outlines. Stroked glyphs that come in with stroked type keep them.
- A text object that takes the page's type, next to glyphs the page has
  only as outlines, lacks those characters: a new warning counts such
  objects.
- vectorcraft_pdf exports the three warnings, is_outlines_note,
  is_glyph_outlines and holds_outlines.

Docs: docs/target-app-parity.md.
2026-10-17 16:38:41 +07:00
japbcoelhoandClaude Opus 5.5 92a801a04f PDF: colours are written in the profiles assigned to the document
A document assigned a CMYK profile (Edit > Assign Profile) keeps it when
it is saved, but the working spaces are the app's, and a new session
starts with the defaults. Opened again, such a document exported its CMYK
colours tagged with the working CMYK space (VectorCraft Generic CMYK
(SWOP-like)) instead of its own profile, made that profile the PDF/X
output intent, and separated its RGB colours through it; print
separations did the same. edit.assignProfile still reported the assigned
profile, and document.exportPdf's documentation says CMYK colours are
tagged with "the document's CMYK profile".

Found on a book cover assigned FOGRA39L Coated: built in several
vectorcraft-cli runs, each reopening the saved document, its PDF/X-4
came out tagged with the SWOP-like profile unless every run set FOGRA39
as the working CMYK space again.

Export and print now work in the document's colour spaces: the active
settings with the assigned profiles in their place (document_cms). An
assigned profile that isn't available leaves the working space, with a
warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:14:42 +07:00
Marco Arnulfo 025ce255ae PDF export: a CMYK image's own soft mask is written as its /SMask, not a luminosity mask (fixes #2159) 2026-10-17 14:43:14 +07:00
vangel76andClaude Opus 5.5 9122ed0a7e MCP: cancel long exports, answer while one runs, report progress (#2165, #2171, #2172)
The MCP server read one line, ran it and answered it before reading the next,
so notifications/cancelled was never seen during a call and every request
(even ping) waited behind a running export.

- vectorcraft_doc::cancel: a thread-local cancel token and progress sink that
  long work polls. Checkpoints: PDF pages, PDF flattening, geometry bake,
  raster-effect flattening, drawing objects, raster artboards, and before any
  file is written. A cancelled command returns EngineError::Cancelled and
  changes nothing.
- Server::serve reads on its own thread: notifications/cancelled drops a
  queued request or stops the running one (no reply, as the MCP spec says),
  engine-free requests (ping, the list methods, prompts/get) are answered at
  once, a request that would wait behind a call running for 2 s is refused
  with -32003 naming that call, and _meta.progressToken gets
  notifications/progress (page n of m). Queue and output are bounded.
- The remote backend stops waiting for the app when the call is cancelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:49:48 +07:00
vangel76andClaude Opus 5.5 6920adf092 PDF export: report RGB colours and images in a CMYK document (#2162)
A CMYK document holding RGB colours or images separates them into CMYK on export, and dark ones come out as rich blacks. The export report now says how many there are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:49:48 +07:00
dotCooCoo fbcb217789 Part of #2384: imported PDF text keeps its place after wide gaps between words
PDF import reads a gap wider than a fifth of an em between two glyphs
as a space, and a space that the line's tracking cannot place gets
tracking of its own. That tracking was clamped to 1000 thousandths of
an em, while type takes tracking from -1000 to 10000. A gap wider than
an em plus the width of a space came in short by the excess, so the
words after it sat to the left of where the file put them, and each
further wide gap added to the shift.

The tracking that import sets for such a space, and for the line around
it, now has the same range as type's.
2026-10-17 13:20:23 +07:00
vangel76andClaude Opus 5.5 7e423f91e9 Overprint Preview: overprinting draws with Darken, so a colour over itself stays that colour (#2415)
Multiply squared a colour overprinting itself (stroke over same-colour fill showed a darker band). Darken keeps the stronger ink per plate/channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:06:51 +07:00
vangel76andClaude Opus 5.5 8ac4c9d268 Color: Generic CMYK compensates black both ways so black round-trips (#2493)
sRGB to Generic CMYK applies black-point compensation (black separates to the
darkest rich black) but CMYK to display did not undo it, so hex 000000 became
C67 M67 Y67 K100 and then showed as #1b1818. The display XYZ now maps the press
black to black when the colour settings use black-point compensation, in the
sRGB conversion, the ink lookup table the canvas uses, Lab of a CMYK colour and
the gamut warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:06:51 +07:00
Gert Pellin ca0673e0bc Align Stroke inside or outside on live type (#2294)
The characters' stroke takes Align Stroke like an object stroke: CharStyle
keeps stroke_align (written only when not centred), stroke.set and
text.setRangeStyle strokeOptions apply align to type, and the Stroke panel
enables the buttons for type. Canvas, PDF and EPS draw it around the glyphs
through stroke_layer(); SVG keeps the text live and writes the aligned
stroke on the glyph outlines, clipped or masked. Type's visual bounds grow
with its character strokes.
2026-10-16 15:27:54 +07:00
dotCooCoo dfe215ca30 Effects: Effect › Path › Outline Object turns type into live, editable outlines
Effect › Path had Offset Path and Outline Stroke but not Outline Object.
The new effect (path.outlineObject) has no options: the Effect menu
applies it at once, and effect.apply refuses any parameter. It is a
geometry effect that returns its input. Type already reaches geometry
effects as its glyph outlines, so type that carries it is drawn,
exported (PDF and SVG as paths, with no font) and expanded as those
outlines, stays editable type, and the effects after it reshape the
outlines. Paths, compound paths and images draw as before, and no
bounds change: with Use Preview Bounds on, type is still measured by
its layout box.

Every complete catalog gets a row for the label.

Docs: docs/effects-parity.md, docs/target-app-parity.md (an evidence
line), docs/mcp.md (the effect, and what Expand Appearance does to
type).
2026-10-16 15:27:54 +07:00
dotCooCoo f46b0f7dc4 PDF export: a page larger than 14,400 pt is written with a UserUnit
PDF export and .ai saves wrote every page in points. ISO 32000-1
(Annex C.2) gives 14,400 units as the longest page side, and a page
with a longer side got a media box past that limit. A PDF/A-2b file
with such a page failed veraPDF's page size rule (6.1.13).

Such a page now gets a UserUnit (PDF 1.6): its longer side divided by
14,400 and rounded up to the next thousandth, with the page's sizes
and coordinates in those units. A 20,000 × 10,000 pt artboard is
written as a 14,398.85 × 7,199.42 page with /UserUnit 1.389, and
VectorCraft's PDF import reads it back at 20,000 × 10,000 pt. Pages
up to 14,400 pt, PDF 1.3 to 1.5 files, PDF/X files and printing are
written as before.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 14:58:09 +07:00
Guillem Hermida d05ccffebc effects: a Transform effect copies a group's symbol instances too, and stops image copies at MAX_REACH
Symbol instances are placed through a transform as images are, so the
Transform effect now moves and copies both (transform_placed), sharing one
copy budget (MAX_COPY_OBJECTS). Copies of either stop before one reaches
past MAX_REACH, as a path's do (distort::within_reach).

Adds end-to-end export tests for a group's image copies in svg and pdf.
2026-10-16 06:16:35 +07:00
Marco Arnulfo 789a151ff9 SVG import: an alpha mask counts by its art's alpha, not its luminance (fixes #2210) 2026-10-16 06:16:35 +07:00
dotCooCoo da43c662e0 Export: type copied by a Transform effect that only moves it stays type
Exports bake live effects into plain art, and a Transform effect on
type or on its group or layer turned the type into glyph outlines, even
when the effect only moved it or made moved copies. A PDF reader could
not select or search that text.

The export bake now keeps such type as type, moved into each place the
effect puts its outlines. It does this for Transform effects without
scale, rotation, reflection or Random, for type whose characters are in
solid colors without underline or strikethrough, and that has no fills
or strokes of its own, inline graphics or perspective, and when the
copies do not overlap. Type under such an effect on its group or layer
also needs no transparency or effects of its own, and the group or
layer needs no fill, stroke or Pathfinder effect of its own. The export
still outlines other type under geometry effects, and the canvas and
Expand Appearance still draw outlines.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 06:16:35 +07:00
dotCooCoo 261b64251d PDF/.ai import: PDF and .ai files open with the File Info they carry
A PDF, PDF-compatible .ai or .ait opened from its pages had an empty
File Info. Nothing read the file's document information
dictionary or its XMP packet, so file.info reported no author,
description or keywords, even for a PDF that VectorCraft exported.

vectorcraft_pdf::file_info reads both. The author, description,
keywords, copyright status, notice and URL and the created and modified
dates come from the XMP packet (dc:creator, dc:description, dc:subject
or pdf:Keywords, xmpRights:Marked, dc:rights, xmpRights:WebStatement,
xmp:CreateDate, xmp:ModifyDate), else from the dictionary's Author,
Subject, Keywords, CreationDate and ModDate. document.open,
file.newFromTemplate and file.revert put each value through file.info's
checks and leave out a value that file.info refuses. A packet that is
not well-formed XML leaves the dictionary's values. File Info does not
set the title, and a file restored from the document it carries keeps
that document's File Info. vectorcraft-pdf depends on roxmltree 0.21,
which usvg already brings into Cargo.lock.

Docs: docs/mcp.md (File Info), docs/file-format-parity.md,
docs/target-app-parity.md.
2026-10-16 06:16:34 +07:00
Gert Pellin 74c391c7ba G13: Object › Intertwine: Make, Release and arranging overlap areas (#1763)
An Intertwine group keeps its objects as they are, in their own stacking
order; each member can be drawn over the others in an area of its own.
The group draws, hits, saves and exports through its live expansion, so the
members stay editable and Release gives them back unchanged.

object.intertwine.arrange picks an overlap area by a click point or by a
closed path drawn around overlaps and toggles which member is on top
there, or brings a member to the front or sends it to the back where three
or more objects overlap.
2026-10-16 04:44:52 +07:00
bflatastic e6b28f0e80 Tests: read the PDF/X output intent's profile with the crate's own xref reader (#1962) 2026-10-16 04:44:31 +07:00
Marco Arnulfo 32dbc4b641 PDF export: the output intent shares the stream of the profile that tags the colours (fixes #1954) 2026-10-16 04:44:31 +07:00
dotCooCoo 31ffbb2152 PDF/.ai import: the PostScript file error names File › Open and File › Place, which read the file
document.pdfInfo and clipboard.importPdf, given a PostScript file (an
EPS, or an .ai in an older PostScript format), failed with "this is a
PostScript file (...), which can't be opened yet: save it as PDF,
PDF-compatible .ai or SVG". document.open and file.place read the same
file through the EPS reader. The error told users to convert a file that
VectorCraft opens.

The error now says the file is not a PDF and that File › Open and
File › Place read it as EPS. The 13 catalogs that carry the message get
the new text.
2026-10-16 03:36:20 +07:00
EricKuckandClaude Opus 5.5 f3b4c3ec78 PDF import: an axial or radial shading keeps its function's curve when sampled into stops
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 03:36:20 +07:00
bflatastic 136810bee2 PDF/.ai import: the tint transform test passes no ink names to space_of 2026-10-16 03:36:20 +07:00
dotCooCoo ca145c5265 PDF/.ai import: spot inks with the same values and different names keep their own swatches
A PDF can define spot inks that have different names and the same
alternate space and tint transform, such as Blue1 and Blue2, both RGB
0 0 255. The interpreter drops the ink name, so the importer matches an
interpreted color to the file's Separation and DeviceN spaces by the
tint transform. The spaces of two such inks matched the same color, so
their paths imported as plain RGB and neither ink became a spot swatch.
This happened with both inks on one page, with each on its own page,
and in the PDF part of a .ai file.

The page walk in import_scan.rs, which follows the interpreter's order
for layers and overprint, now also records the ink of each path, from
the color space set by cs and CS or from the shading of the pattern set
by scn and SCN or drawn by sh, and the ink of the color space of the
text between two paths. When the spaces of several inks match a color,
the importer takes the space with the walk's ink names. It applies
these names to a page only when the page uses such an ink and a
counting pass of the interpreter draws as many paths there as the walk
counted. Otherwise the import works as before.

The file now opens with a spot swatch for each ink. Solid fills and
strokes of paths and type, and the stops of gradients on paths, link
to the swatch of their ink, the points of a gradient mesh keep the
ink's color, and PDF export writes each ink as its own separation.
Paint in these inks inside pattern cells, soft masks and type 3 glyphs,
and gradients on type in these inks, still import as RGB. Type in one
of these inks can also import as RGB when type in another Separation or
DeviceN ink is shown with no path drawn between them.
2026-10-16 03:36:20 +07:00
dotCooCoo 16b8aeb6b7 PDF/.ai import: a .ai whose page has a UserUnit opens with its layers at the page's size
A PDF page's UserUnit (PDF 1.6) sets the size of its user space unit
in multiples of 1/72 inch. The PDF reader has applied it since #1894, so
a page with /UserUnit 10 and a 36 × 50.4 media box opens at 360 × 504
pt. A .ai whose editing data has its artboards in the page's own units
then no longer matched its PDF part. It opened from the PDF part,
without its layers, its hidden objects and the art outside its
artboards, and with its artboards in a row instead of where the
editing data puts them. document.pdfInfo gave such a page's size in
points and its boxes in the file's numbers.

Editing data whose first artboard has the page's size in the page's
units, and not in points, is now compared with the PDF part scaled
down by the UserUnit. The document read from it is then scaled up by
the UserUnit, with its stroke weights, dashes, effects and pattern
tiles. When the layers can't be read, the artboard layout taken from
the editing data is scaled the same way. document.pdfInfo gives the
boxes in points, as it gives the size.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 03:36:20 +07:00
NetterSilverandClaude Haiku 5.5 dfdb98463f Open PDFs and .ai files with an image over 256 MB decoded
Fixes #2005

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 03:36:19 +07:00
Justin Willhite 8da47f11f8 PDF export: a CMYK page without blending through is an isolated group (#2156) 2026-10-16 02:56:34 +07:00
bflatastic 18f8a1bdcf PDF import: a damaged array is refused as damaged, not as functions built on one another 2026-10-16 02:56:34 +07:00
Claude 95d27fe390 PDF.R1: never panic on a damaged array or dictionary in an imported PDF
hayro-syntax's Dict::entries still unwraps each value it reads, so a
dictionary cut short (or with a stray token) aborts the import. The
craft-libs ArrayIter now stops at the first unreadable item instead of
panicking, which also means a hostile item after a self-referencing
function or colour space is silently dropped from nests_without_end.

- import_color: `readable(array)` checks every item parses; `items()`
  yields nothing for an unreadable array; `entries()` reads a dictionary
  key by key through get_raw, skipping values that don't parse.
- pages/editing/import_scan: use the helpers instead of raw_iter() and
  Dict::entries().
- nests_without_end refuses a file with an unreadable array.
- Tests: two refusal cases in pdf tests and an .ai cut inside an array
  or dictionary in engine/tests/import_fuzz.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcHisW1vt6pkVPYbzL6Cpg
2026-10-16 02:56:34 +07:00
Fabio William ConceiçãoandCursor fc14c38883 fix(pdf): collapse Shading match for clippy 1.95 (#2131)
Rust 1.95 clippy -D warnings rejects the nested if under TypedInstruction::Shading
(collapsible_match). Fold the shading lookup into the match arm guard so the Ubuntu
xtask ci gate can pass on the same toolchain Actions pins.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 02:01:49 +07:00
bflatastic b5b9264d71 Revolve: the Cap option is saved as cap, as Extrude & Bevel and Inflate save it 2026-10-16 01:36:23 +07:00
alpha-rock f703e6bd85 M9: smooth Revolve rendering and default Cap
Use automatic vector smooth shading without a separate control.
Enable Cap by default and keep continuous rim curves and bounded seam joins.
Integrate the shared dialog with the other three 3D effects.
2026-10-16 01:36:23 +07:00
dotCooCoo 433d78284d Importers: PDF streams written inside arrays and dictionaries are checked before they are decoded
A PDF is refused when a stream in it would decode to more than 256 MB
or has decode parameters the reader can't apply, but the check looked
only at the file's objects. A stream written directly inside an array
or a dictionary, such as the /Function of a shading, was not checked.
On main at b12a52f7, a calculator function holding 257 MiB of deflated
spaces opened after 3.0 to 4.3 s with a peak of 550 to 818 MB when it
was written in an array, in a dictionary or as a shading's function.
The same stream as an object of its own was refused in 81 ms.

hostile in pages.rs now runs unsafe_stream on every stream that the
walk of the file's objects meets, at any depth in arrays and
dictionaries, before it decodes the stream as a calculator function.
It refuses such a file with the same message. The walk is the one the
nesting check made before (64 deep at most).
2026-10-16 01:36:23 +07:00
bflatastic cf98b7f368 PDF import: one helper for the dictionary values the reader can parse 2026-10-16 01:36:22 +07:00
dotCooCoo 9e90464f20 PDF/.ai import: dictionary values the reader can't parse no longer crash the ink and font scans
The reader's Dict::entries unwraps every value it reads, so it panics on
a value it cannot parse, such as the name /#zz or the number 12-.5
(vendor/hayro-syntax/src/object/dict.rs:104 at craft-libs ca50d63b).
Besides the nesting check in pages.rs, two loops of the import call it:

- Colors::visit (import_color.rs) looks for the file's Separation and
  DeviceN spaces when a page paints in one. craft_pdf's interpret_page
  catches the panic, and the import fails with "PDF page interpretation
  failed internally".
- Walker::note_fonts (import_scan.rs) notes the fonts of each page. The
  import does not catch that panic.

On main the nesting check in pages.rs panics on such a value first
(#2133 fixes it); with it fixed, the import reaches these two loops.

Colors::visit now reads the values with Dict::keys and get_raw, sorted
by key as before, and leaves out a value the reader cannot parse.
note_fonts reads the keys and looks each font up by its key, as before.
2026-10-16 01:34:10 +07:00
dotCooCoo a69ddea930 Importers: a PDF dictionary value the reader can't parse no longer crashes the nesting check
The shared PDF reader (craft_pdf::syntax, craft-libs ca50d63b) indexes
a dictionary by skipping its values. Its skip accepts some values that
its parse refuses, such as a name with a broken # escape (/#zz) and a
number such as 12-.5. Dict::entries unwraps the parse of each value
(vendor/hayro-syntax/src/object/dict.rs:104). The check in pages.rs
that refuses functions and color spaces built on one another without
end (nests_without_end) calls entries on every dictionary of a file
when the file opens. So document.pdfInfo, PDF import,
clipboard.importPdf and loading a .ai panicked on such a file, and
three import fuzz tests fail on main with PROPTEST_RNG_SEED=1182 and
PROPTEST_CASES=20000.

The check now reads a dictionary's values with keys and get_raw and
leaves out a value that doesn't parse.

A test in crates/pdf puts each of those values in a file with a plain
function, which opens, and in a file with a function that stitches
itself, which is still refused.
2026-10-16 01:34:09 +07:00
skylar taylor-barrick 2ecba1fb9c PDF.R1: reconcile shared reader with current writer 2026-10-14 03:59:03 -06:00
skylar taylor-barrick 52960faf63 PDF.W1: reconcile shared writer with current main 2026-10-14 01:23:19 -06:00
skylar taylor-barrick e01a65d8a9 PDF.R1: integrate shared reader with bounded nesting checks 2026-10-13 21:58:01 -06:00
bflatastic f1fd8e85e8 PDF: krilla without its text shaping, so rustybuzz and ttf-parser don't ship (#1730)
krilla's default `simple-text` feature is the only thing that brings in rustybuzz and
ttf-parser, which RustSec lists as unmaintained. The PDF writer places glyphs itself, so the
workspace takes krilla with `raster-images` only; the one test that writes files with
`draw_text` turns `simple-text` on as a dev-dependency feature.
2026-10-14 10:13:45 +07:00
bflatastic 991ce2c635 Importers: PDF streams the reader would fail on are refused (#1700, #1701)
hayro (0.7.2 and 0.8.0 alike) inflates Flate and LZW data with no ceiling, so a few megabytes
of zeros ask for gigabytes, and applies a predictor before checking its parameters: Columns 0
divides by zero (a panic, an abort on the web), a huge Columns allocates a row that large (an
abort everywhere). Both happen inside the reader, before any of our limits.

A file is refused ("a stream in it would decode to more than 256 MB, or its decode parameters
can't be applied") when a stream's decode parameters ask for a predictor the reader can't apply,
or when it decodes past 256 MB through Flate, LZW, run-length and the ASCII encodings before
them, decoded here with a ceiling (only streams large enough to get there are decoded). Every
object's stream is looked at once the file is open (decrypted, references followed), with the
inline images of anything that can be content; object and cross-reference streams, which the
reader decodes while it opens the file, are looked at first in the file's text.
2026-10-14 10:13:45 +07:00