Commit Graph
1680 Commits
Author SHA1 Message Date
b903f08e7b Share bounded frosted backdrop composition
* Share bounded frosted backdrop composition

* Scope upload assertions to the backdrop texture

* Discard texture updates explicitly in headless rendering tests

* Correct texture ID borrowing in upload-count regression

* Bound texture sides before queuing backdrop uploads

---------

Co-authored-by: Fluorine <joseph.anglada92@gmail.com>
Co-authored-by: Joseph Anglada Muñiz <janglada@nd-soft.com>
2026-11-12 10:37:46 -05:00
6026ad570f Share shape gallery choices and procedural previews
* Share shape gallery choices and procedural previews

* Clip shape previews to their supplied rectangles

* Discard texture updates explicitly in headless rendering tests

* Honor fill colors across complete star and heart contours

* Make heart contour collection type explicit

* Use fixed-size triangle chunks in fill regression

---------

Co-authored-by: Fluorine <joseph.anglada92@gmail.com>
Co-authored-by: Joseph Anglada Muñiz <janglada@nd-soft.com>
2026-11-12 10:37:36 -05:00
aionggandaiongg e56b2f1220 craft-window: frame policy v2: the app's bar is the title bar by default
* craft-window: frame policy v2: integrated title bar by default, desktop and backend detection

Add the policy module: Backend, Desktop::detect, TitleBarPref, Os, Frame
{Native, Inset, Integrated}, pure decide, decide_for and apply. Caption
layouts come from GNOME gsettings, kwinrc and GTK settings.ini parsers.
Choice::probe, the root Frame and decorate keep their behaviour.

* craft-window: review fixes for the frame policy

Desktop gains icon_theme, Decision gains desktop, Backend::from_env,
GNOME layout falls back to settings.ini, compositor socket variables mark
tiling, GTK_THEME wins over settings.ini, layout is read only for an
integrated frame, Layout::mirrored.

* craft-window: CRAFT_TITLEBAR applies when the app's own variable is unset

---------

Co-authored-by: aiongg <aiongtaigi@gmail.com>
2026-11-12 10:37:27 -05:00
aionggandaiongg bcf1e69cf4 craft-window: restore store: window.json beside config.toml and a pure place()
* craft-window: restore store: window.json beside config.toml and a pure place()

* craft-window: restore: review fixes (bar strip, mixed DPI, pending writes, work area)

---------

Co-authored-by: aiongg <aiongtaigi@gmail.com>
2026-11-12 10:37:20 -05:00
aionggandaiongg 1329b6bf04 platform: GNOME's light setting reads as light, and Watcher::wait_first for the first frame
* platform: GNOME's light setting reads as light, and Watcher::wait_first for the first frame

GNOME 42-46 writes color-scheme 'default' for Settings > Appearance > Light and the portal
reports 0. Both now decode as Light on GNOME (XDG_CURRENT_DESKTOP, read once at start);
0 stays no preference on other desktops.

Watcher::wait_first(timeout) blocks until the first answer, a known-absent portal with an
empty fallback, a no-preference answer, worker exit or drop, or the timeout.

* platform: GNOME's 'default' is light only on a GNOME desktop

---------

Co-authored-by: aiongg <aiongtaigi@gmail.com>
2026-11-12 10:37:13 -05:00
aionggandaiongg 5b03af83c8 ci: add a craft-menu workflow
* ci: run craft-menu's fmt, clippy (Linux, macOS and wasm targets) and tests when menu, keymap or quit change

---------

Co-authored-by: aiongg <aiongtaigi@gmail.com>
2026-11-12 10:37:06 -05:00
VoiceLessQandVoiceLessQ 5afeda8327 craft-proof it: a ‘ quote can open the next sentence, as “ and « do
In "Vado. ‘poi’ torno." the capital-letter check skipped poi, because ends_sentence didn't count ‘ among the characters that may open the next sentence. This is the last part of storytold/wordcraft#3675; the rest came with #484.

---------

Co-authored-by: VoiceLessQ <t12kaem@gmail.com>
2026-11-12 10:37:00 -05:00
Hamza NaveedandHamza 9b7b748fbb krilla: TrueType subsets keep the font's OS/2 table (embedding permissions)
* krilla: TrueType subsets keep the font's OS/2 table (embedding permissions)

