Moved from GridCraft at 5cf52c5f: the encoding detection, delimiter
sniffing, record parser and field quoting of crates/xlsx/src/csv.rs,
and the SYLK reader of crates/engine/src/io/sylk.rs.
Fields stay text and the SYLK reader returns its own Sheet; typed
values, dates and the workbook stay in GridCraft.
* craft-raw: the file-format layer of camera raw files, with DNG and CRW
A new crate for #587 and #778: bytes in, the sensor data out as a neutral
RawImage (samples, CFA, levels, active area, crop, orientation, colour tags,
opcode lists, profile tables), and back for DNG. One Cargo feature per format:
dng (read and write), jxl (JPEG XL DNG), crw (through craft-crw).
The base is LightCraft's crates/raw at 5ff567a4: dng, dngwrite, tiffraw,
unpack, ljpeg, jxl, semantic, preview, the parsing half of opcodes, profile
and gaintable, the model and probe. Demosaicing and everything after it stay
with the develop pipeline.
Ported from PhotoCraft's and FilmCraft's DNG readers, each with a test: the
default crop's per-tag defaults, an unusable crop falling back to the active
area, non-finite white levels, the white default of a linearized image, the
AsShotWhiteXY range, 8-bit-only lossy JPEG, lossy tiles that must cover their
part of the image, and the CinemaDNG frame tags.
* craft-raw: Hasselblad 3FR and FFF (feature hasselblad)
RawFormat::CfaTiff decodes: the DNG-layout reader plus what these files add.
Uncompressed data or Hasselblad's lossless JPEG (selection value 8), RGGB when
no CFAPattern is stated, an FFF's colour matrix from the 3FR header it embeds,
and the 8-bit RGB preview handed on as a baseline TIFF.
Moved from LightCraft's vendor/hasselblad.rs at 5ff567a4 with its tests.
Ported from PhotoCraft's reader: four BlackLevel values without a
BlackLevelRepeatDim are one per CFA site in a file that is not a DNG.
* craft-raw: Minolta MRW, Epson ERF, Mamiya MEF and Kodak DCS520C
Four formats only LightCraft decodes, one feature each (mrw, erf, mef, kodak),
moved from its vendor modules at 5ff567a4 with their tests; the MRW block
container comes from lightcraft-meta. src/shared.rs holds the level helpers
the maker decoders share.
ERF and MEF state no white balance: the estimate LightCraft made from the
preview is develop-side work, so wb_multipliers is None for them here.
* craft-raw: shared scan budget for tagged previews, tile-sized lossy JPEG budget, bound on Deflate tile padding
Ported from storytold/lightcraft#3695. Candidate preview ranges are deduplicated and charged to one byte
budget and one header-walk budget; a lossy JPEG decoded for a tile may hold no more samples than the tile
rounded up to whole MCUs (and never more than Limits::max_lossy_tile_samples); a Deflate chunk whose nominal
size is mostly padding outside the image is refused before it is inflated.
* craft-raw: lossless JPEG sample bound per referenced byte; previews larger than the file are passed over
Ported from storytold/lightcraft#3910 (the parts whose code is in craft-raw today). check_image refuses a
lossless JPEG image that declares more than 32 samples per referenced byte, counted once per chunk that points
at the bytes so identical tiles stored once still decode. The Hasselblad RGB preview search passes over an
image whose pixel bytes exceed the file and still takes a smaller complete one.
* craft-raw: test for the Deflate padding bound
* craft-raw: Pentax PEF and Samsung SRW
Moved from LightCraft crates/raw (vendor/pef.rs, vendor/srw.rs) at 5ff567a4. The Huffman difference decoder
and bit reader that PEF shares with the Nikon, Panasonic and Sony codings become the huffman module.
---------
Co-authored-by: radialmonster <radial@gmail.com>
The archive half of FigCraft's Penpot import and export
(crates/format/src/penpot.rs and penpot/embedded_fonts.rs at 3b9bf8c9):
ZIP validation, the manifest, bounded JSON members with the shared byte
and value budgets, pages, page shapes, media objects, font signatures,
and the archive writer. Members are handed out as the JSON the file
holds; mapping shapes stays in the app.
Duplicate member names are now refused: the ZIP reader keeps one member
per name, so the per-name check FigCraft has never fires. The directory's
own member count is compared instead.
* Add craft-sketch: the .sketch container, read
The archive half of FigCraft's .sketch import
(crates/format/src/sketch.rs at 3b9bf8c9): detection, the version cut
(Sketch 48 and newer), document.json with its fallback, the pages in
document order, images, and the budgets on member size, compression
ratio, totals and page count. The document and the pages are handed out
as the JSON the file holds; mapping the layers stays in the app.
* craft-sketch: credit line in the README
* Add craft-kiwi: the kiwi binary schema format, decoded and encoded
The generic kiwi decoder and encoder from FigCraft's .fig import and export
(crates/format/src/fig/kiwi.rs at 3b9bf8c9), moved as it is: binary schema
decode and encode, the compact text form of a schema, message decode into a
tree keyed by field name, and message encode checked against the schema.
The value budget that follows a .fig file's compressed size stays with the
.fig reader; here decode_message_within takes the budget and
DEFAULT_MAX_VALUES is the floor FigCraft used.
* craft-kiwi: no scan per field in schema decode; the text form keeps the limits
Schema::decode compared each message field id with every earlier one: 20
definitions of 10 000 fields, within every cap, took a billion
comparisons. A set per definition now.
Schema::parse applies the definition and field caps of the binary form,
looks type names up in an index instead of scanning the lines per field,
accepts a struct or message without fields, and refuses two fields of one
name and a message field id that is 0 (it ends a message) or repeated.
* craft-affinity: bounded reader for native Affinity documents, moved from VectorCraft
The reader of .af, .afdesign, .afphoto and .afpub files (archive, object stream and document
model) as it is in storytold/vectorcraft at 6e873b80 (crates/affinity), under a neutral name.
Its corpus tests read the pinned public documents scripts/fetch-affinity-corpus.sh fetches.
* craft-affinity: fold in what only PhotoCraft's copy of the reader had
From storytold/photocraft at 63b641b5 (crates/affinity), onto the VectorCraft copy:
- Special shapes: clouds, hearts, cogs, callouts, arrows, double stars, tears, crescents,
diamonds and segments come in as their own outlines, not their bounding ellipse.
- Null slots of object lists are charged to the document's work before a list is scanned,
on every visit of a shared object (children, attachments, effects, compound operands,
page lists, paint lists, gradient colours, text blocks and runs, table cells, tile lists).
- A colour or gradient position record is decoded only as far as its components are read.
- 16-bit and 8-bit planes are interleaved through tables, not an f64 for each pixel.
- Limits::max_pixel_bytes: the caller sets what the pixel layers of one import may hold.
- Pixels::cmyka8_to_rgba8 for a caller without colour management.
The crate README and docs describe the model on its own terms.
* craft-affinity: a callout with a right-to-left box no longer panics; an oversized shape imports as its box
Ported from storytold/photocraft#10473 by DJHoltkamp. A callout rectangle clamped its tail
with clamp(x0, x1), which panics when the box is stored right to left. A box with finite
corners whose size overflows gave infinite or NaN outline points; it now imports as its box
with a warning. Also drops the inflated doc.dat once it is parsed.
* craft-mesh: FBX 7.x reader from EffectCraft (`fbx`)
EffectCraft's FBX reader (crates/model/src/fbx.rs: binary with 32- and 64-bit
record offsets and zlib arrays, ASCII, geometry layer elements, the Model
transform chain, Lambert/Phong materials, textures) onto the crate's Scene.
The reader was written by vangel76 (EffectCraft #736); its memory budget and
shared instanced meshes by DWAA1660 (EffectCraft #1234).
It includes the fixes of the open EffectCraft PR #2955: a binary child record
must end within its parent, a failed array inflate reports only its status,
textures that end at one image share it and its bytes are charged to the
budget, and material slots and connections are found through maps.
What differs from the app's copy, because the model here is neutral:
polygons stay whole and layer elements stay indexed pools (no vertex
unification, no computed normals or tangents); images are bytes, not decoded
pixels; the axis system is not applied (Document::axes_to_y_up gives it);
the memory budget is Limits::max_data_bytes; a file without meshes is a
scene, not an error. base64_decode moves from the gltf module to the crate
root so both formats share it (gltf re-exports it).
* craft-mesh: read FBX 6.x
FBX 6.1 files differ from 7.x in how the scene is laid out, not in the
record syntax: a Model holds its own Vertices and layer elements, properties
are Properties60/Property (no label field), Connect lines name objects by
Class::Name instead of by id, and GlobalSettings sits among the objects.
The reader now takes all four, and NormalIndex beside NormalsIndex, so 6.x
geometry, transforms, the hierarchy and materials are read. 6.x textures
(connected to Models) are not.
Ported from storytold/deckcraft#1380 (crates/mesh/src/fbx), whose reader
took 6.1 to 7.7; its mesh building, axis turn and rasteriser stay in
DeckCraft.
---------
Co-authored-by: Ethan Dale <ethandale@Ethans-MacBook-Pro.local>
* craft-timeline: exact media time, reports, media paths, ALE and CMX 3600 EDL
New crate for editorial timeline interchange (#782), moved from FilmCraft at
4bec22bd:
- time: filmcraft-time whole (Tick, FrameRate, TimeRange, SMPTE timecode,
feet+frames, typed-timecode parsing) with its tests.
- report: Report / Level / ReportEntry.
- path: file URL and path conversion, relative references joined to a folder.
- rate: timebase+ntsc and floating rates to exact rates, MAX_RATE.
- ale: AleDoc, parse, write, standard heading and columns.
- edl: the parser (events, lines, timecode fields, M2, AUD, comment extensions),
the frame-rate guess, and the line layout of the writer, on frame counts.
- Format, detect_text, decode_text.
Each format module gives the document as the format writes it. Mapping onto an
application's timeline stays in the application.
* craft-timeline: strip a UTF-8 byte order mark when parsing ALE files
An ALE saved with a BOM was refused as "not an ALE file": the mark made the
first line something other than "Heading".
Ported from storytold/filmcraft#4531.
---------
Co-authored-by: NetterSilver <133535268+Silvertree2010@users.noreply.github.com>
* craft-gcode: bare output for line-by-line senders, and Grbl replies
Writer::bare writes the same moves as Writer::new with every line a
command: no comments, coordinates at up to three decimals without
trailing zeros, M5 at the start, and the return to the origin at the
end. A sender that waits for an ok after each line can stream it as is.
Writer::set_feed changes the feed rate between contours.
The new grbl module has the real-time command bytes (status query, feed
hold, cycle start, soft reset) and the parsers of the controller's
answers: the state of a status report (read up to MAX_REPLY_LEN bytes)
and the ok / error / ALARM acknowledgement of a line. No I/O.
* Add craft-plotter: HP-GL, DMPL and GP-GL from a job in millimetres
One crate for the command languages of vinyl cutters and pen plotters,
one module per language: hpgl, dmpl, gpgl (plain) and gpgl::silhouette
(the Silhouette dialect in its three generations, with registration
marks, the device queries and the parsers of their replies).
A Job is ordered operations: polylines in millimetres plus tool
settings. Each encode gives a Program: the commands, their framing, and
where a sender waits for the registration marks. The crate does no I/O
and builds for wasm.
A job is refused past 64 operations, 2,000,000 points, 10 passes or a
2 mm overcut, or when a value is not finite. Replies are read up to 256
bytes and report at most 8 tool holders. Every limit has a test, and
tests/golden.rs fixes the exact commands of each language.
The README records where the protocol facts come from.
* craft-plotter: optional serde for Mat, as its short name
A caller that keeps a mat in a settings file or a machine table needs to
read and write it. The serde feature serializes Mat as its short name
("12x24") and refuses an unknown name. Off by default; the crate still
has thiserror as its only required dependency.
* Add craft-rtf: RTF tokenizer, text decoding, and a reader and writer for a small model
The tokenizing rules, code pages, multibyte decoding, dates and escaping come
from WordCraft's RTF reader and writer. The model (paragraphs with outline
level and indent, character formatting, tables) covers what DesignCraft's text
import, DeckCraft's outline import and export and PdfCraft's export use.
An unpaired UTF-16 surrogate in \u reads as U+FFFD and a high surrogate pairs
only with the very next \u, as storytold/wordcraft#6105 describes for Word.
* craft-rtf: a symbol font's bytes are its own codes
A font with \fcharset2 (Symbol, Wingdings) has no letters: a byte from 0x80
reads as U+F000 + the byte instead of a letter of the document's code page.
The symbol page applies to body text only, a symbol default font's page does
not apply to text in a font chosen with \f, \plain makes the default font's
page current again, and a code page an entry names with \cpg is kept.
Ported from storytold/wordcraft#6014.
* craft-rtf: alignment, indents, spacing, superscript and subscript; NUL is not text
The model and both directions gain what DesignCraft's RTF export writes:
\ql \qc \qr \qj, \ri, \fi, \sb, \sa, \super, \sub, \nosupersub.
A \u0 or \'00 no longer puts a NUL into the text.
---------
Co-authored-by: Simon Koch-Sultan <126534658+scks1@users.noreply.github.com>
Moves filmcraft-mp3enc (storytold/filmcraft@4bec22b) into craft_audio::mp3,
on craft-media's BitWriter. `encode` turns the feature on, mp3::Error converts
into AudioError::Encode, and a test covers every sample rate and bit rate with
extreme signals.
* craft-raw: the file-format layer of camera raw files, with DNG and CRW
A new crate for #587 and #778: bytes in, the sensor data out as a neutral
RawImage (samples, CFA, levels, active area, crop, orientation, colour tags,
opcode lists, profile tables), and back for DNG. One Cargo feature per format:
dng (read and write), jxl (JPEG XL DNG), crw (through craft-crw).
The base is LightCraft's crates/raw at 5ff567a4: dng, dngwrite, tiffraw,
unpack, ljpeg, jxl, semantic, preview, the parsing half of opcodes, profile
and gaintable, the model and probe. Demosaicing and everything after it stay
with the develop pipeline.
Ported from PhotoCraft's and FilmCraft's DNG readers, each with a test: the
default crop's per-tag defaults, an unusable crop falling back to the active
area, non-finite white levels, the white default of a linearized image, the
AsShotWhiteXY range, 8-bit-only lossy JPEG, lossy tiles that must cover their
part of the image, and the CinemaDNG frame tags.
* craft-raw: Hasselblad 3FR and FFF (feature hasselblad)
RawFormat::CfaTiff decodes: the DNG-layout reader plus what these files add.
Uncompressed data or Hasselblad's lossless JPEG (selection value 8), RGGB when
no CFAPattern is stated, an FFF's colour matrix from the 3FR header it embeds,
and the 8-bit RGB preview handed on as a baseline TIFF.
Moved from LightCraft's vendor/hasselblad.rs at 5ff567a4 with its tests.
Ported from PhotoCraft's reader: four BlackLevel values without a
BlackLevelRepeatDim are one per CFA site in a file that is not a DNG.
* craft-raw: Minolta MRW, Epson ERF, Mamiya MEF and Kodak DCS520C
Four formats only LightCraft decodes, one feature each (mrw, erf, mef, kodak),
moved from its vendor modules at 5ff567a4 with their tests; the MRW block
container comes from lightcraft-meta. src/shared.rs holds the level helpers
the maker decoders share.
ERF and MEF state no white balance: the estimate LightCraft made from the
preview is develop-side work, so wb_multipliers is None for them here.
* Add craft-gainmap: ISO 21496-1 and hdrgm gain map metadata, compute and apply, from LightCraft
Moved from lightcraft-codecs' gainmap.rs at 8ef35987 with its tests: the metadata reader and
writer and the arithmetic. The JPEG container (MPF index, two encoded images) stays in the app.
* craft-gainmap: Apple legacy gain-map reconstruction
Apple's legacy single-channel gain maps interpolate a linear gain ratio, not the log2 gain of
ISO 21496-1: apple::apply rebuilds the HDR rendition from a decoded base and the map, with the
headroom taken from the maker-note pair or the HDRGainMapHeadroom tag, checked inputs and
pixel-centre sampling.
Ported from storytold/lightcraft#3760 (which carries #1978).
---------
Co-authored-by: Ted Mao <tedmao@gmail.com>
* craft-epub: read EPUB 2 and 3 packages (container, package document, bounded entry reads)
The container layer of WordCraft's EPUB import (storytold/wordcraft#3119): container.xml,
the package document's metadata, manifest and spine, href resolution, and entry reads inside
per-entry and total byte budgets. Moved onto craft-xml, read strictly, and extended to EPUB 2
packages (NCX path, cover meta, dc-metadata).
* craft-epub: write EPUB 3 packages (reflowable and fixed layout)
The packaging of DesignCraft's EPUB export (crates/epub/src/package.rs at 513f77b8): the stored
mimetype, container, package document, nested navigation, fixed-layout metadata and the
derived identifier, behind a neutral Book of metadata, resources, spine and navigation. Adds
the dc:subject and dc:description metadata, the W3CDTF check and the stricter language tag
check of WordCraft's EPUB export (storytold/wordcraft#2815).
---------
Co-authored-by: Michael <monashmichaelmu@gmail.com>
PhotoCraft's `photocraft-psd` crate (crates/psd at storytold/photocraft
63b641b55e54bf8f5c83efd977fdb16a2c255431) as `craft-psd`: the lossless
PSD/PSB model, reader, writer and builder, descriptors, the Photoshop
resource files that share its primitives (.abr, .asl, .aco, .act, .ase,
.grd, .pat, .atn, .csh, .shc) and the Photoshop blocks of a layered TIFF.
The crate had no dependency on any other PhotoCraft crate.
Code is unchanged apart from the crate name, craft-libs' rustfmt width,
comments that named other PhotoCraft crates, issue references (now
`photocraft#N`), and the corpus tests' directory (`CRAFT_PSD_CORPUS` or
`corpus/` at the workspace root). Tests, proptests, fuzz targets and
fixtures move with it.
Part of #778, #786.
* Add craft-aep: After Effects project container and typed view from EffectCraft
The .aep (RIFX 'Egg!') and .aepx container as a lossless chunk tree that
writes back byte for byte, and the typed, read-only view over it:
compositions, layers, properties, keyframes, effects and their settings
data, masks, shapes, text documents, markers, cameras, track mattes,
footage, project settings and the saving version.
Moved from effectcraft crates/aep/src/container at f6b4da0a with its unit
tests, the container integration tests, the aepdump and stubicc tools and
docs/aep-format.md. The match-name tables (effect_map, style_map), the
importer and the exporter are written against EffectCraft's model and
stay there. ExtractorChannels::sources takes the caller's name for a
copied channel instead of an EffectCraft constant.
* craft-aep: read the Photoshop layer a footage item shows
A footage item made from one layer of a .psd stores the layer in its
`opti` (tag `8BPS`): the layer record's index (big-endian i32 at 14, -1
for the merged image), the document's two sides (little-endian u32 at 32
and 36), the layer's bounds (little-endian i32 at 78, 82, 86, 90) and its
name (UTF-8 from 344). `Footage::psd_layer` reads them as `PsdLayer`.
The format part of storytold/effectcraft#3449; the importer's use of it
stays in EffectCraft.
---------
Co-authored-by: arnav-hiwarkar <asharnav2008@gmail.com>
Moves filmcraft-mxf (storytold/filmcraft@4bec22b) into craft_media::mxf.
ByteSource is the crate's trait, Codec::codec_id() and
EssenceTrack::codec_params() map tracks to CodecId, and the writer names
craft-media as company and product unless the caller sets them.
Fixes two panics: EssenceTrack::sync_before on a track without picture
samples, and Timecode::format for a drop-frame start near i64::MAX.
Adds Container::Mxf, an ffprobe/ffmpeg oracle test for the reader and the
writer, and hostile-input tests.
The format-only part of DesignCraft's crates/idml (at 513f77b8): the bounded, lenient package
reader, designmap includes, the package writer, the <Properties> accessors and the attribute
value codecs. DesignCraft's own XML tree is replaced by craft-xml, which adds node and attribute
caps per part. crates/textimport/src/tagged.rs moves as craft_idml::tagged behind a neutral
model of paragraphs and runs.
read_smf (formats 0 and 1, format 2 best effort) and write_smf (format 1,
960 PPQ) with the model they read into: Note, CtrlEvent, CtrlKind and
Sequence, in ticks at 960 per quarter note. serde on the model types is
behind the `serde` feature.
Moved from soundcraft crates/midi at 17d3f007: smf.rs, sequence.rs, the
error type, the SMF tests and the Sequence tests. The editing operations
(quantize, transpose, ...) and the display names stay in SoundCraft. Adds
a test for the MAX_TRACKS limit, which had none.
One crate for what WordCraft's docx::package, GridCraft's xlsx::package and
DeckCraft's pptx::opc each carried: bounded ZIP readers (every part up front,
or on demand), part names, relationships, content types and a package writer.
The declared entry count is checked from the end records before the zip crate
indexes the entries. Limits have no defaults: each format passes its own.
* Add craft-drawingml: units, colours, preset shapes, custom geometry and text warps
The parts of DrawingML that need no document model, moved from DeckCraft
(60f91f78): crates/color, crates/geom (preset, warp, Xfrm and the EMU
conversions) and crates/pptx/src/custgeom.rs with CustomPath from
crates/model.
The code is unchanged except that custgeom reads craft-xml trees and writes
to a craft-xml Writer instead of DeckCraft's own XML helper, and matches
elements by local name. The 59 tests of the moved code come with it; six
more cover the custom geometry limits.
* drawingml: evaluate a custom geometry's guide lists once per coordinate space
read_cust_geom rebuilt Guides and evaluated avLst and gdLst again for every
path: up to 1,000 paths times up to 10,000 guides, each name copied and
hashed.
Guides are now cached per path coordinate space (w, h). At most 16 spaces
and 4 MiB of guide names are kept per geometry; paths in further spaces are
left out. The first space is always kept, and a geometry without guides is
not capped.
Ported from storytold/deckcraft#1789. A list counts as having guides only
when it has gd children, as that PR's review asked.
crates/svg-paths becomes crates/svg (package craft-svg); the geometry
reader is craft_svg::paths (was the crate root), color and write keep
their names. craft-svg-paths stays as a crate that re-exports them.
New, merged from the copies the apps keep:
- sniff: is_svg, is_svgz
- svgz: bounded gunzip, UTF-8/UTF-16/ISO 8859-1 decoding, compression
- guard: element nesting and entity expansion measured on the text
(VectorCraft's import/depth.rs text pass and entities.rs)
paths: a smooth segment with no control point to reflect starts at the
current point (S after a quadratic or a close, T after a cubic or a
close), and a relative root width or height (100%, 2em) counts as
absent instead of failing the import.
Part of #778 and #789.
The encodings every Lottie layer, mask and shape reuses, on serde_json
values: the document header, animatable properties and their keyframes
(legacy end values, hold keys, spatial tangents), ease tangents to and
from speed and influence, bezier paths, gradient stop lists, the tolerant
number and colour readers, and embedded images as data URIs.
Taken from effectcraft crates/lottie at e18f48fb (anim.rs, shapes.rs,
import.rs, export.rs, base64.rs) with EffectCraft's project types replaced
by plain values. The layer, shape, effect and text mapping stays in
EffectCraft, and dotLottie is ZIP, which is not this crate's format.
Carved from FilmCraft's filmcraft-captions (crates/captions at 4bec22bd): the
file readers and writers, CEA-608/708 coding, format detection and the cue
document. Rendering and the mapping onto FilmCraft's caption tracks stay there.
- time: the crate has its own exact Tick (1/254016000000 s) and rational
FrameRate, the subset of filmcraft-time the formats use.
- TTML is read through craft-xml instead of roxmltree: no entity expansion,
nesting bounded by the reader (128 levels, as before).
- parse refuses input over 64 MiB and keeps at most 1,000,000 cues.
- MCC and EBU STL name the caller's program (WriteOptions::program).
- plain_text (inline tag stripping) moves with the SCC/MCC writers that need it.
The format half of FigCraft's crates/vectordrawable (at 24cf7032): the
model of paths, groups, clips and gradients, the XML writer, and the
mapping of a gradient on an affine frame to what a drawable can say.
The walk over FigCraft's document, its compatibility modes and warnings
stay in FigCraft.
Part of #778.
* craft-raw: the file-format layer of camera raw files, with DNG and CRW
A new crate for #587 and #778: bytes in, the sensor data out as a neutral
RawImage (samples, CFA, levels, active area, crop, orientation, colour tags,
opcode lists, profile tables), and back for DNG. One Cargo feature per format:
dng (read and write), jxl (JPEG XL DNG), crw (through craft-crw).
The base is LightCraft's crates/raw at 5ff567a4: dng, dngwrite, tiffraw,
unpack, ljpeg, jxl, semantic, preview, the parsing half of opcodes, profile
and gaintable, the model and probe. Demosaicing and everything after it stay
with the develop pipeline.
Ported from PhotoCraft's and FilmCraft's DNG readers, each with a test: the
default crop's per-tag defaults, an unusable crop falling back to the active
area, non-finite white levels, the white default of a linearized image, the
AsShotWhiteXY range, 8-bit-only lossy JPEG, lossy tiles that must cover their
part of the image, and the CinemaDNG frame tags.
* craft-raw: Hasselblad 3FR and FFF (feature hasselblad)
RawFormat::CfaTiff decodes: the DNG-layout reader plus what these files add.
Uncompressed data or Hasselblad's lossless JPEG (selection value 8), RGGB when
no CFAPattern is stated, an FFF's colour matrix from the 3FR header it embeds,
and the 8-bit RGB preview handed on as a baseline TIFF.
Moved from LightCraft's vendor/hasselblad.rs at 5ff567a4 with its tests.
Ported from PhotoCraft's reader: four BlackLevel values without a
BlackLevelRepeatDim are one per CFA site in a file that is not a DNG.
A new crate for #587 and #778: bytes in, the sensor data out as a neutral
RawImage (samples, CFA, levels, active area, crop, orientation, colour tags,
opcode lists, profile tables), and back for DNG. One Cargo feature per format:
dng (read and write), jxl (JPEG XL DNG), crw (through craft-crw).
The base is LightCraft's crates/raw at 5ff567a4: dng, dngwrite, tiffraw,
unpack, ljpeg, jxl, semantic, preview, the parsing half of opcodes, profile
and gaintable, the model and probe. Demosaicing and everything after it stay
with the develop pipeline.
Ported from PhotoCraft's and FilmCraft's DNG readers, each with a test: the
default crop's per-tag defaults, an unusable crop falling back to the active
area, non-finite white levels, the white default of a linearized image, the
AsShotWhiteXY range, 8-bit-only lossy JPEG, lossy tiles that must cover their
part of the image, and the CinemaDNG frame tags.
Moves filmcraft-avi (storytold/filmcraft@4bec22b) into craft_media::avi.
ByteSource is the crate's trait, Error::Io holds the io::Error, and
Stream::codec_params() maps the FourCC and the WAVEFORMATEX tag to a CodecId.
Adds Container::Avi and tests for the stream, header-size, nesting and
OpenDML index limits.
color: parses the CSS colours SVG paint uses (hex, rgb()/hsl(), the 148 named
colours) into an opaque 8-bit sRGB Color, and writes the canonical #rrggbb form.
Shape::stroke is still returned as written; parse_color reads it.
write: SvgWriter (XML declaration, root element with the SVG namespace, elements
and attributes escaped through craft-xml, caller-controlled line breaks), path
data in shortest round-trip form or with fixed decimals, and fixed-decimal
numbers that are never NaN or infinite.
* craft-metafile: DeckCraft's WMF, EMF and EMF+ reader with WordCraft's changes merged
The base is DeckCraft's crates/metafile at cf0d7fbb. WordCraft's changes since
the copy was taken (WordCraft 05679497, the ancestor) are merged three ways from
WordCraft 639b8133: the cropped, depth-once DIB decoder, the bitmap side cap,
the header-only size(), crop_window, the free-slot object table, capacity-based
budget charging and the exact reservation in subpaths. WordCraft's own text
records lose to DeckCraft's richer text model. All 120 DeckCraft tests and all
69 WordCraft tests are kept (132 distinct).
* craft-metafile: line styles, clip paths and the records behind them, from VectorCraft's reader
The model gains what VectorCraft's reader kept and the other two dropped:
LineStyle (cap, join, miter limit, dashes) on paths, and clip paths
(SELECTCLIPPATH) shared between the items they clip. The reader now plays
EXTCREATEPEN style entries and the pen's cap and join bits, SETMITERLIMIT,
SETARCDIRECTION, SCALEVIEWPORTEXTEX, SCALEWINDOWEXTEX, SELECTCLIPPATH and
ABORTPATH; STROKEANDFILLPATH closes open figures and CLOSEFIGURE moves the
current position; the header's micrometre device size is used when present;
BITMAPCOREHEADER, BI_JPEG and BI_PNG bitmaps decode; records of unknown types
are counted in Picture::skipped_records. Each has a test.
* craft-metafile: EMF and WMF writers behind the write feature, bitmap transforms, a decode budget
The writers are VectorCraft's, taking a list of drawing operations (fills,
strokes with pens, images, clips) instead of its document; the document walk
stays in the application. Bitmap items gain a transform, so an image drawn
under a turning world transform keeps its turn (the writer places images that
way). JPEG and PNG bitmaps share one per-file pixel budget. The tests cover the
writers record by record, round-trip both formats through the reader, and play
VectorCraft's hand-built files into the picture model.
* craft-metafile: a test per limit, README, crate docs
Adds tests for the limits no test held to their value (string lengths, clip
region rectangles, placed elements, tile side, palette size, nesting, the EMF+
object table, save stack, point lists, gradient stops, region depth, continued
objects, bitmap masking work) and a truncation and mutation sweep over the new
records. Records that change what is drawn but are not played now count as
skipped instead of ignored. The README has the coverage tables, the limits,
what stays in the apps, the origin of each part and a migration table per app.
* craft-metafile: a region node cap test, the box of a turned bitmap
The region node cap gets a test of its own (a balanced tree of intersections),
and a turned bitmap's rectangle is the box around its four corners, so a caller
that draws rectangles still has an area to draw into.
* Add craft-mesh: neutral mesh model with STL, OBJ/MTL and PLY
* craft-mesh: glTF 2.0 and GLB
* craft-mesh: README, crate list entry, a test for the UV set bound
The base is FilmCraft's filmcraft-cfb (reader, writer, tests) at
filmcraft 6d5269ed. Ported on top of it, each with a test:
From GridCraft's and DeckCraft's readers: only sector shifts 9 and 12;
a file that ends inside its last sector still reads; a chain loop
shorter than the stream is an error instead of repeated data; a damaged
mini stream no longer fails the open; links to missing directory
entries are ignored; a stream size cap (Limits, default 1 GiB, and a
per-call cap); a lenient directory scan (directory_names,
has_entry_named) for files too damaged to open.
From WordCraft's guard: a FAT sector listed twice is refused; spare FAT
sectors and zero-padded DIFAT sectors are accepted.
New: Limits::max_dir_entries, walk() and path() to list entries,
has_stream, Writer::stream_at and write_streams, and only storages the
root reaches are walked.
Almost every Craft app carries its own small XML helper: a bounded
element tree on a pull parser and an escaping string writer. This is
the shared one, so each app can drop its copy.
- An owned tree (El, Node) read from bytes with limits on input size,
depth, node count and attributes per element. A limit exceeded is an
error. No DTD is processed and no entity is expanded beyond the five
predefined ones and numeric character references. UTF-8 and UTF-16.
- Names as written, by local name, or rewritten to the caller's
canonical prefixes by namespace URI, with hooks for a prefix that
depends on the parent and for attribute values that hold prefixes.
- Lenient and strict reading; streaming of repeated elements to a
callback; first_start; a pull reader with byte ranges.
- Accessors, typed attribute getters, mutation and a builder. Where
the apps disagreed (whole or local names, own or deep text, exact or
lossy numbers) there are two spellings.
- esc, esc_attr, compact and pretty serialising, and a Writer.
A name that is not an XML name is never written.
Built on quick-xml 0.41's tokenizer (MIT), the version the apps use.
The apps' own tests for their helpers are ported in tests/apps.rs.
No app is switched here.
MailCraft and WordCraft each parse MIME: MailCraft for mail (crates/mime,
crates/charset), WordCraft for MHT web archives (formats/src/mht.rs). This
adds one shared crate for the format, taken from MailCraft's code where it
is format code, with WordCraft's multipart/related lookups and its fix for
comments in Content-Type.
Reader: parse() gives a tree of parts (header fields, content type and
parameters, decoded body, charset-decoded text). It never fails or panics,
is linear in the input, and is bounded by Limits (input size, header count
and sizes, depth, part count, decoded size); Message::truncated reports a
limit that left something out.
Writer: Entity::text / file / multipart and write_message, with folded
headers, RFC 2047 and RFC 2231 encoding, quoted-printable and base64 with
line limits, and boundaries checked against the content. Every header
value loses CR, LF and NUL, and a name that is no field name is not
written.
Also: address lists, mbox splitting and writing on raw message bytes, and
Date parsing and formatting on a plain struct (chrono is a private
dependency; no date type is in the public API).
No app is switched here. The README has the migration tables and says what
stays in the apps.
FilmCraft's MPEG audio Layer I/II decoder as `craft_audio::mpa` behind
`mpa-decode`, kept over symphonia's: the same quality where both
decode, and it also decodes the MP2 files ffmpeg writes by default
(mono at 384 kbit/s), which symphonia rejects. `TrackDecoder` and the
native read path take MP1/MP2 in MP4, Matroska and raw streams;
symphonia keeps MP3 only, so its `mp1` and `mp2` features are dropped.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
* craft-audio: AC-3 and E-AC-3 decoder
FilmCraft's AC-3 decoder as `craft_audio::ac3` behind `ac3-decode`:
every AC-3 channel mode and frame size, and E-AC-3 independent
substream 0 with spectral extension, in WAV order without downmix.
`TrackDecoder` and the native read path take AC-3 and E-AC-3 in MP4,
Matroska and raw streams.
Not bit-exact: zero-bit mantissas are filled with decoder-specific
dither (A/52 §7.3.4), so tests compare at 65-94 dB SNR on tones and
27-48 dB on white noise. 7.1 E-AC-3 decodes as its 5.1 core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 1 (#447)
An AC-3 / E-AC-3 packet is as long as all the primary syncframes in it.
Before the first decode, TrackDecoder::frames_per_packet gave 1536 for any
AC-3 track, and conceal used the first syncframe's header only. An E-AC-3
stream of one-, two- or three-block syncframes then got the wrong MP4 length
and seek positions and the wrong Matroska positions. A packet that groups
several such syncframes concealed at a fraction of its length.
Seeks in AC-3 and E-AC-3 now have to match the whole-file decode bit for bit,
as every other codec's do. The dither is seeded from each syncframe, so the
old 25 dB bound was looser than the decoder needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 2 (#447)
- AC-3 / E-AC-3 packets that decode to different lengths (in the packets
read ahead, or any decoded since) have no frames_per_packet, so a
Matroska seek finds its packet by timestamp rather than by index times
the first packet's length.
- E-AC-3 syncframes of fewer than six blocks may reuse exponents from any
syncframe before them: once one is seen, a seek decodes from the start
of the stream instead of 1536 frames before the target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 3 (#447)
- Matroska E-AC-3: the first seek reads every packet's syncframe headers (only the headers,
header stripping undone) and places packets by the frames their primary syncframes hold.
Packets that started to differ in length after the eight read ahead were placed as the first
packet's length times their index.
- A packet whose later syncframe fails no longer sets the length lost packets are concealed at,
nor marks the packets' lengths as differing: it decoded only in part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge main; E-AC-3 in Matroska: read packet headers only up to the seek target, and count a packet of dependent syncframes as zero frames
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
* craft-audio: Opus decoder
FilmCraft's `filmcraft-opus` decoder as `craft_audio::opus` behind
`opus-decode`: SILK, hybrid and CELT, every frame size, PLC and FEC,
multistream (`Decoder`) and single stream (`StreamDecoder`), 8-48 kHz
output.
- `TrackDecoder` decodes Opus; mono and stereo without an `OpusHead`
(RTP, MPEG-TS) get a default one. Lost packets use Opus's own
concealment.
- The native read path takes Ogg Opus (pre-skip and end trimming per
RFC 7845), and Opus in Matroska/WebM and MP4, where packet starts
after a seek come from the packets' own lengths.
The RFC 8251 conformance test runs all 36 vectors.
`scripts/fetch-opus-vectors.sh` downloads them (SHA-256 checked) and
the CI test job caches them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 1 (#445)
- CELT concealment keeps its pitch in its own field. It reused postfilter_period_old, which the
next good frame's post-filter cross-fades from.
- Matroska Opus seeks: a failed packet read no longer caches a wrong start; a one-byte packet
is read as one byte.
- A malformed OpusHead in Ogg is reported as malformed instead of falling back.
- The libopus oracle tests skip through the testkit, so CRAFT_MEDIA_REQUIRE_ORACLES fails them.
- Decoder::reset documents that it re-arms pre-skip trimming.
- fetch-opus-vectors.sh retries its size probe.
- CI: the vectors are fetched after ffmpeg is installed, and apt's timeout runs as root so a
stalled apt-get is killed and frees the dpkg lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* craft-audio: review fixes, round 2 (#445)
- CELT: the noise-based concealment sets skip_plc, as libopus does, so
pitch-based concealment waits for two good frames in a row instead of
extending history that holds noise.
- SILK: decode_only_middle is per call, as in libopus. A lost or mono
frame no longer reuses the mid-only flag from an earlier frame.
- A damaged Opus packet is concealed at its own length (from its TOC and
framing) rather than the previous packet's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
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.
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.
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.
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.
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>
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.
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>
FilmCraft's FLAC encoder behind `flac-encode`, kept over SoundCraft's:
linear prediction, stereo decorrelation and the STREAMINFO MD5 give
0.1-37% smaller files on 12 test signals, and the output is bit-exact
through `flac` and ffmpeg. It is 3-5x slower than SoundCraft's, still
about 56x real time.
`lossless-encode` is now `pcm-encode` plus `flac-encode`, and
`EncodeFormat::Flac` writes it through `encode` and `FileEncoder`.
With only one of the two features, the other's formats are refused as
`Unsupported`.
The tests that use FLAC files as decode fixtures (truncation,
corruption, metadata bounds, zero-frame files, container preflight)
come with it, as do the FLAC oracle tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* 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>