Commit Graph
356 Commits
Author SHA1 Message Date
skylartaylor 0f46902e32 Reconcile egui 0.37 with current shared libraries
Preserve the reviewed shared UI tree and the frozen app dependency commit while
bringing in current main. Port the new keymap UI APIs and use verified central
fonts in its strict UI tests and snapshots.
2026-10-24 07:20:20 +02:00
aiongg 2eca9b3a53 pdf: vendor hayro-postscript and skip << and >> runs without recursion (#263)
hayro-postscript 0.1.0 is the token scanner hayro-cmap uses for the
CMap streams PDFs embed. Its object reader skipped each `<<` or `>>`
by calling itself again, so a stream holding a long run of `<` or `>`
bytes recursed once per pair and aborted the process with a stack
overflow.

Vendor the crates.io package unchanged and patch `object::read` to
skip those runs in a loop, returning the same next object as before.
Wire it in like the other Hayro crates: workspace exclude,
[patch.crates-io] entry, SOURCE.json with registry and local hashes,
the backend test script, NOTICE, ATTRIBUTION.md and the reader README.

Tests cover a million `<` and a million `>` bytes, `<< /A 1 >>` and
nested `<<<< /X >>>>` runs.
2026-10-23 23:31:35 -04:00
aiongg d2d5a400da pdf: port PdfCraft's reader fixes, part 1 (syntax and text) (#261)
* hayro-syntax: port PdfCraft's syntax fixes (ledger 17, 19, 20, 23, 4-ext, #3565, base_dimensions floor)

From PdfCraft a2ec27b47:

- (17), (19): a CCITT image with /Rows 0, or with fewer /Rows than its
  /Height, decodes to the image height instead of zero or /Rows rows.
- (20): spaces or tabs between the stream keyword and its line break are
  skipped, so the stream data starts after the line break.
- (4) extension: an inline image's EI followed by a delimiter (EI%, EI[)
  ends the image; only a regular character makes it part of a longer token.
- (23): bytes above 0x7F between two EI candidates don't reject the first;
  the operator look-ahead decides.
- #3565: a doubled sign (--280, +-280) is read and skipped as one number.
- Page::base_dimensions keeps a one-point floor after /UserUnit instead of
  one raw unit.

Also a local bound: a CCITT /Columns past u32::MAX is refused instead of
wrapping to a small width that passed ccitt_size_ok.

Tests: content and number unit tests from PdfCraft, a stream unit test, a
synthetic page test for the floor, a CCITT /Columns unit test, and the two CCITT raster tests in
crates/pdf/tests/upstream_raster.rs.

* hayro-interpret: port PdfCraft's text fixes (ledger 30, 44, P2, P6, P8, 26-ext)

From PdfCraft a2ec27b47:

- (30): WinAnsi 0xA0 and 0xAD and MacRoman 0xCA read as an ordinary space
  and hyphen (Annex D.2 gives them those glyph names) unless /Differences
  names nbspace or sfthyphen.
- (44): a /ToUnicode /Identity-H or /Identity-V name reads two-byte codes
  as their Unicode values; controls and lone surrogates give no text.
- P6: glyph names outside the Adobe Glyph List (f_i, hyphen.case,
  uniXXXXXXXX) read by the glyph list specification's full mapping; names
  that only number a glyph (C39, c251) give no text.
- P8: a substitute font whose charmap lacks a spacing character (U+2002 and
  similar) draws its ordinary space instead of .notdef.
- P2: a Type 3 glyph's /FontBBox is transformed to device space before it
  bounds the glyph's clip and shadings, so shaded glyphs draw at every
  position.
- (26) extension: comma-below letters (s, t, k, l, n, r, G with comma) in
  the standard-font stand-ins are the base letter with the font's comma,
  scaled and placed under the baseline.

Tests: crates/pdf/tests/upstream_text.rs for (30), (44) and P6; the Type 3
shading and comma-below raster tests in crates/pdf/tests/upstream_raster.rs.
P8's PdfCraft test needs a Japanese substitute font and is not ported.

* hayro-syntax: refresh SOURCE.json local hashes for Cargo.lock, ascii_85.rs and filter/mod.rs

They changed in #241 and #253 without their local sha256 values.
2026-10-23 23:30:56 -04:00
skylartaylor c488caa9fb egui037: add shared screenshot and text helpers 2026-10-24 05:25:10 +02:00
aiongg 0073927bf4 Merge pull request #260 from aiongg/keymap-batch
keymap batch: craft-keymap, keyboard layouts, the Keyboard Shortcuts dialog
2026-10-23 22:59:20 -04:00
aiongg 558f8ce2fc Merge upstream main into keymap-batch 2026-10-24 02:42:28 +00:00
aiongg 53837472d6 Merge pull request #259 from aiongg/i18n-batch
i18n batch: isolation, region, sizes, folding and sorting, ICU4X numbers, number parsing, craft-fonts-build
2026-10-23 22:42:02 -04:00
aiongg 2c6b3b5392 hayro-interpret: compute the largest 32-bit Type 0 sample in 64 bits (#257)
PdfCraft ledger hayro-interpret (31), its overflow part: a Type 0 function
with /BitsPerSample 32 computed 2_u32.pow(32), which overflows (a panic with
overflow checks on). The maximum is now ((1u64 << bps) - 1) as f32. The flat
sample array that (31) also brings is a performance change and is not part of
this commit. Test: function::type0::tests::thirty_two_bit_samples_do_not_overflow.

PdfCraft's other reader limits are already covered here: ASCII85 output is
bounded by the decode limit (#253), object-stream indexes are parsed once with
checked offsets, and direct objects nest at most 64 levels (PdfCraft: 128).
2026-10-23 22:32:25 -04:00
aiongg 332a4456dd Merge #186: keymap: Keyboard Shortcuts dialog, named shortcut sets and the shortcut editor
# Conflicts:
#	crates/keymap/src/lib.rs
2026-10-24 02:29:57 +00:00
aiongg f0379def18 Merge #205: keymap: read presses by the keyboard layout 2026-10-24 02:29:52 +00:00
aiongg 4d646b3cbd Merge #164: Add craft-keymap: shared keyboard bindings and shortcut-set import 2026-10-24 02:29:52 +00:00
aiongg ec65b95a65 Merge #213: Add craft-fonts-build: embed craft-fonts faces from an app's build.rs
# Conflicts:
#	Cargo.lock
2026-10-24 02:26:24 +00:00
aiongg 02eb391d39 Merge #202: i18n: parse_number for typed and pasted numbers
# Conflicts:
#	crates/i18n/README.md
2026-10-24 02:26:12 +00:00
aiongg a8b8fbd5a8 i18n: the RTL unit test sets its number format, as the region now decides digits 2026-10-24 02:26:04 +00:00
aiongg 5f4c39ea94 i18n: a region-formatted count is a left-to-right isolate in RTL wordings; trn_plain has no marks 2026-10-24 02:26:04 +00:00
aiongg aaf9f29e4f Merge #210: i18n: numbers for a region on ICU4X, locale digits in counts and sizes
# Conflicts:
#	Cargo.lock
#	crates/i18n/Cargo.toml
#	crates/i18n/README.md
#	crates/i18n/src/lib.rs
2026-10-24 02:25:57 +00:00
aiongg fa78cb52a5 Merge #206: craft-i18n: search folding and locale sorting
# Conflicts:
#	crates/i18n/Cargo.toml
#	crates/i18n/src/lib.rs
2026-10-24 02:25:27 +00:00
aiongg 7cb4a73b3c i18n: file sizes and measures fill {n} as a left-to-right isolate 2026-10-24 02:20:40 +00:00
aiongg c465dd3f47 Merge #204: i18n: file sizes and measures with translatable unit names
# Conflicts:
#	crates/i18n/README.md
#	crates/i18n/src/lib.rs
2026-10-24 02:20:40 +00:00
aiongg 1faeabfead Merge #203: i18n: formatting region apart from the interface language
# Conflicts:
#	Cargo.lock
#	crates/i18n/README.md
#	crates/i18n/src/lib.rs
2026-10-24 02:20:33 +00:00
aiongg 85148fd99c Merge #201: i18n: isolate inserted values in fmt and trn 2026-10-24 02:18:41 +00:00
aiongg 9f75d3dc19 pdf: keep a CID font's /W2 table past an out-of-range first CID; vendor hayro-cmap from PdfCraft (#254)
* pdf: keep a CID font's /W2 table past an out-of-range first CID; vendor hayro-cmap from PdfCraft

- hayro-interpret: a /W2 run whose first CID is past 65535 is skipped, as in
  /W. A run starting at 4294967295 overflowed the CID counter and discarded
  the whole table. Unit test in font::cid.
- hayro-cmap 0.1.0 is vendored from PdfCraft a2ec27b47 with its malformed-CMap
  patch (PdfCraft #3177): bad bfchar/bfrange entries, an unresolvable usecmap
  and an unparseable tail are skipped instead of failing the whole CMap. The
  crate's tests come with it. Wired up like the other vendored Hayro crates
  (workspace exclude, patch entry, SOURCE.json, backend test script); NOTICE,
  ATTRIBUTION.md and vendor/README.craft-reader.md record the source.

* hayro-syntax: refuse an xref-stream subsection that runs past object number u32::MAX

The object number was start + i in unchecked u32: a subsection starting near
u32::MAX overflowed (a panic in debug builds, a wrong object number in
release). Covered in xref::tests::xref_stream_entries_need_backing_bytes.

* hayro-cmap: a binary usecmap counts against the nesting limit; /W2 checks the CID first

- hayro-cmap: a binary CMap's usecmap passed its own depth to the nested
  parse, so a self-referencing binary CMap recursed until the stack
  overflowed. It now counts like a text usecmap. Test:
  self_referencing_binary_usecmap_stops_at_the_nesting_limit.
- hayro-interpret: the /W2 first-CID check runs before the run's metrics
  are read, so an incomplete run past CID 65535 no longer discards the
  rest of the table.

* hayro-syntax: refuse xref-stream object numbers past i32::MAX

ObjectIdentifier stores an i32; a number between i32::MAX and u32::MAX
was cast to a negative object number.
2026-10-23 22:17:04 -04:00
aiongg 7fe95cf81e Merge upstream main into i18n-isolation 2026-10-24 02:09:12 +00:00
aiongg 9c5ebb782e Revert "craft-ui: update egui to 0.37 (#199)" (#251)
This reverts commit 3832c47ef6.
2026-10-23 22:08:41 -04:00
Dylan M. Taylor d6b3fb9671 security: bound vendored hayro ASCII85 output by the stream decode limit (#253)
* security: bound ASCII85 output by the stream decode limit (CodeRabbit)

* fix(ascii85): truncate terminal partial groups to decode limit
2026-10-23 21:54:26 -04:00
aiongg 32e4f5a764 Merge upstream main into i18n-isolation 2026-10-24 01:08:02 +00:00
aiongg 5354479ce3 i18n: scripts, direction and regional Han per language tag (#200)
* i18n: scripts, direction and regional Han per language tag

scripts(tag) lists the ISO 15924 scripts a language's interface text is
written in, direction(tag) follows the main script, and han(tag) gives
the regional Han forms (SC, TC, HK, JP, KR) for picking a CJK face.
I18n::rtl uses direction.

* i18n: non_exhaustive Script and Han; regions that imply a script

pa-PK, uz-AF and az-IR are written in Arabic and sd-IN in Devanagari, as
CLDR's likely subtags give them. A script subtag still wins.

* i18n: han and catalog matching follow region-implied scripts

han(zh-US), zh-AU and the other regions CLDR writes in Traditional
characters give Traditional, and pa-PK picks a pa-arab catalog, never
Gurmukhi pa, as pa-Arab-PK does.

* i18n: Hani and yue-Hant pick Han forms by region; usual scripts for kk, ky, tg, ku, ms, ug

* i18n: base_direction of a paragraph from its first strong character
2026-10-23 21:07:32 -04:00
aiongg 93b7b34f2f Merge upstream main into i18n-isolation 2026-10-24 00:15:07 +00:00
aiongg 599cc8cf95 Merge upstream main into i18n-scripts 2026-10-24 00:14:23 +00:00
aionggandDavis Brief 3832c47ef6 craft-ui: update egui to 0.37 (#199)
* craft-ui: match guard for a stack's hit test (clippy 1.95 collapsible_match)

* Update egui to 0.37

egui 0.37 replaces WidgetType with accesskit's Role and renames a few
methods. Mapped per egui's own table (WidgetType docs): Checkbox is
CheckBox, SelectableLabel is Button, Other is Unknown; UiBuilder::id and
Ui::id are scope_id; Id::new is Id::unique (the same hash). No behaviour
change.

egui's test harness now panics on a glyph no installed font has, and its
bundled fonts dropped U+22EE, so the docking fixture's group header draws
an ellipsis instead of a vertical ellipsis.

* craft-ui: README says egui 0.37

---------

Co-authored-by: Davis Brief <davisbrief@gmail.com>
2026-10-23 20:02:10 -04:00
aiongg dd50095dbb Merge upstream main into i18n-scripts
# Conflicts:
#	crates/i18n/README.md
2026-10-23 23:58:43 +00:00
skylar taylor-barrick bfb116ccaf Merge pull request #247 from benandez/i18n-lazy-parse
i18n: parse each locale lazily on first lookup
2026-10-23 17:00:26 -06:00
Martin BallandClaude Opus 5.5 f7cc8a7df1 i18n: row-problem log and README name precedence (review)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 22:11:30 +01:00
Dylan M. Taylor 1d041f5c03 Merge pull request #249 from dylanmtaylor/ci/sonar-main-not-cancelled
ci(sonar): stop cancelling main-branch runs so coverage is published
2026-10-23 16:59:06 -04:00
Dylan M. Taylor 562aa90af5 ci(sonar): don't cancel superseded runs on main, so coverage is published 2026-10-23 16:51:10 -04:00
aiongg f0ad6c1e1d media: QuickTime v2 sound entries refuse channel counts and lpcm bit depths past 16 bits (#246)
From DeckCraft 6873a25.
2026-10-23 16:14:41 -04:00
Martin BallandClaude Opus 5.5 4ecda6fa8c i18n: parse each locale lazily on first lookup
I18n::new keeps the embedded text and parses nothing; a locale's rows
are parsed on the first lookup that reaches it (also as a fallback) and
its bad rows logged with log::warn!. new returns only the locale-level
problems. check_all parses every locale and returns all problems.
name() scans for the language.name row instead of parsing the locale.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 21:11:48 +01:00
aiongg f97c84d273 media: fixes from FilmCraft's copy (NAL widths, RBSP stop bits) (#245)
* media: reject NAL length prefix widths outside 1 to 4

length_prefixed_nals returns BitError::InvalidNalLengthSize for any other
width before scanning, empty data included. A width of 5 or more was
accepted and shifted bytes out of the length; a zero width returned Eof
only for non-empty data. Width 3 still splits, since
(lengthSizeMinusOne & 3) + 1 can be 3.

From filmcraft#2953.

* media: find the RBSP stop bit once per BitReader

BitReader::new records the position of the last set bit, and
more_rbsp_data compares against it instead of scanning back over the
data's zero suffix on every call (once per macroblock in a slice
parser). FilmCraft caches it in a Cell on first use; computing it in
new keeps BitReader Sync.

From filmcraft#2880.

* media: test the order CodecPrivate stripped headers are restored in

Header-stripping encodings with private-data scope prepend their bytes in
list order, so the last one listed ends up first. Empty prefixes and
frame-only scopes leave CodecPrivate as it is.

From filmcraft#2911.
2026-10-23 15:52:41 -04:00
aiongg 7474826f6f media: craft-media review fixes (frame budget, PCM chunks, spec names) (#196)
* media: look up the track of each traf in a hash map

* media: bound Matroska indexing by a file-wide frame budget

Every indexed frame claims its stored bytes and at least one, so fixed-size
laces of empty frames cannot describe more frames than the file has bytes.
The frame count across tracks is capped at 2^26, the TrackEntry count at
2^16, and blocks find their track through a hash map.

* media: keep the spec spellings of Colour, ColourSpace and colourPrimaries

* media: PCM chunks stay bounded in MOV and MP4

The writer ends a PCM chunk at 1 MiB or half a second, and returns an
error past 2^32 - 1 samples in a track instead of clamping the stsz count.
The reader splits a PCM chunk above 16 MiB into several samples, so files
from writers without chunk limits read whole past 4 GiB. A sparse output
lets the tests write past 4 GiB, which also exercises the writer's co64
path.

* media: clamp max_lace_frames to 256 and round BlockDuration

* media: level-1 Matroska elements read at most the file's length in all

SeekHead entries pointing at nested elements made each one read the rest
of the file.

* media: set_sample_entry rejects a codec change

* media: the mux oracle compares stream counts before streams

* ci: media checkout does not persist credentials

* media: README lists the renames from FilmCraft and the output that differs on valid input

* media: rustfmt

* media: the stream count comparison leaves out timecode streams

* media: PCM chunks end at 1 MiB only, as ffmpeg's do

ffmpeg's PCM chunks run past half a second, so a duration limit split
remuxed chunks the source kept whole.

* media: frames and samples indexed scale with the file's length

Matroska indexes at most one frame per 8 bytes of file and ISO BMFF at most
one sample per 4 bytes, beyond the first 2^20 of each, so the sample tables
stay within about ten times the file's size. Empty fixed-size laces and
one-byte constant-size samples could make them 40 to 76 times its size.

* media: clamp the frame and sample allowances
2026-10-23 15:42:51 -04:00
Dylan M. Taylor f180fc0d35 Use chrono instead of hand-rolled UTC conversion (#219)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled civil_from_days and Day::to_days in
crates/ai-core/src/history/report.rs with chrono, added as a workspace
dependency (std-only, so ai-core still compiles for wasm). The day/month
grouping, ISO and CSV timestamps and the Day/Month display are byte-identical
and pinned by the existing tests. AGENTS.md now says time goes through chrono.

Follow-up to storytold/cadcraft#1785

* history: preserve out-of-range conversion failure in civil_from_days

civil_from_days used `i32::try_from(..).unwrap_or(0)` before calling
NaiveDate::from_num_days_from_ce_opt, so a CE day count past i32 (e.g.
from Day::from_days(i64::MAX)) passed a valid day 0 to chrono and read
as 0000-12-31 instead of the 0000-01-01 fallback the None arm gives for
every other out-of-range date. Convert with `.ok().and_then(..)` so both
the integer-conversion and NaiveDate-construction failures share the one
fallback. Regression test covers i64::MAX and i64::MIN.

Doc comments on the chrono-touched to_days/from_days/civil_from_days now
state the out-of-range behaviour (0 / 0000-01-01) for the docstring gate.

* Convert every i32 year, not only chrono's range

Day holds any i32 year, but NaiveDate stops near ±262143, so years like
262144 read as 0 / 0000-01-01. The Gregorian calendar repeats every 400
years: chrono now converts the day within its cycle and whole cycles are
added back, in both directions. Values match the pre-chrono formula
(tested for 262144, -262145, i32::MAX and i32::MIN). Nonexistent days
still read as 0, and day counts whose year doesn't fit i32 still read as 0000-01-01.

* Name the 400-year range extension as the one exception to the time rule

The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.

* Say the library computes month and day; time-of-day is plain arithmetic

Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
2026-10-23 11:17:51 -04:00
Dylan M. Taylor 9def84d7a5 security: fallible bitmap allocation in vendored hayro-jbig2 (#242)
* security: fallible bitmap allocation in vendored hayro-jbig2 (follow-up to #239)

* security: reinitialize allocates before mutating the jbig2 bitmap (CodeRabbit)
2026-10-23 11:16:19 -04:00
Dylan M. Taylor 35d18945a9 fix(hayro-syntax): read full /W[0] xref entry type width (#241)
* fix(hayro-syntax): read full /W[0] width for xref stream entry type (follow-up to #239)

* hayro-syntax: loader-path test for oversized /W[0]; document patch; drop committed build output (CodeRabbit)
2026-10-23 11:14:16 -04:00
Dylan M. Taylor 6cefd467fc security: bound vendored hayro xref/object-stream/JBIG2/graphics-state work and krilla PNG size (CodeRabbit) (#239)
* security: bound vendored hayro xref, object-stream, JBIG2 and graphics-state work; krilla PNG size

hayro-syntax:
- xref-stream subsections must be backed by decoded bytes: zero /W widths and counts
  longer than the data are refused before inserting (CodeRabbit CWE-400, xref.rs:962)
- object-stream offset tables are parsed once per document and cached in Data
  (CodeRabbit CWE-407, xref.rs:1079)
- embedded JBIG2 pages over 2^28 pixels are refused before allocating, with checked,
  fallible reservation (CodeRabbit CWE-789, filter/jbig2.rs:34)
- a JBIG2Globals stream with an image filter is ignored, so globals cannot recurse
  (CodeRabbit CWE-674, filter/jbig2.rs:20)

hayro-interpret:
- at most 4096 saved graphics states / 64 MiB retained; dash arrays over 256 entries stroke
  solid (CodeRabbit CWE-400, context.rs:226)

krilla:
- PNGs implying more than 1 GiB of decoded output are refused at Image::from_png and in
  the deferred decoder; the buffer is reserved fallibly (CodeRabbit CWE-789,
  graphics/image.rs:565)

SOURCE.json local hashes updated; patches recorded in vendor/README.craft-reader.md and
vendor/krilla/README.craft.md.

* vendor: fix SOURCE.json local hashes; reserve krilla's colour/alpha split buffers fallibly

- SOURCE.json: only the local sha256 changes; the source/upstream hashes of the patched
  files are restored (CodeRabbit review)
- krilla handle_u8_image/handle_u16_image reserve their split buffers with try_reserve_exact
  and return an error, so a valid image under the 1 GiB decode cap cannot abort the process
  on the second allocation (CodeRabbit review)
2026-10-23 08:39:26 -04:00
aiongg fdf121325c ci: build and test craft-i18n with collate 2026-10-23 08:15:24 +00:00
aiongg 72a1dc7728 i18n: the shipped-locale collation test checks each locale's own order 2026-10-23 08:15:24 +00:00
aiongg 6ea2d2c7f2 i18n: standalone Hangul jamo are letters in search, not syllable parts 2026-10-23 08:15:24 +00:00
aiongg f0ed593d9f keymap: update Cargo.lock for the egui dev-dependency with serde 2026-10-23 08:14:06 +00:00
aiongg 135981286f keymap: a layout switch forgets what letter positions typed, and Layout::forget_letters does so on request 2026-10-23 08:03:27 +00:00
aiongg 5050d5dead Merge remote-tracking branch 'ao/craft-keymap' into keymap-layout-rule 2026-10-23 08:02:46 +00:00
aiongg 3f5f57642a Merge remote-tracking branch 'upstream/main' into keymap-layout-rule
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	README.md
2026-10-23 08:02:46 +00:00