Commit Graph
81 Commits
Author SHA1 Message Date
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 24a472717a prepress: transparency flattener (flatten feature) (#194)
* hayro-interpret: ink colors, overprint and font bytes for the flattener

Local patches for craft-prepress's PDF-in adapter, labeled Craft patch
(flatten) and listed in vendor/README.craft-reader.md:

- Device::set_overprint and Device::set_isolated, called where the
  blend mode is set and after an isolated group is pushed.
- Color::ink_paint, ColorSpace::ink_space and ink_values: colors and
  image samples as the inks they print, without converting to RGB.
  RasterImage::with_inks gives native samples with Decode and Matte.
- OutlineFontData::embedded and index, and the CFF bytes of Type1C
  fonts, so text can be embedded again.
- The sh operator ignores /Background (ISO 32000-2 8.7.4.5.1).

* plate-test: flattened-page checks, image alpha and scenes F01, F02

- report::run_flattened separates each scene's page after a caller's
  flattener as well as the original, and structure::transparency lists
  what transparency a page still has. plate-test doesn't depend on the
  flattener.
- Scene images take an alpha channel, written as /SMask.
- F01: a partly transparent image over a flat fill; where its alpha is
  0 the fill keeps its exact inks (vectorcraft#2160).
- F02: art hidden under opaque fills leaves no trace after flattening
  (vectorcraft#2161).

* prepress: transparency flattener (flatten feature)

craft_prepress::flatten turns PDF pages with transparency into pages
without it, keeping inks, overprint and live text, for PDF/X-1a and
other output that can't carry transparency.

- pdf: reads a page into a display list in inks through hayro-interpret:
  paths, outlined strokes, clips, groups, soft masks, overprint, spot
  and DeviceN colors, images in their own color space, gradients,
  rasterized mesh shadings, tiling patterns and glyph runs.
- font: prepares the source fonts for embedding again (TrueType,
  OpenType, CFF wrapped as OpenType); others fall back to outlines with
  ActualText.
- geom: planar regions with the shapes that cover each one (linesweeper).
- compose: composites each region per ink (separable and non-separable
  blend modes, isolated and knockout groups, overprint per plate as ISO
  32000 defines it); regions under images, gradients and soft masks are
  marked complex.
- raster: complex regions as DeviceN images through plate mode, grid
  snapped with a one-pixel outset; flatten_page.
- write: writes the result with the vendored krilla; flatten_document
  writes every page into one PDF with the output intent, page boxes and
  fonts shared across pages when their bytes match.

The regions and the compositor are ported from VectorCraft (commit
c2b15982, crates/pathops and crates/engine/src/cmd/flatten.rs), credited
in the crate README. scripts/plate-check.sh --flatten separates every
scene's flattened page with Ghostscript (local only). CI runs
craft-prepress with all features.

* krilla: DeviceN /Colorants from PDF 1.3, NChannel attributes from 1.6 (patch 11)

* prepress: flatten_document output PDF version and PDF/X version

* prepress: format the PDF/X-1a flatten test

* krilla, craft-prepress: write GTS_PDFXConformance next to GTS_PDFXVersion

PDF/X-1a:2003 files carry GTS_PDFXVersion (PDF/X-1:2003) and
GTS_PDFXConformance (PDF/X-1a:2003). Krilla patch 12 adds
SerializeSettings::pdfx_conformance, and FlattenDocumentOptions passes it
through.

* krilla, craft-pdf, craft-prepress: carry the modification date over

Krilla patch 13 adds Metadata::modification_date, written as /ModDate and
xmp:ModifyDate in place of the creation date. craft-pdf's Metadata gains
modified, and flatten_document takes it from the source's ModDate.

* craft-prepress: count the print annotations a read leaves out

ReadReport::dropped_annotations counts the annotations a printer would
paint (Print flag, not Hidden, with a normal appearance) when
ReadOptions::annotations is false; FlattenTotals sums it.

* craft-prepress: degenerate groups rasterize without a soft mask

A group the region sweep fails on is clipped to its members' boxes, which
no other group's items reach, so the paper is its only backdrop. Its
image is written opaque, as the plates of the items over paper, and the
page keeps no transparency for PDF/X-1a.

* craft-prepress: write untouched JPEG images as they are

A DCT-encoded DeviceGray or DeviceCMYK image that krilla's JPEG
passthrough writes with the same meaning (8 bits, no decode parameters,
the default gray Decode or the inverted Adobe CMYK one) keeps its data
in ImageItem::jpeg, and the writer embeds it unchanged when the image
has no soft mask. Other images are still 8-bit Flate samples.

* craft-prepress: keep group members with no transparency above them

Groups are transitive by bounding box, so a background frame put all the
body text on it under a transparent object far away, and the text came
back as outlined fills. A member is now drawn as it is when it is opaque
and, in every region its shapes are in, only members kept this way are
above it. The region fills under it are the composite of the other
members, and it is drawn over them in paint order.

* prepress: keep the backdrop under elements of a knockout group

* prepress: rasterize complex regions thinner than a point

* prepress: mark flatten errors, reasons and reports non_exhaustive

* prepress: bound the items a tiling pattern fill expands into

Each of up to MAX_TILING_CELLS cells copied the whole cell content, and a
cell can hold many items or an expanded pattern of its own. A fill whose
cells would hold more than MAX_TILING_ITEMS items in all, counting groups,
masks and nested patterns, is read as an image.

* prepress: stroke solid past the dash budget

A dash pattern that cuts a stroke into more than 250,000 pieces is outlined
solid, with the estimate hayro's renderer uses.

* prepress: bound the sampling and memory of rasterized tiling patterns

Each pixel sampled every cell over it, which for a BBox far larger than
its step is millions of cells per pixel; past MAX_TILING_CELLS cells per
pixel the fill paints nothing, and below it the image resolution drops to
keep the samples within MAX_TILING_SAMPLES. The canvases also hold a value
per page ink, so their pixel limits shrink with the ink count to keep the
memory of CMYK canvases, and the canvas size uses checked arithmetic.

* prepress: mark the page reader's error and report non_exhaustive

* prepress: format the tiling tests

* prepress: CodeRabbit fixes: no nonzero overprint mode for images and shadings, count dropped tilings, widen the hhea ascender

* prepress: refuse DeviceN spaces past the output's colorant limit and regions past the separated spot inks
2026-10-24 16:15:41 -04:00
aiongg 95be650aba Merge upstream main into docking-batch 2026-10-24 12:55:36 +00:00
aiongg 4238a2dd57 Merge upstream main into print-color
Conflicts: NOTICE, vendor/README.craft-reader.md and the hayro-interpret and
hayro-syntax SOURCE.json keep the ec0a965 provenance and add hayro-cmap
(a2ec27b) and hayro-postscript (crates.io 0.1.0). type0.rs keeps PdfCraft's
flat sample array with its 64-bit largest sample and main's 32-bit test.
Local sha256 values recomputed for the merged files.
2026-10-24 05:46:57 +00:00
aiongg 8e074ac261 Merge #244: fade a dragged native window on macOS 2026-10-24 04:32:36 +00:00
aiongg a22c6d0c63 docking: a native window fades to half opacity while it is dragged, on macOS 2026-10-24 04:32:17 +00:00
aiongg 69a3ae83c5 Merge upstream main into docking-batch 2026-10-24 03:54:18 +00: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 2529d038b4 Merge #244: ui: floating panels in their own OS windows (native-windows) 2026-10-24 03:13:34 +00:00
aiongg 1ccdad9512 docking: Floating::with_id for layouts built in code; debug builds warn about rebuilt layouts
Floating::new, single and stack draw a fresh id on each call, so a layout
built anew each frame opened a new OS window each frame. with_id gives such
a layout the same windows every frame. Debug builds log a warning once when
three frames running replace every native window with new ones holding the
same panels.
2026-10-24 03:09:14 +00: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 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 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 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 07f1250d6c Merge upstream main into print-color 2026-10-24 02:09:21 +00:00
aiongg c52c6f3ec0 Merge upstream main into print-color
- hayro-syntax ascii_85.rs: main's decode-limit version (same bound as
  the pdfcraft ec0a965 copy, plus its test).
- hayro-interpret and krilla SOURCE.json: source fields from pdfcraft
  ec0a965 / krilla 0.8.2, sha256 recomputed for merged files.
- krilla PNG decode cap from main recorded as patches/11-png-decode-cap.patch
  with its ledger row and README section.
2026-10-24 02:08:42 +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
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
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
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 e2b452482a docking: an example with native floating windows 2026-10-23 18:05:09 +00: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
aiongg f0ed593d9f keymap: update Cargo.lock for the egui dev-dependency with serde 2026-10-23 08:14:06 +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
aiongg 30d17273c3 Merge remote-tracking branch 'upstream/main' into keymap-ui
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	README.md
2026-10-23 07:18:38 +00:00
aiongg 7865923aec Merge upstream main into print-color 2026-10-23 05:21:53 +00:00
aiongg b94e7d8b87 Merge remote-tracking branch 'upstream/main' into craft-keymap
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	README.md
2026-10-23 05:02:48 +00:00
aiongg 8b471e88c2 Merge remote-tracking branch 'upstream/main' into fonts-build 2026-10-23 04:59:39 +00:00
skylar taylor-barrick b1401e33b1 Share browser download resource ownership (#226)
Add opt-in browser download helpers that own detached links and object URLs, clean up failed or abandoned preparation, and register delayed URL cleanup before issuing a click. Applications retain filename, MIME type, pacing, and user-activation policy.

Include browser lifecycle and failure-path tests, public API documentation, and Sonar test-source classification. All 32 PR checks passed; targeted WASM Clippy and formatting passed in local validation.
2026-10-22 20:49:41 +02:00
skylar taylor-barrickandskylartaylor ffe7f8fce8 Share Linux and BSD desktop appearance observation (#225)
* platform: share desktop appearance observation

* Align appearance target guards and document watcher contracts

* Replace appearance wildcard imports with explicit dependencies

---------

Co-authored-by: skylartaylor <skylartaylor@users.noreply.github.com>
2026-10-22 10:13:09 -04:00
skylar taylor-barrickandskylartaylor b9c1a1105e Add craft-platform with shared native dialog lifecycle helpers (#224)
* platform: add native dialog lifecycle services

* Document dialog outcomes and lifecycle contracts

---------

Co-authored-by: skylartaylor <skylartaylor@users.noreply.github.com>
2026-10-22 10:13:08 -04:00
aiongg 315af428c7 keymap: a key whose Latin layout character is no letter runs no letter binding
The suite's egui reports, per key pressed, the character it types on the
Latin layout shortcuts follow (InputState::latin_layout_char). A letter
makes the press that letter; any other character means the key has no
Latin letter, so egui's position fallback no longer turns it into one: on
Dvorak + Russian the key that types й is Dvorak's ', not Ctrl+Q. Without a
report (crates.io egui) the key's position still decides.
2026-10-21 18:05:18 +00:00
aiongg a414736c67 craft-fonts-build: subset_to cuts the role ui faces to the catalogs' characters at build time
The interface faces in craft-fonts are full Regular TrueType sources. With the
subset feature (build-dependencies only), Embed::subset_to cuts each one with
skera to the catalogs' characters from U+2E80 up plus CJK punctuation, kana and
full-width forms, keeping every layout feature, and embeds the cut from OUT_DIR.
for_ui without catalogs leaves the interface faces out with a warning; a CFF
interface face is an error. check_text and the catalogs warn only about
characters no embedded face covers.
2026-10-21 13:31:26 +00:00
Dylan M. Taylor 1f805378d4 feat(window): shared craft-window crate for consistent window decorations (#217)
* feat(window): shared crate deciding who draws the window frame

Add craft-window, which decides whether the desktop or the app draws the
window frame (titlebar, borders, window buttons) and applies it to an
egui ViewportBuilder. This fixes the drift where some Craft apps drew
their own titlebar on KDE while others kept KWin's.

The rule: macOS, Windows and X11 keep the desktop frame; a Wayland
compositor with zxdg_decoration_manager_v1 (KDE, Sway, Hyprland, COSMIC)
keeps its frame; one without it (GNOME) leaves the frame to the app.
<PREFIX>_TITLEBAR=app|system|desktop overrides. When the app draws the
frame, the decision carries the window-button Layout from GNOME's
button-layout so the app's header bar matches the desktop's order.

Decision logic only; drawing the buttons stays in each app. Extracted and
generalized from pdfcraft's decorations.rs. forbid(unsafe_code); default
build has no egui dependency (egui feature adds decorate()).

* style(window): rustfmt the Wayland probe and Dispatch impl

cargo fmt --check failed CI: collapse the thread-spawn builder chain and
the Dispatch::event signature onto the lines rustfmt wants (verified with
rustfmt 1.98 against the workspace rustfmt.toml).

* fix(window): address review — WAYLAND_SOCKET, bounded gsettings, checkout creds

- Skip the Wayland probe when WAYLAND_SOCKET is set: connect_to_env would
  take ownership of the inherited fd and unset the variable, closing the
  descriptor winit needs. Keep the desktop frame instead.
- Run the gsettings button-layout lookup behind the same 500 ms bounded
  worker as the Wayland probe, so a stalled gsettings can't block startup.
  Factored the worker into a shared bounded() helper.
- window.yml: set persist-credentials: false on checkout so the token
  isn't left in .git/config while cargo runs dependency build scripts.
- README: document the socket guard and the bounded gsettings lookup.

Verified with rustfmt 1.98 and clippy -D warnings (both feature sets).

* fix(window): reap the gsettings child on timeout; document probe tradeoffs

Follow-up to review: the button-layout lookup now spawns gsettings with
piped output, reads it on a worker, and kills+reaps the child whether it
finishes or times out — so a stalled gsettings leaves neither a blocked
process nor a parked worker (it is a one-shot startup call).

Also document, on the Wayland probe, that an inherited WAYLAND_SOCKET
(rare; normal launches get WAYLAND_DISPLAY) deliberately falls back to the
desktop frame to protect winit's connection — the same frame an app that
doesn't probe would get, a safe default rather than a regression.

Verified: rustfmt 1.98 clean, clippy -D warnings clean (default + egui),
tests pass.
2026-10-21 05:35:20 -04:00
aiongg b37040cb93 Add craft-fonts-build: embed craft-fonts faces from an app's build.rs 2026-10-21 06:16:08 +00:00
aiongg cf36f3cb21 i18n: numbers for a region on ICU4X, locale digits in counts and sizes
Format writes integers, decimals and percentages for a Region on ICU4X's
decimal formatter: the locale's digits (Arabic-Indic in Egypt, Persian in
Iran) or Latin ones on request, its separators and grouping sizes (Indian
12,34,567), CLDR's percent patterns, and the region's own separators where
the system sets them. I18n::format holds the one trn, file_size and
measure use, the system region's until set_format.

Plural rules move from intl_pluralrules to ICU4X, and trn_decimal picks
the form from the digits shown (1.0 files, 1,5 файла).

The CLDR data (decimal symbols, digits, plural rules, percent patterns)
covers the shipped languages and their regional variants; data/generate.sh
rebuilds it.
2026-10-21 05:14:23 +00:00
69a4154fb8 craft-i18n: search folding and locale sorting
`Folder` folds text for case- and accent-insensitive search (Turkish and
Azerbaijani i by locale, ß, final sigma, ligatures, full-width forms) and
maps matches back to the original text for highlighting. `Collator`
(feature `collate`) sorts lists in the locale's order with digits compared
by value, on ICU4X with collation data for the shipped locales only.

The Turkish i and sigma handling come from PdfCraft's `fold_case`.

Part of #198.

Co-authored-by: snipeTR <31975916+snipeTR@users.noreply.github.com>
Co-authored-by: nevicia2010-hub <nevicia2010@gmail.com>
2026-10-21 04:56:28 +00:00
ce70b2ede8 i18n: formatting region apart from the interface language
Region holds the number and time locales, metric or US measurement, A4 or
Letter, the decimal separator, and the grouping separator and clock when
the system sets them. Region::system reads Windows' regional format with
the user's changes, Apple's current locale, the POSIX LC_* variables each
on their own, and the preferred language on the web and Android.
Region::from_setting takes an app's saved choice ("auto" or a tag).

Ported from WordCraft's Region (storytold/wordcraft#857, #1494).

Co-authored-by: Dmitrii Khlapov <dkhlapov@pm.me>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-21 04:41:41 +00:00
aiongg 07047b7221 Add craft-media: shared MP4/MOV, Matroska and bitstream code from FilmCraft (#182)
* media: add craft-media with bitstream and isobmff from FilmCraft

Moves filmcraft-bitstream and filmcraft-isobmff (storytold/filmcraft@6c3948a)
into one crate with a feature per module. The public API matches FilmCraft's so
its crates can become re-exports. Tests, oracle tests and fuzzing come along;
the ffmpeg discovery helpers replace filmcraft-testkit.

Out-of-contract inputs no longer panic: bit widths above 32, a zero NAL length
prefix width, and PCM frame sizes that overflow u32. Adds
BitReader::read_ue_plus from DeckCraft's copy.

* media: public ISO BMFF box reader (isobmff::boxes)

Make the demuxer's box iterator and big-endian reader public as
isobmff::boxes::{BoxIter, BoxRef, BoxHeader, Reader}, for walkers of
boxes the demuxer does not model (CR3 uuid boxes, HEIF meta items,
JPEG 2000). BoxIter::within keeps offsets relative to the outer slice,
and allow_truncated yields a cut last box. The fuzz test walks every
box of each mutated file.

* media: matroska feature from FilmCraft's filmcraft-matroska

Port storytold/filmcraft@6c3948a crates/matroska as craft_media::matroska
behind the opt-in feature matroska, with the same public API so FilmCraft
can make filmcraft-matroska a re-export. Its unit and ffprobe oracle tests
come with it, on craft-media's test helpers.

From DeckCraft's copy: cluster and block timestamps no longer overflow;
they saturate at i64::MAX. MuxingApp and the default WritingApp name
craft-media. New tests: block additions and mutation fuzzing. The oracle
skips the duration of a track's last packet when ffmpeg 8 shortened it.

* media: MOV PCM uses lpcm v2 above 16.16 rates and for unsigned 16-bit; signed 8-bit as twos

Version 0 sound descriptions store the rate as 16.16 fixed point, so 88.2 kHz
and higher were written as 0 Hz. 8-bit signed PCM was labelled raw (unsigned)
and 16-bit unsigned as twos/sowt (signed). The reader also took raw under a
sound handler for uncompressed video.

* media: stop the top-level box scan at a box that runs past the end of the file

A 64-bit box size near u64::MAX saturated the scan offset, and the next
offset + 8 overflowed.

* media: one sample budget for the whole ISO BMFF file

The sample cap and the file-length bound on samples without table entries
(constant-size stsz, trun without per-sample fields) applied per track or
per trun, so repeating a small trak or trun multiplied the allocation.
Both are now shared by all tracks and fragments.

* media: set_sample_entry rejects entries that change the track's media type or PCM frame size

The handler, stsz constant size and PCM timescale check are fixed at add_track;
a replacement that changed them produced a corrupt file. A bad track index
returns NoSuchTrack like the other writer methods.

* media: saturate the sample end in MkvFile::duration_ns

Cluster timestamps saturate at i64::MAX, so pts + duration could overflow
for files without Info/Duration.

* media: checked add for the Matroska cue cluster position

CueClusterPosition comes from the file; an add past u64::MAX panicked in
debug builds and wrapped in release. Such a cue now falls back to the index.

* media: cap the Matroska TimestampScale at 2^32 and saturate tick conversions

A scale near 2^64 overflowed u128/i128 in the per-lace split and in
Track::ticks_to_ns. A BlockDuration near u64::MAX also overflowed the tick
difference at the default scale. Larger scales are ignored like a zero scale,
and tick and nanosecond values outside i64 saturate.

* media: Matroska fixture generation no longer truncates the shared subtitle inputs

Parallel tests generating subs.mkv or webvtt.webm could read in.srt or in.vtt
while another thread rewrote it, and cache a fixture with missing packets.

* media: rustfmt parse_moof signature

* ci: add craft-media workflow covering feature sets, WASM and ffmpeg oracles

The lint job runs fmt, clippy for each feature set (none, bitstream,
isobmff, matroska, all) and a wasm32 check. The test job runs the
all-features tests with Ubuntu's ffmpeg and CRAFT_MEDIA_REQUIRE_ORACLES=1,
installing ffmpeg while the tests compile.

* media: Matroska lacing keeps timestamp gaps

A laced frame's timestamp is implied by the block's and the track's
DefaultDuration, so lacing collapsed gaps in the input. A frame now joins
a lace only when that implied tick equals its own, and tracks without a
DefaultDuration do not lace. The laced remux oracle compares timestamps.

* media: required oracles fail on fixture generator and ffprobe failures

Every path that gave up on an oracle check (missing tool, failed fixture
generator, failed ffprobe run) goes through one helper that prints
SKIPPED, or fails the test with CRAFT_MEDIA_REQUIRE_ORACLES=1. Fixture
generators of both containers share run_generator, which takes the
required flag explicitly so tests can exercise it without changing the
process environment.

* media: use US spelling

* media: a failed ffprobe probe skips the optional oracle check

* media: the Matroska oracle compares the last subtitle cue's duration
2026-10-20 09:07:12 -06:00
aiongg 4026931196 Merge craft-prepress into the print color PR 2026-10-20 03:51:59 +00:00
aiongg f7ae814d91 plate-test: app-neutral print color scenes, checked by PDF structure and Ghostscript plates
craft-plate-test writes JSON scenes through craft-pdf and checks the written PDF's
structure (CI, under a second) and, locally only, its separated plates through
Ghostscript tiffsep (scripts/plate-check.sh). Apps can run the same scenes through
their own export as a dev-dependency.
2026-10-20 03:41:13 +00:00
aiongg 4585efee97 prepress: rename craft-separations to craft-prepress 2026-10-20 03:39:36 +00:00
aionggandGuillem Hermida c025c0dfff separations: add craft-separations with the ink model and plates
The ink model (process and spot inks, InkSet with PDF name lookup,
InkColor, Overprint) and plates(pdf, page, dpi, opts): ink discovery
and one gray film per ink through craft-pdf's raster plate mode, with
process colour converted through craft-cms (output intent, else the
caller's profile or FOGRA39). Plate rendering, ink discovery, caps and
output-intent selection port pdfcraft #2770's separations.rs.

Co-authored-by: Guillem Hermida <66697068+guillehr2@users.noreply.github.com>
2026-10-20 03:15:36 +00:00
aiongg 39f343c47b keymap: draw the shortcuts dialog: table, detail pane, conflict bar and footer
- view.rs: header with Show (dropdown or tabs), Filter and Search with search
  by key; table and detail pane in a resizable split, the detail pane below
  the table under 720 pt; virtual rows with chips, + and the restore button;
  detail pane with current shortcuts, New Shortcut, typed entry, Context,
  Assign, Use Default and Clear; prompts, conflict bar, messages, clash,
  shadow and problem lines; Undo, Redo, Reset All, Cancel and OK.
- Escape, Enter, Undo and Redo keys when idle; table keys (arrows, Home,
  End, Enter records, Delete removes); focus locks while recording.
- Accessibility labels for every control, row selection, and on_widget
  automation ids.
- style.rs: DialogStyle and the icon-button and divider styles.
- show() sizes the window to the screen; closing it is Cancel.
- tests/ui_dialog.rs: recording, conflicts, search, areas, display, labels,
  table keys, small windows, RTL. tests/ui_snapshot.rs (feature snapshot)
  writes PNGs when CRAFT_KEYMAP_SNAPSHOT_DIR is set.
2026-10-19 12:03:23 +00:00
aiongg b9bd4119cc keymap: shortcuts dialog model behind the ui feature
DialogState with every dialog action as a call on EditSession, Recorder,
editor::holders and Profiles; the table rows (areas from paths, kind and
scope, filters, search by label, translation or chord, grouping, sorting,
a row cache); key capture while recording, including clipboard events,
which removes the events it takes; the Text trait, I18nText and the list
of strings the dialog looks up. Drawing comes separately.
2026-10-19 12:03:23 +00:00
aiongg d6c7b45350 keymap: shared keyboard bindings, scopes, overrides and shortcut-set import
craft-keymap holds the data and logic for keyboard bindings: egui-free
chords with one canonical text form, bindings to commands with params,
several bindings per command, scopes, tool groups, conflicts, overrides
kept in a craft-settings type, an import pipeline for other applications'
shortcut sets, and an optional egui dispatcher. Apps keep their command
catalogue, file readers and UI.
2026-10-19 03:52:23 +00:00