The subsetter writes only the tables it subsets, so a FontFile2 program
had no OS/2 table and the font's embedding permissions (OS/2.fsType)
never reached the PDF: a Preview & Print or Restricted font looked
unrestricted to anything reading the file later (designcraft#1781).

Patch 18 adds the source font's OS/2 table, byte for byte, to a
TrueType subset that lacks one, rewriting the table directory with its
checksums and head.checkSumAdjustment. CFF programs are still embedded
bare. Ledger, patch file and SOURCE.json updated; craft-pdf's new
font_tables test checks the table, its fsType and the font's checksums.

* krilla: the OS/2 test checks the embedded program is a subset

Its sfnt and re-read checks also pass for the source font embedded whole. A subset has fewer glyphs than the fixture's 11, and a shorter glyf.

---------

Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
2026-11-12 10:36:53 -05:00
Marc 5a37e3e383 update: offer Save and Update in the prompt when the app can save (#586)
* update: offer Save and Update in the prompt when the app can save (#586)

With unsaved work the prompt only said to save or close first. An app can now pass can_save to show_with_save: the button then reads Save and Update, a click asks the app to save through take_save_request, and the install starts once nothing is unsaved. save_stopped reports a save that did not get there. show and show_if are unchanged, so other apps see no difference.

* update: test that a second click during the save leaves the prompt open

The saving prompt test only read the painted text. This one clicks where Save and Update was, where a Dismiss would catch the second click of a double click.

* update: Save and Update follows the ready update (#586)

A ready update whose close was canceled now offers Save and Update in place of Restart to Update while the app can save. Once the save ends with nothing unsaved, the app is asked to close in that same frame, once. Tests cover both prompts and the close order.

---------

Co-authored-by: Marc <184940552+marcwgls@users.noreply.github.com>
2026-11-12 10:36:47 -05:00
.CooCoo d426768a06 ci: run Shared PDF for changes under vendor/linesweeper
craft-prepress depends on linesweeper, and #633 adds a copy of it in
vendor/linesweeper and runs its tests from scripts/test-pdf-backends.sh.
Without this path, a pull request that changes only that folder runs
SonarCloud and none of these tests.

---------

Co-authored-by: dotCooCoo <RobertLeeLW@gmail.com>
2026-11-12 10:36:40 -05:00
.CooCoo 596694db4d font-store: name four Language pack tests by the behavior they check
Drop the residual_ prefix from four tests in language_packs/tests.rs.
The two cleanup tests become committed_cleanup_*, matching the
committed removal receipts they exercise. Test bodies are unchanged.

---------

Co-authored-by: dotCooCoo <RobertLeeLW@gmail.com>
2026-11-12 10:10:35 -05:00
.CooCoo 3debdb13fc prepress: one wide curve no longer slows region cleanup for every other curve
* prepress: one wide curve no longer slows region cleanup for every other curve

The flattener's region cleanup looks up the input curves whose bounds hold
both ends of an output piece. The lookup searched back from a point by the
width of the widest input curve, so one curve as wide as the page made every
lookup scan nearly every input curve, and the cleanup grew quadratically with
the number of curves.

The curves are now grouped by bounds width, each group within a factor of two,
and each group is searched within its own widest bounds. The lookup returns
the same curves in the same order as before, so the regions are unchanged.

* prepress: region cleanup keeps a curve held just inside its right edge

Far from the origin, `x - width` can round above the left edge of a curve
whose bounds hold `x`, and the width-group search then skipped that curve.
The search's left edge is now rounded outward.

---------

Co-authored-by: dotCooCoo <RobertLeeLW@gmail.com>
2026-11-12 10:08:20 -05:00
.CooCoo 0c07636bbf Fonts build: the non-UTF-8 checkout test passes on macOS
macOS returns EILSEQ when a file name is not UTF-8, so the test panicked while creating its checkout. The test now ends at that step on macOS for that error. Any other error, and the same error on another platform, still fails the test.

---------

Co-authored-by: dotCooCoo <RobertLeeLW@gmail.com>
2026-11-12 10:06:20 -05:00
Helen Kwok 2bfc45c493 web-api: refuse calls beyond an in-flight limit, and say who can call window.<app>
* web-api: refuse calls beyond an in-flight limit, and say who can call window.<app>

At most 64 calls (PageApi::with_max_in_flight) may wait for the app at once. A call made while that many wait rejects at once with a busy error and never reaches the app, so a page script that calls in a loop without awaiting cannot grow the app queue without bound, as the desktop control server already answers busy when its queue is full. The README also says the API is a property of the page global with no key: any script in the page origin has the same command control as the desktop control server.

* web-api: turn a busy call away before converting its params, and say a same-origin parent or opener can call window.<app>

A call over the in-flight limit used to stringify and convert its params before the busy check, so a large payload still cost the work it was being refused for. The params are now built by a closure the call runs only once it holds a slot.

The README said a parent, a child or an opener cannot reach window.<app>. That holds across origins only: a same-origin parent, child or opener with a window reference calls it directly, and the craft-web-host origin and source checks do not apply to that call.

---------

Co-authored-by: Helen Kwok <107752876+helenkwok@users.noreply.github.com>
2026-11-12 10:06:13 -05:00
.CooCoo d624d5895a linesweeper: line_segment's doc says it can return an exited segment
A segment that exits at a sweep line keeps its position until the sweep advances, and segment_range leaves it out.

---------

Co-authored-by: dotCooCoo <RobertLeeLW@gmail.com>
2026-11-12 10:06:02 -05:00
Ken MantleandKenM76 35b4918481 prepress: composite the plates into a chosen output profile
Plates::composite converts through sRGB, which clips the press gamut
before a print profile sees it. Output converts press CMYK straight into
a gray, RGB or CMYK output profile (CMYK -> PCS -> output) at a chosen
intent and black point compensation, and Plates::composite_to combines
spot inks in that space: a multiply in gray or RGB, a multiply of the
complements in CMYK. CmsConversion::profile exposes the press profile.

The spot ramps move into shared helpers, used by composite and output;
the Lab encoding moves into one used by cms and output.

Part of #838 (item 2). The printer-leg semantics are adapted from pdfcer
(MIT, same author), crates/pdfcer-print/src/icc_proof.rs at 0a249a73;
the code is new, on craft-cms.

---------

Co-authored-by: KenM76 <KenM76>
2026-11-12 10:05:44 -05:00
.CooCoo 47b6bbadf0 update: read whether the copy installs itself on the check's thread, not in tick
* update: read whether the copy installs itself on the check's thread

When a check found a release, Updater::tick opened the startup prompt, and
StartupPrompt::new read the installed program, share, doc and icon trees
to decide whether to offer Update and Restart. On slow or stalled storage
that froze the frame loop.

The check's thread now makes that inspection after the release is found
and sends its answer with the release. The prompt is built with the new
StartupPrompt::with_self_update, so tick reads no installed file. A
prompt that a failed installation reopens keeps the answer of the prompt
that started it. StartupPrompt::new and Updater::offer still inspect on
the calling thread.

---------

Co-authored-by: dotCooCoo <RobertLeeLW@gmail.com>
2026-11-12 10:05:38 -05:00
Zhihui Songandsongzhihui a44920f685 craft-update: allow opted-in quiet per-user MSI self-updates
* Add opt-in quiet per-user MSI self-updates

---------

Co-authored-by: songzhihui <song19891121@qq.com>
2026-11-12 10:05:31 -05:00
.CooCoo cb9d29c913 print: a Windows driver's own settings dialog for one job
* print: driver_settings_dialog sets a Windows driver's own options for one job

A Windows print job could take a driver's own options, such as media type
and print quality, only from the printer's saved Printing Preferences
(open_printer_preferences), so choosing them for one job changed the
printer's defaults for every later job.

driver_settings_dialog(&mut job, parent) shows the driver's dialog
(DocumentProperties) for the job's printer. It opens on the job's earlier
choices for that printer, without the ones the job decides, when the
driver still reads them, else on the settings the driver gives a new job,
with the job's copies and collation. OK stores the settings in the new
Job::driver_settings. Cancel leaves the job as it was, and so do settings
the dialog returns exactly as it opened on them, since some drivers
return OK from Cancel with the settings they were given. craft-print does
not save the settings as the printer's defaults.

When the job prints, Windows converts the settings to a PrintTicket
(PrintTicket::from_dev_mode). craft-print removes the features the job's
own ticket decides: copies, collation, paper, orientation and scaling,
pages per sheet, binding and posters, two-sided when the job sets it, and
color when it asks for grayscale. It then merges the job's ticket after
the rest. The driver's private settings are kept, so a driver that also
keeps pages per sheet there can still apply it. The existing paper,
orientation and option checks run on the ticket the driver validates.
Settings are used only on the printer whose name they were chosen for,
and command_preview marks them as not used when the job, or the default
printer, is another one. Other platforms return an error, and CUPS jobs
ignore the settings.

winprint is pinned to dotCooCoo/winprint.rs 72951d7, which adds
from_dev_mode on top of storytold/winprint.rs main, until that change is
merged there. examples/driver_settings_dialog.rs opens the dialog and
prints a test sheet.

* print: the hand-run driver test accepts zeroed settings read as defaults

the_driver_dialogs_settings_leave_the_jobs_own_options expected every
driver to refuse a DEVMODE of zeros. Two drivers read it as the settings
they give a new job instead. The test now accepts either outcome: settings
the driver refuses are replaced by the dialog, settings it reads are kept,
and either way the dialog opens as if the job had no settings. The README
says such settings stay on the job after Cancel and print as the driver's
defaults.

* print: the README and hand-run test describe settings with no fields set

The hand-run Windows test compares the seed's replaces_unreadable with
whether the driver refused the settings, in place of a branch whose
check always passed, and describes its input as a DEVMODE with only its
size set. The README says such settings stay on the job after Cancel
and after OK with nothing changed, links the DEVMODE dmFields
description, and no longer says how the job prints with them.

* print: refuse a job whose driver replaced the dialog's media type, resolution or quality

A driver that validates the job's ticket can replace a media type, resolution or
print quality chosen in its dialog when it conflicts with the job's paper, and
Windows reports that as a success. The validated ticket is now compared with the
dialog's settings, by option name and scored values, and the job is refused when
one of them changed.

The Windows preview lists two-sided printing and color that the job leaves open as
"driver settings or queue default" and "driver settings or driver default": it
matches the settings to the printer by name and does not ask the driver to read them.

* print: the driver dialog opens without earlier settings the driver reads but refuses to merge

Earlier settings that a driver still converts to a ticket but then refuses to merge, or to convert back to its DEVMODE, are left out, as unreadable ones already are, so the dialog opens and can replace them.

---------

Co-authored-by: dotCooCoo <RobertLeeLW@gmail.com>
2026-11-12 10:05:25 -05:00
ruchirguitar c4b8405d5f ui: resize iconic flyouts from their top edge and free top corner
* ui: resize iconic flyouts from their top edge and free top corner (#1094)

A flyout's top edge and its canvas-side top corner now resize it; the
column-side edge and its corners stay anchored. A top-edge resize keeps
the bottom and saves the top as `Group::flyout_offset`, its distance from
the group's icons, through the new `Action::MoveFlyoutTop`. The offset is
serde-defaulted and kept in close/restore locations, so older workspaces
align flyouts with their icons as before. Saved offsets are finite and
bounded, and placement clamps them inside the float bounds.

* ui: test flyout top drags past the minimum, short viewports and older workspaces (#1094)

* ui: give the flyout's top resize handle a 4 pt band (#1094)

A one-pixel top handle was hard to grab with a real mouse. Frames
whose contents supply their own header can now set
`FloatingFrame::top_handle_height`; the default stays one physical
pixel. Iconic flyouts use the stock resize band's 4 pt, over the top of
the tab strip, and the strip below it still drags the group.

---------

Co-authored-by: ruchirguitar <113586074+ruchirguitar@users.noreply.github.com>
2026-11-12 10:05:16 -05:00
David Mann 86d6e8c5b0 feat(flags): shared feature flags for the Craft apps and the launcher
craft-flags keeps one per-user file, ArtCraft/flags.toml beside the AI
layer's ArtCraft/ai, that the launcher and every app read; CRAFT_FLAGS
overrides it for one run. Every flag is off by default. The first two:
AI-MCP (AI agents through MCP) and AI-MODELS (AI models and services).

Loading never fails: a missing or damaged file means defaults, with the
reason in Flags::problems. set() changes one flag atomically and keeps
everything else in the file, and never overwrites a file it can't read.
In the browser every flag is at its default.

Closes #1118

---------

Co-authored-by: David Mann <daaavid.mann@gmail.com>
2026-11-12 10:05:07 -05:00
Ken MantleandKenM76 83d5ad516c print: bound the printer XML parse, and respell values where they are
* print: bound the printer XML parse; respell values where they are

Every non-test parse of the driver's PrintTicket and PrintCapabilities in
ticket.rs goes through one bounded parse (new xml.rs): at most 16 MiB,
1,000,000 nodes, 256 attributes and 64 namespace declarations on an
element, and 256 levels of nesting. roxmltree's work grows with the square
of an element's attributes and namespaces, so a broken or hostile driver
document could stall Print. The attribute, namespace and depth limits are
checked by a scan that skips comments, CDATA, processing instructions and
quoted values; the node limit is roxmltree's own nodes_limit. A document
past a limit is refused the way an unparseable one already was.

The PrintTicket respell now finds each attribute's value from the
attribute's full range (the quote after its `=`) instead of
`range_value()`, which roxmltree computes from a name length kept in 16
bits and a space count kept in 8, so it pointed elsewhere past a 64 KiB
name or 255 spaces around `=`.

Closes #630. Closes #631.

* print: bound the two parses #411 added

---------

Co-authored-by: KenM76 <KenM76>
2026-11-12 09:51:56 -05:00
Dylan M. Taylorandjonathanwxh-cell 1c5c8a4e14 Add craft-pkcs7: DER, X.509, CMS SignedData, PKCS #12, RFC 3161 and revocation from PdfCraft
* Add craft-pkcs7: DER, X.509, CMS SignedData, PKCS #12, RFC 3161 and revocation from PdfCraft

Moved from PdfCraft's crates/sign at 1d7a9f9f: der, x509, cms, pkcs12, timestamp,
revocation, keys, rsa_pad and ec512, with their unit tests, the integration tests
that need no PDF, and the test keys.

x509 no longer returns PdfCraft's worded sentences: signing_problem gives a
SigningProblem and build_chain_noted a Refused (reason and the two certificates).
The checks behind them are unchanged. Error is SignError without its PDF variants.

* craft-pkcs7: write the certificate serial verbatim in SignerInfo and ESSCertIDv2

A serial with its high bit set and no leading zero was written with one added, so
the signer identifier and the signing-certificate attribute named a different
number than the certificate has.

Ported from storytold/pdfcraft#6868. The test uses a certificate that really has
such a serial and also checks that the signer's certificate is found.

* craft-pkcs7: note why RIPEMD-160 is supported

---------

Co-authored-by: jonathanwxh-cell <jonathanwxh@gmail.com>
2026-11-12 09:45:31 -05:00
Aleksander Helgaker 493476eb8d craft-audio: native MP3 (Layer III) decoder (feature mp3-decode)
* craft-audio: native MP3 (Layer III) decoder (feature `mp3-decode`)

A clean-room Layer III decoder written from ISO/IEC 11172-3 and 13818-3:
MPEG-1, MPEG-2 and MPEG-2.5 (8-48 kHz), CBR and VBR, mono, stereo, dual
channel and joint stereo (mid/side and intensity), long, short and mixed
blocks, and the bit reservoir. It shares the encoder's Huffman and
scalefactor band tables and the MP1/MP2 synthesis filter.

`decode`, `Decoder` and `TrackDecoder` now read raw .mp3 files and MP3 in
MP4 and Matroska natively instead of through symphonia. A Xing / Info / VBRI
frame is not a packet, and the LAME tag's encoder delay and padding are
applied, so a file decodes to its exact presented length. The seek
pre-roll covers how far back the bit reservoir can reach, so a seek is
bit-exact. symphonia stays the fallback for free-format MP3.

The encoder moves to mp3/encoder.rs so that each half builds without the
other.

* craft-audio: MP3 review and CI fixes

- Huffman: big values stop at the granule's part2_3_length. A damaged
  granule no longer reads the next granule's bits, as ffmpeg does.
- Joint stereo runs on the lines in coded order, before short blocks are
  reordered, as ffmpeg and libmad do. Where the two channels' block layouts
  differ, against the specification, each channel is then reordered by its
  own layout.
- Seek pre-roll: sized from the smallest frame the stream can hold (the
  lowest bit rate for its version, rate, mode and CRC), not the last frame,
  so a seek into a VBR stream's small frames still has the reservoir. The
  decoder learns the stream's format from its first frame.
- Mixed-block oracle test: edit only granules whose scalefactors take no
  bits, so the edited streams stay valid, and not before a pure short
  block. ffmpeg's short-block IMDCT drops overlap that a mixed block's long
  subbands carry, where the specification adds it. Use 16 kHz instead of
  12 kHz.
- Routing oracle: ffmpeg before 9 decodes the 1105 samples of the MP3
  Matroska CodecDelay.

* craft-audio: MPEG audio packets of several frames keep their length

An MP4 or Matroska packet may hold several MP1/MP2/MP3 frames.
TrackDecoder now counts the frames each packet holds from their headers:

- frames_per_packet is None once a packet in the lookahead or a decoded
  packet has held several frames, so an MP4 track's length is no longer
  capped at one frame per sample and the container's timing decides.
- Concealment is the length of the frames a damaged packet holds. For MP3
  a packet lost whole is as long as the last packet, and one frame before
  any is decoded.

Also corrects the Huffman doc comment: a big-value pair that starts
before the granule's end is kept whole, as ffmpeg does.

* craft-audio: MPEG audio concealment after partial packets and before the first

- A packet whose later frame fails decodes only in part: like AC-3, it no
  longer replaces the length of the last whole packet that concealment
  uses.
- MP1/MP2: a packet lost before any has decoded, or right after a seek, is
  one frame (384 or 1152 samples) of silence rather than none, as MP3's
  already is.

---------

Co-authored-by: alehel <git@ornumhelgaker.no>
2026-11-12 09:12:40 -05:00
Aleksander Helgaker 77a3ddbf26 craft-video: MPEG-4 Part 2 (Xvid/DivX) Simple profile encoder
* craft-video: MPEG-4 Part 2 Simple profile encoder

Adds a clean-room MPEG-4 Visual (ISO/IEC 14496-2) encoder behind the new
`mpeg4` / `mpeg4-encode` features: the stream type Xvid and DivX write,
which craft-media already identifies (CodecId::Mpeg4Video) in MP4, Matroska,
AVI and MXF but which no crate could produce.

- I- and P-VOPs, 4:2:0 8-bit, H.263 quantisation, half-sample motion with a
  predictor-seeded diamond search, skipped and intra macroblocks in P-VOPs,
  intra refresh within the 132 codings of §7.4.5.
- Constant quantiser or 1-pass average bitrate.
- Headers in band (raw .m4v, AVI) or as extradata / CodecParams (MP4, Matroska).

Tests use ffmpeg/ffprobe as external decoders: every coefficient table entry
and escape mode 1/2 event decodes like the fixed-length escape, and encoded
streams decode without diagnostics, close to the encoder's reconstruction.

---------

Co-authored-by: alehel <git@ornumhelgaker.no>
2026-11-12 09:11:48 -05:00
Dylan M. Taylor 373c242ad7 Add craft-openpgp: OpenPGP signature checking and key reading from MailCraft
Moved from MailCraft's crates/openpgp at a44a45013d1c. Built on rPGP 0.21.0 as
there. The one code change: the Unicode format-character test the From address
check used from mailcraft-names is now a private function here, with a test.
2026-11-12 09:10:41 -05:00
Dylan M. Taylor 6cb00467a5 craft-aaf: rustfmt the roundtrip comparison added in #1108
* craft-aaf: rustfmt the roundtrip comparison added in #1108
2026-11-12 07:49:33 -05:00
Dylan M. Taylor 70fabd64dc craft-aaf: retain master names on unnamed clips
* craft-aaf: retain master names on unnamed clips
2026-11-12 07:30:59 -05:00
Dylan M. Taylor e947d5d799 dxf: a refused allocation for binary tags is an error, not an abort
* dxf: a refused allocation for binary tags is an error, not an abort

* dxf: a refused allocation for binary tags is an error, not an abort
2026-11-12 07:25:00 -05:00
Dylan M. Taylor 3728fd1ffb craft-aaf: use referenced slot rates for source offsets
* craft-aaf: use referenced slot rate for source offsets
2026-11-12 07:04:54 -05:00
Dylan M. Taylor c95885f6d9 craft-aaf: compare a re-written edit without its generated mob ids
`Composition::aaf_mob_id` is documented as a read identity: writers generate
fresh format identifiers and never preserve it. The roundtrip test compared
whole compositions after the second write, so it broke as soon as #1098
exposed the field. Compare them with the id cleared, as the surrounding
comment already described.
2026-11-12 06:53:08 -05:00
Dylan M. Taylor e2dbb04aec craft-pdn: stop surface reads when the visitor fails
* pdn: let surface visitors stop decoding

* pdn: preserve the legacy surface reader error type
2026-11-12 06:26:30 -05:00
Dylan M. Taylor 7e09989ce4 craft-xcf: allow cancellation between layer records
* xcf: stop parsing layer metadata on cancellation
2026-11-12 06:26:24 -05:00
Dylan M. Taylor 9fd3db9484 craft-svg: report the decoded size of nested SVGZ input
* svg: include decoded size in nested stream errors
2026-11-12 06:26:19 -05:00
Dylan M. Taylor d13ac58a15 craft-fig: map FigJam stickies to native board items
* craft-fig: map FigJam stickies to native board items
2026-11-12 06:26:14 -05:00
Dylan M. Taylor ceb78f63af craft-mesh: preserve glTF tangents
* gltf: test preserving authored tangents

* gltf: expose authored tangent attributes
2026-11-12 06:26:09 -05:00
Dylan M. TaylorandNetterSilver e3c6ec4a23 craft-mesh: ignore empty OBJ material assignments
* Ignore empty OBJ material assignments

* Preserve empty material resets in OBJ writes

Co-authored-by: NetterSilver <133535268+Silvertree2010@users.noreply.github.com>

---------

Co-authored-by: NetterSilver <133535268+Silvertree2010@users.noreply.github.com>
2026-11-12 06:26:04 -05:00
Dylan M. Taylor f759a35db8 craft-aaf: reject cyclic root mini-stream chains
* craft-aaf: reject cyclic root mini-stream chains
2026-11-12 06:25:55 -05:00
Dylan M. Taylor 584d87f9e1 craft-aaf: report flattened selector structures
* craft-aaf: report flattened selector structures
2026-11-12 06:25:47 -05:00
Dylan M. Taylor 80144afd9c craft-raw: count a TIFF image's chunks from its offsets table
craft-tiff caps chunk descriptors at the file's length (storytold/craft-libs#827), so a grid of tiles sharing one payload lists fewer chunks than the image declares. Comparing that capped list against the grid refused a file shorter than its tile count for "missing chunks" instead of for the data it actually holds. Compare the offsets table, which is the source of truth, and cover the short-file case.
2026-11-12 06:22:37 -05:00
Dylan M. Taylor d6134e51cb craft-aaf: decode relative media locator escapes (#1101)
* craft-aaf: decode relative media locator escapes

* Preserve literal percent escapes in relative paths
2026-11-12 05:48:58 -05:00
8958340761 craft-drawingml: preset geometry, text warp, custom geometry and colour fixes from open DeckCraft PRs (#1023)
* craft-drawingml preset: the extra outlines dispatch on a Name enum

Ported from storytold/deckcraft#739 by @Bryson14.

* craft-drawingml preset: the 3- and 4-bend elbow and the curved connectors follow presetShapeDefinitions

Ported from storytold/deckcraft#1851 by @Hamza0101.

* craft-drawingml warp: text on a guide sets each glyph down whole

Ported from storytold/deckcraft#1799 by @leobasilio.

* craft-drawingml preset: the Name dispatch test's digests as this repository builds the outlines

The digests in storytold/deckcraft#739 were taken on DeckCraft's fc35ad67. Here two curved arrows
digest differently both before and after the refactor (the preset sources are identical), so the
test now holds the values from craft-libs 2b35d243, before the refactor.

* craft-drawingml preset: every preset's text rectangle from presetShapeDefinitions

Ported from storytold/deckcraft#650 by @NikitkaRa. Its preset::defaults is #1851's
adjust_defaults, which this repository already has.

* craft-drawingml custgeom: connection sites (a:cxnLst) read in the shape space and written back

Ported from the format part of storytold/deckcraft#1748 by @jagjordi.

* craft-drawingml color: behaviour-preserving cleanups

Ported from storytold/deckcraft#1610 by Cédric Legrand.

* craft-drawingml: resolve current-main API and test conflicts

* craft-pptx: use DrawingML writer defaults for custom paths

* craft-drawingml: strengthen geometry regression checks

---------

Co-authored-by: Bryson Meiling <bryson.meiling@gmail.com>
Co-authored-by: Hamza <mail.hamzanaveed@gmail.com>
Co-authored-by: Leonardo <leobasilio@gmail.com>
Co-authored-by: Nikita Rakitin <nnnrrr93@gmail.com>
Co-authored-by: Jordi <jordiag@kth.se>
Co-authored-by: Cédric Legrand <cedric@legrand-tech.fr>
2026-11-12 05:35:48 -05:00
Dylan M. Taylor c6db9e834e craft-aaf: expose composition mob identity (#1098) 2026-11-12 03:29:50 -05:00
Terra854 398c186924 Expose bounded exact X11 color lookup (#1096) 2026-11-12 03:20:05 -05:00
1fc4c17fff prepress: printer's marks (#754)
* prepress: printer's marks

Marks::new lays out crop marks, bleed marks, registration targets and
process color bars around a trim box, with the sheet that holds them and
room for a page-information line. Marks::draw paints them onto a krilla
surface: lines in the registration color (Separation /All), bars in
DeviceCMYK. ISO 32000-2 14.11.3, 14.11.2, 8.6.6.4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* prepress: keep the info line clear of the bottom target

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* prepress: clear targets only when drawn; cap line width at a third of the mark

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: KenM76 <KenM76>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 06:44:39 +01:00
Dylan M. Taylorandwhatever-industries 4cb271b785 craft-audio: apply final Matroska duration to all laces (#1088)
Co-authored-by: whatever-industries <77seventy77@users.noreply.github.com>
2026-11-12 00:14:52 -05:00
Melchi Aaron Zedek bb57d422d0 craft-video: HEVC 4:2:2 8/10-bit decoding (Main 4:2:2 10) (#1087)
Port of storytold/filmcraft#1952, reviewed and approved there before the
decoder moved to craft-libs. 4:2:2 streams that use no range-extension
coding tools (what cameras and libx265 write) now decode:

- chroma geometry from the SPS chroma format (ChromaFormat shifts) in the
  picture store, intra, inter, transform tree, deblocking and SAO;
- Table 8-3 IntraPredModeC mapping for ChromaArrayType 2;
- stacked chroma transform blocks with their second cbf flags;
- chroma QP = Min(qPi, 51) for non-4:2:0 (Table 8-10 applies to 4:2:0 only);
- chroma motion vectors scaled per axis;
- a PPS that scales SAO offsets (a RExt tool) is refused as unsupported.

Tests: 22 libx265 4:2:2 fixtures (8- and 10-bit) compared sample-exactly
with ffmpeg, the Table 8-3 derivation, and the SAO offset scale check.
2026-11-11 23:52:20 -05:00
Dylan M. Taylor 09dcbc225b fix: sync Cargo.lock with craft-rtf's dependencies (#1086)
craft-rtf gained craft-flow, craft-metafile, craft-wordmodel and image in
its manifest, but Cargo.lock was not regenerated. Every --locked cargo
command then tried to rewrite the lock and failed, so all crate workflows
on main stopped during dependency resolution.

Regenerated with cargo metadata.
2026-11-11 22:57:40 -05:00
Dylan M. Taylor fa389c7883 craft-pdf: the PDF object layer from PdfCraft (cos), on filters and crypt
craft-pdf: the PDF object layer from PdfCraft (`cos`), on filters and crypt

* hayro-jbig2: bound a stream's total decode work and what a page retains; no i32 overflow

Port of pdfcraft 85c10051 (patches 5-9), 82439525 (patch 10) and 10107d94 (patch 12).

* hayro-jbig2: PdfCraft's JBIG2 follow-ups (refinement fixes, file header, work accounting, dictionary pixel cap)

Ports pdfcraft 72e763ad, b7ec3669, e3bc4e85, 01d3479f, b9e068aa, 858ce960, 07f44038, e38b9d18, 8717a085, 58c30af3 and the hayro-jbig2 part of e2464e14 (with the hayro-jbig2 part of 0ed33990 they build on).

* Add craft-stream-codecs: the codec modules of PdfCraft's stream filters

LZW, ASCII85, ASCIIHex, RunLength, the TIFF and PNG predictors, bounded CCITT
fax decoding (through hayro-ccitt) with Group 4 encoding (through fax), and
the lossless JBIG2 encoder with its MQ coder, from pdfcraft-filters at
PdfCraft revision 80e26e674d1aa33078e83c8139875047f3289eb6.

The crate sits below craft-pdf and craft-tiff: it knows no container. The
PDF filter chain, Flate and the bounded JBIG2 decoding entry are not part of
it.

* craft-pdf: the PDF filter chain from PdfCraft (`filters`)

Adds the `filters` feature: the /Filter chain of pdfcraft-filters (PdfCraft
revision 80e26e674d1aa33078e83c8139875047f3289eb6) as craft_pdf::filters,
over the codecs in craft-stream-codecs. It adds Flate, strict and tolerant
chain decoding, byte accounting and a shared work allowance, and encoding,
with the chain's unit tests, its property tests and its fuzz target.

The code is unchanged apart from the imports of the moved codecs; FilterError
is craft-stream-codecs' CodecError under its old name.

* craft-stream-codecs: bounded JBIG2 decoding (decode_jbig2_within)

The JBIG2 decoding entry of pdfcraft-filters (PdfCraft revision
80e26e674d1aa33078e83c8139875047f3289eb6, src/jbig2/decode.rs) with its tests.
It spends from the caller's work allowance through hayro-jbig2's
Image::decode_within and charge_reading, which come with
fix/sec-hayro-jbig2-followups (#886).

* vendor/README.craft-reader.md: one #831 paragraph, the follow-ups after it

* craft-pdf: filters re-exports decode_jbig2_within (and lib.rs module order)

* craft-pdf: the object layer from PdfCraft (`cos`)

Adds the `cos` feature: PdfCraft's pdfcraft-cos crate (revision
1d2af9907ba772b368f6b54811359805fa5428e9) as craft_pdf::cos, on crypt and
filters, with its unit tests, integration tests and examples. Its test helpers
come with it: pdfcraft-testkit as src/cos/testkit.rs (cfg(test)) and the qpdf
runner of pdfcraft-oracle as tests/cos/oracle.rs.

The code is unchanged apart from module paths and doc comments. The child test
of file_identifier is named by its module path, since the integration tests
are one binary.

* craft-pdf: cos unit tests build for wasm

The property tests in src/cos/writer.rs need proptest, a dev-dependency of
the non-wasm targets only, and one test in src/cos/document/tests.rs times a
load on a thread, which wasm32-unknown-unknown does not have. Both are
compiled for non-wasm targets only; the other unit tests still build for wasm.
2026-11-11 22:44:59 -05:00
Dylan M. Taylor 7dcc9950a1 craft-xlsx: pictures in cells and dynamic-array metadata
craft-xlsx: pictures in cells and dynamic-array metadata

* craft-xlsx: read parts with canonical prefixes so kept trees can be written back

The part layer read every part by local name, so extLst children (x14:, xr:, xm:),
mc:AlternateContent and r:id lost their namespaces and could not be written back.
Parts are now read with one fixed prefix per namespace (the main namespace unprefixed),
mc:Ignorable and mc:Choice/@Requires are rewritten to match, and ns::declare prepares a
kept tree for writing. craft-xml: a namespace listed with the empty prefix reads unprefixed.

* craft-xlsx: write SpreadsheetML workbooks (Document read and write)

Document holds a whole workbook: the workbook part, shared strings, styles, each worksheet's
rows and the rest of its part, and every other part kept as bytes. write regenerates the
workbook part, worksheets, shared strings and styles from the model, keeps unknown children
in place (schema order for regenerated ones), keeps part names and relationship ids, and
writes kept parts unchanged. Writers: Styles::to_xml and to_el for each table entry,
StringItem::to_el (phonetic runs checked as on reading), SheetPart::to_xml with rows
streamed, Workbook::to_el, XlsxKind. Row and DefinedName keep their other attributes;
CellStyle reads iLevel. craft-opc: LazyPackage::canonical gives a part's stored name.

* craft-xlsx: typed worksheet features and comments, written back

SheetPart::features lifts sheet views, print setup, data validation, conditional formats,
hyperlinks and the legacy drawing into types that mirror their elements (optional attributes,
unnamed ones kept) and writes them in schema order. Comments come from the comments part,
note shapes from the VML drawing; a sheet that gains comments without a VML drawing gets one.
A hyperlink to a new URL gets its own relationship. craft-sheet: Range derives Default.

* craft-xlsx: drawings, charts and the theme on craft-drawingml

Drawing reads a sheet's xdr:wsDr into anchors with typed placements and objects kept as
written; Anchor gives the object's kind, properties, chart or image relationship and xfrm.
Package::drawing, Package::chart (c: and cx: parts through craft-drawingml) and
Package::theme. Document worksheets hold their drawing part, regenerated with its
relationships kept by id; add_chart and add_picture add new chart parts (chartEx behind
mc:AlternateContent) and images with their anchors. More drawing namespaces get prefixes
(a16, asvg, aink, am3d, cx1-cx4, dgm).

* craft-xlsx: tables (ListObjects) read and written back

Table mirrors CT_Table: every attribute, columns with calculated and totals formulas, the
style info, autoFilter/sortState/extensions kept as written. Package::tables reads a sheet's
table parts; Document worksheets hold them as TableParts, regenerated with their
relationships and tableParts entries. Part names a document keeps are reserved before new
parts are named, so a new part never takes a name a later sheet keeps.

* craft-xlsx: PivotTables (caches, records, tables) read and written

Typed pivot cache definitions (source, fields, shared items, grouping),
streamed cache records and PivotTable definitions (location, fields and
items, axes, page and data fields, style), with every unmodelled child
and attribute kept. Workbook::pivot_caches lifts pivotCaches. A Document
holds caches and tables and regenerates their parts, relationships and
pivotCaches entries; an OLAP cache or a table on one stays a kept part.
SharedItems::describe gives Excel's shared-item flags and records_xml
writes a records part, after GridCraft's pivot writer.

* craft-xlsx: pictures in cells and dynamic-array metadata

Package::cell_pictures follows a cell's vm through the value and future
metadata to its _localImage rich value and image, bounded and lenient as
GridCraft's richdata.rs reads it. Document::add_cell_metadata writes the
metadata part (XLDAPR for dynamic arrays, XLRICHVALUE for pictures), the
rich-value parts and the images as GridCraft writes them.
2026-11-11 22:44:39 -05:00