Commit Graph
72 Commits
Author SHA1 Message Date
dotCooCoo 5be501e47f DXF: an open path's fill outline is closed, as the fill is
A filled open path wrote its outline as an open polyline, so with fill
hatches left out (and in R12, and in CNC / CAM) the area lost its closing
edge. Fill outlines now close each open subpath that can enclose an area;
strokes of the same path stay open.
2026-11-11 22:09:14 -08:00
dotCooCoo f1251af6e4 DXF: export options leave placed images and fill hatches out, and CNC / CAM turns both off
DXF Options gets Include Placed Images and Include Fill Hatches, and
document.exportDxf and document.export {format: dxf} take
includeImages and includeHatches. Both are VectorCraft's own options
and default to true, so Custom exports write what they wrote before.

- includeImages: false writes no IMAGE entity, IMAGEDEF, reactor or
  linked file for a placed image, and does not decode its pixels.
- includeHatches: false writes each filled area's outlines without the
  solid HATCH after them.
- Each adds one note to warnings when it left something out. R12,
  which has neither, keeps its own notes.
- The CNC / CAM preset sets both to false, so its drawing holds the
  cutting paths alone; the options given still override it, and
  changing either checkbox shows Custom.

Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md.
2026-11-11 21:10:30 -08:00
dotCooCoo 925300c159 HP-GL: a .plt file needs at least one instruction the reader knows
A file read as HP-GL by its extension that holds only instructions of
another set (`GC5;`) is now refused as "not an HP-GL file" instead of
opening as an empty plot. At least one of the instructions the check
reads must be one the reader knows.
2026-11-11 18:05:05 -08:00
dotCooCoo 64988aaa49 HP-GL: a .plt file opens when its first instruction is SI, DI or another known one
A file read as HP-GL by its extension no longer has to start with an instruction plot files usually start with. Any instruction the reader knows, or one of another set that a semicolon ends, may come first; the rest of the check still refuses files that are not HP-GL. Telling HP-GL apart by content alone keeps the stricter check.
2026-11-11 17:27:49 -08:00
dotCooCoo 74ea25cd6c File formats: HP-GL (.plt) plot files open and place
File › Open and File › Place refused plotter files: HP-GL/2 files
(.plt, .hpgl, .hgl, .hpg) were not a format VectorCraft reads.

The format table gets a read-only HP-GL format, read by a new
vectorcraft_cad::hpgl module. Each line the pen draws becomes a stroked
path in its pen's color and width, with the line type as a dash pattern
and the line attributes as caps and joins. Circles, arcs, wedges and
Bézier curves are drawn as curves. Filled and edged rectangles, wedges
and polygons (polygon mode) become filled or stroked paths, and encoded
polylines, scaling points, user units and rotation are read. Plotter
units (1016 to the inch, y up) become points, and the artboard holds
what the art paints. Place brings the art in as one group. Older HP-GL
files are read by the HP-GL/2 rules for the instructions the two share.

A file is recognized by its first instructions, after any printer
escape sequences and PJL lines, so a plot file also opens under an
extension no other format reads. VectorCraft's own rules where the
device decides: before the first SP the pen is pen 1, and the hard-clip
limits are 11 x 8.5 in, which P2 takes when the file sets no scaling
points. Labels, symbol-mode symbols and raster images are not drawn,
the input window does not clip, hatched fills are filled solid, and
later pages are drawn over the first; the warnings list each. Objects,
path points and pen widths are capped, and the import fuzzer covers
random, mutated and garbage plot files.

An importer plug-in that lists .plt, .hpgl, .hgl or .hpg now opens
those files only when document.open names it with `importer`.

Docs: docs/mcp.md, docs/file-format-parity.md,
docs/target-app-parity.md, docs/releasing.md.
2026-11-11 16:03:41 -08:00
dotCooCoo 5ac7d97d85 Guides: ruler guides are document objects on their layers
Ruler guides were a list kept beside the layers. Each guide is now an
object of its layer or sublayer, always directly in it and above the
layer's art. These behave as on main:

- Guides draw on screen only. Exports (SVG, PDF, PNG, PSD), Fit All,
  Select All, the art bounds, clicks and marquees, and a layer's
  Pathfinder and Transform effects leave them out. The Layers panel
  lists them as guide rows of their layer, the highest guide.list
  index first.
- Deleting a layer deletes its guides, and merging layers keeps their
  guides in the merged layer.
