- #2587: a frame turned by anything but a quarter turn keeps its size about its turned
centre; the box of its two mapped corners collapsed it (a 10 x 10 square turned 45° became
14.14 x 0, and the OLE header with it) for EXPLODE, REFEDIT, paste and array placement
(austin62jake, CodeRabbit). Moves, scales, mirrors and quarter turns keep the exact
corner mapping. `recase` reads only the rewritten digits and a few after them, not the
whole 310 line (austin62jake). The PR's rotated-frame test expects the kept size.
- #2585: an `output` that contradicts the path's extension is refused, as a device that
does is; it lost silently to the extension.
- #2608: the window's state is dropped when another dialog took its place (it missed a UI
pass), so reopening reads the drawing instead of an old unsent edit (CodeRabbit); TEXT and
ATTDEF get a one-line field (Enter commits), so a line break can't turn into a space on
save; the module comment says the shortcut menu opens it.
- #2471: an unnamed CLASS record keeps its place in the class list a DWG maps proxies by,
so the classes after it keep theirs; a proxy of the unnamed class is left out instead of
taking the next class's name (the ODA refuses such a DWG).
- #1626: the first *ACTIVE VPORT record is the caller's to say. A first record with a
usable view but an unusable tile kept no tile, so the next *ACTIVE record was taken for
the first and its view replaced the saved one (also austin62jake on #1626).
- #2584: a tilted LWPOLYLINE/HATCH run is flattened to at most a million vertices, then
its arcs stay chords (a hostile file of arcs asked for gigabytes; austin62jake). The
file-format-parity.md hunk is dropped: maintainers update the parity docs.
- #2471: another program's proxy objects are in the DWG loss report as its proxy entities
are (left out before 2013, or when their class can't be named); they went silently.
- #1626: a drawing saved at a view keeps it in the desktop app and when it has xrefs: the
app's first frame and the xref pass both fitted the extents over it. OPEN's result says
`saved_view`; one `view::saved_view` replaces the copy of `usable_view` in files.rs.
A misplaced doc comment goes back to its test.
- #2028: a plot rotation (73) outside 0-3 is written back as 0, not as read.
- #1026: the binary code-page retry drops the first pass's tags before it runs (the peak
doubled near the tag limit); a float spelling of an integer group clamps to what the
width holds unsigned too, so 65535.0 reads as 65535 does, not 32767.
- #2658: the reference names Undo by the last command ("Undo Group of commands"); Undo
matched nothing and went to the end of Edit. Ordering and its test share one rule.
- #2612: its test's Insert gets `clip: None` (XCLIP landed after it was written).
#1031's SHX reader, TextFont::Shx wiring and doc edits are superseded by main's SHX support
(#2751, which landed #1944) and resolved to main's. Kept from #1031: the accented Latin
letters of CADCraft Stroke (stroke.rs, composed from its own letter bodies and marks), their
test (fonts/tests/latin_accents.rs) and a synthetic SHX text-through-DXF test
(io/tests/shx_text.rs), which passes against main's reader.
render_look's DARK repeated the rasteriser's default background; both now
read RasterOptions::DARK_BACKGROUND. ECHO and LISTED_TABLES are renamed
MAX_ECHO_CHARS and MAX_LISTED_TABLES. No change in behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main added view twist. Kept from both: a drawing opens on the view it was saved at, with its saved twist; one that stores none fits its extents through main's twisted box. The VPORT reader takes main's twisted view centre and keeps this PR's first-*ACTIVE view; the writer writes the saved view, else main's twisted fit, with the fallback still fitting the width through the record's aspect.
A plot style alone drew black lines on the dark canvas: the tables that
change colours print colour 7 (and, in monochrome.ctb, every colour)
black. With `plot_style` and no `background`, the render now draws on
white, as a plot prints on paper; a given background still wins.
`plot_style: "none"` (or "") draws every object with its own
properties, also in a layout that shows its own plot styles. An unknown
table's error lists the built-in tables and at most ten of the
drawing's own. The app's ui.render test now draws a red line, so it
fails if the plot style isn't passed on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The MCP render tool and ui.render, in the app and headless, always drew
on the dark canvas with no plot style, so a PNG placed on a page needed
the SVG export instead.
They now take `background` ("dark", the default; "white", "paper" or
"#rrggbb") and `plot_style` (a built-in plot style table such as
monochrome.ctb or grayscale.ctb, or one the drawing holds). Colour 7 is
drawn black on a light background and white on a dark one, as on a plot.
An unknown value is refused with what is accepted. Both copies of
ui.render read the options through one function in the engine
(render_look). The render tool's description also says it returns an
image block, not text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The renderer draws a zero-width frame as a line, so leaving it out of the hit geometry made a
visible object unpickable. Also corrects where the corner tolerance switches to ulps (4 ulps
pass 1e-6 near 1.1e9).
The ulp term takes over from CORNER_EPS from about 1.1e9, and the doc now says
so. The case of the nearest letter for each hex digit is found in two linear
passes instead of a search per digit; same result, covered by
moving_an_ole_frame_keeps_each_hex_digits_case.
A matrix every frame accepts (a rectangular array, plain translations) now needs no
look at the objects, and an empty matrix list never touches the drawing. ARRAYPATH
scans the selection once instead of once per item.
Covered by the existing refusal tests; copy_all is not reachable without a drawing
through any command, so there is no no-drawing test.
Putting the frame in prims() also made it a TRIM/EXTEND edge, an object snap
target and dimension geometry. prims() is back to nothing for it, the extents
arm is restored, and select::hit_polylines adds the frame's outline for picking
(none for a frame with no width or height or a bad corner).
Tests: clicking_an_ole_frame_edge_selects_it (now also a frame with no width),
ole_frame_is_not_a_trim_edge_or_snap_target.
* fix(ui): About credits tables fit without side-scrolling and their columns resize
The Contributors table and the Models table were egui Grids inside a
two-way scroll area: they opened wider than the dialog, so they scrolled
sideways, and Company and Model sat almost touching. Both are now
egui_extras tables. Each column opens at the width of its header and widest
cell, the spare width goes to the text columns (Name; Company and Model),
and every column edge can be dragged. Widening a column grows the table
like a spreadsheet, and only then does it scroll sideways; the header stays
put while the rows scroll. Counts and dates are right-aligned and every
cell is vertically centred.
The sorted column stays highlighted and its direction shows on the
toolbar's sort-order button, so headers no longer reserve room for an
arrow. The opening widths come from fit_widths, a pure function with unit
tests that a fitted table fills its room exactly and that only the text
columns give way when room is short.
The About dialog opens 820 points wide, so every column fits at its natural width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Let every credits column be dragged, and refit changed tables
The last column was egui's remainder column, which has no drag handle, so
it could only be resized from the edge before it. It is now an ordinary
column with its own handle.
A table keeps its column widths, so switching to a longer name left the
Name column at its old width and clipped the text. The table now goes
back to the opening fit whenever that fit changes: a longer widest cell
or a resized window. Dragged widths are dropped at that point.
fit_widths could overflow the room when one column hit the minimum,
because the other columns did not take up its share. Columns now give
way in proportion to their width above the minimum.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refit credits tables on what they measure, not the fitted widths
Comparing the fitted widths missed the case it was meant for. Name is the
only growing column, so a longer name takes its extra width from the
spare room and the fit comes out the same: a dragged Name width stayed,
clipping the longer name.
It could also drop dragged widths by accident. The fit comes from the
available width inside the scroll area, which scrolling can nudge, so
scrolling a widened table could reset it.
The table now compares the natural widths and the clip rect's width,
which scrolling does not move.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
copy_all scanned the selection for an OLE frame once per matrix before copying.
It now looks at the objects once and then only tests each matrix.
Covered by the existing refusal tests (classic_polar_array_refuses_a_turned_frame_but_not_an_upright_one).
The corner match was capped at 1e-6, under one ulp above about 8.6e9, so a frame
there never matched its header; a few ulps of the larger value now also match.
The rotation bound had a floor of 1 on the scale, so a small real turn at a tiny
scale passed; it is now purely relative. Hex digits are re-encoded in the case of
the digit at the same place, so untouched bytes keep their text.
Tests: ole_frame_moves_at_2e10_with_a_one_ulp_text_corner, ole_frame_refuses_a_small_turn_at_a_tiny_scale, moving_an_ole_frame_keeps_each_hex_digits_case.
An OLE2FRAME is drawn but prims() gave nothing for it, so hit testing never saw
it and a click on its outline picked nothing. prims() now returns the frame's four
edges from the same corner helper the extents use, so the extents arm is gone.
The renderer keeps its closed-polyline arm (one joined outline, not four segments).
Test: clicking_an_ole_frame_edge_selects_it.
- The linked-folder test (Unix only) passes a slice without a clone (clippy
cloned_ref_to_slice_refs, which failed the Linux gate).
- In a DOS 862 Hebrew SHX font, %%d still draws from the old degree shape 127: only 128-129
are letters there.
- The viewport highlight, drag preview and command preview caches key on the font set too,
so text in them is drawn again when fonts change.
* New and redrawn icons (drawn in code); a click on a painted control leaves the keyboard with the drawing
Split out of #2034. No image files: every icon is drawn in code.
- New: Zoom In / Zoom Out / Zoom to Fit, Move to Top / Up / Down /
Bottom, Text Style and Dimension Style (with a small gear); redrawn:
Undo, Redo, Plot, Preview, the dimension tools and others.
- icons::button_bare: a button without its hover text, for callers that
show a richer tooltip.
- Clicking a painted control (a file tab) no longer takes keyboard focus,
so Ctrl+C in one drawing and Ctrl+V in another keep working; Tab still
reaches it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzTHYMZTCtkW9FExLCKj5V
* Undo/Redo icons of our own design; the clipboard test requires the copied text
Review (CodeRabbit): the Undo/Redo comment named another product's icons as
the model. The icons are redrawn to our own design (a straight run and a
half-turn back, in the icon colour) and the reference is gone.
The cross-drawing clipboard test no longer falls back to placeholder text:
it installs a DXF writer and requires the copy to reach the system
clipboard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzTHYMZTCtkW9FExLCKj5V
* Icons: fit upstream's split icon painter (the pen's painter, theme colours, dim flag)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzTHYMZTCtkW9FExLCKj5V
* Icons: drop the dangling tool_button doc link; describe the tab click focus as it behaves
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
- A face found by family name in the user's font folders is loaded within the font-byte
budget: no uncharged read past it (CodeRabbit on #2316).
- Linked font folders are followed again, as before #2316, in the system scan, the user
scan and the SHX folder search; each real folder is read once, so a circle of links
can't use up the scan's entry budget (CodeRabbit on #2316).
- The user-font-folder test changes process-wide font settings and emptied the font table
under tests running beside it (2 of 3 runs failed): it moves to its own test binary,
tests/ttf_font_folders.rs; that a list keeps no font bytes is checked on a local table.
ALIGN builds its matrix from two angles that agree only to rounding, so a large
scale leaves a rotation part of about scale * 1e-16 that an absolute 1e-12 bound
refused as a turn. The bound now grows with the scale; a real turn is still refused.
Test: ole_frame_accepts_a_large_scale_with_rounding_noise_in_its_rotation.
The extents took the first copy and the far corner copy only. Turned, the grid's
other two corner copies lie outside that box, so ZOOM Extents cut them off. All
four corner copies count now, and the counts are clamped to the 10,000 columns
and rows the renderer draws. Found by CodeRabbit on #2756.
main's #2673 runs the DWG writer in a child process. The kept version now goes with the request
(encode_kept_noted, worker protocol 2), so the worker reads the bridge DXF as that version too.
The proxies proxy_classes leaves out are now counted in Unwritten, as ACAD_PROXY_OBJECT and
ACAD_PROXY_ENTITY.
Rebuilt on main after #1944 (SHX shape fonts, big fonts, unifonts) landed: this part now
keeps only what #1944 doesn't do.
- User font folders (Options > Fonts, fontFolders): TrueType and OpenType files in them are
found before the system's and listed first (ttf::set_font_dirs, installed, available,
rescan); a face is found by its family name in any file there. SHX files in them, or in a
folder under them, are found by the SHX font table too (shx::set_font_dirs), after the
drawings' folders and before the search path. Folders are cleaned and capped, links to
folders are not followed.
- Hebrew SHX fonts: a one-byte font with the letters at their Windows-1255 or DOS 862 places
draws Hebrew text from them; a Cyrillic font with letters at those places is not taken for
Hebrew. In a DOS 862 font, %%d %%p %%c come from shapes 256-258 only.
- The canvas draws text again when the TrueType or SHX font set changes (a font folder, a
rescan, a drawing folder), the active viewport's model space included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RzTHYMZTCtkW9FExLCKj5V
Group 69 is a 16-bit code. Large values passed the filter and `as u32`
truncated them, so 69 = 4294967297 read as a second id 1. Also drop the
docs/file-format-parity.md row: PRs name affected rows in the description.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>