Commit Graph
260 Commits
Author SHA1 Message Date
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 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 3596b2ee8c i18n: round-trip tests for Windows' equal marks, a chosen digit system and the C locale 2026-10-23 05:47:11 +00:00
aiongg b0be5d5155 Merge branch 'i18n-format' into i18n-parse-number 2026-10-23 05:47:11 +00:00
aiongg 5620df6e45 i18n: a system grouping mark equal to the decimal mark is dropped 2026-10-23 05:47:10 +00:00
aiongg cc7020654e i18n: the C locale is A4, metric and ungrouped; a chosen digit system stays in the locale 2026-10-23 05:47:10 +00:00
aiongg 1711202871 Merge branch 'i18n-region' into i18n-format 2026-10-23 05:47:10 +00:00
aiongg 1477ffff09 craft-fonts-build: warn instead of cutting to kana when subset_to gets no catalogs, and when the manifest has no interface face; watch the manifest only after reading it 2026-10-23 05:40:04 +00:00
aiongg a30ff6bf2f craft-fonts-build: reject faces under a non-UTF-8 path instead of embedding a lossy include_bytes! path 2026-10-23 05:05:13 +00:00
aiongg d413913b6b craft-fonts-build: bound each cmap subtable to its declared length 2026-10-23 05:05:13 +00:00
aiongg 8b471e88c2 Merge remote-tracking branch 'upstream/main' into fonts-build 2026-10-23 04:59:39 +00:00
Dylan M. Taylor 4919088427 Merge pull request #238 from dylanmtaylor/security/media-allocation-bounds
security: bound craft-media metadata, sample reservation, header stripping and cue bookkeeping (CodeRabbit)
2026-10-22 22:42:59 -04:00
Dylan M. Taylor c67b1e16b8 Merge pull request #236 from dylanmtaylor/security/docking-group-move-permission
security: require every floating-group member to be movable for MoveFloating (CodeRabbit)
2026-10-22 22:42:36 -04:00
Dylan M. Taylor f0d9c32e67 Merge pull request #237 from dylanmtaylor/security/print-parsing-bounds
security: bound print dialog names, PPD labels, lpstat merge and inherited page geometry (CodeRabbit)
2026-10-22 22:26:23 -04:00
Dylan M. Taylor f721e1b969 Merge pull request #234 from dylanmtaylor/security/cms-eval-work
security: bound cms CLUT interpolation and inverse-curve table work (CodeRabbit)
2026-10-22 22:26:18 -04:00