Commit Graph
167 Commits
Author SHA1 Message Date
Dylan M. Taylor c7fb7544c2 craft-stencilcraft-format: add native JSON schema 2026-11-13 02:23:04 +00:00
skylar taylor-barrickandskylartaylor 86ed498b49 Prepare v0.1.0 with release notes, versioning policy and pinned adoption
* Prepare the first coordinated craft-libs release

---------

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

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

Closes #1118

---------

Co-authored-by: David Mann <daaavid.mann@gmail.com>
2026-11-12 10:05:07 -05:00
Dylan M. Taylorandjonathanwxh-cell 1c5c8a4e14 Add craft-pkcs7: DER, X.509, CMS SignedData, PKCS #12, RFC 3161 and revocation from PdfCraft
* Add craft-pkcs7: DER, X.509, CMS SignedData, PKCS #12, RFC 3161 and revocation from PdfCraft

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

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

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

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

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

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

---------

Co-authored-by: jonathanwxh-cell <jonathanwxh@gmail.com>
2026-11-12 09:45:31 -05:00
Dylan M. Taylor 373c242ad7 Add craft-openpgp: OpenPGP signature checking and key reading from MailCraft
Moved from MailCraft's crates/openpgp at a44a45013d1c. Built on rPGP 0.21.0 as
there. The one code change: the Unicode format-character test the From address
check used from mailcraft-names is now a private function here, with a test.
2026-11-12 09:10:41 -05:00
Dylan M. Taylor d516b44691 Add craft-doc-binary: WordCraft's .doc reader on craft-wordmodel and craft-cfb
Add craft-doc-binary: WordCraft's .doc reader on craft-wordmodel and craft-cfb

* craft-doc-binary: WordCraft's crates/docbin as it is at wordcraft 07f97737

The source, unchanged, so the next commit shows only what the move changes.
It has no manifest yet, so it is not built.

* craft-doc-binary: on craft-wordmodel, craft-cfb and craft-codepage

- The compound file is read with craft-cfb (bounded directory, streams refused
  above 1 GiB before anything is allocated), in place of the cfb crate and
  WordCraft's cfb_guard precheck; an oversized stream is DocbinError::Limit.
- 8-bit text uses craft-codepage in place of wordcraft_doc::encoding.
- wordcraft-doc is craft-wordmodel; wordcraft-geom's helpers are its geom.

The tests keep building their files with the cfb crate (a dev-dependency), so
craft-cfb's reader is checked against another implementation's writer. The
repeating-DIFAT regression is craft-cfb's own; the guard asserts in the
valid-files test go with the guard.
2026-11-11 22:43:11 -05:00
e9e1da3a58 craft-raw: Canon CR3 (CRX), header plans and the remaining preview branches (#1070)
* 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.

* craft-raw: Canon CR2, with sRAW and mRAW

Moved from LightCraft crates/raw (vendor/cr2.rs, vendor/cr2/sraw.rs) at 5ff567a4. The white-balance presets a
CR2 lists come back as data in ColorData::kelvin_presets (the ordering from LightCraft's kelvin_presets.rs;
the temperature and tint arithmetic stays with the developer).

* craft-raw: an sRAW preview larger than the file is refused before its buffer is reserved

Ported from storytold/lightcraft#3910 (cr2::sraw::read_preview).

* WIP craft-raw: Nikon NEF/NRW (does not compile yet: limits threading, develop() calls in tests)

* craft-raw: Nikon NEF/NRW, with PhotoCraft's maker-note compression routing and 12-bit D4 white level

* craft-raw: Panasonic RW2 / RWL / RAW, every raw format

* craft-raw: RW2 format 8 refuses strips stating fewer bits than samples

Ported from storytold/lightcraft#3910.

* craft-raw: RW2 format 8 refuses samples above the declared bit depth

Ported from storytold/photocraft#11838.

* craft-raw: the RW2 format 8 robustness seed carries a valid stream

Its random payload decoded only while out-of-depth samples were clamped; mutations now start from a file that decodes.

* craft-raw: Fujifilm RAF, uncompressed and compressed, with lens corrections

* craft-raw: compressed RAF stripes cap their capacity hint at the stripe's bits

Ported from storytold/lightcraft#3910.

* craft-raw: test RAF orientation and camera from the embedded JPEG's Exif

* WIP craft-raw: Olympus ORF

* craft-raw: RAF embedded preview, and its range from the header alone (bounded_preview)

* craft-raw: Olympus ORF, the uncompressed layouts, with its maker-note preview and WB_GLevel

* WIP craft-raw: Sony ARW

* craft-raw: tiled lossless ARW decodes hold one group of tiles at a time

Ported from storytold/lightcraft#3910.

* craft-raw: dark 12-bit uncompressed ARWs keep Sony's 14-bit levels, and the DNG writer's depth holds the white level

Ported from storytold/lightcraft#3429.

* craft-raw: Sony ARW family, docs, seeds and the derivation records

* WIP craft-raw: Canon CR3

* craft-raw: Canon CR3 (CRX) through craft-metadata's container, header plans, CR3 previews and the coverage records

---------

Co-authored-by: radialmonster <radial@gmail.com>
Co-authored-by: wnanbei <wnanbei@gmail.com>
Co-authored-by: lownlyrider <lownlyrider@gmail.com>
2026-11-11 22:01:07 -05:00
Dylan M. Taylorandbflatastic 428541a512 craft-sf2: SoundFont 2 reader from LoopCraft (#1076)
The SoundFont 2 reader of LoopCraft's loopcraft-audiofile crate (sf2.rs at
storytold/loopcraft@0d150c1), over a neutral model of its own: the sample
pool, presets and resolved key/velocity regions with tuning, loop points,
pan, gain and volume envelope. The fixture writer its tests use is the
test-support feature, so readers in other crates can test with real files.

Co-authored-by: bflatastic <bombayapirat@gmail.com>
2026-11-11 21:58:16 -05:00
Dylan M. Taylorandshuff57 49dfd6ddc0 craft-odf: OpenDocument Text documents on craft-flow (feature text), from WordCraft (stacked) (#1064)
* Add craft-wordmodel: WordCraft's document model without its editing code

WordCraft's crates/doc (wordcraft-doc, at 07f97737) moved as it is, so the DOCX,
RTF, ODT and other text-format crates can read into and write from one model.
Module paths, type names and serde attributes are unchanged: an app that
re-exports the crate keeps its native file format.

Left in the app: range editing (edit.rs), IME text and equation editing. Not
model, and already in craft-libs: code pages (craft-rtf), the ZIP entry-count
check (craft-opc), the compound-file guard (craft-cfb). The paragraph walks
moved to walk.rs and the unit helpers of wordcraft-geom to geom.rs.

* craft-wordmodel: math_tree, the arguments of an equation node and a walk over its runs

The DOCX reader and writer walk an equation's runs. The walk and the child
accessors it uses were in WordCraft's equation editor (math_edit.rs), which
stays in the app; they are structure, not editing.

* craft-mime: detect, for bytes that name no character set

is_text, decode (BOM, unmarked UTF-16, UTF-8, else the legacy encoding
the bytes look like), legacy and legacy_label, behind a detect feature
that brings in chardetng. Moved from WordCraft's crates/formats/src/txt.rs
at 06c2ae6f with its text-sniffing tests. Also charset::is_known, so a
caller can tell a label decode would read as UTF-8 from one it knows.

* craft-wordmodel: keep the document operations (edit.rs) with the model

The DOCX and RTF readers use them: alternative-format chunks are merged into
a document with adopt_blocks, and a reader formats ranges with format_range
and inserts objects. They operate on the document, not on a view, so they
belong beside it. The tests that drove them come back with them; the
paragraph walks stay in walk.rs and edit re-exports them.

base64 0.23, as WordCraft and craft-offcrypto use.

* craft-codepage: one table per legacy code page, for craft-rtf, craft-dxf and craft-mime

craft-rtf and craft-dxf each carried tables for DOS 437, 737, 850, 852 and
857 (and craft-rtf one for 866). They are now one set in craft-codepage,
which takes craft-rtf's codepage module (Windows and Mac tables, OEM glyphs,
font charsets) and craft-dxf's DOS tables. craft_rtf::codepage re-exports it
unchanged; craft-dxf keeps its $DWGCODEPAGE names and Decoder and looks the
DOS tables up there.

Every byte of every code page either crate decodes is unchanged: the
public decoders of both crates were dumped over all 256 bytes of each page
(and the East Asian pairs) before and after, and the dumps are identical.

craft-mime now reads the DOS pages by their IANA names (IBM437, cp850,
ibm-852, 857...), which it read as UTF-8 before.

* Add craft-math: the equation tree with OMML, MathML, LaTeX and UnicodeMath

The math tree, LaTeX, linear-format, symbol and tree-walk modules come
from craft-wordmodel (storytold/craft-libs#963, WordCraft's crates/doc at
07f97737) unchanged, with the run colour (Rgb) and the symbol-font tables
they use. MathML is WordCraft's crates/formats/src/mathml.rs and OMML is
WordCraft's crates/docx/src/read/math.rs and write/math.rs (06c2ae6f /
07f97737), moved onto craft-xml. The HTML-specific MathML tests stay in
WordCraft; the rest moved with the code.

mathml::parse and omml::parse/read_str/write_str are new: string entry
points for callers that have no tree of their own.

* craft-math: read MathLive's and other spellings of LaTeX math commands

