Commit Graph
1565 Commits
Author SHA1 Message Date
Claude 766f1c6f2d print: respell leaves a ticket alone when an element or attribute name would move
Review follow-up. When two vendor namespaces trade prefixes, both prefixes stay
declared, so a name written with one of them as an element or attribute name
(`<ns0000:Feature/>`, `ns0000:flag="1"`) still parsed and resolved after the
root declarations changed, but to the other namespace. respell only rewrites
`name`/`constrained` values and QName values, so such a name would not follow.

respell now returns None when any element or attribute is named in a namespace
whose prefix changes. Names in a namespace that keeps its prefix do not block
the respelling, and the real Brother tickets (vendor names only in `name`
attributes and QName values) are respelled as before.

Tests: regression cases for element and attribute names in a swap and in a
prefix chain, and a control for names in an untouched namespace. Each ticket
is asserted well-formed with every prefix resolving, so the case is refused
for the name, not for a parse failure. The refusal tests fail with the guard
disabled.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97zniM5U
2026-10-25 02:58:18 +00:00
aiongg 865c9af7fd print: collated copies that can't be one job print as a job per copy (#308) 2026-10-24 22:25:28 -04:00
aiongg 2869b8cc10 print: collated copies on a Windows driver that can't collate repeat the pages in one job (#305) 2026-10-24 22:11:16 -04:00
aiongg e95bc0d2a2 docking: blocking input hands the keyboard to the main window (#306)
* docking: blocking input hands the keyboard from a native window to the main window

* docking: the keyboard goes to the main window only if a native window still has it after a short wait
2026-10-24 22:10:52 -04:00
Claude fb59c587df print: respell refuses a nested redeclaration; test the vendor-element case for real
Review of #307:

- respell rewrites the root's namespace declarations, so an element below the
  root that declares either prefix of a rename again (ns0000 or brpsk) would make
  a name resolve to another namespace once the prefixes change. It now returns
  None for such a ticket, and a test covers both prefixes.
- The vendor-element test replaced only an opening tag, left the closing tag
  behind and so never parsed: it passed because respell refused the broken XML,
  not because of what it was meant to check. Every refusal fixture is now
  asserted well-formed, and the element is replaced whole. Doing so showed the
  element guard was redundant: an element or attribute name in a renamed
  namespace leaves an undeclared prefix, which the final parse of the result
  already refuses. The guard is removed and the test now exercises that check.
- Doc comments on the new constants and tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97znim5u
2026-10-25 02:06:11 +00:00
Claude 0348d2990f print: give the job ticket the driver's own prefix for its vendor namespace
Windows' PTMergeAndValidatePrintTicket names a vendor namespace with a generated
prefix (ns0000) as soon as the merged ticket uses a name from it. driver_ticket
merges the driver's own paper option from its capabilities, which carries vendor
properties, so a Brother HL-L2390DW's ticket came back with ns0000 where the
driver's own default ticket says brpsk. XML-wise the two are the same; the
driver's print pipeline reads the prefix as text. The job was accepted and then
ID2D1PrintControl::AddPage (or Close, in older builds) failed with 0x80040003,
reported by winprint as "Render error" / "Failed to close print control: No such
interface supported".

The same ticket sent raw with brpsk prints; with only the prefix changed to
ns0000 it fails the same way, so the prefix is the whole difference.

driver_ticket now re-spells the validated ticket with the prefixes of the driver's
own default ticket (an empty ticket, validated), through ticket::respell: pure
XML, tested on every platform, and a no-op unless a vendor namespace is spelled
differently. It rewrites only the root declarations and the vendor names in name
/ constrained attributes and QName values, handles two namespaces trading
prefixes, and leaves the ticket as it is when a rename could be ambiguous or leave
a prefix undeclared.

Verified on a Brother HL-L2390DW: the job spools, and pdfcraft head's doc_print
that failed at AddPage prints.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FQ5PzHVUcntEHU97znim5u
2026-10-25 01:35:49 +00:00
aiongg 869928d2a0 docking: app drags in panel bodies reach other native windows (#302)
* docking: route app drags in panel bodies across native windows

While an app's drag that began in a panel body lasts, the owner window's
input hook sends pointer moves over another of the app's windows to that
window in its coordinates (PointerMoved, PointerGone, and the primary
release), and the owner sees the pointer gone there. Drop sites in the
target window see hover and release with no app changes.

* docking: wrap the body-drag docs

* docking: a stopped body-drag route takes back the events it queued

* docking: route only body drags that carry a payload, and hold the owner's release

A body drag reaches other windows only with an egui drag-and-drop
payload or when the app calls docking::route_app_drag that frame or the
last; sliders, scrollbars and other plain drags keep the owner's
pointer. When the release goes to another window, the owner's own
release waits for its next input, so a target drawn before the owner
reads the release while egui's payload still exists. A stop never
withdraws that held release.

* docking: routed and held events come before a window's own input

* docking: a held release waits for the main window's next pass, with the pointer events after it

* docking: the owner sees the pointer go in the pass its release is held
2026-10-24 20:36:23 -04:00
aiongg 5c232fd83d Merge pull request #303 from aiongg/print-verify-capabilities
craft-print: refuse a Windows job option only when the driver offers it and dropped it
2026-10-24 20:23:56 -04:00
skylartaylor 650b1c0b05 M037: reconcile current libraries and preserve native IME reset acknowledgements 2026-10-25 01:58:55 +02:00
aiongg 13bb99663e print: DocumentDuplex counts as a driver's duplex feature 2026-10-24 23:37:08 +00:00
skylartaylor b282cb2d29 egui037: Reconcile shared UI pins with reviewed PDF updates 2026-10-25 01:29:13 +02:00
aiongg 00f0656e36 print: refuse a Windows job option only when the driver offers it and dropped it
The driver's PrintCapabilities tell a driver that dropped a requested value
from one that never had the feature. Grayscale on a driver with no colour
option and uncollated copies on one that can't collate are accepted, like
one-sided on a queue without duplex. Collated copies on a driver that can't
collate are refused with their own message. The capabilities are read only
when the strict check fails.
2026-10-24 23:23:12 +00:00
aiongg 941316b7ce Merge storytold/craft-libs#141 (Windows ticket names a colour only for grayscale) 2026-10-24 23:17:33 +00:00
aiongg 286212626e Merge remote-tracking branch 'upstream/main' into print-verify-capabilities 2026-10-24 23:16:52 +00:00
aiongg 1938dad374 Merge pull request storytold/wordcraft#4194 from aiongg/martin-batch
Merge 27 open PRs (tested batch)
2026-10-24 18:33:13 -04:00
aiongg 6e7f28518d Merge pull request #299 from aiongg/craft-jpeg
Add craft-jpeg: 12-bit and arithmetic-coded JPEG decoder (from PhotoCraft)
2026-10-24 18:27:43 -04:00
aiongg 38c4ecab17 docking: native windows ignore input while the app shows a modal (#298)
* docking: native windows ignore input while the app shows a modal

DockArea::input_blocked disables each native floating window's widgets, drops its pointer, key, text and scroll input, covers it with DockStyle::blocked_dim over a layer that takes the pointer, offers no drop targets, forwards no events and cancels its OS close request. Turning it on cancels a drag in progress.

* docking: a blocked native window also drops hovered and dropped files

* docking: blocked palettes drop to the normal window level

* docking: a blocked native window still forwards key releases

* docking: while input is blocked, activation raises the main window last

* docking: blocked palettes stay shown, and the main window comes in front on the app's return
2026-10-24 18:27:19 -04:00
aiongg acb7290804 docking: normal floating windows come forward when the app is activated (#301)
* docking: normal floating windows come forward when the app is activated

* docking: drop windows that became palettes from the raise queue

* docking: the main window comes forward with the normal windows, in focus order
2026-10-24 18:12:06 -04:00
aiongg f9181cc7b9 docking: replacing the workspace cancels a drag in progress (#294)
* docking: replacing the workspace cancels a drag in progress

A dock area keeps the layout it drew while a drag is in progress, with
the actions it returned. When the next layout is neither that one nor
that one with the actions applied, the app replaced or changed it, and
the area cancels its tab, group, column, window bar, native bar, divider
and cross-window drags itself. cancel_drag also ends a native window's
bar drag.

* docking: replacing the layout also ends a divider drag in a native window
2026-10-24 18:11:42 -04:00
aiongg 955831a3a2 jpeg: Limits is non_exhaustive; examples set fields on Limits::default() 2026-10-24 22:05:06 +00:00
aiongg b4e764e44c jpeg: conformance fixtures for jpegtran's progressive and 12-bit arithmetic transcodes 2026-10-24 22:04:21 +00:00
aiongg 2628b8ac9c jpeg: reject decoder buffers above isize::MAX bytes whatever max_alloc allows 2026-10-24 22:03:57 +00:00
aiongg c91d190a7e jpeg: DC-only blocks skip the IDCT; README advises a smaller max_alloc for UI and preview decodes 2026-10-24 22:03:34 +00:00
aiongg e96bf9552b jpeg: arithmetic scans take zeros at a marker without limit
libjpeg's encoder drops trailing zero bytes, so a flat tail of a few thousand blocks decodes from zeros supplied at EOI. Damage is data that runs to the end of the file or a lost restart marker. whitetail-ari.jpg (cjpeg -arithmetic) and its Huffman transcode cover it.
2026-10-24 22:02:59 +00:00
VoiceLessQ cb77c398b9 Proofing: a dictionary load from before a folder change is not kept
A dictionary is read outside the lock. If the dictionaries folder changed
while it was being read (File › Options › Proofing), the reload emptied
the slot first and the late load then filled it with the old folder's
dictionary. It stayed until the next reload. Every language's copy of
the loader had this window; the shared slot now closes it in one place.

The slot counts reloads. A load that started before a reload still
answers the check that asked for it, but it isn't kept: the next check
reads the new folder.

Found by CodeRabbit on storytold/wordcraft#4191. Tests: a_load_from_before_a_reload_is_not_kept;
not counting reloads fails it. Proof: 288 tests.
2026-10-24 20:57:37 -01:00
VoiceLessQ b06aece4eb Proofing: a candidate found again at a lower cost ranks at that cost
The suggestion rounds don't run in rising cost: a MAP diacritic drop (11)
comes before a REP confusion (10, 9 for Russian). The engine checked each
candidate once, at the first cost it was offered, so the same word found
again more cheaply kept the dearer cost and could rank one place too low.
Czech's engine always did this, and the merged engine took it over.

The candidate set is now a map to the cheapest cost offered so far, and
an offer that is no cheaper is skipped as before.

Found by CodeRabbit on storytold/wordcraft#4191. Tests: a_cheaper_later_offer_wins; skipping
every repeat offer again fails it. Proof: 288 tests.
2026-10-24 20:57:35 -01:00
aiongg d12a6db750 docking: an input hook and dropped files for every native window (#293)
* docking: an input hook and dropped files for every native window

eframe runs raw_input_hook only for the root viewport, and each native window's
dropped files arrive only in that window's input. DockArea::native_input_hook runs
an app hook on each native window's RawInput through an egui plugin, before egui
reads it, and DockOutput::window_files reports files hovered or dropped on native
windows with the window and the panel under the pointer. The test host takes
hovered and dropped files per window.

* docking: a discarded pass reports no hovered files; share hook state through a mutex

* docking: one file report per native window when its pass is discarded
2026-10-24 17:53:29 -04:00
aiongg 75da958619 docking: panels closed one by one reopen into one group (#290)
* docking: panels closed one by one reopen into one group

Showing a hidden panel whose anchor is hidden too follows the chain of
hidden anchors to its far end, so a floating location no longer opens a
second window. The hidden panels the restore passed then anchor back on
the panel before them in the chain, on the recorded side, so reopening
them joins the restored panel instead of rebuilding their group or
window. A restored tab whose next tab is not in the anchor's group goes
last.

* docking: a panel beside an edge frame keeps its own location on reopen

* docking: a panel reopened through a hidden neighbour keeps its own group state; the chain stops reversing at an edge frame
2026-10-24 17:52:34 -04:00
aiongg 35bd2f3891 jpeg: CodeRabbit fixes: the max_alloc and damaged docs describe the combined check and block budget, SOF2's message names its coding 2026-10-24 21:30:42 +00:00
aiongg c75bf618f0 docking: viewer panels float as normal windows (#296)
* docking: viewer panels float as normal windows

DockContent::window_kind marks panels as palettes (the default) or normal
windows. A native floating window holding any Normal panel gets the normal
window level and stays shown while the app is in the background; drags
treat it as stacked with the main window by focus.

* docs: a normal window behind the main window takes drags outside it
2026-10-24 17:13:37 -04:00
aiongg c98ee15a7f docking: tabs shrink before they overflow (#295)
* docking: tabs shrink before they overflow

Tabs in a narrow strip shrink, widest first, down to DockStyle::min_tab_width (54 pt at the
default font), cutting their labels with "…" and naming them in a tooltip. Only tabs that do
not fit at that width go to the overflow menu.

* docking: the drag bar narrows once the strip is narrower than the header plus the bar
2026-10-24 17:13:12 -04:00
aiongg 36db71c5c3 docking: floating windows stay in the app window on Wayland (#292)
* docking: floating windows stay in the app window on Wayland

The compositor reports no window positions, so native windows could not
dock again by drag or reopen where they were. The dock area detects
Wayland (WAYLAND_DISPLAY or WAYLAND_SOCKET set and a main window that never
reported its outer rect) and draws floats in-window, keeping their native
rects. DockArea::native_windows(bool) overrides the choice.

* docking: a missing DISPLAY also counts as a possible Wayland backend

* Revert "docking: a missing DISPLAY also counts as a possible Wayland backend"

This reverts commit a804afb1d3.

* docking: the Wayland session is a pure function of the environment; without a Wayland variable only a real window counts
2026-10-24 17:13:00 -04:00
aiongg 059d1519a6 jpeg: test DAC conditioning and interleaved subsampled arithmetic scans 2026-10-24 21:06:47 +00:00
aiongg 3ab100f46e jpeg: test that truncated arithmetic data is reported damaged 2026-10-24 21:06:47 +00:00
aiongg cb28b0c194 jpeg: use the quantization table in force at a component's first scan
The test encoder takes several components, scans of any of them, segments
between scans and per-table conditioning, and drops trailing zero bytes as
libjpeg does.
2026-10-24 21:06:47 +00:00
aiongg b636dad56e jpeg: read the reference PPM with as_chunks 2026-10-24 21:03:28 +00:00
aiongg 3acc132ff0 jpeg: mutation test, fuzz target and CI workflow
tests/mutation.rs runs a seeded mutation pass over the test images and
small SOF9, SOF10 and 12-bit seeds, checking that decode never panics
and stays within small limits. fuzz/ is a cargo-fuzz crate (own
workspace) with a decode target and those seeds. jpeg.yml runs fmt,
clippy, tests and a wasm32 check on Linux. The README gains a testing
section.
2026-10-24 21:00:51 +00:00
VoiceLessQ 140c7041e1 Proofing: the spelling options are read in one place
Czech, Croatian, Indonesian, Italian, Polish, Portuguese and Spanish began
their spelling checks with the same thirteen lines, byte for byte: no
dictionary means no marks, single letters and words without letters pass,
words with digits and short words in capitals pass when the File › Options
› Proofing choices say so, and words in another script pass. Each also had
its own copy of the Latin-script test. These were the lines storytold/wordcraft#3966 had to fix
in six copies one by one, and where the Russian and Turkish copies stayed
wrong until the first commit here.

spelling.rs has them once (`word_to_check`), and each language goes on
from there with its own rules (Polish apostrophe endings, Italian elisions,
Indonesian clitics, Uruguayan `vos` forms).

Tests: words_needing_no_lookup. Ignoring the uppercase option there fails
it and all seven languages' proofing-options tests. Proof: 286 tests.
2026-10-24 19:58:30 -01:00
aiongg 3be6c3d09d jpeg: bound decoder memory and work
Check coefficients, planes and output against max_alloc together. Stop an
arithmetic scan once it has consumed more than 8 zero bytes past its data or
misses a restart marker, and cap the decoded blocks at 64 passes over the
frame instead of capping the scan count.
2026-10-24 20:57:23 +00:00
VoiceLessQ a388c4787c Proofing: one language table instead of nine lists
lib.rs named every dictionary language in nine places: reloading for a new
folder, the installed list, load errors, the missing-dictionary hint, the
loaded check, the spelling message, spelling, suggestions and grammar. The
lists had drifted. dictionary_load_error had no Russian or Czech entry, so a
Russian or Czech dictionary that failed to load showed no reason in File ›
Options › Proofing, though the status bar, which reads another list, said
why.

LANGUAGES holds one row per language: its dictionary slot, its spelling
message, and its spelling, suggestion and grammar functions. The nine
functions read the row, so a language now joins with its module, its
ProofLang and one row. The per-language reload_dictionary and
installed_path wrappers had no callers left, inside the crate or out, and
are gone.

Tests: every_dictionary_language_reports_its_load_error, which fails for
Russian on the old code, and the_language_table_covers_the_proofed_languages
(each ProofLang::PROOFED language but English has exactly one row). Proof
285, engine 927 and layout 501 (lib).
2026-10-24 19:55:43 -01:00
aiongg 6fdd2efbba docking: showing a panel with no known place opens it in the first dock group or at an edge (#286)
* docking: open an unanchored panel in the first dock group or at an edge

* docking: an unanchored open prefers the end-side dock; an edge frame is a quarter wide

* docking: drop the redundant edge resize; an edge frame is already a quarter wide

* docking: an unanchored open passes over minimized groups and iconic columns
2026-10-24 16:52:51 -04:00
aiongg f28de79037 jpeg: conformance tests against the libjpeg-turbo test images
Commit testorig.jpg, testimgari.jpg and testorig12.jpg with a jpegtran
Huffman transcode of testimgari.jpg and libjpeg-turbo's 12-bit decode
of testorig12.jpg. The arithmetic-coded image must match its transcode
exactly; the 8-bit and 12-bit decodes are compared against zune-jpeg
and libjpeg-turbo within a small tolerance. Replaces the ignored
conformance_samples test.
2026-10-24 20:49:41 +00:00
VoiceLessQ c19b1cae73 Proofing: one suggestion engine with per-language tuning
Czech's dictionary-driven ranking (case, MAP, REP, missing spaces, KEY,
TRY edits, a stem scan) had five older copies. Polish, Portuguese,
Croatian, Italian and Russian each kept one: 300 to 400 lines of the same
rounds with a few lines of language knowledge mixed in. Fixes reached some
copies only. NOSUGGEST (storytold/wordcraft#1048) and the first-difference tie-break (storytold/wordcraft#797)
never got to all of them, and none had the KEY round or the bound on TRY
letters.

affix_suggest.rs is that ranking once. A language's `Tuning` holds what is
its own: the short words that run into their neighbours (Polish,
Portuguese, Croatian, Italian), Russian's split rule, the letter groups and
confusions its dictionaries lack (Croatian, Russian), letters to try for a
dictionary without a TRY line, and cost hooks for Polish final devoicing
and Russian е/ё. Czech, Indonesian and Spanish use the dictionary's tables
alone. Turkish keeps its own suggester, which reads another dictionary type
with rounds of its own.

The language tables add to the dictionary's instead of replacing them, and
TRY letters are tried in the dictionary's order, commonest first, as the
copies did (the Czech engine sorted them).

Old and new compared on the test dictionaries, with every one-letter typo
of each word plus the tests' misspellings, top eight suggestions: Czech,
Croatian, Italian and Portuguese give the same for all 7,903 inputs.
Polish differs for 44 of 3,244 and Russian for 33 of 2,346, all orderings
among candidates of equal cost, from the tie-break they now share (`ie` →
`nie` before `i`).

New tests pin the moved knowledge: the tuning mechanism, Croatian's groups
without MAP lines, and Russian's costs and split rule. Turning each knob
off fails one. Proof: 283 tests.
2026-10-24 19:49:22 -01:00
VoiceLessQ 172c935fca Proof tests: the user-dictionary file test waits for the dictionary tests
user_dictionary_persists_through_a_file saves the whole user dictionary to
a file and loads it back. Run beside
user_dictionary_and_ignore_all_match_both_normalization_forms, it could
catch a word that test had added and bring it back after the test removed
it, so the other test's last check failed now and then (once in about two
dozen full runs here). It now holds DICTIONARY_TESTS, as the other test
does.
2026-10-24 19:48:47 -01:00
aiongg 6354ef2728 jpeg: README and crate list entry 2026-10-24 20:43:51 +00:00
aiongg 5b609acb6f jpeg: make the decoder a standalone crate
Own Error and Limits types, a Decoded image with typed samples, a
public frame() probe for routing, and the tests adapted to them.
2026-10-24 20:43:14 +00:00
aiongg 1dd057f534 Merge PR storytold/wordcraft#4109: Proofing: web addresses after a no-break space are not marked as misspelled 2026-10-24 20:38:49 +00:00
Higgs32584andClaude Opus 5.5 537ac17c7c JPEG: cap the scans decoded from one stream at 500
Every scan walks its whole MCU grid, and an arithmetic-coded scan does so
even with no entropy-coded data (the decoder is fed zeros), so a small file
repeating empty scan headers multiplied the work by the frame size. Scans
past the cap are dropped and the image is reported damaged, keeping what
the earlier scans decoded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:37:15 +00:00
Higgs32584andClaude Opus 5.5 425bd319c0 JPEG: 12-bit and arithmetic-coded files keep their Photoshop resources
The jpeg_ext path set exif, xmp and dpi but not photoshop_resources, so APP13 8BIM blocks
(paths, Photoshop settings) were lost on import and re-export. Both decoder paths now fill
the same Metadata. Test: a 12-bit JPEG with an APP13 block keeps it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:37:15 +00:00
Higgs32584andClaude Opus 5.5 5e2d33cd82 JPEG: a Huffman scan that runs out of data is damaged, not clean
Bits now tracks how many buffered bits are zero padding supplied at a
marker or the end of the data; a read that consumes padding marks the
scan exhausted and the block returns an error, so the scan is reported
as damaged (or fails if no MCU decoded) even when EOI is present.
Arithmetic termination is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:37:15 +00:00