* CI speed: line tables for workspace crates, no debug info for dependencies
* CI speed: xtask ci runs nextest and doctests, one wasm check, the CLI without default features
* Allow OFL typefaces whoever designed them (AGENTS.md §1.1)
Noto CJK and Source Han from their open-source releases are allowed like any
other OFL font. The fonts build no longer skips them, the asset check accepts
an OFL font credited to Adobe, and the docs describe the Chinese faces that
result. The ban on Adobe icons, artwork, UI graphics and the fonts Adobe
products install stays.
* Leave out whole CJK faces other than the Japanese ones for size
A Chinese or Korean face is embedded whole only under 4 MiB, so the 16.4 MB
Noto Sans CJK SC in craft-fonts stays out of the binary and the embedded
total against the pinned craft-fonts is unchanged (25,204,828 bytes).
* docs: replacement boxes for Chinese without craft-fonts only on the web or without an installed Chinese face
* xtask assets: the OFL font exemption needs at least one licence ID
* fonts build: count a CJK script tag with a region (Hant-HK) as CJK, shared with the embedding test
* AGENTS.md: the asset gate exempts OFL fonts from the Adobe-author check
* fonts build: count a Hangul-only face (Hang) as CJK for the size rule
* fonts: the CJK embedding test checks the size rule, sharing its constants with build.rs
* docs: the missing Hans face is a property of the pinned craft-fonts, not of every build
* M6: calculations read their notation only from a comment, and a regex after a condition as a regex
Two findings from CodeRabbit's review of #3550, both reproduced first:
- parse_calculate and calculation_sites found BVCALC and EVCALC anywhere
in the script, so `var s = "BVCALC a EVCALC";` read as the notation `a`:
renaming `a` rewrote the string, and the form calculated `a` instead of
running the script. The lexer now keeps the text of each comment, and
the notation is read from the first comment that holds BVCALC and the
EVCALC after it (notation_range), by both the Calculate tab and a
rename. A line comment counts, and a comment that never ends runs to
the end of the script, as before.
- A `/` after any `)` divided, so after the condition of `if`, `while`,
`for` or `with` a regular expression was read as code: renaming `a`
rewrote `if (flag) /getField("a")/.test(x)`, and in
`if (flag) /"/.test(x)` the quote started a string that hid the
script's real getField calls. The lexer now notes which `(` opened
such a condition (not a method of that name, `o.if(x)`), and a `/`
after its `)` starts a regular expression.
af::tests::notation_is_read_only_from_a_comment,
af::tests::a_regex_after_a_condition_is_not_read_as_code and
tests::a_script_that_spells_the_markers_in_a_string_runs_as_a_script
fail without the fix. Parity, ROADMAP.md and docs/roadmap.md updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: form.props-calculate notes that a regex right after a brace is still read as code
From CodeRabbit's review of this PR. The lexer still divides after `}`,
which ends a block as often as an object literal, so in
`if (x) { } /"/.test(s); event.value = getField("a").value;` the quote
starts a string and the getField is hidden from a rename and from the
calculation order. Checked with a scratch test before writing it down.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: a line comment ends at any line terminator, so code after `\r` is still read
From CodeRabbit's review of this PR. A `//` comment ran to the next `\n`,
so with `\r` line endings it hid the rest of the script: a rename missed
the getField calls after it (since #3550), and a string after it that
spells BVCALC ... EVCALC read as the notation. A `/` taken for the start
of a regular expression likewise looked past `\r` for its end, and could
swallow the next line's code.
Both now stop at any of JavaScript's line terminators (`\n`, `\r`,
U+2028, U+2029; line_ends_at), and U+2028 and U+2029 between tokens are
skipped rather than read as the start of a word.
af::tests::a_line_comment_ends_at_any_line_terminator fails without each
of the three changes: with `\r`, the string read as notation `a`; with
the comment fixed but not the regex scan, `i++ / 2;\r` swallowed the
getField after it; without skipping U+2028, the getField after it was
not a call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: a word, number or regex's flags end at U+2028 and U+2029
From CodeRabbit's review of this PR. The scans for an identifier, a
number and a regular expression's flags took every byte past ASCII as
part of a word, U+2028 and U+2029 included, so in `x\u{2028}getField("a")`
the getField was part of one word and not a call: a rename missed it, and
the calculation order didn't read it.
`word` now takes a position and is false where line_ends_at is true, so
the three scans share one rule. The arm that skips U+2028 and U+2029
between tokens stays: without it, the punctuation arm would step one byte
into the character.
af::tests::a_token_ends_at_a_line_separator fails without the fix, for a
word, a number and a regex's flags alike; it and
a_line_comment_ends_at_any_line_terminator fail without the skipping arm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
* M6: the `while` that ends a `do` loop counts as a condition, said and tested
A `/` after the `)` of a do-while starts the next statement, so it starts a
regular expression there too. The comment on BEFORE_CONDITION says so, and
a_regex_after_a_condition_is_not_read_as_code covers it (from review).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BfMGHQNgdq5yf5tKZHGR3z
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Build each crate's integration tests as one binary
Every tests/*.rs file is its own executable that links the whole crate
graph. Move each crate's test files into tests/it/ as modules of one
tests/it/main.rs, for apps/pdfcraft, apps/pdfcraft-cli, automation, cos,
crypt, engine, render, sign and ui-egui. ui-egui's ui_group binary and
the support modules move into tests/it/ too. Crates with a single test
file keep it. The test executables go from 150 to 52, and every crate
runs the same number of tests as before.
ui-egui perf keeps its own binary (own process): its frame-time budgets
would charge each measurement for the other tests running beside it.
What the move needs:
- include_str!/include_bytes! and #[path] paths gain one "../".
- ui-egui: one GPU lock in tests/it/main.rs replaces the five per-file
ones (content, document_links, editing, fill_sign, view), so one
process creates one wgpu device at a time. The drop-line test in ui.rs
and the screenshot blocks behind PDFCRAFT_*_SHOT(S) and
CRAFT_UI_DOCKING_DIR, which render without holding it, take it too.
- pdfcraft winit_wayland_scroll: the vendored winit source it compiles
names crate::event, so that shim lives in main.rs.
- parity evidence names the new paths.
* Name the one-binary test paths in docs and comments
- READMEs, docs, ATTRIBUTION and source comments name tests/it/<file>.rs.
- The oracle README's example filters with --test it encryption::qpdf_agrees.
- pdfcraft's GPU lock comment points at the UI tests' shared lock in
crates/ui-egui/tests/it/main.rs.
The track merges used a union driver for these files, which kept two
versions of a milestone row or status line wherever two tracks changed the
same one. They are recomputed as base plus each track's changes, with two
edits of one line combined into one line. The status lines that #2805, #3808
and #3532 put above the Stage line join the status list below it, newest
first.
* M14: add Norwegian Bokmål interface and date language
A 2,592-entry `nb.tsv` covering every key the other bundled catalogs have,
written from the meaning of the English labels (clean-room); terminology
reviewed by a native-speaking contributor. Registers `nb` with the English
one/other plural rule, adds Norwegian month and weekday names for Fill &
Sign dates, resolves `no` and `nn` locales to Bokmål until a Nynorsk catalog
exists, attributes the catalog and records it in parity.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: translate the strings main added since the Norwegian catalog was written
69 keys (signature appearance labels, word count, full-screen prompt, print
as image, image crop, redaction mark appearance, …) and the attribution hash.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: translate the picture-menu and label-paper strings in the Norwegian catalog
Seven keys main added (Cut/Redact Image, Create Link…, Recognize Text…,
Turn upside down, the 4 x 6 in label paper) and the attribution hash.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: translate the page-group folding commands in the Norwegian catalog
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* M14: drop the superseded localization summaries left beside the current ones
Concurrent merges on main left older copies of several localization
summaries next to their replacements. Review of #1836 flagged them:
- localization-parity.md: remove the 2026-10-13 status line and the
14-language summary that still listed Norwegian as missing; the
current summary keeps the Italian (and now Norwegian Bokmål)
measurement dates.
- localization.md: keep one catalog-coverage paragraph (now naming
Norwegian Bokmål, up to about 2,700 entries) and one font paragraph.
The font paragraph folds in what only the older copies said: the
installed Chinese face fallback (#826) and Slovenian's Latin faces and
four plural forms.
- roadmap.md: end the M14 Missing list before the Fontconfig note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: aiongg <67543387+aiongg@users.noreply.github.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: arrays sized 30.
ATTRIBUTION.md: both sides' rows; count regenerated after all merges.
docs/localization.md: main's text with the ckb name, code and catalog-list entry added.
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows, arrays sized 28.
crates/ui-egui/tests/fonts.rs: both catalog lists.
ATTRIBUTION.md: both sides' rows; count regenerated after all merges.
ROADMAP.md: main's summary with Romanian added (15 more catalogs); both log lines.
docs/localization-parity.md: main's rows plus the ro row; 28 interface languages; both revision rows.
docs/localization.md: main's text with Română, the ro locales and code, and the PR's catalog-list edits.
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows, arrays sized 27.
crates/ui-egui/tests/fonts.rs: both catalog lists; Catalan added to main's comment.
ATTRIBUTION.toml/.md: both sides' assets; hashes and counts regenerated after all merges.
ROADMAP.md: both log lines.
docs/localization-parity.md: main's rows plus the ca row; 27 interface languages; both revision rows.
docs/localization.md: main's text with Català, the ca locales, code and catalog paragraph added.
Conflict resolutions:
- ATTRIBUTION.toml: main's entries kept; #3101's page-gap provenance note appended to the 22 catalogs it translates (hashes recomputed separately).
- ATTRIBUTION.md: main's side kept; regenerated from ATTRIBUTION.toml separately.
- ROADMAP.md: #3101's two 2026-10-18 progress-log lines placed after the 2026-10-19 entries.
- docs/roadmap.md: both 2026-10-18 status lines kept; M3 row carries both sides' insertions.
crates/engine/src/dates.rs, crates/ui-egui/src/i18n/mod.rs: both sides' language rows (vi, fi, sq), arrays sized 25.
crates/ui-egui/tests/fonts.rs: both catalog lists; Albanian added to main's comment.
ATTRIBUTION.toml/.md: both sides' assets; hashes and counts regenerated after all merges.
ROADMAP.md: both log lines.
docs/localization-parity.md: main's rows plus the sq row; 25 interface languages; both revision rows.
docs/localization.md: main's text with Shqip, the sq locales and code added.
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
ROADMAP.md: summary names Vietnamese, Brazilian Portuguese and Spanish against the current union; both log lines.
docs/localization-parity.md: main's status line with the Spanish change added; the PR's denominator paragraph naming Brazilian Portuguese too; the PR's lt row plus main's fi row; revision rows newest first.
Conflicts:
- ATTRIBUTION.toml: ours (sha256 lines only); hashes recomputed at the end of the track
- crates/engine/Cargo.toml: main's workspace chrono entry, plus the PR's pdfcraft-testkit dev-dependency
ROADMAP.md, docs/roadmap.md: all status lines and progress-log lines kept; M12 row takes both 'Done since' insertions.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
ROADMAP.md: progress-log line added at the top
docs/roadmap.md: the M11 note applied onto main's row (which #3622 extended)
crates/optimize/tests/optimize.rs: new tests appended after main's
ROADMAP.md, docs/roadmap.md: both status lines and progress-log lines kept; M12 row takes both 'Done since' insertions.
crates/automation/tests/automation.rs, crates/xfa/src/fixtures.rs: union of #2624's and #2619's tests and fixtures.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
ROADMAP.md: both progress-log lines, newest first.
docs/localization-parity.md: the PR's status line; both revision rows, newest first.
docs/roadmap.md: the PR's rows (main's rows plus the PR's wording).
ROADMAP.md: kept main's stage line and status lines, added this PR's status and log lines.
docs/roadmap.md: kept main's status lines and M8/M9/M10 rows (main had merged the duplicate M8
row), added this PR's status line and its M10 sentence.
crates/ui-egui/src/lib.rs: persist keeps main's `v` and its separately set keys; print_paper
set the same way.
crates/ui-egui/tests/print.rs: kept both sides' tests.
ATTRIBUTION.toml: kept main's sha; rehashed after all merges.
ROADMAP.md: both progress-log lines, newest first.
crates/ui-egui/src/i18n/cs.tsv: both sides' entries, no duplicate keys.
docs/localization.md, docs/roadmap.md: main's single paragraph and row, with the PR's Czech wording applied.
ROADMAP.md: kept both sides' progress-log lines.
crates/edit/src/lib.rs: re-export list is the union (TextSession from #3942; BlockKey, block_overlapping from #2756).
parity/acrobat-features.toml: form.highlight-fields evidence and notes from both sides (the renamed remembered test, the read-only test)
docs/roadmap.md: M6 row keeps both added sentences
ROADMAP.md: union of the progress-log lines
crates/xfa/src/script.rs: template_field and named_in moved to crate::data in #3397; field_grows_with_text calls crate::data::template_field.
parity/acrobat-features.toml: form.xfa evidence lists unioned, notes take both sides' insertions.