\abs{...} (an absolute value), \lvert \rvert \lVert \rVert, \nparallel,
\backprime, \infinity, \cross, \differentialD, \exponentialE and
\imaginaryI (MathLive's d, upright e and i), \emph, \textsf, \hbox,
\mathnormal and \underleftarrow / \underrightarrow /
\underleftrightarrow were unknown and read as literal text. They are read
as equaSHion's typed-input parser reads them (storytold/craft-libs#29).

* craft-math: equaSHion's equation corpus as test data

corpus/equations.tsv, corpus/latex.tsv and the OMML samples of
storytold/craft-libs#29 (7a6ef6b9), unmodified.

* craft-math: corpus round trips, GridCraft's OMML cases, entry points and limits tested; README

The corpus equations go through OMML, MathML and LaTeX and back; the OMML
samples are written back as kept and from the tree. GridCraft's OMML to
LaTeX test cases (crates/xlsx/src/omml.rs) give LaTeX that reads back the
same through the tree. omml::parse and read_str are tested against other
prefixes, the strict namespace, kept OMML, junk, the element cap and deep
nesting.

* Add craft-flow: the flow model the text formats map to, on craft-wordmodel

WordCraft's crates/formats/src/model (07f97737) moved as it is: Flow,
FBlock, Para, Inline, Fmt, tables and cells, pictures, to_doc and
from_doc, picture sniffing, colours, base64 and the Dropped report. Only
the paths changed (wordcraft_doc is craft_wordmodel); the report helpers
are public so the format crates can call them; the image re-encoding
helper stays with WordCraft's RTF writer.

* Add craft-markdown: Markdown read and written, on craft-flow

WordCraft's crates/formats/src/markdown (07f97737) with its 35 tests,
moved onto craft-flow and craft-wordmodel. Character references come from
craft-html. The table cell budget lives in craft-flow, which names it in
its warning. Two test lines that wrote plain text through WordCraft's txt
module stay in WordCraft.

* WIP: craft-latex sources from WordCraft (unmodified)

* Add craft-latex: LaTeX documents read and written, on craft-flow

WordCraft's crates/formats/src/latex (07f97737) with its 21 tests, moved
onto craft-flow and craft-wordmodel; bytes are decoded by craft-mime's
detect. The LaTeX parts of WordCraft's cross-format tests (nesting,
column specs, labels, no TeX commands from the document, the fuzz and
round-trip properties) come along as tests/hostile.rs. The ligature test
builds its three-font paragraph as a flow instead of reading HTML.

* craft-math: latex_text, LaTeX math and the linear format converted as text