- The guide commands number guides as guide.list does, in the order
  they were made.

New: each guide takes an id from the document's object ids. An object
command given a guide's id fails with an error, and node.move cannot
put a guide into a group or out of its layers. A guide made while an
opacity mask, a symbol or a pattern is edited moves to the top
visible, unlocked layer when editing ends.

File format: native files still save the guides in the document's
guides list, in every file version, so older builds read them. Each
entry may now also carry the guide's id, name, locked and hidden
flags; older builds ignore those fields. Opening a file puts each
saved guide on its layer (on the top visible, unlocked layer when the
file names none or a layer it does not have), and each saved guide
counts against the object limit for opening a file. A ruler guide node
that a file holds below a layer's art moves up to the layer's guides,
and one anywhere else (in a group or another object, beside the
layers, or in a symbol) is removed. Ruler guides read from an .ai file
become guide objects on the layer they were on, numbered in the order
the file lists them.
2026-11-11 09:52:28 -08:00
dotCooCoo 7b8d5ba8f5 Type: Tab and Shift+Tab move a list item down and up a level
Squashed from #5037 onto main.
2026-11-09 03:39:05 +07:00
dotCooCoo cd8e98c6cd DXF: a CNC / CAM preset sets the export options for cutting software
Squashed from #5500 onto main.
2026-11-09 00:25:53 +07:00
dotCooCoo 1246c62095 DXF: paths of lines and circular arcs export as polylines with bulges
A path made of straight segments and circular arcs (a slot, a D shape,
a rounded rectangle, a pie) was written as one SPLINE, or as a flattened
polyline in R12. It is now one polyline whose vertices carry a bulge
(group 42) for each arc: an LWPOLYLINE in R14 and later, a POLYLINE with
VERTEX entities in R12 and R13, closed when the path is closed. Each run
of cubics on one circle becomes one arc. A path with any other curve
stays a SPLINE, and whole circles, arcs and ellipses keep their CIRCLE,
ARC and ELLIPSE entities.

- The circle test's tolerance is 5e-4 of the smaller of the radius and
  the curve's extent, so a shallow curve that is not circular is no
  longer written as an ARC.
- The drawing extents take each ARC's and each bulge's furthest points.

Part of #1299
2026-11-04 08:12:52 +07:00
Dylan M. Taylor ac391b0f85 DXF import: charge every dash copy and symbol name, name the block-work limit
Follow-up to #5288. A dashed compound path keeps its dash pattern on the compound node and on each loop, so the dash budget is charged for every copy. A symbol's name is charged against the text budget before the symbol is kept. Running out of block work now says so instead of reporting more than 2,000,000 objects.
2026-11-03 22:52:28 +07:00
bflatastic a9ce928f06 Format with rustfmt 2026-11-03 16:03:38 +07:00
Dylan M. Taylor cd1afaf1cf DXF import: budget dash lengths, copied text and names, and block items looked at
A drawing could make the importer keep far more than its own size: every dashed path held its
own copy of its linetype, every text object and symbol instance of an array copied its text,
font family or block name, and each placement of a block looked at all of its items, hidden or
not, without charging the work budget.

- Dash lengths kept across the import are capped; past the cap lines are drawn solid.
- Text, font family and block-name bytes copied into objects share one 64 MB budget, and a
  font family longer than 256 bytes falls back to the default.
- Each block placement charges one visit per item it looks at, and stops once the budget is spent.
2026-11-03 16:03:38 +07:00
Dylan M. Taylor ed4a663986 DXF export: charge symbol art as it is placed unclipped, test the segment budget
Count nine-slice symbol art with clips off, as place_symbol_art_unclipped
places it, so art crossing the guides is not over-charged; add a test that
the flattening segment budget stops a path independently of the point
budget; format.
2026-11-02 22:33:39 -05:00
Dylan M. Taylor f1d4969864 DXF export: bound curve flattening work and expanded symbol art
Flattening a path for DXF capped the points it kept but kept flattening every remaining curve
after the cap, each curve at a fixed tolerance however large it was. Symbol instances placed
inside symbols were cloned and walked once per instance with only the nesting depth bounded, so
a small symbol graph of empty symbols could fan out into billions of visits that write nothing.

- Flatten through vectorcraft_geom::flatten (a bounded number of segments per curve) and stop
  at the first curve past the point or segment budget.
