* SAVEAS portableFonts: Windows fonts for Linux text style fonts
A text style using a Linux TrueType font (Liberation, DejaVu, Open Sans)
shows in AutoCAD on Windows with a substitute it picks. A save now names
those styles on the command line (and as `nonPortableFonts` in the
result), and SAVEAS {"portableFonts": true} writes the Windows equivalent
in the file (Liberation -> Arial, Times New Roman, Courier New; DejaVu
Sans -> Verdana; Open Sans -> Arial) while the drawing keeps its own
fonts. Narrow and condensed families have no Windows stand-in (Arial
Narrow comes with Office) and are kept and named. QSAVE takes no SAVEAS
options.
Tests: stand_in_tests in cmd/file.rs, tests/save_portable_fonts.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Portable fonts kept in the file, applied by the writer
Reworked after review. SAVEAS `portableFonts` writes a copy of the drawing
with `Drawing::portable_fonts` set, and the DXF/DWG writer turns each
style's Linux font into its Windows font, keeping the style's own font in
the CADCraft FONT xdata already used for the built-in font. Reading the
file gives the Linux fonts back (unless the font was changed since, in
AutoCAD); the drawing itself is never changed.
It applies to that save: a later QSAVE writes the drawing's fonts and
says so, naming the styles and SAVEAS portableFonts. The family table
moves to `cadcraft_fonts::windows`, used by the writer and the note.
Styles from an xref aren't saved and aren't named. The parity docs are
left to the maintainers (CLAUDE.md).
Tests: crates/io/tests/it/portable_fonts.rs (DXF, DWG, a font changed in
AutoCAD), crates/engine/tests/save_portable_fonts.rs, windows.rs tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Portable fonts: match the Windows font by file name
A STYLE whose Windows font carries a path (C:\Windows\Fonts\…) still
gets its own font back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Portable fonts: say the font list again only when it changes
A save still returns nonPortableFonts, but the command line note isn't
repeated on every QSAVE of the same drawing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Thai text: stacked marks, Thai fonts first, MTEXT word wrap, Windows fonts under WSL
Thai text in DWG/DXF files decoded correctly but laid out badly. Vowels and tone marks
stacked on one consonant collided (ดิ์), because only right-to-left text went through the
shaper.
- Thai lines go through the harfrust shaper (complex::needed). Shaped left-to-right glyphs
keep their characters (TextGlyph) for PDF text.
- Thai fallback fonts: Cordia, Angsana and Browallia first (Thai drawings are laid out with
their widths), then IBM Plex Sans Thai, Noto Sans/Serif Thai and the fonts each OS ships.
- The web build embeds the craft-fonts Thai regular faces.
- MTEXT wraps a run of Thai between words when the run is too wide for the column by itself.
Text that fits at its spaces keeps those line breaks, so existing drawings lay out as
before. Word breaks come from icu_segmenter's LSTM model (about 410 KB). ICU's dictionary
segmenter was not used: after loanwords it breaks inside words, even between a consonant and
its tone mark. Breaks inside a character cluster are dropped, and results are cached.
- Under WSL the font scan also reads /mnt/c/Windows/Fonts, at the lowest priority, so drawings
find the Windows fonts they name (Cordia New). The scan reads entry types from the
directory listing instead of a stat per file.
* Thai fallback: match older Angsana and Browallia file names; document touched functions
Today's Windows ships Angsana New and Browallia New as angsana.ttc and browalia.ttc. Older
Windows used single faces (angsa.ttf, angsab.ttf, browa.ttf, ...), which still turn up when
fonts are copied to Linux machines. The Thai fallback list now matches the shorter stems
`angsa` and `browa`, which cover both. Review feedback on #2591.
Doc comments for scan, plain_lines and the paragraph layout's word_width and place.
* MTEXT: wrap a Thai run after a first-line indent or a tab
A Thai run that fit a line at the left indent, but not the room left after a first-line indent
or a tab stop, wasn't split between its words: the first line of a paragraph can't wrap, so it
ran out of the column. An empty line now measures the run from where it starts. Review feedback
on #2591.
Also restores the space in `image = {` in Cargo.toml.
* Font scan: take the WSL font folder out of this change
Thai text doesn't depend on it: with no Windows or system fonts, the craft-fonts Thai faces
(Noto Sans Thai, IBM Plex Sans Thai) stack marks and wrap MTEXT the same way. Reading the
Windows host's /mnt/c/Windows/Fonts under WSL is a separate decision, so the scan is back to
main's: Linux system and user font folders only.
LEADER had only its JSON form, so a script's LEADER drew nothing. This
adds the command-line command with AutoCAD 2024's prompts: the points,
Format (Spline/STraight/Arrow/None) and Undo, then annotation lines, or
the options None, Block, Copy, and Mtext and Tolerance (which come back
to the first line at the command line, as in accoreconsole).
Measured, and followed:
- A hook line, a horizontal last segment DIMASZ long, is added when the
last segment is straight and more than 15 degrees off horizontal and
text follows: 14.9 and 15 degrees keep the end, 15.1 hooks. A spline,
no annotation and a block take none.
- The text sits DIMGAP past the end on the side the leader runs to:
above it, bottom-attached, with DIMTAD on; centred on it with DIMTAD
0. Sizes are DIMSCALE times the style's, with SETVAR overrides.
- A copied text keeps its contents, height and width, is centred on its
line, and with DIMTAD on the leader ends half the text height lower.
The JSON form now draws through the same code. The drawing model has no
link from a leader to its annotation, so the two are separate objects.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* -WBLOCK at the command line, as AutoCAD runs it
WBLOCK had only its JSON form, so a script's -WBLOCK did nothing: the
objects it should write and erase stayed in the drawing. This adds the
command-line command, with AutoCAD 2024's prompts:
- "Current settings: Object conversion=Delete", then the output file. A
name without .dwg or .dxf gets .dwg. A DXF file asks for its decimal
places first. A file that exists asks "... already exists, do you want
to replace it? [Yes/No] <N>"; No ends the command.
- An existing block by name, "=" (the block named as the file), "*" (the
whole drawing), or Enter for objects. A name that isn't a block is
answered Block "NAME" not found. and asked again.
- For objects: the base point (with mOde), then the objects, which are
written and erased. AutoCAD erases them whatever mOde says, and keeps
Delete as the setting. Nothing selected writes nothing.
Its JSON form, -wblock, erases what it writes. WBLOCK's JSON form moves
into the new module unchanged, sharing the writer. The web build refuses
-WBLOCK rather than erase objects it can't save.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* -WBLOCK's JSON form: nothing to write writes no file
With no handles, an empty selection or only handles of objects that
aren't there, the JSON form wrote an empty drawing. It now refuses with
"no objects selected" before writing or erasing anything, as the command
line writes nothing when nothing is selected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* -WBLOCK: only Yes replaces an existing file
At "... already exists, do you want to replace it? [Yes/No] <N>", any
text starting with "y" counted as Yes, so "yodel" replaced the file, and
any other text ended the command. Now only the Yes keyword replaces it,
No and Enter end the command, and anything else gets "Invalid option
keyword." and the prompt again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* WBLOCK and -WBLOCK write their file through a temporary file
Replacing an existing file truncated it first, so a write that failed
part way lost it. The file now goes through atomic_write::write_atomic,
as SAVE, EXPORT and autosave write theirs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* -ATTDEF at the command line: attribute modes, AFLAGS, and the text's prompts
-ATTDEF asked for the tag first. A script's mode answer ("_V", "_C",
"_P") became the tag; the attribute kept no mode; AFLAGS never changed.
The definition was made at the start point, so the height and rotation a
script gives next ran as commands. It now asks as AutoCAD 2024 does:
- "Current attribute modes:" from AFLAGS, then "Enter an option to
change [Invisible/Constant/Verify/Preset/Lock position/Annotative/
Multiple lines] <done>", each answer toggling a mode, kept in AFLAGS.
- The tag (spaces taken, upper-cased; an empty one gets "Tag cannot be
null"), the prompt and default value, or a constant attribute's value.
- "Current text style: ...", the point with Justify and Style, the
height and the rotation.
The definition takes Invisible, Constant, Verify, Preset and Lock
position from the modes; a constant attribute stays in its block.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* -ATTDEF: a typed height is a distance, as TEXT takes it
The height was read with str::parse, so 1'6" (or any distance TEXT
takes) was refused as "Requires a positive height." It now goes through
the shared distance parser, `number`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* -ATTDEF: a wrong answer at Justify is asked again
A wrong answer at "Enter an option [Left/Center/...]", or Align or Fit
(not available here), went on to the point prompt with the justification
unchanged, so a script's next answers landed one prompt off. Only a
justification goes on to its point now; anything else is asked again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* -ATTDEF test: the modes start from AFLAGS, not from the default
The other cases start from AFLAGS 16, which is also the default, so
reading the modes from the drawing went untested. This starts from 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Window opens centred; the Chinese menus have no English entries left (#1129)
The main window opened where the system cascades new windows, off to the
top-left on wide screens; it now opens centred. 26 menu and command
labels the Simplified Chinese update missed (Multileader > Collect,
Block Editor, dynamic blocks, Express Tools, Customize, ...) are
translated, and a test keeps every menu, command, tool and snap label
translated in Chinese. The start page question in the issue is left to
the owner.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* File > Open Recent's Clear Recent Files is translated in Chinese (#1129)
The Open Recent submenu is built at run time, so the Chinese completeness
test never saw its labels, and Clear Recent Files showed in English. The
label is now menus::CLEAR_RECENT, the test collects it with OPEN_RECENT,
and zh.tsv translates it. The other runtime-built menu labels (context
and tab menus) are all in zh.tsv already.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(zh): translate Change and Units, which landed after this branch (#1129)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
* Add -ARRAY, the classic command-line array, as AutoCAD lays it out
Rectangular arrays move the copy in row r and column c by (c * column distance, r * row
distance), asking for a distance only when there is more than one row or column; polar arrays
space n items 360/n apart around a full circle and fill/(n - 1) otherwise, turning the copies or
moving them with the group 10 point of the object selected last. The copies are separate
objects; the type asked defaults to the one used last.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* -ARRAY: refuse an unknown type and oversized arrays before making transforms
The JSON form errors on a type other than rectangular or polar, and checks MAXARRAY before the
copies' transforms are made; the command line bounds the columns by the rows (CodeRabbit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Turkish catalog: fix wrong registers and resolve term collisions
Proofreading by a native speaker. 32 rows, wording only:
- From/To were e-mail words (Kimden/Kime) -> Kaynak/Hedef
- ViewCube Home was Giris (login) -> Baslangic
- untranslated "or" in the zoom scale prompt -> veya
- BREAK vs Cut both "Kes" -> BREAK family uses "Kir"
- TRIM vs CLIP both "Kirp" -> TRIM uses "Buda"
- spline vs curve both "egri" -> spline stays "spline"
- plot style/preview/window said "Cizim X" (= drawing) -> "Cizdirme X"
- Elevation vs Height both "Yukseklik" -> Elevation is "Kot"
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add GULERTOLGA to contributors
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Add Vietnamese (vi) to the interface languages
vi.tsv covers every key of the other catalogs and the three strings none of them has yet, with
one plural form. Its tone-marked letters (U+1EA0-U+1EF9) aren't in egui's bundled faces, so the
Vietnamese interface takes the first system interface font that has them (Segoe UI, Arial, SF,
DejaVu Sans, Noto Sans) and otherwise stays English and says why, as Korean does without Hangul.
AI-assisted; native-speaker review pending.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Vietnamese: the terminology Vietnamese AutoCAD users speak
Polyline, Pan, Offset, Spline and Grip stay in English as they are said in the office; Modify
is Hiệu chỉnh, LINE draws an Đoạn thẳng, Explode is Phân rã, Linetype Loại nét, the plot style
table Bảng nét in, Polar and Object Snap Tracking Dò theo góc / Dò theo điểm bắt, Settings Cài
đặt; the snap modes Endpoint, Midpoint and Nearest are Đầu mút, Điểm giữa and Điểm gần nhất.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Vietnamese: name the language by the font loaded; Arabic's own message
The picker writes Tiếng Việt only when the interface's loaded system font has its letters
(another interface loads the first installed candidate, which may lack them). The font choice is
one function with a unit test, gated off wasm with its callers. Choosing Arabic without an
Arabic font no longer gets the Chinese message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add CHANGE, changing objects by a change point or their properties as AutoCAD does
A change point moves each line's nearer end to it (level or plumb under ORTHOMODE), passes a
circle through it, and places a text, attribute definition or block reference by it when that is
the one object selected; otherwise "Change point ignored" and the object's questions follow, in
selection order. Properties is CHPROP's loop with Elev, applied as the command ends. Objects on
locked layers are left out, through selection helpers now shared with CHPROP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CHANGE: refuse a JSON call without a readable point or elevation
A `point` or `elevation` that can't be read is an error, and so is a call with neither, instead
of changing nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* CHANGE: Elev on every kind AutoCAD sets it for; Aligned and Fit text; rotation default
Measured in AutoCAD 2024: Elev also sets the z of ellipses, multiline texts, solids and flat
3D polylines (a 3D polyline between heights keeps them), and of hatches, which stop being
associative ("Hatch boundary associativity removed."). Aligned text isn't asked its height or
rotation and Fit text its rotation, their two points setting them. The rotation default shows
as the drawing measures angles (ANGBASE 90 shows a text at 0 as 270.00). Shared z is compared
to rounding, and the radius and height prompts say what they need.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* -INSERT: ATTREQ, Preset and Verify attributes as AutoCAD asks them
ATTREQ 0 inserts blocks with their attributes' default values without asking (a session
setting, as AutoCAD keeps it in the registry). Preset attributes are never asked for, "Enter
attribute values" heads the questions, and Verify attributes are asked again after "Verify
attribute values", each with the value typed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* -INSERT: a click doesn't answer a Verify attribute
Only typed text or Enter answers "Verify attribute values"; anything else asks again (CodeRabbit).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
RECTANG's polyline now starts at the first corner and runs along X to
the other corner, as AutoCAD 2024 draws it; each chamfer cuts the edge
into its corner by the first distance and the edge out by the second,
and fillet arcs start on the edge into their corner (clockwise outlines
get negative bulges). RECTANG keeps its chamfer, fillet, width,
elevation, thickness and rotation for the next rectangle, lists them as
"Current rectangle modes:" and offers the rotation as "<50.50>"; chamfer
and fillet clear each other. A circumscribed POLYGON starts at the end
of the picked edge counter-clockwise from its midpoint.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
AutoCAD 2024's SCALE is a uniform 3D scale about the base point: every Z
and elevation scales about the base point's Z (the current ELEVATION for a
point given without one) and every thickness by the factor. cadcraft
scaled X and Y only. The JSON form also takes a Z with the base point.
OFFSET's copy now keeps the source's height: a line's end Zs, a circle's
or arc's centre Z, a polyline's elevation. A circle's or arc's copy has no
thickness and a polyline's keeps it, as AutoCAD makes them.
Grip scaling, ALIGN's scale option and MOCORO stay 2D: their base point is
often a grip or snapped point whose Z cadcraft doesn't keep.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add -UNITS; show due surveyor bearings as one letter
-UNITS asks for the length format and precision, the angle format and
precision, the direction of angle 0 and whether angles turn clockwise,
with AutoCAD 2024's prompts, listings, defaults and messages. As in
AutoCAD, each answer is kept as it is given, and choosing another format
resets its precision to 4. The direction is typed in the chosen angle
format and turns as ANGDIR says.
Surveyor's units show a bearing that is due north, south, east or west as
that letter ("E"), except to whole degrees ("N 90d E"), as AutoCAD's
ANGTOS does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* units: read a due bearing typed as one letter (E, N, W, S) in surveyor's units
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Refuse mirror lines whose points are closer than the geometry EPS
Found while testing crates/engine/src/cmd/modify/transform.rs:run_mirror (coincidence check `a.near(b, 1e-12)`), with crates/geom/src/mat.rs:43-47 (Mat3::mirror)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* mirror: the interactive prompt also refuses points closer than the geometry EPS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Found while testing crates/engine/src/sysvars.rs:~404-425 (generic fallthrough in set: String branch, then the `Some(HVal::Real(_))` arm)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/cmd/utility/calc.rs:~355-360 (call, "min" | "max" branch using f64::min/f64::max)
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
* wblock: write the source drawing's $INSUNITS and $MEASUREMENT
run_wblock built the written drawing from Drawing::new_imperial() and
never copied the source's units, so every WBLOCK file declared inches
($INSUNITS 1) and imperial measurement ($MEASUREMENT 0) whatever the
source: a block written from a metric drawing came back at 0.0254 of
its size in any program that honours $INSUNITS on insert. The written
drawing now takes the source's values for both variables, as AutoCAD's
-WBLOCK does. A unitless source ($INSUNITS 0, kept by the reader per
#1209) writes unitless.
Co-Authored-By: GLM-5.3 <noreply@z.ai>
* wblock: start from the source drawing's header and styles
Clone the source header (resetting $INSBASE as before) and copy the
multileader, table and multiline styles and the annotation scale list
along with the other tables, so a block written from a metric drawing
keeps its text size, current dimension style, limits and scales, not
the imperial template's (review on #2023).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wblock_units: each WBLOCK call writes into its own TempDir
The two tests shared one temp path and raced when cargo ran them in
parallel (review on #2023).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: GLM-5.3 <noreply@z.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): dash only the visible part of a highlight, so zooming far in doesn't freeze (#2299)
With the pointer on an object, its rollover highlight is dashed, and draw_highlight dashed the
object's whole outline in screen space, off-screen parts included: one shape per 10 points of
it. Zoomed far in, a short line is billions of points long, so the app froze and then ran out
of memory, which is the freeze and crash #2299 reports. Wheel-zooming a circle and two lines
with the pointer on them reached 7.9 million shapes in one frame at a view height of 0.001
before a 1 GB allocation failed.
The highlight now clips each outline to the view, plus a margin, in world coordinates first
(precise when zoomed far in), and dashes only what is left. Each visible piece starts its dash
pattern at its distance along the whole outline, so dashes don't crawl along a line that runs
off screen while the view pans. The two dashed rectangles drawn in screen space, a layout's
printable area and the crossing window, clip each edge to the painter's clip rect for the same
reason. The same zoom now stays under 1,300 shapes per frame down to the zoom limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ui): a clipped rectangle edge keeps its dashes where the whole edge puts them
dashed_rect clipped each edge to the screen and then started its dashes at the clip boundary,
so a printable area or crossing window running off screen had its dashes move with the view,
and its bottom and left edges were drawn in reverse. Each edge now keeps its direction and its
dashes start at their distance along the whole edge, as the highlight's outlines do. The
zoomed-far-in app test also checks that the line really is hovered, so it can't pass with no
highlight drawn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: austin62jake <340374338+austin62jake@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
rstar 0.13 can panic when inserting after removals in a bulk-loaded tree, and picking runs outside the command guard. The patch now runs on a copy under catch_unwind and falls back to a bulk load. The web build can't catch a panic, so it always bulk-loads. Diagnosis, reproduction and fix from #2468.
Fixes#2468
Co-authored-by: octopoulpos <32617732+octopoulpos@users.noreply.github.com>
plan.dwg and plan.dxf in one folder shared plan.bak. A DWG keeps <name>.bak, as AutoCAD names it, and any other drawing appends .bak. The naming comes from #1006.
Co-authored-by: Filippo Reimer <49290121+filforce95@users.noreply.github.com>
* Validate new linetype names in the rename command
Found while testing crates/engine/src/cmd/props.rs:1154-1200 (run_rename, "linetype" branch)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* rename: a linetype's new name is stored trimmed, as -RENAME types it (CodeRabbit)
---------
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Since #2518 the write syncs the folder after the rename, so the autosave
file exists while the write is still running. Until it finishes the drawing
is busy and the next change isn't planned, so the test's tick() returned
None. Autosaver::writing() says whether a write is still running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(dxf): add deterministic codec and drawing round-trip matrices
Exercise typed tags and model-space LINE/CIRCLE/TEXT through in-memory ASCII and binary DXF at every save version. Compare both reads with the original model and check independent handwritten byte/entity fixtures. Keep desired-behaviour tests for extrusion and ByBlock plot-style loss explicitly ignored until fixed. No production or docs changes.
* test(dxf): independently verify common entity properties
* test(io): add a broad DWG preservation matrix with focused pending regressions
Share deterministic Drawing cases and exact assertions between DXF and DWG without reducing DXF coverage. Run representable DWG cases at all six save versions, twice in RAM. Test R2000 color/transparency and the single TEXT elevation as explicit format limits. Keep six independently reproduced loss regressions ignored without weakening comparisons. No production or docs changes.
* test(io): close binary-width and referenced-linetype coverage gaps
Check every integer group range against independently constructed binary payloads, catching shared reader/writer width mistakes. Resolve linetypes with Drawing lookup and compare layer linetypes too. Add missing/changed-reference self-tests and inherited-pattern DXF/DWG round trips. Reproduced the review counterexamples before and after; full CI passes with two nextest threads. Test-only changes.
* test(io): expand DXF and DWG matrices across entities and drawing collections
Add basic coverage for all 29 modeled entity kinds, block graphs and attributes, all ACI colors, richer properties/XDATA, sparse handles, headers, symbol tables, views, groups, layouts and foreign payloads. Preserve typed fields and authored references through two RAM saves. Add independent DXF groups and native DWG bridge checks, plus explicit refusal-or-preservation tests for unknown entities. Keep confirmed discrepancies and unresolved preservation contracts as focused ignored tests; native linetype angles and silently dropped unknown DWG entities are independently reproduced. Version limits and foreign object handle remapping are explicit contracts, not blanket normalization. Tests only; no production or docs changes. Full CI passes with two nextest threads: 2777 passed, 27 skipped.
* test(io): cover bounded property domains and targeted field interactions
Independently check all ACI values and transparency percentages, native
layer/style flag combinations, and literal large handles. Add shared DXF/DWG
cases for alignment with mirrored styles, inherited versus explicit zero
properties, repeated nested attributes, and integer-boundary handles.
Keep exhausted handle allocation separate: DWG refuses the save; a focused
ignored DXF regression reproduces silent loss of all nine model entities.
No production or documentation changes.
Validation: full cargo xtask ci with NEXTEST_TEST_THREADS=2 passes all seven
gates (2792 passed, 28 skipped). The pending DXF regression fails explicitly
when run with --ignored.
* test(dxf): separate chunk conformance from binary reader tolerance
Address PR #2237 review: shared exact-tag round trips only use hex chunks
within the documented 127-byte group limit. Independently inspect ASCII
and binary writer bytes, reassemble payloads, and verify following-record
framing. Keep full 0..255 length-byte read tolerance in handwritten fixtures,
not as a claim of conforming writer output.
Add two focused ignored regressions for oversized ASCII/binary output,
reproduced with 128-byte payloads. They extend existing issue #1025.
No production changes.
Validation: cargo xtask ci passes all seven gates with nextest 0.9.150
and two test threads (2782 passed, 30 skipped). Both new pending checks
fail explicitly when run with --ignored.
* test(io): compare DWG dimension blocks through their references
Anonymous dimension-block suffixes may change between drawing sessions (Autodesk documentation; ODA DWG section 20.4.52.1). Resolve their names through preserved dimension identities and keep strict checks for cache content, entity handles, block properties and reference integrity.
Enable the pending dimensions test across all six DWG versions and two saves. Add a paired-cache fixture and comparator controls rejecting swapped/merged/dangling references, geometry and property changes, stale caches, and named-block renaming. Leave the shared comparator and DXF contract unchanged.
Validation: cargo xtask ci (all seven stages; 2786 passed, 29 skipped); roundtrip matrix (211 passed, 26 ignored).
Resolves the preservation-contract decision in #2257 (tracker #2238, B4); no production codec fix.
* test(io): specify WIPEOUT explicit closure on roundtrip
An implicit WIPEOUT boundary gains one explicit closing marker in DXF/DWG. Compare against independently authored explicit boundaries in both winding directions across all six versions and two saves; retain strict field and resave comparisons. Enable the two pending closure checks without changing the shared comparator.
Distinguish this valid file representation from the grip-edit defect: the latter is fixed separately in PR #2284, with engine, outline and rendering regressions. No production change in this test-only branch.
Validation: cargo xtask ci, all seven stages passed (2788 passed, 27 skipped); active roundtrip matrix 213 passed; remaining ignored checks reproduce 24 drawing failures and two codec failures. One unrelated HTTP control-server test failed transiently, then passed in isolation and in the full rerun.
Related #2256 and tracker #2238 B3.
* test(io): expose multiline ATTRIB alignment corruption
Keep the original origin witness and add distinct nonzero TEXT/MTEXT anchors. An active R2018 ASCII/binary two-save check verifies world-space multiline geometry and value edits; this does not excuse corrupted fallback TEXT fields.
Retain the strict pending #2255 check and add comparison of independently downgraded R2000/R2004 geometry. The observed Left -> Center change is a real reader error, not permitted alignment-point canonicalization. The reader hunk from existing PR #1942 makes the extended strict check pass in an isolated experiment; no competing production fix is included here.
Validation of this test-only tree: all seven cargo xtask ci stages pass (2789 passed, 27 skipped); active matrix 214 passed; remaining 24 drawing and two codec diagnostics still fail. An unrelated HTTP control-server test failed transiently, then passed alone and in the complete rerun.
Related #2255, #1942 and tracker #2238 B2.
* test(io): separate ATTRIB value and legacy-placement guarantees
Assert exact saved ATTRIB values, read_as and authored embedded MTEXT contents before and after value edits. A mutation test rejects corrupt fields, including a value/read_as pair whose rendered geometry remains unchanged.
Make legacy placement an independently runnable pending regression instead of a callback behind the failing field comparator. Compare direct R2000/R2004 exports with exports after one/two R2018 roundtrips, both encodings in each stage, and verify that the primary single-line fallback actually survives. Keep all three contracts local and distinct; remove the old cross-module callback.
The new legacy test fails directly on world-space geometry with the unchanged reader. Both pending ATTRIB tests pass with the reader hunk from existing PR #1942 in an isolated experiment (548 IO tests also pass). No production fix is included.
Validation: all seven cargo xtask ci stages pass with NEXTEST_TEST_THREADS=1 (2790 passed, 28 skipped). Earlier parallel runs and one isolated retry hit pre-existing HTTP/busy control-server failures; no exclusions or test changes. Active matrix 215 passed; all 25 drawing and two codec pending diagnostics still fail.
Addresses review of #2237; related #2255, #1942 and tracker #2238 B2.
---------
Co-authored-by: cloudj <jonbahnes@gmail.com>