WordCraft's crates/formats/src/latex/math.rs (07f97737): the converter its
LaTeX document reader and writer use, which keeps the source's spacing.
It moves here so craft-math is the one place that converts LaTeX math;
craft-latex calls it, and its output does not change. Its tests (from
WordCraft's latex tests and cross-format tests) come along.

* craft-math: latex_text's group finders are public, for craft-latex's length parser

* craft-latex: math through craft_math::latex_text; label timing test by ratio

The document math converter moved to craft-math (#1040) unchanged; this
crate calls it there (and its length parser uses its group finders), so
export output is the same. The label test compared one read with a
ten-second wall clock and failed on a loaded machine; it now compares the
fastest of three reads of 25,000 and 100,000 labels, which a linear read
keeps near 4x and a quadratic one near 16x.

* Cargo.lock after the merge

* WIP: WordCraft's HTML document reader and writer, unmodified (crates/formats/src/html at 07f97737)

* craft-html: HTML documents on craft-flow, behind a flow feature

WordCraft's crates/formats/src/html (07f97737) as craft_html::doc: the
tag-soup builder, CSS subset, ruby and MathML reading, page decoding and
the writer, with its 44 tests. It reads tokens from this crate's
tokenizer through a small adapter (decoded text and attributes, names in
lower case) instead of its own lexer, and its character references are
this crate's. Lexer::set_pos is new: the ruby reader hands a token back.
The HTML parts of WordCraft's MathML and cross-format tests come along as
tests/doc.rs, the linear-time check as a ratio.

* Cargo.lock after the merge

* WIP: WordCraft's ODT reader and writer, unmodified (crates/formats/src/odt at 07f97737)

* craft-odf: OpenDocument Text documents on craft-flow, behind a text feature

WordCraft's crates/formats/src/odt (07f97737) as craft_odf::text, with
its 467 tests. Moved onto this crate's own layers: packages are read with
Package and written with PackageWriter, password protection is crypt
and flat files are flat (the copies in the WordCraft module are gone),
and its XML event stream is craft-xml's pull reader instead of quick-xml.
Paths: craft_flow, craft_wordmodel, craft_math::mathml, craft_metafile.
The date helper it borrowed from the spreadsheet reader came along.
Tests read packages through Package instead of zip.

* craft-math: operator names take their LaTeX scripts directly again (restores #1019)

#1019 (22161d2d) wrote \sin^2\theta as \sin ^{2}{\theta } so it reads back as
the same tree. The squash merge of #1038, whose branch predated it, put
math_latex.rs and the corpus test back as they were before. This applies
#1019's change again, unchanged, with its test.

* Cargo.lock: craft-html's flow dependencies

---------

Co-authored-by: shuff57 <62350898+shuff57@users.noreply.github.com>
2026-11-11 21:39:13 -05:00
Dylan M. Taylorandshuff57 db616480e2 craft-html: HTML documents on craft-flow (feature flow), from WordCraft (#1050)
* Add craft-wordmodel: WordCraft's document model without its editing code

WordCraft's crates/doc (wordcraft-doc, at 07f97737) moved as it is, so the DOCX,
RTF, ODT and other text-format crates can read into and write from one model.
Module paths, type names and serde attributes are unchanged: an app that
re-exports the crate keeps its native file format.

Left in the app: range editing (edit.rs), IME text and equation editing. Not
model, and already in craft-libs: code pages (craft-rtf), the ZIP entry-count
check (craft-opc), the compound-file guard (craft-cfb). The paragraph walks
moved to walk.rs and the unit helpers of wordcraft-geom to geom.rs.

* craft-wordmodel: math_tree, the arguments of an equation node and a walk over its runs

The DOCX reader and writer walk an equation's runs. The walk and the child
accessors it uses were in WordCraft's equation editor (math_edit.rs), which
stays in the app; they are structure, not editing.

* craft-mime: detect, for bytes that name no character set

is_text, decode (BOM, unmarked UTF-16, UTF-8, else the legacy encoding
the bytes look like), legacy and legacy_label, behind a detect feature
that brings in chardetng. Moved from WordCraft's crates/formats/src/txt.rs
at 06c2ae6f with its text-sniffing tests. Also charset::is_known, so a
caller can tell a label decode would read as UTF-8 from one it knows.

* craft-wordmodel: keep the document operations (edit.rs) with the model

The DOCX and RTF readers use them: alternative-format chunks are merged into
a document with adopt_blocks, and a reader formats ranges with format_range
and inserts objects. They operate on the document, not on a view, so they
belong beside it. The tests that drove them come back with them; the
paragraph walks stay in walk.rs and edit re-exports them.

base64 0.23, as WordCraft and craft-offcrypto use.

* craft-codepage: one table per legacy code page, for craft-rtf, craft-dxf and craft-mime

craft-rtf and craft-dxf each carried tables for DOS 437, 737, 850, 852 and
857 (and craft-rtf one for 866). They are now one set in craft-codepage,
which takes craft-rtf's codepage module (Windows and Mac tables, OEM glyphs,
font charsets) and craft-dxf's DOS tables. craft_rtf::codepage re-exports it
unchanged; craft-dxf keeps its $DWGCODEPAGE names and Decoder and looks the
DOS tables up there.

Every byte of every code page either crate decodes is unchanged: the
public decoders of both crates were dumped over all 256 bytes of each page
(and the East Asian pairs) before and after, and the dumps are identical.

craft-mime now reads the DOS pages by their IANA names (IBM437, cp850,
ibm-852, 857...), which it read as UTF-8 before.

* Add craft-math: the equation tree with OMML, MathML, LaTeX and UnicodeMath

The math tree, LaTeX, linear-format, symbol and tree-walk modules come
from craft-wordmodel (storytold/craft-libs#963, WordCraft's crates/doc at
07f97737) unchanged, with the run colour (Rgb) and the symbol-font tables
they use. MathML is WordCraft's crates/formats/src/mathml.rs and OMML is
WordCraft's crates/docx/src/read/math.rs and write/math.rs (06c2ae6f /
07f97737), moved onto craft-xml. The HTML-specific MathML tests stay in
WordCraft; the rest moved with the code.

mathml::parse and omml::parse/read_str/write_str are new: string entry
points for callers that have no tree of their own.

* craft-math: read MathLive's and other spellings of LaTeX math commands

\abs{...} (an absolute value), \lvert \rvert \lVert \rVert, \nparallel,
\backprime, \infinity, \cross, \differentialD, \exponentialE and
\imaginaryI (MathLive's d, upright e and i), \emph, \textsf, \hbox,
\mathnormal and \underleftarrow / \underrightarrow /
\underleftrightarrow were unknown and read as literal text. They are read
as equaSHion's typed-input parser reads them (storytold/craft-libs#29).

* craft-math: equaSHion's equation corpus as test data

corpus/equations.tsv, corpus/latex.tsv and the OMML samples of
storytold/craft-libs#29 (7a6ef6b9), unmodified.

* craft-math: corpus round trips, GridCraft's OMML cases, entry points and limits tested; README

The corpus equations go through OMML, MathML and LaTeX and back; the OMML
samples are written back as kept and from the tree. GridCraft's OMML to
LaTeX test cases (crates/xlsx/src/omml.rs) give LaTeX that reads back the
same through the tree. omml::parse and read_str are tested against other
prefixes, the strict namespace, kept OMML, junk, the element cap and deep
nesting.

* Add craft-flow: the flow model the text formats map to, on craft-wordmodel

WordCraft's crates/formats/src/model (07f97737) moved as it is: Flow,
FBlock, Para, Inline, Fmt, tables and cells, pictures, to_doc and
from_doc, picture sniffing, colours, base64 and the Dropped report. Only
the paths changed (wordcraft_doc is craft_wordmodel); the report helpers
are public so the format crates can call them; the image re-encoding
helper stays with WordCraft's RTF writer.

* Add craft-markdown: Markdown read and written, on craft-flow

WordCraft's crates/formats/src/markdown (07f97737) with its 35 tests,
moved onto craft-flow and craft-wordmodel. Character references come from
craft-html. The table cell budget lives in craft-flow, which names it in
its warning. Two test lines that wrote plain text through WordCraft's txt
module stay in WordCraft.

* WIP: craft-latex sources from WordCraft (unmodified)

* Add craft-latex: LaTeX documents read and written, on craft-flow

WordCraft's crates/formats/src/latex (07f97737) with its 21 tests, moved
onto craft-flow and craft-wordmodel; bytes are decoded by craft-mime's
detect. The LaTeX parts of WordCraft's cross-format tests (nesting,
column specs, labels, no TeX commands from the document, the fuzz and
round-trip properties) come along as tests/hostile.rs. The ligature test
builds its three-font paragraph as a flow instead of reading HTML.

* craft-math: latex_text, LaTeX math and the linear format converted as text

WordCraft's crates/formats/src/latex/math.rs (07f97737): the converter its
LaTeX document reader and writer use, which keeps the source's spacing.
It moves here so craft-math is the one place that converts LaTeX math;
craft-latex calls it, and its output does not change. Its tests (from
WordCraft's latex tests and cross-format tests) come along.

* craft-math: latex_text's group finders are public, for craft-latex's length parser

* craft-latex: math through craft_math::latex_text; label timing test by ratio

The document math converter moved to craft-math (#1040) unchanged; this
crate calls it there (and its length parser uses its group finders), so
export output is the same. The label test compared one read with a
ten-second wall clock and failed on a loaded machine; it now compares the
fastest of three reads of 25,000 and 100,000 labels, which a linear read
keeps near 4x and a quadratic one near 16x.

* Cargo.lock after the merge

* WIP: WordCraft's HTML document reader and writer, unmodified (crates/formats/src/html at 07f97737)

* craft-html: HTML documents on craft-flow, behind a flow feature

WordCraft's crates/formats/src/html (07f97737) as craft_html::doc: the
tag-soup builder, CSS subset, ruby and MathML reading, page decoding and
the writer, with its 44 tests. It reads tokens from this crate's
tokenizer through a small adapter (decoded text and attributes, names in
lower case) instead of its own lexer, and its character references are
this crate's. Lexer::set_pos is new: the ruby reader hands a token back.
The HTML parts of WordCraft's MathML and cross-format tests come along as
tests/doc.rs, the linear-time check as a ratio.

* craft-math: operator names take their LaTeX scripts directly again (restores #1019)

#1019 (22161d2d) wrote \sin^2\theta as \sin ^{2}{\theta } so it reads back as
the same tree. The squash merge of #1038, whose branch predated it, put
math_latex.rs and the corpus test back as they were before. This applies
#1019's change again, unchanged, with its test.

* Cargo.lock: craft-html's flow dependencies

---------

Co-authored-by: shuff57 <62350898+shuff57@users.noreply.github.com>
2026-11-11 21:33:47 -05:00
Dylan M. TaylorandJordi Altayó 5538d8a0d7 Add craft-pptx: the PresentationML model (from DeckCraft) (#1044)
* 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.

* drawingml: the model types for fills, lines, text, tables, charts and diagrams

Moved from DeckCraft (60f91f78) crates/model/src: style.rs, text.rs,
table.rs, chart.rs, numfmt.rs and trendline.rs, plus Diagram, DiagramNode
and the MediaId and SlideId newtypes from lib.rs.

The files are unchanged except that deckcraft_color:: is crate::color:: and
Fill and TableBorder allow clippy::large_enum_variant. Their 60 tests come
with them. Readers and writers follow.

* drawingml: the theme model (colour, font and format schemes)

Moved from DeckCraft crates/model/src/theme.rs at 60f91f78: FontSet, ScriptFont, FontScheme,
FormatScheme, Theme and default_format. The app's theme gallery, its variants and its own default
theme stay in the app. Theme::default() is a plain theme built in place, so nothing here depends
on a gallery.

* drawingml: readers and writers for fills, lines, text, charts, themes, table styles and diagrams

Moved from DeckCraft crates/pptx/src at 60f91f78: read/dml.rs, write/dml.rs, read/chart.rs,
read/chartex.rs, write/chart.rs, read/theme.rs, write/theme.rs, tables.rs and dgm.rs, onto
craft-xml. Package lookups (Imp, Part, Exp, Out) are the caller's part::Source and part::Sink.
The chart frame budget and kept-part capture stay with the presentation reader.

* drawingml: what WordCraft and GridCraft read that DeckCraft's copy did not

Every ST_PresetColorVal name has its colour, in any case and with the dk/lt/med abbreviations;
a sysClr without lastClr takes the usual value of its name; a Strict percentage (50%) reads
anywhere a percentage does; a chart series keeps its c:f formulas and the writer uses them.

* craft-drawingml: extra named theme colours and explicit tint rows

Ported from the format part of storytold/deckcraft#1746 by @jagjordi: ColorScheme::{extra, tints,
computed_tints}, ExtraColor, GalleryVariant, variants_of, and the a:clrScheme extension that
keeps them in a file.

* drawingml xmlx: copy only used, bounded namespace declarations onto kept fragments and mc branches

From storytold/deckcraft#1740 (988a4920), which landed after the extraction commit 60f91f78.

* Add craft-pptx: the PresentationML model (from DeckCraft)

Presentation, masters, layouts, slides, shapes, placeholders, media,
comments, sections, show settings, properties, embedded fonts, kept parts
and drawing guides from DeckCraft's crates/model/src/lib.rs, and
transitions and animations from anim.rs. The DrawingML types come from
craft-drawingml. The placeholder matching from DeckCraft's resolve.rs
moves as placeholder::parents, without the rest of resolve.

Presentation::default() is now an empty 16:9 deck with no masters; the
starting deck stays with the application.

---------

Co-authored-by: Jordi Altayó <jordiag@kth.se>
2026-11-11 21:04:26 -05:00
Dylan M. Taylorandshuff57 bdf06f1029 Add craft-latex: LaTeX documents read and written, on craft-flow (stacked) (#1038)
* Add craft-wordmodel: WordCraft's document model without its editing code

WordCraft's crates/doc (wordcraft-doc, at 07f97737) moved as it is, so the DOCX,
RTF, ODT and other text-format crates can read into and write from one model.
Module paths, type names and serde attributes are unchanged: an app that
re-exports the crate keeps its native file format.

Left in the app: range editing (edit.rs), IME text and equation editing. Not
model, and already in craft-libs: code pages (craft-rtf), the ZIP entry-count
check (craft-opc), the compound-file guard (craft-cfb). The paragraph walks
moved to walk.rs and the unit helpers of wordcraft-geom to geom.rs.

* craft-wordmodel: math_tree, the arguments of an equation node and a walk over its runs

The DOCX reader and writer walk an equation's runs. The walk and the child
accessors it uses were in WordCraft's equation editor (math_edit.rs), which
stays in the app; they are structure, not editing.

* craft-mime: detect, for bytes that name no character set

is_text, decode (BOM, unmarked UTF-16, UTF-8, else the legacy encoding
the bytes look like), legacy and legacy_label, behind a detect feature
that brings in chardetng. Moved from WordCraft's crates/formats/src/txt.rs
at 06c2ae6f with its text-sniffing tests. Also charset::is_known, so a
caller can tell a label decode would read as UTF-8 from one it knows.

* craft-wordmodel: keep the document operations (edit.rs) with the model

The DOCX and RTF readers use them: alternative-format chunks are merged into
a document with adopt_blocks, and a reader formats ranges with format_range
and inserts objects. They operate on the document, not on a view, so they
belong beside it. The tests that drove them come back with them; the
paragraph walks stay in walk.rs and edit re-exports them.

base64 0.23, as WordCraft and craft-offcrypto use.

* Add craft-math: the equation tree with OMML, MathML, LaTeX and UnicodeMath

The math tree, LaTeX, linear-format, symbol and tree-walk modules come
from craft-wordmodel (storytold/craft-libs#963, WordCraft's crates/doc at
07f97737) unchanged, with the run colour (Rgb) and the symbol-font tables
they use. MathML is WordCraft's crates/formats/src/mathml.rs and OMML is
WordCraft's crates/docx/src/read/math.rs and write/math.rs (06c2ae6f /
07f97737), moved onto craft-xml. The HTML-specific MathML tests stay in
WordCraft; the rest moved with the code.

mathml::parse and omml::parse/read_str/write_str are new: string entry
points for callers that have no tree of their own.

* craft-math: read MathLive's and other spellings of LaTeX math commands

\abs{...} (an absolute value), \lvert \rvert \lVert \rVert, \nparallel,
\backprime, \infinity, \cross, \differentialD, \exponentialE and
\imaginaryI (MathLive's d, upright e and i), \emph, \textsf, \hbox,
\mathnormal and \underleftarrow / \underrightarrow /
\underleftrightarrow were unknown and read as literal text. They are read
as equaSHion's typed-input parser reads them (storytold/craft-libs#29).

* craft-math: equaSHion's equation corpus as test data

corpus/equations.tsv, corpus/latex.tsv and the OMML samples of
storytold/craft-libs#29 (7a6ef6b9), unmodified.

* craft-math: corpus round trips, GridCraft's OMML cases, entry points and limits tested; README

The corpus equations go through OMML, MathML and LaTeX and back; the OMML
samples are written back as kept and from the tree. GridCraft's OMML to
LaTeX test cases (crates/xlsx/src/omml.rs) give LaTeX that reads back the
same through the tree. omml::parse and read_str are tested against other
prefixes, the strict namespace, kept OMML, junk, the element cap and deep
nesting.

* Add craft-flow: the flow model the text formats map to, on craft-wordmodel

WordCraft's crates/formats/src/model (07f97737) moved as it is: Flow,
FBlock, Para, Inline, Fmt, tables and cells, pictures, to_doc and
from_doc, picture sniffing, colours, base64 and the Dropped report. Only
the paths changed (wordcraft_doc is craft_wordmodel); the report helpers
are public so the format crates can call them; the image re-encoding
helper stays with WordCraft's RTF writer.

* Add craft-markdown: Markdown read and written, on craft-flow

WordCraft's crates/formats/src/markdown (07f97737) with its 35 tests,
moved onto craft-flow and craft-wordmodel. Character references come from
craft-html. The table cell budget lives in craft-flow, which names it in
its warning. Two test lines that wrote plain text through WordCraft's txt
module stay in WordCraft.

* WIP: craft-latex sources from WordCraft (unmodified)

* Add craft-latex: LaTeX documents read and written, on craft-flow

WordCraft's crates/formats/src/latex (07f97737) with its 21 tests, moved
onto craft-flow and craft-wordmodel; bytes are decoded by craft-mime's
detect. The LaTeX parts of WordCraft's cross-format tests (nesting,
column specs, labels, no TeX commands from the document, the fuzz and
round-trip properties) come along as tests/hostile.rs. The ligature test
builds its three-font paragraph as a flow instead of reading HTML.

* craft-math: latex_text, LaTeX math and the linear format converted as text

WordCraft's crates/formats/src/latex/math.rs (07f97737): the converter its
LaTeX document reader and writer use, which keeps the source's spacing.
It moves here so craft-math is the one place that converts LaTeX math;
craft-latex calls it, and its output does not change. Its tests (from
WordCraft's latex tests and cross-format tests) come along.

* craft-math: latex_text's group finders are public, for craft-latex's length parser

* craft-latex: math through craft_math::latex_text; label timing test by ratio

The document math converter moved to craft-math (#1040) unchanged; this
crate calls it there (and its length parser uses its group finders), so
export output is the same. The label test compared one read with a
ten-second wall clock and failed on a loaded machine; it now compares the
fastest of three reads of 25,000 and 100,000 labels, which a linear read
keeps near 4x and a quadratic one near 16x.

---------

Co-authored-by: shuff57 <62350898+shuff57@users.noreply.github.com>
2026-11-11 20:58:36 -05:00
Dylan M. TaylorandMartin Ball d197b1e72f Add craft-dav: WebDAV multistatus reader from MailCraft (#975)
MailCraft crates/dav/src/xml.rs at a44a450, re-based onto craft-xml: a
multistatus read fail-closed into a bounded tree with fixed prefixes for
the DAV, CalDAV, CardDAV and related namespaces, and the properties of a
response's successful propstats. Its tests come along.

Co-authored-by: Martin Ball <98639962+benandez@users.noreply.github.com>
2026-11-11 20:51:12 -05:00
a7a00c7b0a craft-raw: Olympus ORF (uncompressed) (#1032)
* 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.

* craft-raw: Canon CR2, with sRAW and mRAW

Moved from LightCraft crates/raw (vendor/cr2.rs, vendor/cr2/sraw.rs) at 5ff567a4. The white-balance presets a
CR2 lists come back as data in ColorData::kelvin_presets (the ordering from LightCraft's kelvin_presets.rs;
the temperature and tint arithmetic stays with the developer).

* craft-raw: an sRAW preview larger than the file is refused before its buffer is reserved

Ported from storytold/lightcraft#3910 (cr2::sraw::read_preview).

* WIP craft-raw: Nikon NEF/NRW (does not compile yet: limits threading, develop() calls in tests)

* craft-raw: Nikon NEF/NRW, with PhotoCraft's maker-note compression routing and 12-bit D4 white level

* craft-raw: Panasonic RW2 / RWL / RAW, every raw format

* craft-raw: RW2 format 8 refuses strips stating fewer bits than samples

Ported from storytold/lightcraft#3910.

* craft-raw: RW2 format 8 refuses samples above the declared bit depth

Ported from storytold/photocraft#11838.

* craft-raw: the RW2 format 8 robustness seed carries a valid stream

Its random payload decoded only while out-of-depth samples were clamped; mutations now start from a file that decodes.

* craft-raw: Fujifilm RAF, uncompressed and compressed, with lens corrections

* craft-raw: compressed RAF stripes cap their capacity hint at the stripe's bits

Ported from storytold/lightcraft#3910.

* craft-raw: test RAF orientation and camera from the embedded JPEG's Exif

* WIP craft-raw: Olympus ORF

* craft-raw: RAF embedded preview, and its range from the header alone (bounded_preview)

* craft-raw: Olympus ORF, the uncompressed layouts, with its maker-note preview and WB_GLevel

---------

Co-authored-by: radialmonster <radial@gmail.com>
Co-authored-by: wnanbei <wnanbei@gmail.com>
2026-11-11 20:48:30 -05:00
Dylan M. Taylor 1e3d3968c1 Add craft-markdown: Markdown read and written, on craft-flow (stacked on #1025) (#1030)
* Add craft-wordmodel: WordCraft's document model without its editing code

WordCraft's crates/doc (wordcraft-doc, at 07f97737) moved as it is, so the DOCX,
RTF, ODT and other text-format crates can read into and write from one model.
Module paths, type names and serde attributes are unchanged: an app that
re-exports the crate keeps its native file format.

Left in the app: range editing (edit.rs), IME text and equation editing. Not
model, and already in craft-libs: code pages (craft-rtf), the ZIP entry-count
check (craft-opc), the compound-file guard (craft-cfb). The paragraph walks
moved to walk.rs and the unit helpers of wordcraft-geom to geom.rs.

* craft-wordmodel: math_tree, the arguments of an equation node and a walk over its runs

The DOCX reader and writer walk an equation's runs. The walk and the child
accessors it uses were in WordCraft's equation editor (math_edit.rs), which
stays in the app; they are structure, not editing.

* craft-wordmodel: keep the document operations (edit.rs) with the model

The DOCX and RTF readers use them: alternative-format chunks are merged into
a document with adopt_blocks, and a reader formats ranges with format_range
and inserts objects. They operate on the document, not on a view, so they
belong beside it. The tests that drove them come back with them; the
paragraph walks stay in walk.rs and edit re-exports them.

base64 0.23, as WordCraft and craft-offcrypto use.

* Add craft-flow: the flow model the text formats map to, on craft-wordmodel

WordCraft's crates/formats/src/model (07f97737) moved as it is: Flow,
FBlock, Para, Inline, Fmt, tables and cells, pictures, to_doc and
from_doc, picture sniffing, colours, base64 and the Dropped report. Only
the paths changed (wordcraft_doc is craft_wordmodel); the report helpers
are public so the format crates can call them; the image re-encoding
helper stays with WordCraft's RTF writer.

* Add craft-markdown: Markdown read and written, on craft-flow

WordCraft's crates/formats/src/markdown (07f97737) with its 35 tests,
moved onto craft-flow and craft-wordmodel. Character references come from
craft-html. The table cell budget lives in craft-flow, which names it in
its warning. Two test lines that wrote plain text through WordCraft's txt
module stay in WordCraft.
2026-11-11 20:46:15 -05:00
Dylan M. Taylor 12917a6cf3 craft-stream-codecs: bounded JBIG2 decoding (decode_jbig2_within), on #886 (#1026)
* hayro-jbig2: bound a stream's total decode work and what a page retains; no i32 overflow

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

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

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

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

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

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

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

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

* vendor/README.craft-reader.md: one #831 paragraph, the follow-ups after it
2026-11-11 20:41:56 -05:00
Dylan M. Taylor baf6bd58f0 Add craft-flow: the flow model the text formats map to (#1025)
* Add craft-wordmodel: WordCraft's document model without its editing code

WordCraft's crates/doc (wordcraft-doc, at 07f97737) moved as it is, so the DOCX,
RTF, ODT and other text-format crates can read into and write from one model.
Module paths, type names and serde attributes are unchanged: an app that
re-exports the crate keeps its native file format.

Left in the app: range editing (edit.rs), IME text and equation editing. Not
model, and already in craft-libs: code pages (craft-rtf), the ZIP entry-count
check (craft-opc), the compound-file guard (craft-cfb). The paragraph walks
moved to walk.rs and the unit helpers of wordcraft-geom to geom.rs.

* craft-wordmodel: math_tree, the arguments of an equation node and a walk over its runs

The DOCX reader and writer walk an equation's runs. The walk and the child
accessors it uses were in WordCraft's equation editor (math_edit.rs), which
stays in the app; they are structure, not editing.

* craft-wordmodel: keep the document operations (edit.rs) with the model

The DOCX and RTF readers use them: alternative-format chunks are merged into
a document with adopt_blocks, and a reader formats ranges with format_range
and inserts objects. They operate on the document, not on a view, so they
belong beside it. The tests that drove them come back with them; the
paragraph walks stay in walk.rs and edit re-exports them.

base64 0.23, as WordCraft and craft-offcrypto use.

* Add craft-flow: the flow model the text formats map to, on craft-wordmodel

WordCraft's crates/formats/src/model (07f97737) moved as it is: Flow,
FBlock, Para, Inline, Fmt, tables and cells, pictures, to_doc and
from_doc, picture sniffing, colours, base64 and the Dropped report. Only
the paths changed (wordcraft_doc is craft_wordmodel); the report helpers
are public so the format crates can call them; the image re-encoding
helper stays with WordCraft's RTF writer.
2026-11-11 20:41:34 -05:00
b471b84fd4 craft-raw: Fujifilm RAF (#1016)
* 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.

* craft-raw: Canon CR2, with sRAW and mRAW

Moved from LightCraft crates/raw (vendor/cr2.rs, vendor/cr2/sraw.rs) at 5ff567a4. The white-balance presets a
CR2 lists come back as data in ColorData::kelvin_presets (the ordering from LightCraft's kelvin_presets.rs;
the temperature and tint arithmetic stays with the developer).

* craft-raw: an sRAW preview larger than the file is refused before its buffer is reserved

Ported from storytold/lightcraft#3910 (cr2::sraw::read_preview).

* WIP craft-raw: Nikon NEF/NRW (does not compile yet: limits threading, develop() calls in tests)

* craft-raw: Nikon NEF/NRW, with PhotoCraft's maker-note compression routing and 12-bit D4 white level

* craft-raw: Panasonic RW2 / RWL / RAW, every raw format

* craft-raw: RW2 format 8 refuses strips stating fewer bits than samples

Ported from storytold/lightcraft#3910.

* craft-raw: RW2 format 8 refuses samples above the declared bit depth

Ported from storytold/photocraft#11838.

* craft-raw: the RW2 format 8 robustness seed carries a valid stream

Its random payload decoded only while out-of-depth samples were clamped; mutations now start from a file that decodes.

* craft-raw: Fujifilm RAF, uncompressed and compressed, with lens corrections

* craft-raw: compressed RAF stripes cap their capacity hint at the stripe's bits

Ported from storytold/lightcraft#3910.

* craft-raw: test RAF orientation and camera from the embedded JPEG's Exif

* craft-raw: RAF embedded preview, and its range from the header alone (bounded_preview)

---------

Co-authored-by: radialmonster <radial@gmail.com>
Co-authored-by: wnanbei <wnanbei@gmail.com>
2026-11-11 20:33:51 -05:00
Dylan M. Taylor e3e31693c7 Add craft-docx: WordCraft's DOCX reader and writer on craft-wordmodel, craft-opc and craft-xml (#1015)
* Add craft-wordmodel: WordCraft's document model without its editing code

WordCraft's crates/doc (wordcraft-doc, at 07f97737) moved as it is, so the DOCX,
RTF, ODT and other text-format crates can read into and write from one model.
Module paths, type names and serde attributes are unchanged: an app that
re-exports the crate keeps its native file format.

Left in the app: range editing (edit.rs), IME text and equation editing. Not
model, and already in craft-libs: code pages (craft-rtf), the ZIP entry-count
check (craft-opc), the compound-file guard (craft-cfb). The paragraph walks
moved to walk.rs and the unit helpers of wordcraft-geom to geom.rs.

* craft-wordmodel: math_tree, the arguments of an equation node and a walk over its runs

The DOCX reader and writer walk an equation's runs. The walk and the child
accessors it uses were in WordCraft's equation editor (math_edit.rs), which
stays in the app; they are structure, not editing.

* craft-wordmodel: keep the document operations (edit.rs) with the model

The DOCX and RTF readers use them: alternative-format chunks are merged into
a document with adopt_blocks, and a reader formats ranges with format_range
and inserts objects. They operate on the document, not on a view, so they
belong beside it. The tests that drove them come back with them; the
paragraph walks stay in walk.rs and edit re-exports them.

base64 0.23, as WordCraft and craft-offcrypto use.

* craft-docx: WordCraft's crates/docx as it is at wordcraft 07f97737

The source, unchanged, so the next commit shows only what the move changes.
This commit does not build on its own (it still names wordcraft-doc and
wordcraft-geom, and is not a workspace member yet).

* craft-docx: on craft-wordmodel, craft-opc, craft-xml and craft-offcrypto

- The package (zip reading, part names, relationships, content types,
  zip writing) is craft-opc's; the module keeps WordCraft's bookkeeping
  (passthrough keys, the embedded-part manifest) and its own Rel/Rels shape.
- Parts are parsed with craft-xml's canonical names (the Strict drawing
  names and mc:Choice/@Requires through its hooks), then given the tree
  shape the reader was written against.
- Encryption and Restrict Editing hashes are craft-offcrypto's; this crate
  maps its errors and the w:documentProtection attributes.
- wordcraft-doc is craft-wordmodel; wordcraft-geom's helpers are its geom.

Tests that covered what now lives in craft-opc and craft-offcrypto are left to
those crates; the DIFAT regression is craft-cfb's. Test-only zip use follows
zip 9's fallible names. The attribute-cap test times the read only.
2026-11-11 20:31:40 -05:00
Dylan M. Taylorandshuff57 85b4dabf0f Add craft-math: equations as one tree with OMML, MathML, LaTeX and UnicodeMath (#1013)
* Add craft-math: the equation tree with OMML, MathML, LaTeX and UnicodeMath

The math tree, LaTeX, linear-format, symbol and tree-walk modules come
from craft-wordmodel (storytold/craft-libs#963, WordCraft's crates/doc at
07f97737) unchanged, with the run colour (Rgb) and the symbol-font tables
they use. MathML is WordCraft's crates/formats/src/mathml.rs and OMML is
WordCraft's crates/docx/src/read/math.rs and write/math.rs (06c2ae6f /
07f97737), moved onto craft-xml. The HTML-specific MathML tests stay in
WordCraft; the rest moved with the code.

mathml::parse and omml::parse/read_str/write_str are new: string entry
points for callers that have no tree of their own.

* craft-math: read MathLive's and other spellings of LaTeX math commands

\abs{...} (an absolute value), \lvert \rvert \lVert \rVert, \nparallel,
\backprime, \infinity, \cross, \differentialD, \exponentialE and
\imaginaryI (MathLive's d, upright e and i), \emph, \textsf, \hbox,
\mathnormal and \underleftarrow / \underrightarrow /
\underleftrightarrow were unknown and read as literal text. They are read
as equaSHion's typed-input parser reads them (storytold/craft-libs#29).

* craft-math: equaSHion's equation corpus as test data

corpus/equations.tsv, corpus/latex.tsv and the OMML samples of
storytold/craft-libs#29 (7a6ef6b9), unmodified.

* craft-math: corpus round trips, GridCraft's OMML cases, entry points and limits tested; README

The corpus equations go through OMML, MathML and LaTeX and back; the OMML
samples are written back as kept and from the tree. GridCraft's OMML to
LaTeX test cases (crates/xlsx/src/omml.rs) give LaTeX that reads back the
same through the tree. omml::parse and read_str are tested against other
prefixes, the strict namespace, kept OMML, junk, the element cap and deep
nesting.

---------

Co-authored-by: shuff57 <62350898+shuff57@users.noreply.github.com>
2026-11-11 20:31:02 -05:00
0da3928eee craft-raw: Panasonic RW2, RWL and RAW (#1000)
* 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.

* craft-raw: Canon CR2, with sRAW and mRAW

Moved from LightCraft crates/raw (vendor/cr2.rs, vendor/cr2/sraw.rs) at 5ff567a4. The white-balance presets a
CR2 lists come back as data in ColorData::kelvin_presets (the ordering from LightCraft's kelvin_presets.rs;
the temperature and tint arithmetic stays with the developer).

* craft-raw: an sRAW preview larger than the file is refused before its buffer is reserved

Ported from storytold/lightcraft#3910 (cr2::sraw::read_preview).

* WIP craft-raw: Nikon NEF/NRW (does not compile yet: limits threading, develop() calls in tests)

* craft-raw: Nikon NEF/NRW, with PhotoCraft's maker-note compression routing and 12-bit D4 white level

* craft-raw: Panasonic RW2 / RWL / RAW, every raw format

* craft-raw: RW2 format 8 refuses strips stating fewer bits than samples

Ported from storytold/lightcraft#3910.

* craft-raw: RW2 format 8 refuses samples above the declared bit depth

Ported from storytold/photocraft#11838.

* craft-raw: the RW2 format 8 robustness seed carries a valid stream

Its random payload decoded only while out-of-depth samples were clamped; mutations now start from a file that decodes.

---------

Co-authored-by: radialmonster <radial@gmail.com>
Co-authored-by: wnanbei <wnanbei@gmail.com>
2026-11-11 20:22:37 -05:00
Dylan M. Taylor a4b57d47b9 Add craft-crs: Camera Raw crs: settings in XMP, read and written, from PhotoCraft (#996)
* Add craft-crs: Camera Raw crs: settings in XMP, read and written, from PhotoCraft

The format half of PhotoCraft's Camera Raw settings files (crates/io/src/camera_raw_xmp.rs at
abeb16c96): the crs: settings of an XMP packet as written, Camera Raw's number and curve point
text, the crop and its orientation, a writer, and the toolkit marker. Which setting drives
which control stays in the app.

* craft-crs: structures carry their element and are written back

Local corrections and other structured settings were dropped, and an rdf:Seq of descriptions
read as empty strings. A structure now holds its crs: element, a sequence is only plain text
items, descriptions inside a structure are not read as top-level settings, and to_xmp writes
structures back.

* craft-crs: read packets through craft-metadata's XMP reader

One XMP reader for every crate: Settings::parse goes through craft_metadata::parse_xmp and
Settings::from_xmp takes its XmpData. Settings are kept by name (the reader's map has no file
order), structures are craft-metadata's XmpValue and are written back as rdf:parseType
Resource elements and rdf:Seq arrays. toolkit stays a bounded first-start-tag scan, as the
reader does not return x:xmptk.
2026-11-11 20:19:00 -05:00
Dylan M. Taylor c516452b47 craft-codepage: one table per legacy code page, for craft-rtf, craft-dxf and craft-mime (#991)
craft-rtf and craft-dxf each carried tables for DOS 437, 737, 850, 852 and
857 (and craft-rtf one for 866). They are now one set in craft-codepage,
which takes craft-rtf's codepage module (Windows and Mac tables, OEM glyphs,
font charsets) and craft-dxf's DOS tables. craft_rtf::codepage re-exports it
unchanged; craft-dxf keeps its $DWGCODEPAGE names and Decoder and looks the
DOS tables up there.

Every byte of every code page either crate decodes is unchanged: the
public decoders of both crates were dumped over all 256 bytes of each page
(and the East Asian pairs) before and after, and the dumps are identical.

craft-mime now reads the DOS pages by their IANA names (IBM437, cp850,
ibm-852, 857...), which it read as UTF-8 before.
2026-11-11 20:17:35 -05:00
Dylan M. Taylor 0d68632c9b Add craft-ods: OpenDocument spreadsheet tables on craft-odf (#990)
* Add craft-sheet: the cell positions, values and tables the spreadsheet formats share

craft-xls carried its own CellRef, Range, Value, CellError, Visibility, sheet-name
quoting, built-in number format table and indexed palette. craft-xlsx and craft-ods
need the same ones, so they move to a crate with no dependencies and craft-xls
re-exports them: an application gets one Value and one CellRef whichever file it read.

Added on the way: A1 parsing (CellRef::parse, Range::parse), Range::count and a1,
CellError::parse and the newer error values SpreadsheetML stores as text,
builtin_format_id for writers.

* Add craft-ods: OpenDocument spreadsheet tables on craft-odf

read() checks the spreadsheet mimetype, refuses an encrypted package, and reads the
tables of content.xml into sheets of cells that keep the file's terms: value type,
the raw value, displayed text, formula text, style name, covered cells, with
repeats expanded and merged ranges. Cell::value() gives the shown value, dates and
durations as serial numbers.

From GridCraft crates/xlsx/src/ods.rs and its tests (7e0d96d7), split from
GridCraft's document model and moved onto craft-odf and craft-xml's pull reader.
Options::only_sheet covers WordCraft's reader of one chosen sheet.
2026-11-11 20:16:51 -05:00
Dylan M. Taylor 55d120f201 Add craft-figma-rest: a typed model of the Figma REST file JSON (#954)
* Add craft-figma-rest: a typed model of the Figma REST file JSON

The neutral model for design files: the .fig and .sketch readers map onto
it and an application's importer takes it. Typed records for the document
tree, paints, effects, text styles, layout grids, constraints, export
settings and the component and style metadata; every record keeps the
fields it has no name for, so a file read and written loses nothing.
The vectorNetwork extension is typed and documented with the others.

The layer tree is converted level by level in both directions, and the
depth and node limits are checked on the JSON before any typed value is
built.

* craft-figma-rest: Warnings and style_name for the readers that build this shape

Moved from FigCraft's REST importer and .fig reader (crates/format/src/rest.rs
Warnings, fig.rs style_name), which the .fig and Sketch mappings share.
2026-11-11 20:05:20 -05:00
Dylan M. Taylor 491fb0095f craft-dxf: model::write, a drawing as tags (R12 to 2018) (#947)
* craft-dxf: model, the drawing above the tags (header, tables, blocks, layouts)

Document::read indexes a tag list into header variables, table records,
blocks with their entities, the ENTITIES records (a POLYLINE with its
vertices, an INSERT with its attributes) and the objects, each a slice of the
tag list. The typed table records, LAYOUT and DICTIONARY are made on request.
Also the colour index palette, colour, lineweight and transparency values,
extended data lookup and the object coordinate system.

Merged from CADCraft's cadcraft-io dxf_read (5a6aa9b6) and VectorCraft's
vectorcraft-cad import (566f7208). Neither app's document types appear.

* craft-dxf: model entities (geometry, text, MTEXT columns, hatches, dimensions)

Entity::read types an entity record: the properties every entity has, and
LINE, POINT, CIRCLE, ARC, ELLIPSE, LWPOLYLINE, POLYLINE with its vertices
(2D, 3D, mesh, polyface), SPLINE, RAY, XLINE, TEXT, MTEXT with columns,
ATTDEF, INSERT with its attributes (multiline ones included), DIMENSION,
LEADER, SOLID, TRACE, 3DFACE, HATCH (polyline and edge loops, pattern lines,
seeds, gradient), VIEWPORT, IMAGE, WIPEOUT and TOLERANCE. Other types stay
readable from the record's tags. model::text has the caret encoding of
strings, both ways.

Merged from CADCraft's cadcraft-io dxf_read (5a6aa9b6) and VectorCraft's
vectorcraft-cad import (566f7208).

* craft-dxf: a hatch spline edge without fit data leaves the loop's source count

Before R2010 a spline edge has no 97 of its own, so the 97 after it counts the
loop's source entities. It was read as the edge's fit point count and the
sources were lost.

* craft-dxf: model::write, a drawing as tags (R12 to 2018)

Drawing::tags writes the header, classes, the nine symbol tables, blocks,
entities and (R2000 and later) the dictionaries, plot style placeholder and
layouts around the layers, linetypes, styles, dimension styles, blocks and
entities it is given, in the model's own types. entity_tags writes one entity
for a caller with its own file. Handles given with the content are kept;
the rest are numbered after them.

The file skeleton is VectorCraft's vectorcraft-cad writer.rs (566f7208); the
MTEXT column groups are CADCraft's dxf_read/mtext_dxf.rs column_tags
(5a6aa9b6).

* README: keep main's crate list after the merge
2026-11-11 20:03:30 -05:00
Dylan M. Taylor 83eb8f4422 craft-exr: bounded part listing, flat decoding and writing (feature decode) (#992)
PhotoCraft's exr-crate wrapper (preview check, per-part and total size limits,
file-size plausibility, part ranking, one-part decode) and EffectCraft's
streaming channel filter, as one reader that returns a neutral model: part
headers with text attributes, and the kept channels in their stored type.
2026-11-11 19:47:07 -05:00
Dylan M. TaylorandMartin Ball 977208a013 Add craft-ews: the EWS SOAP layer from MailCraft (#988)
MailCraft crates/ews at a44a450: the request envelope and the escaping
element builder (soap.rs), and the judging of an answer's envelope, faults
and response messages (reply.rs, the response codes of error.rs), now read
with craft-xml in place of MailCraft's own XML reader. The operations, HTTP,
Autodiscover, authentication and sync stay in MailCraft.

Co-authored-by: Martin Ball <98639962+benandez@users.noreply.github.com>
2026-11-11 19:46:19 -05:00
Dylan M. TaylorandMartin Ball 8c6dbccb8b Add craft-jmap: the JMAP protocol core from MailCraft (#982)
MailCraft crates/jmap's protocol core at a44a450, moved as it is with all
its tests: session, requests with result references, responses, ids,
addresses and templates, credentials, retry and the judgement of HTTP
answers, over any HttpExchange. The mail client on top stays in MailCraft.

Co-authored-by: Martin Ball <98639962+benandez@users.noreply.github.com>
2026-11-11 19:45:03 -05:00
Dylan M. Taylor 08882273d1 Add craft-wordmodel: WordCraft's document model without its editing code (#963)
* Add craft-wordmodel: WordCraft's document model without its editing code

WordCraft's crates/doc (wordcraft-doc, at 07f97737) moved as it is, so the DOCX,
RTF, ODT and other text-format crates can read into and write from one model.
Module paths, type names and serde attributes are unchanged: an app that
re-exports the crate keeps its native file format.

Left in the app: range editing (edit.rs), IME text and equation editing. Not
model, and already in craft-libs: code pages (craft-rtf), the ZIP entry-count
check (craft-opc), the compound-file guard (craft-cfb). The paragraph walks
moved to walk.rs and the unit helpers of wordcraft-geom to geom.rs.

* craft-wordmodel: math_tree, the arguments of an equation node and a walk over its runs

The DOCX reader and writer walk an equation's runs. The walk and the child
accessors it uses were in WordCraft's equation editor (math_edit.rs), which
stays in the app; they are structure, not editing.

* craft-wordmodel: keep the document operations (edit.rs) with the model

The DOCX and RTF readers use them: alternative-format chunks are merged into
a document with adopt_blocks, and a reader formats ranges with format_range
and inserts objects. They operate on the document, not on a view, so they
belong beside it. The tests that drove them come back with them; the
paragraph walks stay in walk.rs and edit re-exports them.

base64 0.23, as WordCraft and craft-offcrypto use.
2026-11-11 19:43:08 -05:00
Dylan M. Taylor 01173ddad7 craft-webp: WebP container reader and writer (#958)
The WebP container code the apps wrote themselves: EffectCraft's chunk walk
(canvas, alpha, animation frames and their durations) and PhotoCraft's RIFF
writer around an encoded VP8 frame with alpha, ICC, EXIF and XMP chunks. The
codecs are not part of the crate.
2026-11-11 19:42:34 -05:00
Dylan M. Taylorandkiwi4boy 119415f46f Add craft-xlsx: XLSX and XLSB part layer (workbook, strings, styles, streamed rows) (#976)
* Add craft-sheet: the cell positions, values and tables the spreadsheet formats share

craft-xls carried its own CellRef, Range, Value, CellError, Visibility, sheet-name
quoting, built-in number format table and indexed palette. craft-xlsx and craft-ods
need the same ones, so they move to a crate with no dependencies and craft-xls
re-exports them: an application gets one Value and one CellRef whichever file it read.

Added on the way: A1 parsing (CellRef::parse, Range::parse), Range::count and a1,
CellError::parse and the newer error values SpreadsheetML stores as text,
builtin_format_id for writers.

* Add craft-xlsx: the part layer of XLSX and XLSB workbooks

Package::open finds the workbook part through craft-opc and reads the workbook
part, shared strings (runs and phonetic readings), the styles tables as indexed
tables, and a worksheet's rows one at a time with the rest of the part as a tree.
Binary workbooks give the same workbook, strings, rows and merged cells from their
records. Every other part stays reachable through Package::opc.

From GridCraft crates/xlsx (7e0d96d7): read.rs, sheet_read.rs, styles.rs, xml.rs
(string items, xstring escapes), xlsb.rs and its tests, split from GridCraft's
document model.

* craft-xlsx: XLSB opens up to 4 million cells, records counted once over the workbook

Binary workbooks over 1 million cells were refused, a quarter of what .xls allows,
and an allowance of 5 million records per part refused ordinary files: Excel writes
four records per non-empty row besides the cells, and one BrtCellBlank for every
formatted empty cell. Cells are now 4 million over the workbook (also the most
shared strings), text 256 MiB, and records 40 million for every part together, as
in craft-xls.

Ported from storytold/gridcraft#3807.

---------

Co-authored-by: kiwi4boy <kiwi4boy@gmail.com>
2026-11-11 19:32:04 -05:00
Dylan M. Taylor fa26a2a4c6 Add craft-pdn: Paint.NET PDN3 reader and a data-only MS-NRBF reader, from PhotoCraft (#968)
The format half of PhotoCraft's Paint.NET import (crates/io/src/pdn.rs and pdn/nrbf.rs at
abeb16c96): the PDN3 header, the .NET binary serialization graph, the layer list and blend
modes, and the chunked gzip pixel blocks. Surfaces, blend-mode mapping and warnings stay in
the app.
2026-11-11 19:30:55 -05:00
Dylan M. TaylorandMartin Ball ed16993b3b Add craft-ical: iCalendar lines, values, rules and VTIMEZONE from MailCraft (#966)
MailCraft crates/ical at a44a450: content lines, DATE/DATE-TIME values,
durations and text, the common subset of recurrence rules, and VTIMEZONE
definitions read and written, with their tests. Times are chrono values; the
zone database the writer needs is the caller's, through ZoneRules. The event
and to-do mapping onto MailCraft's model stays in MailCraft.

Co-authored-by: Martin Ball <98639962+benandez@users.noreply.github.com>
2026-11-11 19:30:23 -05:00
Dylan M. Taylorandradialmonster 69d812dd7a craft-raw: Nikon NEF and NRW (#965)
* 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.

* craft-raw: Canon CR2, with sRAW and mRAW

Moved from LightCraft crates/raw (vendor/cr2.rs, vendor/cr2/sraw.rs) at 5ff567a4. The white-balance presets a
CR2 lists come back as data in ColorData::kelvin_presets (the ordering from LightCraft's kelvin_presets.rs;
the temperature and tint arithmetic stays with the developer).

* craft-raw: an sRAW preview larger than the file is refused before its buffer is reserved

Ported from storytold/lightcraft#3910 (cr2::sraw::read_preview).

* WIP craft-raw: Nikon NEF/NRW (does not compile yet: limits threading, develop() calls in tests)

* craft-raw: Nikon NEF/NRW, with PhotoCraft's maker-note compression routing and 12-bit D4 white level

---------

Co-authored-by: radialmonster <radial@gmail.com>
2026-11-11 19:29:51 -05:00
Dylan M. Taylor 219f63f64c Add craft-xcf: GIMP XCF reader from PhotoCraft (#960)
* Add craft-xcf: GIMP XCF reader from PhotoCraft

The format half of PhotoCraft's XCF import (crates/io/src/xcf.rs at abeb16c96): the header,
precisions, property lists with their length repairs, parasites, layer structures and item
paths, channels, and the uncompressed, RLE and zlib tiles. The mapping onto a document (blend
modes, surfaces, profiles, warnings) stays in the app.

* craft-xcf: an RLE run of zero length is an error

A long run (op 127 or 128) with a 16-bit length of 0 consumed input without producing output,
so a tile of them was work the pixel budget never charged.

Ported from storytold/photocraft#11138.

* craft-xcf: channel names are charged to the metadata budget

Layer names already were. Many channel pointers to one channel with a large name made a copy
per read, multiplying a small file into gigabytes of retained names.

Ported from storytold/photocraft#11138.
2026-11-11 19:25:01 -05:00
Dylan M. Taylor eb640fd992 Add craft-sheet: the cell positions, values and tables the spreadsheet formats share (#956)
craft-xls carried its own CellRef, Range, Value, CellError, Visibility, sheet-name
quoting, built-in number format table and indexed palette. craft-xlsx and craft-ods
need the same ones, so they move to a crate with no dependencies and craft-xls
re-exports them: an application gets one Value and one CellRef whichever file it read.

Added on the way: A1 parsing (CellRef::parse, Range::parse), Range::count and a1,
CellError::parse and the newer error values SpreadsheetML stores as text,
builtin_format_id for writers.
2026-11-11 19:24:39 -05:00
Dylan M. Taylor 16936716aa Add craft-stream-codecs: the codec modules of PdfCraft's stream filters (#953)
LZW, ASCII85, ASCIIHex, RunLength, the TIFF and PNG predictors, bounded CCITT
fax decoding (through hayro-ccitt) with Group 4 encoding (through fax), and
the lossless JBIG2 encoder with its MQ coder, from pdfcraft-filters at
PdfCraft revision 80e26e674d1aa33078e83c8139875047f3289eb6.

The crate sits below craft-pdf and craft-tiff: it knows no container. The
PDF filter chain, Flate and the bounded JBIG2 decoding entry are not part of
it.
2026-11-11 19:24:06 -05:00
Dylan M. Taylor dca2a2cd8e craft-metadata: EXIF, IPTC-IIM, XMP and GPX from LightCraft (#949)
lightcraft-meta at 5b61ce1e, as one crate with a module per format: exif
(read, write, maker-note lens, focus point, tag listing), iptc, xmp (parse,
write, merge), gpx, and the JPEG, PNG, WebP, TIFF, RAF and CR3 containers.
They share Metadata, DateTime, Gps and Orientation.

Changed in the move:
- TIFF comes from craft-tiff; Point, Rect and Orientation are the crate's own.
- The app namespace is a parameter: parse_xmp_with, write_xmp_with and
  MergeRules::namespaces take it, and XmpData no longer has lc_settings.
- The HEIF, PSD and MRW readers stay out, since craft-heif, craft-psd and
  craft-raw read those containers; from_embedded takes the blocks they find.
2026-11-11 19:22:42 -05:00
Dylan M. Taylor e0a3b2b8c4 craft-dxf: model entities (geometry, text, MTEXT columns, hatches, dimensions) (#927)
* craft-dxf: model, the drawing above the tags (header, tables, blocks, layouts)

Document::read indexes a tag list into header variables, table records,
blocks with their entities, the ENTITIES records (a POLYLINE with its
vertices, an INSERT with its attributes) and the objects, each a slice of the
tag list. The typed table records, LAYOUT and DICTIONARY are made on request.
Also the colour index palette, colour, lineweight and transparency values,
extended data lookup and the object coordinate system.

Merged from CADCraft's cadcraft-io dxf_read (5a6aa9b6) and VectorCraft's
vectorcraft-cad import (566f7208). Neither app's document types appear.

* craft-dxf: model entities (geometry, text, MTEXT columns, hatches, dimensions)

Entity::read types an entity record: the properties every entity has, and
LINE, POINT, CIRCLE, ARC, ELLIPSE, LWPOLYLINE, POLYLINE with its vertices
(2D, 3D, mesh, polyface), SPLINE, RAY, XLINE, TEXT, MTEXT with columns,
ATTDEF, INSERT with its attributes (multiline ones included), DIMENSION,
LEADER, SOLID, TRACE, 3DFACE, HATCH (polyline and edge loops, pattern lines,
seeds, gradient), VIEWPORT, IMAGE, WIPEOUT and TOLERANCE. Other types stay
readable from the record's tags. model::text has the caret encoding of
strings, both ways.

Merged from CADCraft's cadcraft-io dxf_read (5a6aa9b6) and VectorCraft's
vectorcraft-cad import (566f7208).

* craft-dxf: a hatch spline edge without fit data leaves the loop's source count

Before R2010 a spline edge has no 97 of its own, so the 97 after it counts the
loop's source entities. It was read as the edge's fit point count and the
sources were lost.

* README: keep main's crate list after the merge
2026-11-11 19:21:50 -05:00
Dylan M. Taylor ce72c01bdc craft-odf: OpenDocument packages, password protection and flat documents (#896)
The container every OpenDocument file shares, moved from WordCraft
(crates/formats/src/odt/crypt.rs, odt/flat.rs, the package reading and
writing in odt/import.rs and odt/package.rs, crates/doc/src/zip_guard.rs
at 06c2ae6f) so that the ODT and ODS readers build on one copy.

- Package and PackageWriter: the ZIP with its mimetype and manifest,
  within Limits (entries, bytes per file, bytes per package).
- Manifest: read by namespace whatever the prefix, as GridCraft's ODS
  reader did; written as LibreOffice lays it out.
- crypt: AES-256-CBC with PBKDF2, read and written, with the per-file and
  per-package key-derivation budgets.
- flat: a flat document to a package and back, for any media type (the
  WordCraft code wrote the text one only).
- zip_guard: the entry-count check before zip parses a directory.

No document content is read here.
2026-11-11 19:00:01 -05:00
Dylan M. Taylor 7db4645d1f Add craft-prc: the PRC 3D model reader from PdfCraft (#945)
PdfCraft's pdfcraft-prc crate (revision
80e26e674d1aa33078e83c8139875047f3289eb6), moved whole with its tests. It reads
PRC (ISO 14739-1), the model format of PDF 3D annotations: the container, the
bit-level primitives, the schema, tessellations and the product tree.

The code is unchanged. The package and its README are renamed, the doctests
and tests import craft_prc, and the Double code table taken from prc-rs (MIT)
has its row in ATTRIBUTION.md and its licence in licenses/prc-rs.
2026-11-11 18:55:20 -05:00
Dylan M. Taylor e5299a3be0 craft-html: a tolerant tokenizer, character references, escaping and the declared charset (#944)
The bottom layer every HTML reader in the Craft apps carried its own copy
of. The tokenizer is MailCraft's (crates/html/src/tokenizer.rs), with what
it dropped and capped turned into Options and Limits, a self-closing flag,
and raw_content for callers that read a style sheet's text. The character
references are WordCraft's (crates/formats/src/html/entity.rs at 06c2ae6f):
its name table and the 94 legacy names that decode without a semicolon,
with MailCraft's Latin-1 and Greek tables and its handling of numbers too
long to be numbers.

No tree, no CSS, no decoding of bytes.
2026-11-11 18:54:48 -05:00
Dylan M. Taylorandskylar taylor-barrick 17e55c96db Add craft-zip: bounded ZIP reading and a deterministic writer for the package crates (#941)
* craft-zip: ZIP preflight, bounded entry read and Budget (from craft-archive, #129)

The ZIP half of the craft-archive proposal (#126, #129): an allocation-free check of the end
records and central directory that runs before a ZIP reader indexes the archive, a bounded
entry read that distrusts the declared size, and the named Budget both charge. The XML half
of #129 is not carried: craft-xml bounds XML now.

* craft-zip: Archive (entry-count guard, duplicate names, budgeted reads) and Writer

check_entry_count is craft-opc's guard: the declared entry count, ZIP64 included, checked on
the raw bytes before zip parses the central directory. Archive reads entries within a per-entry
limit and a total Budget through read_entry. Duplicate names are found by counting the central
directory headers against the entries zip kept. Writer is craft-opc's ZipOut: stored or deflated
entries, ZIP64 for large ones, one fixed timestamp.

---------

Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
2026-11-11 18:53:53 -05:00
Dylan M. Taylor 77591e5efb craft-gif: GIF block reader (#939)
One walk over a GIF's blocks, without decompressing pixels: the logical
screen, each frame's position, delay, disposal and transparent colour, the
loop count and the first frame's colour table. Replaces PhotoCraft's two
block walkers and gives the other apps a header-only animation probe.
2026-11-11 18:53:16 -05:00
Dylan M. TaylorandNetterSilver 4f096b64d6 craft-aaf: AAF and OMF readers and writers over one composition model (from FilmCraft) (#918)
* craft-audio: integer PCM of WAV and AIFF files bit for bit, and minimal files around it

wav_pcm and aiff_pcm (feature decode-native) return a file's integer PCM as stored
(RawPcm: little-endian interleaved bytes, 8-bit unsigned, with channels, rate and
bits), built on the existing WAV and AIFF chunk parsers. pcm_wav_file,
pcm_wav_header, pcm_aiff_file and pcm_aiff_header (feature pcm-encode) write the
44-byte canonical WAVE header and the plain AIFF header around such bytes.

For essence embedded in AAF and OMF files, which is moved bit for bit and whose
descriptors hold a bare header as a summary. The writers are FilmCraft's
(filmcraft-interchange wav.rs); the readers replace its second WAV/AIFF parser
with this crate's.

* 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.

* craft-aaf: AAF (Edit Protocol) and OMF 2.0 readers and writers over one composition model

New crate (#782), moved from FilmCraft's filmcraft-interchange at 4bec22bd:
the mob-style model types of comp.rs, aaf/{ids,store,read,write}.rs and
omf/{bento,read,write}.rs, with their structural and hostile-input tests
rebuilt on model documents instead of FilmCraft projects.

The container is craft-cfb, time and reports are craft-timeline's, and the
WAVE/AIFF wrappers of embedded PCM are craft-audio's. The writer's identity
(company, product, version, AAF product id) is passed in instead of being
FilmCraft's. The private tags and properties FilmCraft wrote keep their names
so existing files read back.

The mapping between the model and an application's project stays in the
application.

---------

Co-authored-by: NetterSilver <133535268+Silvertree2010@users.noreply.github.com>
2026-11-11 18:52:44 -05:00
Dylan M. TaylorandMartin Ball f3944ba991 Add craft-vcard: vCard reader and writer from MailCraft (#942)
* craft-mime: strict base64, mailto links and code page labels from MailCraft

What MailCraft's mime and charset crates still had that craft-mime did not:
strict RFC 4648 base64 (crates/charset), mailto: links (RFC 6068,
crates/mime) and the Windows code page to charset label table (crates/pst).
With these, MailCraft's charset crate has nothing craft-mime lacks.

* Add craft-vcard: vCard reader and writer from MailCraft

MailCraft crates/vcard at a44a450: vCard 2.1, 3.0 and 4.0 read, 3.0 written,
with its limits and tests. The model is the crate's own Contact (the vCard
fields of MailCraft's); the birthday is a chrono::NaiveDate. Character sets
and strict base64 come from craft-mime.

---------

Co-authored-by: Martin Ball <98639962+benandez@users.noreply.github.com>
2026-11-11 18:50:38 -05:00
Dylan M. Taylor 471abca087 craft-media: MPEG-2 transport and program stream demuxer from FilmCraft (mpegts) (#861)
* craft-media: MPEG-2 transport and program stream demuxer from FilmCraft (feature `mpegts`)

Moves filmcraft-mpegts (storytold/filmcraft@4bec22b) into craft_media::mpegts.
ByteSource is the crate's trait, Error is written without thiserror so the
feature has no dependencies, and Codec::codec_id() and Stream::codec_params()
map streams to CodecId. Adds Container::MpegTs and Container::MpegPs, an
ffprobe oracle test over ten ffmpeg-made files, and hostile-input tests.

* craft-media: module order after merging main
2026-11-11 18:47:39 -05:00
Dylan M. Taylor bd79e32630 craft-lottie: the document model, with a bounded reader and a writer (#938)
`craft_lottie::model` is the Lottie document as the file has it: one
struct per object of the lottie-spec 1.0 object model (animation, assets,
layers by type, transform, masks, effects, shape items, text, markers,
fonts, glyphs), one field per key. `Animation::read` reads within
`Limits` (bytes, nesting, layers, assets, shape items); `to_json` writes.

Keys the model does not name, values of another type than expected, and
layers and shape items of an unknown type stay in `extra` and are written
back, so a document survives a read and a write. Animatable properties
stay JSON (`Animated`) and are read with `read_prop`.
2026-11-11 18:47:08 -05:00
Dylan M. Taylor 43f65a9210 craft-svg: usvg behind a bounded tree feature; attribute guard; Inkscape layer names (#937)
* craft-svg paths: a shape carries the name of its Inkscape layer

Shape::layer is the inkscape:label (else the id) of the innermost
<g inkscape:groupmode="layer"> around the element, so a CAD or cutter
import can keep the drawing's layers.

* craft-svg guard: bound attributes per element and over the document; find entities holding markup

too_wide is the attribute check of storytold/figcraft#2251: an XML
reader checks each attribute against the earlier ones on its element,
so an element with millions of them is quadratic work before any other
limit applies. entity_holds_markup lets a caller refuse a document type
whose entities become elements, as PhotoCraft does.

* craft-svg: feature `tree`, usvg behind one bounded entry point

usvg 0.48.1 (Apache-2.0 OR MIT), re-exported as craft_svg::tree::usvg,
so every app parses with one version and one set of checks:
tree::parse runs svgz, the entity, attribute and nesting guards, the
reference-chain height (VectorCraft's depth::height, on this crate's
cascade), then usvg. tree::image_resolver bounds embedded SVGs
(FigCraft's budget::resolver). tree::draw bounds what drawing a tree
with resvg allocates (PhotoCraft's svg/limits.rs).

Part of #778 and #789.
2026-11-11 18:46:38 -05:00