- Charge each symbol instance's art to MAX_SYMBOL_NODES with charge_symbol_art before placing
  it, as the document does elsewhere; once spent, no further instances are expanded.
2026-11-02 20:43:17 -05: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
bflatastic 87979644cd DXF: unicode_escape gets its own doc comment, above escapes' 2026-11-02 14:36:14 +07:00
Max MittelstädtandGPT-5.6-Luna 9b93c609b9 Decode DXF surrogate pairs
Found while testing crates/cad/src/import/text.rs:80

Co-Authored-By: GPT-5.6-Luna <noreply@openai.com>
Co-Authored-By: GPT-6-Luna <noreply@openai.com>
2026-11-02 14:36:14 +07:00
dotCooCoo 427bce12b0 Character: Font Size can measure the cap height, x-height or ICF box
Squashed from #5026 onto main.
2026-11-01 11:08:55 +07:00
bflatastic f22caa6108 Fix #4938: Dynamic symbols keep per-instance appearance linked to the symbol 2026-11-01 04:53:38 +07:00
Don Omondi 22b1c6bc2e Fix #3085: DXF LAYOUT objects write only the group codes the DXF Reference defines
The PLOTSETTINGS part of each LAYOUT wrote the paper image origin as a point
(148 and 158), but the origin is two values at 148 (x) and 149 (y), and 158 is
not a PLOTSETTINGS code. Strict readers such as Autodesk Fusion can reject the
file. The ezdxf re-save the reporter found Fusion accepts differs from our export in
these objects, not in the geometry.

- 148/149 for the paper image origin
- shade plot mode, resolution level and DPI (76/77/78) from DXF 2004
- plot flag 1024 (model type) on the Model layout
- the plot style ACDBPLACEHOLDER without a subclass marker

Test r2000_and_later_files_have_the_structure_cad_apps_require checks the
required sections and table entries, handles below $HANDSEED, references
that resolve, and LAYOUT group codes.

# Conflicts:
#	docs/file-format-parity.md
2026-10-31 19:43:17 +07:00
dotCooCoo 958566468a DXF export: dashed polylines continue their linetype around vertices
A polyline with a linetype now sets group 70 bit 128, so the dash pattern runs on through its vertices instead of restarting at each corner. This applies to LWPOLYLINE and the R12 POLYLINE.
2026-10-31 19:43:15 +07:00
dotCooCoo d318a77560 DXF export: Alter Paths for Appearance outlines only strokes a CAD line can't draw
Alter Paths for Appearance wrote every stroke as its filled outline, so a
1 pt circle became two circles and a hatch, and an open arc became a
closed outline and a hatch: two cut lines where a CAM program expects
one. The option now outlines only the strokes a line can't draw: the ones
Preserve Appearance outlines, strokes wider than the widest lineweight
(2.11 mm, to the hundredth), and every stroke in R13 and R14, which have
no lineweights. Other strokes stay CIRCLE, ARC, SPLINE and LWPOLYLINE
entities with their lineweights; R12 keeps them as POLYLINEs with their
width.

A dashed stroke whose offset starts part way into its pattern is now
outlined with Preserve Appearance and with Alter Paths: a linetype always
starts at the start of its pattern, so the dashes were written in the
wrong place.
2026-10-31 19:43:15 +07:00
dotCooCoo d0a903d5d7 DXF export: ellipses and unevenly scaled circles are written as ELLIPSE
A closed path of four cubics that is the four-cubic circle under an
affine map (an ellipse, or a circle scaled out of proportion) is written
as a native ELLIPSE in R13 and later, with its center, major axis and
minor to major ratio. The check maps the path back onto the unit circle
and runs the existing circle recognition on it. R12 keeps the existing
fallback, and ellipses with a ratio under 1e-6 stay splines. The ratio
and end parameter are written in full so that a thin ellipse keeps its
size and a whole turn reads back closed.

Part of #1299.
2026-10-31 19:43:15 +07:00
dotCooCoo 06e8d711ae DXF export: an open path of one straight segment is written as a LINE
An open path with a single straight segment was written as a two-vertex
LWPOLYLINE (POLYLINE before R14). It is now a LINE entity from its start
to its end point. A stroke with a weight in R12, R13 and R14 keeps its
polyline, since a LINE has no width. Closed paths and paths with more
than one segment are unchanged.

