mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-14 06:22:24 +00:00
ca99f91a18f40341ea4fa1119bdfacc378304ff5
153
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ca99f91a18 |
craft-audio: AAC encoder (6/10) (#444)
* craft-audio: AAC decoder
FilmCraft's `filmcraft-aac` decoder as `craft_audio::aac` behind
`aac-decode`, kept over symphonia's: the same quality (about 135 dB
SNR against ffmpeg), and it decodes 5.1, which symphonia refuses.
HE-AAC decodes as its AAC-LC core, upsampled 2x to the stream's rate.
- `TrackDecoder` decodes AAC, including ADTS-framed packets without
extradata (MPEG-TS, raw `.aac`).
- The native read path takes AAC in MP4 and Matroska and raw ADTS
streams (frame sync over the file, with resync after damage).
- AAC errors convert into `AudioError`.
The AAC module's shared parts (Huffman, MDCT, TNS, ICS) arrive whole
as ported; their encoder half is unused until the encoder PR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 1 (#443)
Raw ADTS and TrackDecoder:
- Channel configuration 0 (a PCE in the stream, as ffmpeg's -aac_pce and
AudioToolbox's 7.x write) decodes; it was refused.
- Frames with several raw data blocks are refused; only their first block
was decoded, silently losing the rest.
- A frame that changes the sample rate, profile or channel configuration is
concealed; it played at the wrong speed.
- A source that fails during the frame scan is AudioError::Io; it looked
like the end of the file.
- A stream after an ID3 tag longer than the file head is found.
- conceal counts all the ADTS frames of a packet, and a packet of more than
1024 frames is refused before it is decoded into memory.
AAC decoder:
- Indexed channel configurations place elements by arrival order, not tag,
as ffmpeg does; tagged differently, channels were swapped.
- A PCE in an access unit that fails no longer changes the layout, which
left every later packet failing.
- Non-finite samples are zeroed before the output and the overlap.
MP4: probe and decode agree: the samples' durations end the track when no
single edit does, and an edit cannot outlast the media.
The routing oracle fails on a fixture ffmpeg cannot write when oracles are
required. The README's Limits names HE-AAC v2 as mono, multi-block ADTS,
iTunSMPB, Matroska's tick precision and AAC in CAF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 2 (#443)
Corrects round 1:
- A multi-block ADTS frame after the stream has started is concealed at its
whole length; round 1's filter skipped it, shifting the audio after it.
- Sample durations only trim an MP4's last packet: wrong ones (some count a
HE-AAC packet's core frames) were cutting audio. Durations of 0 with an edit
use the edit.
Raw ADTS packets now keep their headers, so TrackDecoder alone handles format
changes, multi-block frames and concealment, for apps as for files, and nested
ADTS payloads are not split again. Headers come from the frame scan's reads,
not one read per frame. The stream's format is the one most of its first frames
share, so one damaged header does not silence the file. The ID3 walk and a
packet's ADTS split are bounded.
AAC:
- HE-AAC is delayed by the SBR filterbanks' 481 core samples; it played 19.5 ms
early against ffmpeg in every container.
- A failed access unit leaves the decoder's state as it was, so the next packet
decodes as if it had not been there.
- reset restarts the noise generator, so a seek into PNS repeats.
- A signalled rate that is not twice the core's plays the core; it reported the
signalled rate with the core's samples.
The decoder oracle's tolerance tightens from 1e-3 to 1e-5. Tests cover HE-AAC.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 3 (#443)
Corrects round 2:
- ADTS headers are compared with the stream's format only when the first header
gave it. With extradata the format comes from there, so ADTS in MP4 whose
headers say something else decodes again; every packet had been concealed.
- A stream's only program config element is found in its first frames even when
that frame's header is damaged.
- The test of wrong sample durations patched the second run's count, not the
delta, so "durations of one" changed nothing. It now patches the delta, and
checks a file was changed and that seeks land on the same audio.
MP4 seeks place a packet by its index when every packet decodes to the same
length, as decoding does; they used the durations round 2 had stopped trusting
for the length, and returned the wrong audio. A last delta of 0 is overridden too.
Docs: what TrackInfo says for raw ADTS, that opening one reads the whole file, and
which codecs symphonia reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 4 (#443)
- TrackDecoder takes an ADTS stream's format from the one most of the lookahead
shares, as the file path did, so one damaged first header does not fail every
packet of an MPEG-TS stream. open_adts now just asks it.
- MP4 samples of several ADTS frames keep the sample table's timing; a packet
count of one frame each truncated them silently.
- Sample durations past what the packets decode to no longer over-report the
length.
- split_adts_upto is not behind aac-decode, as split_adts is not: an aac-encode
build in the next PR did not compile.
Removes an unreachable ID_END check and open_adts's unused core rate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 5 (#443)
No change in decoding. An aac-encode build in the next PR needs the
split_adts_upto re-export behind aac-decode, and ID_END only in tests.
Docs say the lookahead's headers give an ADTS stream's configuration, that AAC in
CAF still goes through symphonia, and that ProgramConfig is not #[non_exhaustive].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 6 (#443)
Two tests from round 1 failed on ffmpeg before 9, including CI's 8.0. Neither was
a decoding fault:
- ffmpeg 9 ends an MP4 with no edit list at its sample durations and earlier
versions present the whole last packet, so the length is checked against the
file's known duration, not ffmpeg's decode.
- The order ffmpeg gives channels with -aac_pce differs by version, so each decoded
channel must match a different one of ffmpeg's, whatever the order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 7 (#443)
Reading:
- an element of another type or a tag a program config does not list is
refused, not written to another channel; only an LFE coded as an SCE
is still allowed, as ffmpeg allows it
- an explicit sampling rate of 0 is refused
- a packet's later ADTS frames are searched for SBR
- a multi-frame ADTS packet that fails leaves the decoder as it was, and
one whose frames change the channel count is an error
- the whole ADTS frames before a damaged end of a packet decode
- a failed read of a raw stream's first frames is an I/O error
- an MP4 sample of several ADTS frames past the lookahead keeps its length
- an Ogg file with leading bytes keeps its track order and mask comments
Writing:
- ProgramConfig::write, AudioSpecificConfig::to_bytes and
AdtsHeader::write refuse what their fields cannot hold
Tests:
- the routing test checks one alignment for the ffmpeg version under test
- a default decode past an Ogg stream symphonia has no track for
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 8 (#443)
From wanikhawar's review:
- AudioSpecificConfig::parse reads backward-compatible SBR and PS signalling
(a 0x2b7 sync extension after the config), which craft-media's Matroska
demuxer writes for legacy HE-AAC IDs. Without lookahead those streams decoded
as their core.
- An MP4 media edit after one empty edit is applied: its priming skip and its
end. The empty edit's delay is not added as silence.
- A packet lost whole is concealed as long as the last one decoded, so a stream
of multi-frame ADTS packets keeps its timing.
- ADTS-framed MP4 samples keep their sample table's timing, instead of every
sample being read at open to look for several frames in one.
- AdtsHeader::write refuses an object type, sampling frequency index or channel
configuration its fields cannot hold; it masked them into other values.
- AudioSpecificConfig::lc no longer panics for a sampling frequency index with no
rate, and the decoder refuses such a config.
- AudioSpecificConfig::to_bytes refuses channel configuration 0 without a program
config element, which would not parse back.
- Upsample2x starts each channel's history afresh when the channel count
changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 9 (#443)
From CodeRabbit's review of round 8:
- AudioSpecificConfig::parse consumes extensionFlag3 after the config when
extensionFlag is set, so the 0x2b7 sync extension after it is found.
- AudioSpecificConfig::to_bytes refuses a sampling rate of 0, which would not
parse back.
- ADTS-framed HE-AAC in MP4 whose sample durations count core frames is not cut
to the durations' half length: durations short of one frame per sample no
longer end an ADTS-framed track, and its length is unknown until decoded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 10 (#443)
ADTS-framed AAC in MP4: durations short of the frames in the samples read
ahead no longer end the track. With HE-AAC samples of two ADTS frames each,
timed as two core frames, the durations matched one output frame per sample,
so the undercount check passed and the track stopped at half its length,
which probe reported as known. The frames each read-ahead sample holds are
now counted, and durations short of them leave the length unknown until
decoded, as durations short of one frame per sample already did. Correctly
timed AAC-LC samples of several frames keep their end trim.
Reported by FredApps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 11 (#443)
Corrects round 10. A short ADTS-framed MP4 track, whose last sample is in
the read-ahead, lost that sample's trim when it cut more than one frame:
the trim counted as durations falling short, so the length was left to
decoding and its padding was presented. The track's last sample is now left
out of the read-ahead comparison.
Reported by CodeRabbit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: document the ADTS-in-MP4 seek and edit limits (#443)
AAC in MP4 stored as ADTS frames, with sample durations that count fewer
frames than the samples hold, decodes in full from the start, but seeks
place samples by those durations and land later than asked, and an edit
list measured in them can end the track early. The README's Limits section
now says so, and the seek example points to it.
Reported by FredApps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: say why the ADTS-in-MP4 seek limit is kept (#443)
The Limits entry from
|
||
|
|
139a39e94c |
cms: write the profile ID into encoded profiles (#749)
encode() stamps the header profile ID: the MD5 of the profile with the flags, rendering-intent and ID fields zeroed (ICC.1:2022 7.2.18). profile_id(bytes) computes it for any profile. Adds md-5 (RustCrypto, MIT OR Apache-2.0, no default features); digest was already in the lock. Closes #748. Co-authored-by: KenM76 <KenM76> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
73702b326a |
Add craft-silhouette-studio: Silhouette Studio document reader (#805)
Bounded, fail-closed reader for .studio and .studio3 documents: outlines, line colours and page size in millimetres. Protected designs are refused. |
||
|
|
3dcaa07a81 |
Add craft-svg-paths: SVG geometry reader (#802)
Reads an SVG's shapes, clips and page size into kurbo paths in millimetres, with a bounded CSS cascade and fail-closed handling of unsupported content. The caller maps style to its own shape kinds. |
||
|
|
34685c4cdc |
Add craft-gcode: Grbl-style G-code writer for laser cutters and plotters (#801)
Polylines in millimetres to G0/G1 moves with laser power, feed and passes. Comments are kept free of Grbl real-time bytes. |
||
|
|
60ede99985 |
print: xps_driver says whether a Windows printer's driver renders XPS itself (#804)
* print: xps_driver says whether a Windows printer's driver renders XPS itself craft_print::xps_driver(printer) reads PRINTER_DRIVER_XPS through winprint's new PrinterDevice::is_xps_driver (storytold/winprint.rs#21): Some(true) for v4 drivers and v3 drivers with an XPS rendering filter, Some(false) for GDI drivers, None when it can't be read and on other systems. On a GDI driver, reached through Windows' XPS-to-GDI conversion, a page with transparency prints with halos, as black boxes or not at all (storytold/pdfcraft#6155, #5838, #5940), so PdfCraft will flatten it for those drivers only. The Windows end-to-end test checks the CI queue's "Generic / Text Only" driver reads as GDI. * Test: the GDI-driver check in a helper (keeps the end-to-end test under the complexity limit) * winprint bc06a7c48b768eb27a828c7986f6d2868b245ee0: storytold/winprint.rs main with #21 (PrinterDevice::is_xps_driver) |
||
|
|
5a93050c9f |
update: integration of the open fixes to craft-update (#763)
* update: an unsigned Windows copy gets Download instead of Update and Restart The update helper installs nothing for an unsigned copy, so the prompt's Update and Restart button on a local or unsigned build downloaded the release and then stopped with "this copy isn't signed". is_self_updating now answers false for an unsigned copy on Windows, and the prompt offers Download. CRAFT_UPDATE_DEV still offers the update. * update: check that a Windows copy can install before downloading windows_precheck ran after the download, so a copy that could not install downloaded the whole release first. The checks that do not depend on the download now run before it; a new MSI's signature is checked after it. The cache folder is created first, so a first update passes the check. * update: a failure recorded before the running program is not reported take_outcome reported any recorded failure at the next start, so a copy put in place after an update had failed still opened with "The update could not be installed". A failure record older than the running program is now removed without being reported. A failure recorded after the program was put in place, such as a failed helper's, is still reported. * update: a Cargo build with a separate build directory is a development build With build.build-dir set, Cargo keeps .rustc_info.json and the profile's .fingerprint, deps and build folders in the build directory, and the program's own folder has only its lock file. Such a build counted as an installed copy and was offered Update and Restart. A profile folder with Cargo's .cargo-lock under a target root with CACHEDIR.TAG is now a development build, and gets the download link. * update: a cache folder that can't be created is reported as that same_cache_as_helper ignored the error from creating the folder, so a failure read as a cache in the wrong place. It now names the folder and the error. * craft-update: a slow macOS signature check is not reported as a bad signature `signed_by` gave `codesign` 120 seconds and treated every failure as a bad signature, so a Mac that could not finish in time was told the app "isn't signed by its developer". The check now has 10 minutes and checks one bundle at a time. A job waiting for its turn reports `JobPhase::Waiting` and can be canceled. A timeout, a `codesign` that cannot start or is stopped by a signal, and any exit other than 1 or 3 report that the signature couldn't be checked. Exit 3 refuses the app as not signed by its developer. Exit 1 refuses it with `codesign`'s own reason, because `codesign` also exits 1 for a bundle it cannot read. The app is not installed in any of these cases. On macOS, an install or uninstall now stops when `pgrep` gives no answer, where a timeout used to read as "not running". `pgrep` has 30 seconds. An install looks again after the signature check, which can wait behind other apps' checks, and does not replace an app opened during the wait. A self-update skips that second look. * craft-update: a copy that cannot be replaced gets Download A macOS bundle, an AppImage or a Linux standalone program was offered Update and Restart even when the update could not replace it, so the update failed only after the whole download. `current_at` now leaves out such a copy when `access(2)` refuses what the update needs. On macOS that is adding a subdirectory to the parent, and adding a subdirectory to and deleting the bundle. On Linux it is writing and searching the folder. A macOS copy under App Translocation is also left out. Any other error from the check leaves the update on offer, and `CRAFT_UPDATE_DEV` still offers it. * craft-update: a macOS standalone copy that cannot be replaced gets Download macOS `detect` returns `Install::Standalone` for a loose program, and the update replaces it by writing beside it, as on Linux. `can_replace` now checks that folder on macOS too, so a copy in a folder the user cannot write gets Download instead of failing after the download. `CRAFT_UPDATE_DEV` still offers the update. * craft-update: a standalone copy whose share files cannot be replaced gets Download The update replaces a standalone copy's files under `share` as well as the program when the app's `share/<id>` folder sits next to `bin`. The offer now also checks those folders, the `applications` and `icons/hicolor` folders a release writes to, and the nearest existing parent of one that doesn't exist yet. * craft-update: a standalone macOS program without a release tarball gets Download On macOS a program outside an app bundle counts as a standalone copy, and the self-update replaces it from a macOS .tar.gz. Releases carry a disk image and a command-line zip for macOS and no .tar.gz, so the prompt showed Update and Restart and the update stopped with "has no Tarball (.tar.gz) for macOS". The prompt now also asks release_installs, which on macOS offers Update and Restart to a standalone program only when the release lists that asset. Without it the prompt shows Download. A release whose file list was not loaded keeps the offer, because the install fetches the full release first. CRAFT_UPDATE_DEV still offers the update. Other platforms and other kinds of install are not checked against the release. * craft-update: CRAFT_UPDATE_DEV no longer skips the release check A standalone macOS program run with CRAFT_UPDATE_DEV was still offered Update and Restart for a release without a macOS .tar.gz, and that update failed. release_installs now checks the release for that copy too. release_installs_at takes the executable, the opt-in setting and the platform, and a new test checks a loose program in a temporary folder against releases with and without the .tar.gz. The tests use SoundCraft's release files, since craft-launcher ships no command-line zip. * craft-update: a failed self-update keeps its download for the next try On macOS, Linux and FreeBSD, prepare_update removed the downloaded release file whether or not the install succeeded, so every retry after a failed copy, signature check, package install or swap downloaded the whole release again. A failed install now keeps the file, as install_at already does, and the next try reuses it through Http::download, which checks it against the published SHA-256 first and fetches it again when it does not match. - The cache folder's self-update-download.txt records the name and size of the release file before the download starts. A self-update for a different release file removes the finished and partial download that the record names, so each app keeps the files of one release at most. - An install that succeeds removes the file and the record. So does a failed install whose record could not be written, since nothing would remove that file later. - On macOS, when hdiutil cannot attach the disk image, the file is still removed: an image left attached by an install that ended part way makes hdiutil refuse the same file with "Resource busy", while a new download of it attaches. - Windows is unchanged: its update helper removes the download after the app exits. * craft-update: a macOS install that stopped partway no longer blocks the next install When a macOS install ends before it finishes (the app quits or crashes), the copy it was making stays beside the app and the update's disk image stays attached. The next attach of the same file fails with "Resource busy". - Each staging folder's name now carries the installing process's id, and the folder holds an empty marker file. Before it stages a copy, an install removes the marked staging folders of processes that are no longer running, and their empty unmarked ones. It also removes a staging folder from earlier launcher versions when it holds only the app's bundle and has not changed for a day. Backup folders and other apps' folders are left alone. - Before it attaches an image, an install detaches the images that earlier installs of the same app left at their mount points in the cache, when their process has ended or, in this process, when no install uses them any more. - When the attach fails, the install detaches its image if it is attached with no volume mounted and no other attach of the same file is running. - A staging folder that can't be removed, or an image that stays attached, is logged, and the error of a failed install says so after its reason. * craft-update: the kept-download test accepts either signature refusal An unsigned bundle is refused as "isn't signed" today and as a failed signature check once codesign's exit status is read, so the test checks that the install stopped on its signature. * craft-update: the share check covers nested and pixmaps folders A standalone copy in a prefix gets Update and Restart only when the update can write every `share` folder it replaces files in. The check covered `share/<id>`, `share/applications` and `share/icons/hicolor` only, but the update writes a copy of each file next to its destination first. A folder inside `share/<id>` (such as `models`), an icon folder such as `icons/hicolor/256x256/apps`, or `share/pixmaps` (where an older flat release puts its icon) that the user can't write stopped the update after the whole download. The check now also takes in every folder inside `share/<id>`, each `icons/hicolor/<size>/apps` folder that holds the app's icon, and `share/pixmaps`. Icon folders of other packages and other themes are not checked. A linked folder is checked but not walked. The walk reads at most 4,096 folder entries, so a large tree does not slow every start; a folder past that is reported by the install as before. The same check runs for a standalone copy on Linux, so a Linux copy whose nested share folder or `share/pixmaps` it can't write now gets the download link too. * craft-update: a release folder that can't be read stops a standalone update A standalone update in a prefix copies every file under the release's `share` folder. The walk skipped any folder it could not list, and the archive keeps each folder's mode, so a folder that nobody may list left its files out while the update reported success. An error reading an entry of the release's `bin` folder was dropped the same way. These are now errors that name the folder, and the update stops before any installed file changes. A release without a `share` folder still updates. A release whose `share` or `bin` is a link was followed, so the update copied files from outside the release into the prefix. A `share` or `bin` that is a link, or a `share` that is a file, now stops the update. Links further down under `share` are still skipped. The unpacked release is removed after a failed update even when it holds a folder that its owner can't list. Before, such a folder stayed in the cache. * craft-update: the share check covers doc, metainfo and mime, and follows links The share check for a standalone copy in a prefix now also takes in `share/doc/<id>` and every folder inside it, `share/metainfo` and `share/mime/packages`. Release archives such as VectorCraft's Linux and FreeBSD ones put files in all three. The walk now follows links, as the update's writes do. A copy whose `share/<id>` is itself a link had none of its folders checked, and a folder behind a link inside `share/<id>` was not checked either. A folder the walk reaches twice is read once. When `doc/<id>`, `metainfo`, `mime/packages` or `pixmaps` is missing, the check no longer falls back to `share` itself. A copy whose `share` the user can't write, but whose app, `applications` and `icons/hicolor` folders exist, gets Update and Restart again, as it did before the previous commit. The entry limit of 4,096 now applies to each of `share/<id>`, `doc/<id>` and `icons/hicolor` separately, and it counts files and folders. A large app folder no longer turns off the icon folder check. In a folder with the sticky bit, such as `/tmp`, the user can rename over a file only when the user owns the file or the folder. A copy now gets the download link when such a folder holds one of the app's files that another user owns: the program itself, its other programs (`<id>-<name>`, such as `<id>-cli`), anything in `share/<id>` or `share/doc/<id>`, or a `share` file named `<id>.<ext>` or `<bundle id>.<ext>`. An icon size that the installed copy doesn't have yet is still not checked, and a read-only folder that the user added under `share/<id>` does count. Linux runs the same checks for a standalone copy, and the sticky check on the program file for an AppImage. * craft-update: the app-program check has its own name The check for the app's programs in a bin folder was named is_program, the name of the Windows check for an MZ header, so the crate did not build on Windows. It is now is_app_program. * craft-update: a missing doc, metainfo or mime folder is checked through share A Linux release writes files into `share/doc/<id>`, `share/metainfo` and `share/mime/packages`, and the update creates each of these folders when it is missing. When one was missing, the share check took its nearest existing parent inside `share` and never `share` itself. A copy whose `share` the user can't write and that lacked one of these folders was offered Update and Restart, and the update failed after the whole download. For a missing `doc/<id>`, `metainfo` or `mime/packages`, the check now takes the nearest existing parent with `share` included, as it already did for `applications`. Only the old flat release layout writes to `share/pixmaps`, so a missing `pixmaps` is still checked inside `share` only, and a copy whose `share` the user can't write keeps Update and Restart when every other release folder exists. A folder in the app's folder or in `doc/<id>` that the user can't read is still checked against its own permissions, as before. The copy gets Download when the user can't write and search that folder. The check can't list the folders inside it and does not check them. A new test keeps this rule, and the check does not refuse a copy only because a folder in it can't be read. * craft-update: the staging cleanup's doc says which folders it removes A marked staging folder is removed once its process has ended, whichever app that install was for: the folder's name does not record the app. Only staging folders of earlier launcher versions are checked for the app's bundle. * craft-update: a failed attach leaves its image attached when pgrep gives no answer detach_unmounted, from #766, checked for another attach of the same image file with is_running, which reads a pgrep that gives no answer as "not running", and then force-detached the image's unmounted attachments. It now uses process::running, from #753, and detaches only when the check answers that nothing is running. |
||
|
|
006e4df225 |
Add craft-dxf: DXF tag reader and writer, from CADCraft (#796)
Moved from cadcraft-dxf (cadcraft 2f8f1861) unchanged apart from names and docs: ASCII and binary DXF as group-code tags, sections and entities, pre-2007 code pages, versions, and writing. VectorCraft's hand-written DXF writer can move onto it next. Part of #778, #791. |
||
|
|
3b3104981b |
Add craft-tiff: TIFF and BigTIFF IFD reader and writer, from LightCraft (#795)
Moved from lightcraft-tiff (lightcraft b188295e) unchanged apart from names and docs. The first step of shared RAW support (#783, #587): LightCraft's raw and metadata crates read through it, and PhotoCraft's and FilmCraft's own TIFF parsing can follow. Part of #778. |
||
|
|
6e21c5b0b1 |
Add craft-heif: the HEIF/HEIC decoder shared by LightCraft and PhotoCraft (#794)
Moved from lightcraft-heif (lightcraft b188295e), the more complete of the two copies: libheif-matching YCbCr conversion from the VUI, monochrome, thumbnails and the box caps. PhotoCraft's grid-tile ICC inheritance tests are ported to it. Part of #778. Closes #784 once both apps switch. |
||
|
|
0e4943cb5e |
print: winprint 3c978dfc: pages at the job's resolution, QName ticket values, padded integers (#761)
winprint.rs#19 draws each page at the resolution the job's ticket asks for instead of ~96 dpi (storytold/pdfcraft#4941, #2676). #12 writes QName property values as prefix:local, which drivers such as the Microsoft IPP Class Driver otherwise reject when merging the ticket (storytold/pdfcraft#3823). #3 accepts whitespace around xsd:integer values in PrintCapabilities (Toshiba Universal Printer 2, storytold/pdfcraft#2430). The public API craft-print uses is unchanged; only the rev and the lockfile move. |
||
|
|
7ee35a9fbd | chore(print): refresh internal winprint pin after bounded stream test repair | ||
|
|
e6489d9226 | print: preserve the public GDI dependency identity while updating the PDF loader | ||
|
|
45b7ededd9 | Merge main after #605 and adopt the reviewed Windows stream test repairs | ||
|
|
058b047760 |
print: winprint 64e7afd4, whose Direct2D factory is multi-threaded (#605)
Windows.Data.Pdf draws a page on several of its own worker threads, which share the print control's device context. With winprint 5f80b9b's single-threaded factory Direct2D takes no locks, so those threads race in d2d1.dll and the app closes while printing, most often on pages with Type 3 fonts (storytold/pdfcraft#5006, storytold/pdfcraft#2684). storytold/winprint.rs#11 made the factory multi-threaded; it was merged as 64e7afd4. craft-winprint-gdi moves to the same revision, so one checkout of winprint.rs serves both; crates/gdi is unchanged between 417ac3c and 64e7afd4. Co-authored-by: mertan-wq <235487900+mertan-wq@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
481f66d287 |
Merge pull request #633 from dotCooCoo/chore/vendor-linesweeper
linesweeper: keep a copy of the crates.io 0.5.0 package in vendor/ |
||
|
|
c0e0b2bb75 |
Merge pull request #577 from dotCooCoo/fix/language-pack-removal-record
font-store: preserve Language packs when removal cannot commit |
||
|
|
93d7c7b57a | print: adopt on-demand file loading without whole-job buffering | ||
|
|
e398084b63 | print: take stream loading alongside the multithreaded Direct2D fix | ||
|
|
f14cc7f21e |
linesweeper: keep a copy of the crates.io 0.5.0 package in vendor/
Add the linesweeper 0.5.0 package to vendor/ with no file changed, so that a fix to it can reach the Craft apps through this repository without waiting for a linesweeper release. The GitHub repository its manifest names is archived, and its README says development moved off GitHub. Leave out the test font (fonts live in craft-fonts), the logo artwork, the snapshot images and the package's repository tooling. SOURCE.json lists each with its hash. Add a workspace exclude, a [patch.crates-io] entry, SOURCE.json with the archive hash and per-file hashes, NOTICE and ATTRIBUTION.md rows, and the package's tests in the backend test script. craft-prepress's flatten feature now builds against the copy, so Cargo.lock drops the package's registry source and checksum. |
||
|
|
a231e517c3 |
craft-update: zip without zlib-rs so apps keep flate2's miniz backend (#608)
zip's `deflate` feature pulls in deflate-flate2-zlib-rs (and zopfli). Cargo unifies flate2's features across the whole app, so on Windows every flate2 user in an app depending on craft-update switched to zlib-rs, whose level 1 is a static-Huffman quick mode that can produce output larger than its input (Lightcraft's 16-bit ZIP TIFFs on Windows came out bigger than uncompressed). Use `deflate-flate2` with flate2's default miniz_oxide backend instead; the updater only reads Deflate zips, which that covers. |
||
|
|
6485116bca |
print: winprint 64e7afd4, whose Direct2D factory is multi-threaded
Windows.Data.Pdf draws a page on several of its own worker threads, which share the print control's device context. With winprint 5f80b9b's single-threaded factory Direct2D takes no locks, so those threads race in d2d1.dll and the app closes while printing, most often on pages with Type 3 fonts (storytold/pdfcraft#5006, storytold/pdfcraft#2684). storytold/winprint.rs#11 made the factory multi-threaded; it was merged as 64e7afd4. craft-winprint-gdi moves to the same revision, so one checkout of winprint.rs serves both; crates/gdi is unchanged between 417ac3c and 64e7afd4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f1ae1ecacb |
Merge main into web-host
Keep both craft-web-api and craft-web-host in the workspace list, the README and the lockfile. Point the host README at the command API that landed in #413, and the command API README at these host messages. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
a94d849df2 | web-api: one window.<app> command API for the web builds | ||
|
|
ce99c8d61c | craft-web-host: tests for message types, file names and the host objects the bridge parses, and a CI workflow that runs them | ||
|
|
da0b274a28 |
craft-web-host: the host messages as one contract and one bridge
The messages between a Craft app's web build and the page that hosts it (ready, host, open, save, saved, dirty, failed, handoff, handoff-done), as proposed on #358. The contract part (message kinds, the version, the ?host=parent / data-host="page" switch, file-name clean-up) builds everywhere and is tested natively; the web module (wasm32) is the bridge an app's web shell calls: same-origin parent only, files as bytes or a Blob without copying, saves as a transferred ArrayBuffer, dirty repeats suppressed. Compatible with the bridges shipped before it: optional fields stay optional, and a host treats an unknown ready.version as 1. |
||
|
|
38740b43a9 |
craft-audio: core types, WAV/AIFF read and write, peaks, symphonia (#441)
* craft-audio: core types, WAV/AIFF read and write, peaks, symphonia The new crate without any codec module: what every later codec plugs into. - Shared types: `AudioBuffer`, `AudioInfo`, `ChannelLayout`/`Speaker`, `AudioError`, `EncodeOptions` (built with `new` and `with_*`), `EncodeFormat`, `BitDepth`, `BwfInfo`, `SampleFormat`. - WAV/BWF/RF64 and AIFF/AIFF-C reading and writing from SoundCraft's `soundcraft-audio-io`, kept over EffectCraft's and FilmCraft's smaller writers (only it has BWF, dither, any speaker mask and streaming without a known length). `encode` and `FileEncoder` behind `pcm-encode`; `lossless-encode` and `encode` turn it on. - `decode`, `probe`, `tracks` and the streaming, seekable `Decoder` (feature `decode-native`). The native path reads WAV, AIFF, and PCM in MP4/MOV and Matroska through craft-media's demuxers, packet by packet, with edit lists and `CodecDelay`/`DiscardPadding` applied. `TrackDecoder` decodes PCM; each codec PR adds its codec to it. - symphonia 0.6 (MPL-2.0, used unmodified) behind the `symphonia` feature, part of `decode`: the fallback for every format with no native decoder yet. Its size fields are checked before it reads, and its panics are caught where they unwind. - `peaks`: the min/max/RMS pyramid for waveform drawing. Workspace: craft-media is a workspace dependency without default features, so an encode-only craft-audio does not build the MP4 demuxer, and `symphonia-bundle-flac` builds without overflow checks in dev profiles (its 32-bit LPC restore wraps on valid input). CI: `audio.yml`, with clippy per feature, rustdoc, WASM, MSRV, and the tests with ffmpeg oracles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-audio: review fixes (CodeRabbit on #441) - Reading: AIFF that declares frames without an SSND chunk is Malformed; a Matroska PCM track's length counts from its first frame; seek targets saturate; an empty FLAC has one track; a failed header read is an Io error; deinterleave allocates fallibly; concealed silence is capped like a decoded packet, and symphonia's uses the damaged packet's own duration when known. - Peaks sum squares in f64, so loud float samples give a finite RMS. - Writing: .aifc maps to AIFF; BWF text is cut at a character boundary; a WAV byte rate past 32 bits is an error; the FileEncoder docs say nothing may follow the sink's position. - README: the Limits list says which codecs go through symphonia and which MP4 edit lists apply. - Tests for each, alias and dither coverage, and a FLAC case in the routing oracle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-audio: review fixes, second round (CodeRabbit on #441) - Every header read is checked: `Native::open`, `native::tracks` and the no-symphonia error read it with `head_checked`. craft-media demuxer errors that are I/O failures (not a file ending early) are returned as `AudioError::Io` instead of sending the file to the other path. - A Matroska track's presented length runs from its first frame plus `CodecDelay` (unclamped, in timestamp ticks with the delay rounded to one) to its end. - symphonia's concealment allocates fallibly, and a damaged first packet is concealed when the demuxer gives its duration. `deinterleave` reserves its channel list fallibly too. - `TrackDecoder::conceal` documents its cap at the decode limit. - Tests: an I/O failure past the first 64 KiB, codec delay with the first block at -20, 0 and 50 ms (checked with `probe`), a seek back after an overshoot, and the loud-sample RMS value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-audio: TrackDecoder::conceal returns a Result Concealment allocates fallibly and fails with TooLarge when the silence would pass the 2^30-sample decode limit, instead of returning less than the packet's length. The native path, the README, the packets example and the doc example pass the error on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-audio: review fixes, third round (CodeRabbit on #441) - Concealed silence is held to the decode limit inside pcm::silence, so symphonia's path checks frames × channels too, and symphonia's decoded audio is copied into fallibly allocated planes. - TrackDecoder::reset forgets the last packet's length. - Without symphonia, a damaged MP4, Matroska or Ogg file (truncated, invalid, no moov) is Malformed instead of Unsupported; a feature the demuxer does not read still names the build. - Tests: an MP4 with no moov in a native-only build, conceal after a reset and past the limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-audio: review fixes, fourth round (CodeRabbit on #441) - A file the native demuxers find damaged is Malformed with symphonia too, when symphonia calls it unsupported; before, an ftyp-only MP4 was Unsupported in symphonia builds. - symphonia's decoded blocks are held to the 2^30-sample decode limit before allocating. - Symph::seek forgets the last packet's length, so a lost packet after a seek is not concealed with the length of one before it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-audio: review fixes, fifth round (CodeRabbit on #441) - The damaged-file test covers Matroska as well as MP4, and fails when either branch of native::damage is removed. - native::damage leaves out Ogg: ogg::open reports no damage (it skips damaged pages and calls a file with none it can read not Ogg), and symphonia reports damaged Ogg as malformed itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-audio: run the ffmpeg oracle tests on ubuntu-26.04 ubuntu-latest's apt ships ffmpeg 6.1.1, which writes MP2 and E-AC-3 in Matroska with ends a few milliseconds early, so the routing tests on the later codec PRs cannot pass on it. ubuntu-26.04 ships ffmpeg 8.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
40cd57930a | font-store: confine recovery cleanup and preserve valid receipts | ||
|
|
6ae033e6c1 |
Merge pull request #525 from dotCooCoo/feat/doc-pcraft-pixels
craft-doc: PhotoCraft layer pixels and masks behind the pcraft-pixels feature |
||
|
|
b96da36713 |
plate-test: separate plates in a private random temporary directory
The Ghostscript work directory had a predictable name under the shared temporary directory, and a failed removal of an existing one was ignored. Another local user could create it first with an in.pdf symlink, and writing the input would follow it. The directory is now created with tempfile (random name, owner-only permissions, fails if it exists), and in.pdf is created with create_new. |
||
|
|
3798d807de |
craft-doc: PhotoCraft layer pixels and masks behind the pcraft-pixels feature
pcraft_layer_rgba(bytes, layer, &Limits) decodes one layer of a .pcraft bundle over the canvas: a raster layer's surface, or the rendered copy saved with a text, shape or smart object layer, as RGBA at the layer's 8, 16 or 32-bit sample type, with the layer mask as its own plane. Grayscale repeats the gray value, missing tiles hold the default pixel, and tiles outside the canvas are not read. Other color modes give Unsupported; groups, adjustment and fill layers give None. Tiles are read with craft-pcraft-format's TileRef and tile paths, decoded with ruzstd and checked against their BLAKE3 hash, as PhotoCraft reads them. The decoded layer and mask count against max_entry and max_total before they are allocated; a frame is decoded only up to its tile's size, and a frame larger than the tile plus an eighth and 4 KiB, or one that declares a window over 8 MiB, is refused. The feature adds ruzstd and blake3, both pure Rust. The doc workflow runs clippy and the tests with --all-features. Part of #59. |
||
|
|
fa1a657476 |
Add craft-doc: read PhotoCraft, VectorCraft and EffectCraft documents without the apps (#404)
* Add craft-doc: read PhotoCraft, VectorCraft and EffectCraft documents without the apps
A new crate, craft-doc, reads the native files of three apps from bytes:
PhotoCraft .pcraft (a ZIP bundle), VectorCraft .vectorcraft (JSON, plain
or gzip, with the version 4 data after the JSON) and EffectCraft .ecproj
(JSON).
- sniff tells which app wrote a file from its first bytes.
- info gives the format version, the writer the file names, and whether
the version is one the crate reads.
- renditions returns VectorCraft's preview and PDF and PhotoCraft's
thumbnail and composite, when the file has them.
- outline returns PhotoCraft's canvas and layer tree, VectorCraft's
artboards and top-level layers, and EffectCraft's compositions with
their layers.
A file whose version is newer than the crate reads is refused with
Error::TooNew. Limits bound the JSON, each entry, the total bytes
decompressed and the JSON nesting, and each is checked before the bytes
it covers are allocated. ZIP entries are read by a small STORE and
DEFLATE reader in the crate. The crate depends on serde, serde_json,
thiserror and flate2 and builds for wasm32-unknown-unknown.
The tests build every document they read: JSON written by hand, PNG,
ZIP and gzip bytes assembled in the test. doc.yml runs fmt, clippy, the
tests and a wasm32 check.
* ci: do not keep the checkout token in the doc workflow
The checkout step sets persist-credentials: false, as jpeg.yml and media.yml do. No later step in the job uses the token.
* craft-doc: read each format's envelope through its format crate
craft-doc no longer keeps its own copies of the envelope types and
constants. It reads them with craft-pcraft-format (`MANIFEST`,
`FORMAT_VERSION`, `THUMB`, `COMPOSITE`, `check_manifest`),
craft-vectorcraft-format (`Head`, `Image`, `sniff`, `check`, `VERSION`,
`BLOB_MAGIC`, base64) and craft-ecproj-format (`Head::read`,
`SCHEMA_VERSION`). Its own base64 module is removed. A `.pcraft`
manifest is still read in one pass without building a
`serde_json::Value`, with the checks `read_manifest` applies. The
outlines are still read with craft-doc's read-only views of each
document, and the public API (`sniff`, `info`, `renditions`, `outline`,
`Limits`) does not change.
These inputs are now read as the format crates read them:
- A `.pcraft` manifest without `generator`, or with a `thumbnail` other
than `thumb.png` or a `composite` other than `composite/preview.png`,
is malformed for `renditions` and `outline`; `info` still reports its
version. A manifest with more than 4,194,304 JSON items is refused
with `LimitExceeded`.
- A `.vectorcraft` preview or PDF member without `mime` gives no
rendition.
- `sniff` takes `format` only from a top-level member, so
`{"meta":{"format":"vectorcraft"}}` is not a VectorCraft file.
tests/envelopes.rs writes a file of each format with the crates' types
and reads it through every call, and checks the inputs above.
doc.yml also runs when one of the three crates changes.
* craft-doc: sniff reads only top-level members of an EffectCraft project
`sniff` took any JSON object whose first member is `savedBy` for an
EffectCraft project when `"schema"` appeared anywhere in its first
bytes, so `{"savedBy":"1.0","metadata":{"schema":1}}` was claimed. It
now checks the top-level member names only, one at a time, without
keeping them.
* craft-doc: bound list elements with max_items; no rendition without a MIME type
Limits gains max_items (default 1,048,576): EffectCraft project items and
composition layers, VectorCraft artboards and layers, and PhotoCraft layers
at every level are counted before each one is read, and a file that lists
more gives Error::LimitExceeded. A list of empty objects within max_json no
longer expands into gigabytes of layers.
A VectorCraft preview or pdf member with an empty mime gives no rendition, as
one without a mime does; a preview may be PNG or JPEG. An EffectCraft
composition without a duration or a frame rate gets no timing instead of a
duration of 0.
|
||
|
|
431b34ad13 |
Merge pull request #486 from benandez/feat/font-store-language-packs
font-store: item kind language-pack (CP4.1) |
||
|
|
7cd6010068 |
Merge pull request #485 from benandez/feat/craft-proof-packs
craft-proof: installed Language packs (CP4.2) and the CRAFT_PROOF_EMBED switch (CP4.4 prep) |
||
|
|
fcf3be172e |
Merge pull request #482 from benandez/feat/craft-proof
craft-proof: WordCraft's proofing library, shared (spelling, grammar, hyphenation, Hunspell) |
||
|
|
006b50d2ae |
craft-media: shared codec identifiers, one ByteSource, the testkit (1/10) (#439)
* craft-media: shared codec identifiers, one ByteSource, the testkit The types a codec crate needs to describe a file and configure a decoder, ahead of craft-audio: - `Container`, `CodecId`, `PcmFormat` and `CodecParams` (`id.rs`), always built, with optional serde. - `codec_params()` on `isobmff::Track` and `matroska::Track`, both returning `CodecParams` in the same form; `extradata` follows Matroska's `CodecPrivate`. - One `ByteSource` trait (`source.rs`), with `ReadSeekSource`; `isobmff::ByteSource` and `matroska::ByteSource` are that trait. - craft-media-testkit (`publish = false`): the ffmpeg oracle helpers from craft-media's tests, so the codec crates share one copy. The existing tests prove the refactor; the new types have their own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-media: APV extradata is the bare record; reseek after a poisoned lock An MP4 apvC track gave CodecParams.extradata with the 4-byte FullBox header in front, where Matroska's V_APV CodecPrivate is the bare APVDecoderConfigurationRecord. ApvConfig::record_bytes serializes the record alone and extradata uses it. ReadSeekSource kept its tracked position after recovering a poisoned lock or a failed seek, so a reader moved by a panic was read from the wrong offset with no error. Both now force a seek on the next read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-media: MP4 codec params per sample description Track::codec_params always described the first stsd entry, while each sample selects its own through description_index. It now describes the first sample's entry; codec_params_for gives the parameters of any entry, and has_mixed_descriptions says whether one set covers every sample. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-media: test file-backed ByteSources; a File read past i64::MAX is an EOF A File and a ReadSeekSource over a file now have the buffer's test: the same bytes at the same offsets, and UnexpectedEof past the end. The test found that a File read at an offset past i64::MAX failed with the OS's InvalidInput instead of the UnexpectedEof the trait promises; it now reports the end of the source like the other implementations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-media: Matroska codec params match MP4 for ALAC, DNx and Opus Matroska's codec_params gave Unknown for A_ALAC and for DNxHD/DNxHR stored as V_MS/VFW/FOURCC (AVdn, AVdh), where MP4 gives Alac and Dnxhd; ALAC's extradata is the CodecPrivate, the ALACSpecificConfig MP4's alac box holds. Opus now reports 48 kHz from Matroska as from MP4, whatever rate the muxer declared. ReadSeekSource clears the poison when it recovers the lock, so one panicked read costs one recovery instead of a seek on every later read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * craft-media: MP4 reports the MPEG audio layer the first frame names An MP4 track with MPEG audio reported CodecId::Mp3 whatever the layer, since mp4a + 0x69/0x6B names none (ffmpeg stores MP2 that way), while Matroska reports Mp1/Mp2/Mp3 from its CodecID. open() now reads the frame header of each such entry's first sample and codec_params() reports the layer it names; without a valid header, QuickTime's .mp2 gives Mp2 and anything else Mp3 as before. CodecConfig::Mp3 is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b8f7d93b00 |
Add craft-pcraft-format: the envelope of PhotoCraft's .pcraft bundle (#407)
* Add craft-pcraft-format: the envelope of PhotoCraft's .pcraft bundle The manifest around the document, with the document as a type parameter (Manifest<D>), TileRef, the bundle's entry paths and hash check, FORMAT_VERSION, the manifest decode limits with the nesting and item scan, and the migration runner as a generic step list. Messages and serialized field order are PhotoCraft's, so its saved manifests stay byte-identical when it switches to these types. Part of #59. * ci: do not keep the checkout token in the pcraft-format workflow The checkout step sets persist-credentials: false, as jpeg.yml and media.yml do. No later step in the job uses the token. * pcraft-format: build no entry name from an invalid hash tile_path and blob_path return None unless the hash is 64 lowercase hex digits, so a hash read from a file such as "../x" never becomes an entry name. TileRef::path gives a tile's entry the same way. The docs note that MAX_GROUP_DEPTH is a copy of PhotoCraft's constant, and that check_manifest also refuses a manifest larger than max_manifest_bytes, which PhotoCraft's check before a save does not. * ci: pin actions/checkout by commit and Rust to 1.95 in the pcraft-format workflow * pcraft-format: read_manifest is the one reader of a manifest read_manifest(json, limits, steps, target) checks the JSON against the limits, refuses a format_version newer than target, runs the migration steps, and reads the document as D and the other envelope members. read_manifest_with reads the document with the caller's function instead. Manifest no longer implements Deserialize. Error::TooNew holds the manifest's generator, so a message can name the writer; its message is unchanged. TooNew, Manifest and TileRef are #[non_exhaustive], and Manifest::new and TileRef::new build them. TILES_DIR, BLOBS_DIR, COMPOSITE_DIR and OBJECT_SUFFIX name the bundle's directories and the object suffix. The docs of max_blob_bytes and max_total_bytes say the caller enforces them. * pcraft-format: read_manifest reports errors in serde's struct order read_manifest reads the envelope members and the document in the order the parsed object holds them, and reports a missing member after the members that are present. A manifest with several faults gives the same error as a derived Deserialize of the whole manifest, as PhotoCraft reads it today. * pcraft-format: document that a missing or invalid format_version is Corrupt read_manifest's docs and the README say that a missing or invalid format_version gives Error::Corrupt from migrate, and that a missing generator or document, or any other mistyped member, gives Error::Json. A test covers a string and a fractional format_version. * pcraft-format: read_manifest refuses preview names other than its own A manifest's `thumbnail` and `composite` named any entry the reader then opened, while tile entries go through `is_valid_hash`. `read_manifest` now gives `Error::Corrupt` for a `thumbnail` other than `thumb.png` or a `composite` other than `composite/preview.png`, the only names PhotoCraft writes. The check runs after the members are read, so JSON errors keep their order. The reader's doc now says a missing or invalid `format_version` is `Error::Corrupt`, from `migrate`, not `Error::Json`. |
||
|
|
14d5dbb50c |
Add craft-vectorcraft-format: the .vectorcraft envelope, with the document as a type parameter (#408)
* Add craft-vectorcraft-format: the .vectorcraft envelope, with the document as a type parameter File<D>, Head and Image hold the members VectorCraft writes, in its order, with its skip rules: serde_json output of a File<D> is the JSON VectorCraft writes, compact or pretty. Tail places entries after the JSON from version 4 (INLINE_MAX bytes or more, after BLOB_MAGIC), and split_tail and Image::bytes read them back. The crate also has the version and name constants, check() for the format name and newer versions, sniff() for plain and gzip files (the top-level "format" member must be vectorcraft or drawcraft), and standard base64. No dependency on any app; builds for wasm32-unknown-unknown. * ci: do not keep the checkout token in the vectorcraft-format workflow The checkout step sets persist-credentials: false, as jpeg.yml and media.yml do. No later step in the job uses the token. * vectorcraft-format: limit the bytes copied from the data after the JSON Entries::bytes reads a file's entries and returns Error::TooBig once the blob ranges it has copied add up to more than the data after the JSON holds, so entries that name the same bytes again and again are refused, as VectorCraft's reader refuses them. A generator, preview or pdf of the wrong shape now reads as None in File and Head, as VectorCraft opens such a file. File::tail gives a Tail for the file's own version. * ci: pin actions/checkout by commit and Rust to 1.95 in the vectorcraft-format workflow * vectorcraft-format: document that base64 decode ignores the bits before the padding decode reads a group such as Zh== like Zg==, and Zm9= like Zm8=, as VectorCraft's own base64_decode does. The doc comment says so, and a test pins both cases to the canonical bytes. * vectorcraft-format: entries naming the same range share one buffer VectorCraft's reader keeps the bytes of each [offset, length] it has read and gives a repeated entry the same buffer without counting it again (crates/format/src/unpack.rs). Entries::bytes refused such a file once the repeats passed the size of the data after the JSON. It now returns Arc<[u8]>, caches each range, and counts it once. Overlapping ranges that differ are still copied and counted each time. * vectorcraft-format: a refused entry does not count against the copy limit Entries::bytes added a refused entry's length to the bytes copied, so a later entry that fit was refused too. It now counts an entry only once it is read. * vectorcraft-format: Contents reads the document without keeping the preview and PDF File<D> checks the shape of generator, preview and pdf by holding each value in memory, and keeps the preview and PDF base64 it reads. Contents<D> reads format, version, profiles, document and images, and skips the other three as the JSON is read. It accepts and refuses the same files as File<D>. * vectorcraft-format: Contents reads a repeated generator, preview or pdf VectorCraft opens a file whose generator, preview or pdf member is repeated, since its reader does not declare those members, and refuses a repeated format, version, profiles, document or images. Contents now leaves the three members undeclared, so serde skips every copy of them, and still refuses the other repeated members. File and Head are unchanged. * vectorcraft-format: sniff decodes JSON escapes in the format member serde_json reads a file whose format key or value is written with escapes, such as \u0066ormat, as a VectorCraft file, and sniff compared the raw bytes and refused it. sniff now decodes the key and the value before comparing them. * vectorcraft-format: File::read and Contents::read check the version File::read and Contents::read are the readers of a file's JSON. Both refuse another format or a version newer than VERSION after the JSON reads, with ReadError::Envelope holding the Error (Format or TooNew), and return ReadError::Json otherwise. File and Contents no longer implement Deserialize, so a reader cannot skip the check. Both read one implementation and treat repeated members the same way: a repeated generator, preview or pdf is accepted, and File keeps the last value, as serde_json reads a repeated key into a Value. A repeated format, version, document, images or profiles is refused. A top-level array holds format, version, document, images and profiles in that order, as VectorCraft reads it; Contents' fields follow that order. - Image::bytes returns None for an entry of more than MAX_ENTRY (1 GiB, VectorCraft's limit on one entry of a compressed file), and Image::bytes_at_most takes another limit. Entries keeps no per-entry limit, as VectorCraft opens larger entries in uncompressed files. - Entries::blob reads a range; Entries returns Arc<Vec<u8>>. - marker_at finds BLOB_MAGIC where the writer puts it in a file's first bytes; split_tail uses it. - File, Contents, Head, Image, Part and ReadError are #[non_exhaustive]. |
||
|
|
ca1b5e2edb |
Add craft-ecproj-format: the envelope of EffectCraft project files (#409)
* Add craft-ecproj-format: the envelope of EffectCraft project files
An .ecproj file is one JSON object: savedBy, then the saved view's
members, then the project's members, written with two-space indentation.
The crate defines that envelope with the view and the project as type
parameters, so the project model stays in EffectCraft:
- SCHEMA_VERSION (2) and check_schema, which refuses a newer schema
with EffectCraft's own message.
- File { saved_by, view, project }, serialized with both parts
flattened, and File::to_json.
- Head, which reads savedBy and schema from a JSON object, and
saved_by, which reads savedBy alone.
The tests write synthetic projects and compare the text with JSON
written by hand in the shape EffectCraft's writer gives. The workflow
runs fmt, clippy, the tests and a wasm32-unknown-unknown check.
* ci: do not keep the checkout token in the ecproj-format workflow
The checkout step sets persist-credentials: false, as jpeg.yml and media.yml do. No later step in the job uses the token.
* ecproj-format: write compact files, read savedBy as EffectCraft opens it
EffectCraft's Save Compact Project Files setting (effectcraft 07639b5f)
writes .ecproj files on one line with serde_json::to_string.
File::to_json_compact writes the same text, and the docs say the crate
writes and reads both forms.
Head now reads every file EffectCraft's Project::from_json opens: a
savedBy that is not a string, or appears more than once, reads as None,
and a project written as a JSON array reads with its first element as
the schema. saved_by also gives None for a repeated savedBy, as
EffectCraft's saved_by does. File's docs say the crate does not check
values and that serde_json writes NaN and infinity as null.
* ecproj-format: skip a non-string savedBy without keeping it
Head::read and saved_by read savedBy into a serde_json::Value before
dropping any value that is not a string, so a wide or deeply nested
savedBy was held in memory, and one nested past serde_json's recursion
limit made Head::read fail. A non-string savedBy, and every repeated
savedBy, is now skipped element by element with IgnoredAny, as other
members are. It still reads as None.
* ecproj-format: Head::read refuses a newer schema and reads objects only
Head::read is the one reader of a project file's envelope. It returns
ReadError: Json when the text is not one JSON object with a u32 schema
(the array form included), or TooNew when the schema is newer than
SCHEMA_VERSION, holding the file's savedBy so a message can name the
version that wrote it. TooNew reads as before. saved_by() is removed,
and Head no longer implements Deserialize.
TooNew, ReadError and ReadError::TooNew are #[non_exhaustive].
|
||
|
|
576cf914e3 |
CP4.2/CP4.4: craft_proof::packs reads installed Language packs; only en-* embedded by default
- packs::set_packs_dir / reload / installed / licence_texts / load_errors / embedded_locales - spelling lookup: dictionaries folder -> installed pack -> system Hunspell -> (English) core list - hyphenation: installed pack patterns before the embedded ones, reloaded without a restart - hostile packs: size caps, no symlinks, pack.toml path traversal rejected, errors reported - build.rs embeds only en-* unless CRAFT_PROOF_EMBED names more (Language builds) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
d342a50635 |
CP4.1: review — packs hold only the contract's files and say which locale they are; every zstd frame is read; a corrupt record is rebuilt
- Every zstd frame is decoded; anything but tar padding after the archive is refused. - pack.toml must name the index entry's locale; the files the index promises (spelling, hyphenation) must be in the archive. - Allow-list: pack.toml, spelling.aff, spelling.dic, hyphenation.pat at the root, LICENCES/<[A-Za-z0-9._-]> without a leading or trailing dot; Windows device names (con, prn, aux, nul, com1-9, lpt1-9, any extension) refused in names and locales. - Pack names lose control and bidi/format (Cf) characters before they are stored or listed. - An unreadable language-packs.json is kept as .bad and rebuilt from the pack folders (each pack.toml); no pack drops out. - A bad hand-made entry is Error::Index(IndexError::Invalid), not BadUrl; index parse errors are Index errors too. - Dead file:// handling removed. - language_pack_index_overridden() exposes the developer override; README says it bypasses the pin and works in release builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
630e42f42c |
CP4.1: font store item kind language-pack (pinned index, verified .tar.zst install, remove, list)
Additive: new src/language_packs.rs (+ tests); store.rs only widens a few helpers to pub(crate). Developer override CRAFT_LANGUAGE_PACK_INDEX for local end-to-end testing (no index pin; archives still verified). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
b758ba6943 |
CP1.2: craft-proof embeds Language data from CRAFT_I18N_DIR; works without it
- Package renamed wordcraft-proof -> craft-proof; [workspace.dependencies] entry; public API unchanged. - build.rs embeds craft-i18n language-data/ (en-us, de, pl, pt, it, ru) instead of include_str!/include_bytes! of WordCraft's assets/: warns when CRAFT_I18N_DIR is unset, errors under CRAFT_I18N_REQUIRED=1. - No data: English spelling marks and suggests nothing, hyphenation that needs data is off, grammar and rule-based hyphenation work. no_data.rs runs every public entry point without data; data-pinning tests are ignored then (cfg craft_proof_data). - es timing bound 75 -> 150 ms: craft-libs tests run at opt-level 0. - proof.yml (fmt, clippy -D warnings, tests with and without data, release guard, wasm32), README, CODEOWNERS for crates/proof. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FypgCX8LsYYE8fsj4byBm2 |
||
|
|
c5a1cbfb5f | update: CheckResultDialog shows the result of a manual update check | ||
|
|
b645a09174 |
Merge pull request #365 from rwoldberg/clean-room-crw-format-decode
Clean room crw format decode |
||
|
|
1065653e75 |
craft-update: the startup prompt shows the app's icon, accent colour and developer (#434)
The update prompt drew the app name's first letter in a blue square and used that blue for
its buttons and highlights. App gains accent and developer, and the prompt now draws:
- App::icon_png (decoded once per context, PNG only, at most 1024 px a side and never past
the renderer's texture limit), keeping the letter badge for an app without a usable icon;
- App::accent filling its buttons, the badge and an UPDATE chip, with white or black text,
whichever contrasts more, so every Craft app's accent reaches WCAG AA (4.5:1) for these
small labels; the What's new link is body text with an accent underline (accent-coloured
text missed 4.5:1 for several apps). No accent keeps the default blue;
- "by <developer>" beside the app name: App::developer, ArtCraft (app::DEVELOPER) for every
app in the storytold organisation unless the app passes its own;
- its own button states: backgrounds for unfilled buttons (LATER, DISMISS) when hovered and
pressed, as the host app's style can hover in a colour its dark text doesn't read on, and a
2 px focus ring in the body text colour on every button (egui shows focus in that state)
and on the What's new link.
Apps opt in by passing their own App, e.g.
App { icon_png: Some(include_bytes!("…128x128…png")), accent: Some([0x3f, 0x5f, 0xe0]), ..suite::WORDCRAFT }.
|
||
|
|
733a038aa5 |
craft-print: log why the printer list is empty when lpstat can't run; document Flatpak needs (#429)
* craft-print: log why the printer list is empty when lpstat can't run; document Flatpak needs Flatpak apps saw "No printers found" with working CUPS printers: the Freedesktop runtime has libcups but not lpstat/lp, and the sandbox had neither the CUPS socket nor Avahi (driverless printers come from lpstat -e). Log the spawn failure and say in the README what a Flatpak must bundle and grant. * craft-print: log a failed lpstat -p -d; README separates what each Flatpak piece is for Review (CodeRabbit): a non-zero lpstat exit was dropped silently, and the README said any missing piece empties the printer list when only lpstat and the socket do. * craft-print README: driver options need the PPD, remote servers need network, socket paths are remapped Review (CodeRabbit): printer_options reads /etc/cups/ppd before lpoptions, so it is the PPD, not lpoptions, that a Flatpak lacks; a client.conf server is reached only with --share=network. * craft-print README: scope the CUPS_SERVER override to one app; describe the stricter socket wrapper Review (CodeRabbit): flatpak override without an app id applies to every app. |
||
|
|
fea36c4309 | Merge branch 'main' into clean-room-crw-format-decode | ||
|
|
1a11a48978 |
craft-dialogs: native file and message dialogs that never block the event loop (#412)
* Add craft-dialogs: native file and message dialogs that never block the event loop Requests queue and show at the end of the frame on the main thread; answers come back with the app's continuation on a later frame. One dialog at a time, a drop guard and a panic guard so a dialog never stays open, and an override for tests. On macOS, Open and Folder show as a free-standing NSOpenPanel (beginWithCompletionHandler, which rfd can't show), Save and message boxes as sheets through rfd's async dialogs, never falling back to runModal. Filter extensions match in any letter case on Linux. * craft-dialogs: Dialogs::raise brings the open macOS panel to the front * CI: dialogs workflow (Linux clippy and tests for craft-dialogs) * craft-dialogs: per-instance macOS panel, parent window check, override panic guard, CI lints macOS code * craft-dialogs: raise only a visible panel; Open and Folder need a running app |
||
|
|
a0c8a08020 |
craft-crw: Canon CRW (CIFF) raw decoder, moved from LightCraft with its clean-room record
The clean-room Canon CRW decoder written in LightCraft (crates/raw/src/vendor/crw.rs, crw_tables.rs, branch clean-room-crw-format-decode, revision 70c755e4) becomes the shared crate craft-crw: the CIFF heap container, the entropy coding derived by black-box analysis (frozen code tables for selectors 0-2), sensor black and white levels, as-shot white balance, orientation and the make/model/time records. Public types are the crate's own (Error, Limits, Cfa, Rect, BlackLevel, Header, Image); the capture time is handed on as Unix seconds so no calendar code lives here; LightCraft's shared plateau estimate is copied as saturation_plateau. Algorithm, tables and thresholds are unchanged. Tests: the ported synthetic round-trip, table-structure and hostile-input tests; a proptest mutation sweep (tests/robust.rs); and tests/samples.rs, which pins the eleven CC0 raw.pixls.us sample files (tests/samples.json: URL, SHA-256, licence) to the SHA-256 of the verified decode plus levels, white balance and metadata, so the port is proven byte-identical to the LightCraft decode without redistributing the reference TIFFs. scripts/fetch-crw-samples.py downloads the files outside the checkout; CRAFT_CRW_SAMPLES and CRAFT_CRW_ORACLE point the tests at them. The crate builds for wasm32. The whole derivation record (starting assumptions, plan with the maintainer's decisions, source log, container and payload notes, derivation log, frozen format, verification, colour/levels notes, private-corpus and chart notes, scripts, logs and the frozen table JSON) moves verbatim to crates/crw/clean-room/ with a README that indexes it and maps the LightCraft paths it cites; tools/gen_tables.py regenerates src/tables.rs from the JSON, byte for byte. Repository: .github/workflows/crw.yml (fmt, clippy, tests on Linux, Windows and macOS; wasm check; the sample-file job with cached downloads), README and ATTRIBUTION rows, NOTICE source provenance, docs/clean-room.md (the clean-room standard a format crate follows here), and AGENTS.md expanded with the start-of-session steps, the new-crate checklist, clean-room, assets and test rules; CLAUDE.md points at it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |