* Depend on craft-pdf (krilla writer, reader for tests) at craft-libs 9f75d3dc
The root patches pin the patched krilla and hayro-syntax/hayro-jbig2 to
the same revision, as craft-pdf requires.
* Plot PDFs with craft-pdf's krilla writer
The hand-written object, xref, stream and font writer goes. Each sheet's
display list is built, written as its page and dropped before the next.
Strokes with the same colour, width and transparency stay one path, and
each filled primitive is one path of its triangles. Text in embeddable
TrueType fonts is drawn as glyphs; krilla subsets the fonts and writes
their ToUnicode maps, so the hand-written subsetter goes too and
cadcraft-fonts keeps only the fsType embedding check. Primitives with a
point that is not finite or beyond 1e7 pt are left out. A font krilla
cannot embed after all makes the export retry with text as outlines.
Optional content (PDF layers) is not written until craft-libs' writer
supports it: the layer transitions go through one empty function, layers
that are off are not plotted, and the layer tests are ignored.
The tests read the PDF back with craft-pdf's reader.
* Pin craft-pdf and its krilla and hayro patches to craft-libs cd160e73
cd160e73 adds optional content groups to the patched krilla.
* Write drawing layers as PDF optional content groups
Each drawing layer a page draws on gets one group, named after the layer
and shared by all pages, created before the first page that uses it. A
group starts hidden when its layer is off in the drawing, so `offLayers`
again plots layers that are off on hidden PDF layers. At most 4096
groups per file; objects on further layers print unmarked. A single
sheet with groups opens with the viewer's layer panel (/PageMode /UseOC).
Layer changes end and start the marked content, and the last mark ends
before the page finishes.
The layer and text tests that check the groups run again, and a test
covers the group cap.
* Plot a zero-length line as a round dot in PDFs
krilla leaves out a path without extent, so a lone zero-length line drew
nothing. It is filled as the dot its round-capped stroke would make, as
wide as the stroke, with 0.25 pt for a hairline.
* PDF: retry as outlines when the writer fails inside a font; cap layer names; restore two test checks
* Tests: read plotted PDFs back through the shared reader in plot_layout_scale and plot_styles
* Pin craft-pdf, krilla, hayro-syntax and hayro-jbig2 to craft-libs main (7895f5e)
* PDF tests: read path coordinates in page space; cover the font retry and out-of-range points
- pdf_read maps path operands through the CTM (`page_ops`, used by `drawn` and
`clip_rect`) and checks that the plot area is drawn in page space.
- The 1:1 layout test finds the viewport corner at (72, 72) in page space, so a
mirrored page fails it.
- A font with a short hmtx passes the writer's font checks but fails subsetting;
the plot retries and draws its text as outlines.
- A primitive with a point past 1e7 pt is left out, and the rest still plots.
* PDF: clip a primitive with a far vertex to the plot area instead of leaving it out
A polyline or fill with a point past the writer's f32 range now keeps the
parts inside the plot area (polylines with the area widened by the stroke
width), each piece its own subpath. Primitives in range are written as before.
The test reader requires a decodable content stream.
The optional `CRAFT_FONTS_DIR` build input lets a font manifest select the file
each face is embedded from. `build.rs` canonicalised that file with no
containment check, so an absolute path, a `..` traversal, or a symlink inside
the checkout could make the generated `include_bytes!` read an arbitrary file
on the build host and expose its bytes through `CRAFT_FONTS` (CWE-22).
It also printed the resolved path straight into Cargo's newline-delimited
build-script protocol (`cargo::rerun-if-changed=…`). A path containing an
embedded newline — reachable through a crafted symlink target — could inject a
second directive such as `cargo::rustc-env=OUT_DIR=…`, redirecting the
generated-source include to attacker-supplied Rust (CWE-74).
- `resolve_font` rejects absolute and `..` manifest paths and requires the
canonicalised result to stay inside the canonicalised checkout root.
- `rerun_if_changed` refuses any path containing CR/LF before emitting the
directive; `one_line` collapses CR/LF in checkout-derived diagnostics.
Addresses findings rows 5 (CWE-22) and 14 (CWE-74).
* security: bound font outline geometry and MTEXT line scanning (CWE-400/407)
Two font/text paths let crafted input monopolise the rendering thread:
- Glyph shaping copied a glyph's full contour set for every placed character
with no cumulative budget, so a compact CFF/OTF font with a large expanded
outline, repeated across a short string, could grow the shaped result without
bound. `shape` now charges retained outline points against a cumulative
per-line budget and stops placing geometry once it is exhausted (advances
still accumulate, so the width stays sensible) (row 9).
- Default MTEXT layout recomputed `line.text.chars().count()` before appending
each segment or space, rescanning the whole accumulated line every time —
multiplicative on a long line. The line now keeps a running character count,
updated only from newly appended text (row 20).
New tests cover the MTEXT layout; all font tests pass and render still builds,
clippy clean.
Addresses findings rows 9, 20.
* fix: budget glyph geometry before outline placement
* fix: skip empty outlines without text metadata
---------
Co-authored-by: skylartaylor <26588250+skylartaylor@users.noreply.github.com>
* AutoCAD interop: justified text, DIMPOST, current dimension style, *D block colours
What AutoCAD takes from CADCraft's DWG/DXF as it is:
- Justified TEXT/ATTRIB/ATTDEF (#1050): group 10 is now where the
baseline starts, from the alignment point and the text's width, not
the alignment point itself; AutoCAD drew centred and right-aligned
text from DWG starting at its alignment point. A stored point close to
CADCraft's own (AutoCAD's, exact for its fonts) is kept.
- DIMPOST (#1053) is written even when empty: the DWG bridge defaulted a
missing one to "<>", and AutoCAD then dropped the "R" of radius
dimensions when it regenerated them.
- The current dimension style (#1051): the DXF header now carries its
DIM* variables (AutoCAD draws new and updated dimensions from them);
the DWG bridge points $DIMSTYLE at the style by name (its handle was
renumbered, so the writer fell back to Standard), and reading a DWG
gives the current dimension and text styles their names back.
- *D blocks (#1052): lines, arrows, extension lines and text take the
style's DIMCLRD/DIMCLRE/DIMCLRT instead of ByBlock.
#1054 (group 42 of a rotated linear dimension) was already fixed on
main by #855; acad_interop.rs keeps it covered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test: angular dimensions store their angle in radians (group 42)
Three-point (60°, 174°) and two-line (120°) angular dimensions, read back
from the DWG as DXF. Already right on main since #855; this guards it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Justified text made here: Left Middle and Left Bottom write their baseline start too
text_insert kept a stored insertion point that was within 0.1 x an
estimated width + 0.5 x height of CADCraft's baseline start, taking it for
AutoCAD's own. The width was estimated from the alignment point's offset,
which for Left Middle is only h/2 (vertical), so a text made here, whose
stored point is the alignment point, passed and kept it: AutoCAD drew it
half a height high (Left Bottom: a third). Reported with a test by
@lee-cyrille-maskell.
A stored point equal to the alignment point is now always stale. Otherwise
the offset is split along and across the baseline: along it the same slack
as before (a tenth of the text's width plus half its height, as fonts and
their substitutes differ most in width), across it a fifth of the height.
Multiline attribute values keep the distance-only check, since their lines
are placed by the embedded MTEXT.
On 12 drawings made in AutoCAD (1,055 justified texts) the insertion points
written are the same as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Show Thai file names and text in the interface with an installed Thai font
A drawing whose file name is Thai showed boxes in its file tab and in the
window title: the interface loaded one Latin system font (plus a CJK font for
the Chinese interface), and neither has Thai.
install_fonts now also adds an installed Thai font (Thonburi, Leelawadee UI,
Noto Sans Thai, Loma...) after the Latin one, in any interface language, since
file names and drawing text are Thai whatever the interface is in. No font is
bundled; without one installed nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add Korean interface localization and Hangul font support
---------
Co-authored-by: Chanthawat Kiriyadee <enwuft@icloud.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* MTEXT: paragraph indents and tab stops are in drawing units
AutoCAD writes \pi, \pl, \pr and tab stops in drawing units, not multiples of the text
height. A file with \pi27.485 on 6-high text in a 120-wide box laid out with a 165-unit first
line indent: the first line vanished and wrapped words landed elsewhere. The JSON mtext
parameters indent, firstIndent, rightIndent and tabs follow (a list's default indent is now
3 text heights, in drawing units).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* MTEXT editor: list indent of three text heights in drawing units, matching the new \p units
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: simanov <asimfali@mail.ru>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
AutoCAD stores a TrueType text style's family (e.g. "GOST type A") in the style's ACAD xdata
and leaves the font file name empty. Styles without a file now take that family.
Family-name lookups also look in files whose name starts with the family's first word, so
"GOST type A" finds GOST.TTF (the stem shares only 4 characters with the family name).
Co-authored-by: simanov <asimfali@mail.ru>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Drawing (#674): in stroke-font (SHX) text, East Asian wide characters
from a fallback font fill a square cell from the baseline to the text
height, as a big font's do, instead of being scaled by the fallback
font's Latin cap height (about 1.3-1.5x too large and too widely spaced,
so CJK labels overran their space). A line no longer ends with the
stroke font's letter gap after a fallback glyph.
Interface (#513): a CJK font is installed after the Latin one in every
interface language, not only Chinese, so Japanese or Chinese file,
layout and layer names stop showing as boxes. A Japanese font comes
first unless the interface is Chinese; a craft-fonts face embedded in
the build comes last, for systems (and the web build) without one.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
For every justification but baseline-left (and aligned/fit) the DXF
Reference makes group 11 the point the text is placed by; group 10 is a
computed point on the baseline. Left-justified text with a vertical
alignment was drawn, measured and picked from group 10 and then shifted
by the alignment again: TL and ML too low, BL too high. The renderer
(fonts::place) and text extents (doc text_box, used for picking) now
place it from the alignment point; a mirrored (extrusion 0,0,-1) TL/ML/BL
text keeps its alignment point when it turns right-justified; and the
Properties position moves justified text by its alignment point.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* DIMANGULAR: accept polyline segments (fixes#10)
Picking a side of a rectangle or a PLINE segment was rejected with
"Select a line or an arc." because only LINE and ARC entities were
accepted. The picked object is now resolved to the segment under the
cursor: straight polyline segments work like lines and arc segments
like arcs.
* Redraw edits made while a command is still running (fixes#4)
LINE and PLINE add geometry on every point, but the canvas only rebuilt
its display list when the drawing revision or the document pointer
changed. The revision was bumped when a command finished, and the
pointer changed only on the first copy-on-write edit, so only the first
segment appeared until the command ended. Session::feed now bumps the
revision after each input once the running command has changed the
drawing.
* Fix#14: include DWG in the Open drawing filter
* Blocks window: click a block to insert it (#8)
Each block name in the Blocks window is now a button. Clicking it starts INSERT with that block already chosen, so the command line asks for the insertion point.
* SCALE: Reference option (#17)
SCALE > Reference printed "not available yet". It now asks for the reference length (typed, or two points) and the new length (typed, or a point measured from the base point), and scales by new / reference. Zero, negative or non-finite lengths are rejected and asked again.
* Print and Ctrl+P ask where to save the PDF (#5)
PLOT/EXPORTPDF started from the menu, the toolbar, Ctrl+P or the command line had no path,
so the PDF was built in memory and dropped. They now open the save dialog (PDF filter first)
and write the file there. JSON calls from scripts, the control channel and MCP still never
open a dialog. Ctrl+P was shown in the menu but not bound; it is now.
* Scripts: split a line at keyword prompts so POLYGON 6 200,20 I 15 works (#43)
* Layer Properties Manager: stop the window growing every frame (fixes#29)
The vertical separator between the filter tree and the table takes all the
height it is offered, and a resizable window offers its previous height, so
the window grew 30px per frame until it filled the screen. Cap the row at the
table's fixed height. Regression test runs the dialog headless for 30 frames
and checks the height stays put.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ui.render: fit the drawing and return pngBase64 like the headless render (#49, #50)
* Linux: default to XWayland for file drag-and-drop on Wayland desktops
* Fonts: per-character fallback, FONTALT and .ttc for CJK text in SHX styles (#130)
CJK text in a style that uses an SHX font (romans.shx + chineset.shx big font)
drew every ideograph as an empty box: the SHX name resolves to our stroke font,
which has no CJK glyphs, and the font scan skipped .ttc collections, where most
CJK system fonts live.
- Per-character fallback: a character the text's font lacks (stroke font or
TrueType) is drawn from an installed font that has it, checked by cmap.
Wide-coverage fonts go first (Noto Sans CJK, Source Han, WenQuanYi, Droid
Sans Fallback, PingFang, Microsoft JhengHei/YaHei, MS Gothic, ...), regular
weights before others. Fallback glyphs are sized like TrueType text (cap
height = text height). Lookups are cached; the search is bounded (24 files,
160 MiB, 256 faces per character, files over 96 MiB never read) and skips
spaces, controls and private-use code points. No fallback: the box as before.
- FONTALT (sysvar, seeded from CADCRAFT_FONTALT): font used for a style font
that can't be found (an SHX file, a missing TTF). Empty = our stroke font.
- FONTFALLBACK (sysvar, seeded from CADCRAFT_FONTFALLBACK): fonts tried first
for missing characters, e.g. "Noto Sans CJK TC".
- .ttc/.otc are scanned; TextFont::Outline carries a ttf::Face (bytes + face
index). Fonts are also found by family name among files with a similar
stem, so "Noto Sans CJK TC" picks face 3 of NotoSansCJK-Regular.ttc.
- Cap height falls back to the height of "H" when OS/2 has none (DejaVu
Sans), which fixes ttf_tests::system_font_outlines_when_available and
render's truetype_text_is_filled_or_outlined on machines without Arial.
- README: a Fonts section for the two settings; ROADMAP M4 note.
Native SHX / big-font rendering stays on the roadmap (M4).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fonts: cap the total bytes of system font files loaded from disk
A drawing naming many text styles could otherwise load every installed font
into memory through family-name lookups.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pan with a two-finger trackpad swipe; keep wheel and pinch zoom
Trackpad two-finger swipes arrive as scroll events, and the canvas turned
every scroll into a zoom, so trackpad users had no realtime pan (middle-drag
needs a middle button).
Scroll now follows the device: precise point deltas (trackpad, Magic Mouse)
pan the view by x and y; notched line deltas (mouse wheel) zoom about the
cursor as before. Pinch and Cmd+scroll still zoom through zoom_delta, and
middle-drag still pans.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019uMCkRAB8DMd8MEmoY9Vyr
* Add Tru10 to contributors/people.toml
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019uMCkRAB8DMd8MEmoY9Vyr
* Trackpad pan: only on macOS, where point deltas mean a trackpad
Browsers and Wayland smooth-scroll mice report ordinary wheels in points too,
so the wheel keeps zooming there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep arc-length dimensions as arc-length through DXF save and reopen (#100)
* PLINE: Halfwidth is centre-to-edge, and Width answers taper the next segment (fixes#83, #104)
Interactive PLINE kept one width value: Halfwidth answers were stored as
the total width, and the ending-width answer overwrote the starting one,
so a taper became a constant width. The next segment's starting and
ending widths are now tracked separately, Halfwidth answers are doubled,
and each new segment (including the closing one) gets its widths on its
first vertex; later segments are uniform at the ending width, as in
AutoCAD. Uniform polylines are still stored as const_width, so their DXF
output is unchanged.
Fixes#83. Fixes#104.
* Arc-length dimensions: write a standard aligned record plus CADCraft xdata
DXF has no arc-length DIMENSION type, so write type 1 and mark the kind and
arc centre in CADCRAFT xdata (like ASSOC). Other programs read an aligned
dimension; CADCraft restores the arc length, through DXF and DWG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Desktop: write log records to standard error and a rotating log file
The desktop app installed no logger, so every log::warn!/log::error!/
log::info! in the workspace was discarded: a broken app icon, the GPU
canvas set-up, the engine's command-line echo at debug level. A launch
from a desktop menu has no terminal either, so there was nothing to
attach to a bug report.
apps/cadcraft/src/logging.rs (no new dependency):
- standard error plus <settings dir>/logs/cadcraft.log; each launch
rotates the previous log to cadcraft.1.log and .2.log, so the log of a
run that crashed survives the next start; the file is capped at 16 MiB
- records logged before the settings directory is known are buffered
(bounded) and written once the file is attached
- default levels: info for cadcraft*, warn for everything else;
RUST_LOG replaces them with env_logger-style directives
- never panics: an unusable directory or a failed write leaves standard
error as the only sink; a poisoned lock is tolerated
CADCraft had no per-user directory yet. config_dir in main.rs adds one:
CADCRAFT_CONFIG_DIR, else $XDG_CONFIG_HOME/cadcraft or ~/.config/cadcraft
(Linux, BSDs), ~/Library/Application Support/CADCraft (macOS),
%APPDATA%\CADCraft (Windows).
The logger is installed first in main and the file is opened after
argument parsing (--version leaves no file). The control server's
eprintln!s now go through the logger. A --control or
CADCRAFT_CONTROL_PORT value that is not a port is now logged instead of
silently leaving the control channel off. Documented in README.md
(Quick start > Logs).
---------
Co-authored-by: SBaksa <sabaksa11@gmail.com>
Co-authored-by: DWAA1660 <dwatnip123@gmail.com>
Co-authored-by: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com>
Co-authored-by: lloyd <34131158+LloydNicholson@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: vangel76 <vangel1976@googlemail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Tru10 <133776756+Tru10@users.noreply.github.com>
Co-authored-by: Gert Pellin <gert@pellin.be>
Annotation (M4):
- TrueType text from system fonts (filled/outlined), shared placement for all text
- MTEXT formatting codes: fonts, heights, colours, under/overline, stacks, wrapping
- Full DIMSTYLE variables, per-dimension overrides, all arrowheads and ticks,
unit formats, tolerances, limits, alternate units, colours by role
- Associative dimensions that follow geometry; DIMREASSOCIATE/DIMDISASSOCIATE
- STYLE/DIMSTYLE management commands, DIMTEDIT, DIMSPACE, TABLE create/edit/merge
Parametric (M9):
- New cadcraft-constraints crate: weighted damped Gauss-Newton solver, conflict
and redundancy detection, expressions, AutoConstrain inference
- All GC*/DC* commands, GEOMCONSTRAINT/DIMCONSTRAINT, PARAMETERS, DELCONSTRAINT,
constraint bar / display settings; re-solve after every edit
Also: MODEL/PAPER status bar toggle, cargo xtask alias, roadmap update
(233/491 menu items, 290 commands).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>