Part of #1299.
2026-10-31 19:43:15 +07:00
el-j c0d7d22d42 Fix #4151: verify continuous angular direction for cubic arc conversion (#4151) 2026-10-29 09:57:34 +07:00
el-j a24326e046 fix(cad): verify circular subpaths complete a full turn without reversing (#4151) 2026-10-29 09:57:34 +07:00
el-j 7f48664fed test(cad): add regression test for closed sliver along a circle in DXF (#4151) 2026-10-29 09:57:34 +07:00
Max MittelstädtandGPT-6-Luna c714797e99 Reject fractional DXF integer values
Found while testing crates/cad/src/import/reader.rs:69

Co-Authored-By: GPT-6-Luna <noreply@openai.com>
2026-10-29 08:45:52 +07:00
NetterSilver 472cd812f9 Keep escaped backslash literal before U+XXXX in DXF MTEXT
Squashed from #4000 onto main.
2026-10-24 20:54:42 +07:00
NetterSilverandClaude Haiku 5.5 41c26c6997 Keep the denominator of DXF MTEXT stacked fractions written with ^
Found while testing crates/cad/src/import/text.rs:14-27 (mtext calls escapes at :14, which treats `^2` as a caret escape and drops it; the `\S` branch at :25 expects the `^`)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 20:45:46 +07:00
NetterSilverandClaude Haiku 5.5 2a4aafe346 Require four hex digits in DXF text \U+XXXX escape
Found while testing crates/cad/src/import/text.rs:73-74 (escapes)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 19:02:09 +07:00
dotCooCoo bc041e15ef Type: the Numbered List button numbers paragraphs 1, 2, 3
Squashed from #3960 onto main.
2026-10-24 19:02:08 +07:00
Dylan M. TaylorandClaude Sonnet 5.5 a7910edd64 Review fixes: keep later CAD layouts, ordered-stop gradient sampling, skip full-stroke previews, cache shift line
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 17:19:59 +07:00
Dylan M. TaylorandClaude Opus 5.5 20bba9ebae Security: linear work for shortcut review, gradients, recovery names, include cycles, batch references, DOCX style ids, CAD layouts and long strokes
CodeRabbit findings #2, #16, #31, #103, #114, #124, #162, #179, #182, #183, #185:

- Shortcut import review: example names for each code in one pass over the rows.
- Gradients: a midpoint stop is mixed from its two stops; freeform recolor samples its
  increasing positions with one advancing search (`Gradient::sample_increasing_with`).
- Recovery: image entry names are looked up by address, and dead ones swept when the map
  doubles, not on every image.
- Script checker: include cycles from strongly connected components (iterative Tarjan), not
  a reachability row per file.
- Batch step references copy at most 256 MB of values for one step.
- DOCX: style ids over 256 bytes are ignored where defined and where used.
- DXF: layouts keep at most 4 MB of names; a hatch's loops count against the node limit.
- Freehand and Liquify: past 1000 samples a drag previews every n/1000 samples, strokes keep
  at most 100,000 samples, and the stroke kept is previewed in full before it is committed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:19:59 +07:00
bflatastic a82bcfe7a5 DXF export: format the far-coordinate check 2026-10-24 04:49:07 +07:00
bflatastic 62333a2128 DXF export: the far-coordinate check compares with partial_cmp (also refuses NaN; clippy) 2026-10-24 04:49:07 +07:00
bflatastic 711b10ca59 Security: DXF export bounds (layer and style names, flattening, entity count, image size) and import dash patterns
Squashed from #3886 onto main.
2026-10-24 04:49:07 +07:00
bflatastic c2ad9f8bbb Type: emoji go with type that effects reshape and Expand Appearance
Squashed from #3879 onto main.
2026-10-24 04:49:06 +07:00
dotCooCoo 2a2e63856c Type: the Paragraph panel's Bullets button turns paragraphs into a bulleted list
VectorCraft had no lists. A bullet could only be typed as a character,
and the tab after it did not stop at the left indent. The reference app
turns paragraphs into a bulleted list from the Paragraph panel, the
context menu and Type › Bullets and Numbering › Apply Bullets.

text.setFormat takes list ("none" or "bullet") and bullet ("none",
"dot", "dash", "circle" or "square"). The layout draws the bullet before
the first line of each list paragraph that has text, in the font, size
and color of its first character, and starts the text at the next tab
stop, with the left indent counted as a stop. Wrapped lines start at
the left indent, and type on a path draws the bullet too. In a
horizontal right-to-left list paragraph the indents are mirrored: the
bullet hangs at the right, and the wrapped lines start at the right
where the first line's text starts after the bullet. The bullet is not
a character of the text. A paragraph whose left and first-line indents
are both 0 gets 18 pt and -18 pt when it joins a list. Return continues
the list, paragraph styles carry it, Reset Panel turns it off, and the
native file saves it only when it is set.

The Paragraph panel has a Bullets button with a list of the five
presets, and Type › Bullets and Numbering › Apply Bullets and the
context menu run type.applyBullets. Live SVG text writes the bullet's
character, and SVG writes type on a path with bullets as outlines. DXF
writes the bullet as a TEXT entity of its own, and legacy .ai export
warns that bulleted lists are left out. The caret, selections, Touch
Type and Show Hidden Characters skip the bullet.

Live SVG text of horizontally scaled type now writes each tab in a
tspan of its own. Before, the characters before a tab shared its tspan,
and its textLength made a viewer stretch them across the tab's gap.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-24 03:09:11 +07:00
bflatastic 07762956d2 Cap path elements in DXF import across the whole file
Squashed from #3800 onto main.
2026-10-24 01:43:58 +07:00
NetterSilverandClaude Haiku 5.5 609132987b Bound DXF import work for nested inserts that produce no nodes
Fixes #3572

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 01:43:58 +07:00
bflatastic 5455e9be07 Cap the total array insert copies in a DXF import
Squashed from #3789 onto main.
2026-10-24 01:43:57 +07:00
NetterSilverandClaude Haiku 5.5 23bc1c0027 Fix quadratic paper layout de-duplication in DXF import
Fixes #3595

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 01:43:56 +07:00
NetterSilverandClaude Opus 5.5 fb53e53eaf Stop tiny DXF spline weights from splitting every span to the cap
Fixes #3588

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:59:46 +07:00
NetterSilverandClaude Haiku 5.5 77d1f8901f Stop DXF hatch source-handle loop at first missing handle
Fixes #3590

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 11:38:06 +07:00
NetterSilverandClaude Haiku 5.5 b16f44d87f Make DXF import symbol-name uniquing linear
Fixes #3589

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 11:20:15 +07:00
dotCooCoo 4e4723699d Export: EPS, legacy .ai, EMF/WMF and DXF give one note for images that can't be decoded
EPS, EMF, WMF and DXF exports gave each image they left out because it
couldn't be decoded its own warning, "image '<key>' could not be
decoded and was left out", which names the internal key the document
stores the image under. A version 7 .ai said "an image that can't be
decoded or is too large was left out".

The four writers now give the note raster exports, PDF and the legacy
.ai flattening give, "an image couldn't be decoded and is missing from
the picture", once per export. In a version 7 .ai it is one line with
the note for flattened art. The other image notes in the same
functions no longer name the key: EPS's note for an image too large to
write, and DXF's notes for an image with no pixels (its rows in 23
catalogs are reworded) and one that could not be written. Legacy .ai
gives a separate note for an image that is empty or too large.

Docs: docs/mcp.md (EPS, DXF export, EMF and WMF, aiVersion "7").
2026-10-22 06:13:32 +07:00
dotCooCoo 68d8a9c755 Export: area type that Shrink Text to Fit scales down is written at the size it draws at
Shrink Text to Fit scales area type down at layout time, and the canvas,
PDF export and the exports that write type as outlines draw it at that
size. SVG export wrote the live text at its stored font size, letter
spacing and baseline shift, so a viewer drew it larger than the canvas
does. Legacy .ai saves wrote the stored size, leading and baseline shift
into the text objects, DXF export took the TEXT heights from the stored
size, and the CSS Properties panel gave the stored font size, letter
spacing and line height.

The text crate's scaled, which the layout scales shrunk text with, is
now public. SVG export, legacy .ai saves and the CSS Properties panel
take the type's styles from it at the layout's fit scale, and DXF
export multiplies the TEXT heights by that scale. Positions and
textLength in SVG still come from the layout.

Docs: docs/target-app-parity.md.
2026-10-21 16:55:16 +07:00
Mirko Kerezovic af48dda322 M4: address DXF circle export review (#1299) 2026-10-19 23:34:20 +07:00
Mirko Kerezovic b87cb157ed M4: export DXF circles and arcs as native entities (#1299) 2026-10-19 23:34:19 +07:00