check_dim_value's char branch only checked len == 1, so '\n', '\t', '\x00'
and non-BMP characters passed and the headless writer stored ord(value)
unguarded: '\n' split the dimension text over two lines, NUL read back as a
comma, and U+1F600 reached $DIMDSEP / group 278 as 128512, outside the 16-bit
range and refused by AutoCAD as "not a single character". ezdxf's audit
caught none of it. The same hole 827719f closed for names and fonts, left
open for the one variable stored as a character code.
The gate now refuses code < 32, DEL, and code > DIMDSEP_MAX_CODE (0x7FFF,
the signed short DWG stores) with a ValueError naming DIMDSEP, so nothing
reaches a backend. Pinned by a parametrized refusal test (newline, tab, NUL,
DEL, U+1F600, U+8000, U+FFFF), a positive test that the middle dot, the
Arabic decimal separator and U+7FFF still pass, and the ezdxf refusal test
now proves dimstyle_create / dimstyle_modify write nothing for these.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
Three headless defects in the styles block, each measured before the fix:
* `_normalise_dimstyle_rounding` cleaned `Standard` only, while ezdxf's
exporter writes DIMRND 0.0 for every DIMSTYLE. A user style (ISO-25 / ANSI)
came back from save/open, transaction_rollback and drawing_undo rounding
every dimension to a whole unit ("33" for 33.333, "R6" for R6.35) with
dimstyle_list still reporting DIMDEC 2. It now walks the whole table.
* `drawing_purge` followed only entity `.dxf.style`, so it deleted the text
style a DIMSTYLE (DIMTXSTY), an MLEADERSTYLE (text_style_handle) or the
header ($TEXTSTYLE / $DIMTXSTY) referenced; the next dimension silently
rendered in Standard and drawing_save_as raised DXFTableEntryError after
leaving a truncated file. `_style_table_references` now counts those, plus
the arrowhead blocks a DIMSTYLE / MLEADERSTYLE / MULTILEADER references,
compared case-insensitively.
* `_sync_header_to_dimstyle` wrote only the attributes the target style
stores, leaving the previous style's $DIM* in the header — folded onto the
next dimension by `_with_header_dimvars` (ISO-25 DIMZIN 0 / DIMSCALE 2.0
then Restore Standard rendered 5.0 mm "33.30") and written to the file as
current-style overrides; $DIMRND 0.5 outlived the style's own discard. It
now writes the schema default for every whitelist variable the style omits
(`Standard` for DIMTXSTY), which reproduces a fresh header exactly.
tests/test_styles.py: the reopen test now renders a dimension after the
reopen, plus one test per defect; all four fail on the previous backend.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd