From 5b11b9062b44b796616ec14b73b54644e60e6175 Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Fri, 28 Aug 2026 16:41:21 +0900 Subject: [PATCH 01/12] feat: prepare three-layer jlreq 0.1.0 release --- .cargo/initial-release-patch.toml | 7 + .cargo/mutants.toml | 12 +- .github/REPOSITORY-SETTINGS.md | 43 + .github/workflows/ci.yml | 7 +- .github/workflows/mutants.yml | 20 +- .github/workflows/release-check.yml | 59 + .github/workflows/release.yml | 34 +- ARCHITECTURE.md | 281 +-- CHANGELOG.md | 65 +- Cargo.lock | 404 ++- Cargo.toml | 7 +- DEVELOPMENT-HISTORY.md | 8 +- Justfile | 57 +- LICENSE-APACHE | 73 + LICENSE-MIT | 18 + README.md | 275 +- REUSE.toml | 2 + ROADMAP.md | 87 +- SECURITY.md | 92 +- clippy.toml | 4 +- crates/jlreq-conformance/Cargo.toml | 6 +- crates/jlreq-conformance/src/main.rs | 2 +- crates/jlreq-conformance/src/sample_engine.rs | 22 +- .../tests/reference_integration.rs | 4 +- crates/jlreq-core/Cargo.toml | 30 + crates/jlreq-core/LICENSE-APACHE | 73 + crates/jlreq-core/LICENSE-MIT | 18 + crates/jlreq-core/README.md | 43 + .../examples/composer.rs | 4 +- .../{jlreq => jlreq-core}/examples/minimal.rs | 4 +- .../examples/vertical.rs | 6 +- crates/{jlreq => jlreq-core}/src/construct.rs | 0 crates/{jlreq => jlreq-core}/src/generated.rs | 0 .../src/generated/appendix_a.rs | 4 +- .../src/generated/folding.rs | 4 +- .../src/generated/ideograph.rs | 4 +- .../src/generated/script.rs | 4 +- .../src/generated/table1.rs | 2 +- .../src/generated/table2.rs | 2 +- .../src/generated/table3.rs | 2 +- .../src/generated/table4.rs | 2 +- .../src/generated/table5.rs | 2 +- .../src/generated/table6.rs | 2 +- crates/{jlreq => jlreq-core}/src/layout.rs | 0 crates/jlreq-core/src/lib.rs | 64 + crates/{jlreq => jlreq-core}/src/limits.rs | 0 crates/{jlreq => jlreq-core}/src/model.rs | 0 crates/{jlreq => jlreq-core}/src/normalize.rs | 0 crates/{jlreq => jlreq-core}/src/paragraph.rs | 0 crates/{jlreq => jlreq-core}/src/pipeline.rs | 0 crates/{jlreq => jlreq-core}/src/spec.rs | 0 crates/{jlreq => jlreq-core}/src/style.rs | 0 .../{jlreq => jlreq-core}/tests/public_api.rs | 110 +- crates/jlreq/Cargo.toml | 28 +- crates/jlreq/README.md | 79 +- crates/jlreq/examples/quick_start.rs | 28 + crates/jlreq/src/document.rs | 719 ++++++ crates/jlreq/src/engine.rs | 2207 +++++++++++++++++ crates/jlreq/src/error.rs | 246 ++ crates/jlreq/src/font.rs | 441 ++++ crates/jlreq/src/lib.rs | 94 +- crates/jlreq/src/options.rs | 469 ++++ crates/jlreq/src/result.rs | 903 +++++++ crates/jlreq/src/units.rs | 122 + crates/jlreq/tests/high_level.rs | 821 ++++++ data/manifest.toml | 64 +- deny.toml | 18 +- docs/RELEASING.md | 162 +- ...ed-public-crate-and-process-conformance.md | 2 +- docs/adr/0023-the-project-is-named-jlreq.md | 7 +- .../adr/0024-independent-reference-engines.md | 7 +- docs/adr/0025-three-product-layers.md | 53 + docs/decisions/README.md | 36 +- docs/decisions/construct-break-refusal.md | 2 +- docs/decisions/expansion-ladder-scope.md | 2 +- .../group-ruby-flush-single-character.md | 4 +- .../inexpressible-advance-remarks.md | 2 +- .../jidori-inserted-space-locale-split.md | 2 +- .../jidori-room-and-solid-boundaries.md | 2 +- docs/decisions/ornamented-complex-geometry.md | 2 +- .../ruby-distribution-and-rounding.md | 2 +- docs/decisions/ruby-overhang-permission.md | 2 +- docs/decisions/stacked-structure-geometry.md | 2 +- docs/decisions/tab-line-correspondence.md | 6 +- .../tate-chu-yoko-spacing-sources.md | 2 +- docs/decisions/unstated-alignment.md | 2 +- docs/decisions/warichu-bracket-listing.md | 2 +- docs/design/api-spine.md | 199 +- docs/design/conformance.md | 4 +- docs/design/generation.md | 6 +- docs/error-codes.md | 54 +- docs/mutation-ledger.toml | 64 +- docs/public-api.toml | 50 +- engines/ocaml/README.md | 4 +- engines/racket/README.md | 2 +- fuzz/Cargo.lock | 360 ++- fuzz/Cargo.toml | 9 + fuzz/fuzz_targets/composition.rs | 2 +- fuzz/fuzz_targets/high_level_layout.rs | 76 + fuzz/fuzz_targets/input_validation.rs | 2 +- fuzz/seeds/high_level_layout/basic | 1 + release-plz.toml | 18 +- scripts/check-semver.sh | 48 +- scripts/finalize-release.sh | 100 + scripts/run-mutation-smoke.sh | 3 +- scripts/verify-crates.sh | 105 +- scripts/verify-mutation-ledger.sh | 6 +- scripts/verify-release-state.sh | 27 +- spec/derived/appendix-a.tsv | 2 +- spec/derived/classes.tsv | 2 +- spec/derived/folding.tsv | 2 +- spec/derived/ideographs.tsv | 2 +- spec/derived/scripts.tsv | 2 +- typos.toml | 2 + xtask/src/api.rs | 47 +- xtask/src/classes.rs | 8 +- xtask/src/conform.rs | 2 +- xtask/src/direction.rs | 16 +- xtask/src/generate.rs | 4 +- xtask/src/purity.rs | 60 +- xtask/src/repository.rs | 166 +- xtask/src/shared.rs | 6 +- xtask/src/spacing.rs | 26 +- 123 files changed, 8879 insertions(+), 1117 deletions(-) create mode 100644 .cargo/initial-release-patch.toml create mode 100644 .github/REPOSITORY-SETTINGS.md create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 crates/jlreq-core/Cargo.toml create mode 100644 crates/jlreq-core/LICENSE-APACHE create mode 100644 crates/jlreq-core/LICENSE-MIT create mode 100644 crates/jlreq-core/README.md rename crates/{jlreq => jlreq-core}/examples/composer.rs (94%) rename crates/{jlreq => jlreq-core}/examples/minimal.rs (84%) rename crates/{jlreq => jlreq-core}/examples/vertical.rs (80%) rename crates/{jlreq => jlreq-core}/src/construct.rs (100%) rename crates/{jlreq => jlreq-core}/src/generated.rs (100%) rename crates/{jlreq => jlreq-core}/src/generated/appendix_a.rs (99%) rename crates/{jlreq => jlreq-core}/src/generated/folding.rs (98%) rename crates/{jlreq => jlreq-core}/src/generated/ideograph.rs (92%) rename crates/{jlreq => jlreq-core}/src/generated/script.rs (94%) rename crates/{jlreq => jlreq-core}/src/generated/table1.rs (99%) rename crates/{jlreq => jlreq-core}/src/generated/table2.rs (99%) rename crates/{jlreq => jlreq-core}/src/generated/table3.rs (99%) rename crates/{jlreq => jlreq-core}/src/generated/table4.rs (99%) rename crates/{jlreq => jlreq-core}/src/generated/table5.rs (99%) rename crates/{jlreq => jlreq-core}/src/generated/table6.rs (99%) rename crates/{jlreq => jlreq-core}/src/layout.rs (100%) create mode 100644 crates/jlreq-core/src/lib.rs rename crates/{jlreq => jlreq-core}/src/limits.rs (100%) rename crates/{jlreq => jlreq-core}/src/model.rs (100%) rename crates/{jlreq => jlreq-core}/src/normalize.rs (100%) rename crates/{jlreq => jlreq-core}/src/paragraph.rs (100%) rename crates/{jlreq => jlreq-core}/src/pipeline.rs (100%) rename crates/{jlreq => jlreq-core}/src/spec.rs (100%) rename crates/{jlreq => jlreq-core}/src/style.rs (100%) rename crates/{jlreq => jlreq-core}/tests/public_api.rs (97%) create mode 100644 crates/jlreq/examples/quick_start.rs create mode 100644 crates/jlreq/src/document.rs create mode 100644 crates/jlreq/src/engine.rs create mode 100644 crates/jlreq/src/error.rs create mode 100644 crates/jlreq/src/font.rs create mode 100644 crates/jlreq/src/options.rs create mode 100644 crates/jlreq/src/result.rs create mode 100644 crates/jlreq/src/units.rs create mode 100644 crates/jlreq/tests/high_level.rs create mode 100644 docs/adr/0025-three-product-layers.md create mode 100644 fuzz/fuzz_targets/high_level_layout.rs create mode 100644 fuzz/seeds/high_level_layout/basic create mode 100644 scripts/finalize-release.sh diff --git a/.cargo/initial-release-patch.toml b/.cargo/initial-release-patch.toml new file mode 100644 index 0000000..a6cd63c --- /dev/null +++ b/.cargo/initial-release-patch.toml @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2026 jlreq contributors +# SPDX-License-Identifier: MIT OR Apache-2.0 + +# Used only by local package and publish dry-run verification before jlreq-core 0.1.0 +# exists in the crates.io index. Published manifests retain the version-only dependency. +[patch.crates-io] +jlreq-core = { path = "crates/jlreq-core" } diff --git a/.cargo/mutants.toml b/.cargo/mutants.toml index 4739ee8..c56cd9a 100644 --- a/.cargo/mutants.toml +++ b/.cargo/mutants.toml @@ -4,15 +4,15 @@ # These files are emitted from attested specification data. `just generate-check` and # `just attest` validate them byte-for-byte; mutation testing covers the adjacent, # handwritten `generated.rs` integrity checks. -exclude_globs = ["crates/jlreq/src/generated/**"] +exclude_globs = ["crates/jlreq-core/src/generated/**"] # Each proven-equivalent mutant is pinned to the source hash and justified individually # in docs/mutation-ledger.toml. Anchoring the full cargo-mutants name prevents a broad # expression class from being hidden by accident. exclude_re = [ - '^crates/jlreq/src/generated[.]rs:35:5: replace [|] with \^$', - '^crates/jlreq/src/generated[.]rs:36:5: replace [|] with \^$', - '^crates/jlreq/src/generated[.]rs:37:5: replace [|] with \^$', - '^crates/jlreq/src/generated[.]rs:38:5: replace [|] with \^$', - '^crates/jlreq/src/generated[.]rs:55:41: replace < with <= in ascends$', + '^crates/jlreq-core/src/generated[.]rs:35:5: replace [|] with \^$', + '^crates/jlreq-core/src/generated[.]rs:36:5: replace [|] with \^$', + '^crates/jlreq-core/src/generated[.]rs:37:5: replace [|] with \^$', + '^crates/jlreq-core/src/generated[.]rs:38:5: replace [|] with \^$', + '^crates/jlreq-core/src/generated[.]rs:55:41: replace < with <= in ascends$', ] diff --git a/.github/REPOSITORY-SETTINGS.md b/.github/REPOSITORY-SETTINGS.md new file mode 100644 index 0000000..c7db51f --- /dev/null +++ b/.github/REPOSITORY-SETTINGS.md @@ -0,0 +1,43 @@ +# Repository settings required for release + +These controls live in GitHub and cannot be made reproducible by a workflow in this tree. +A repository administrator must verify them against the candidate and record the reviewer +and verification date in the release handoff. No crate credential belongs in these +settings during preparation. + +## Actions and branch protection + +- Require every third-party GitHub Action to be pinned to a full 40-character commit SHA. + The checked-in workflows already satisfy this; keep the platform setting enabled so a + later workflow cannot weaken it. +- Protect `main` and require the aggregate CI job plus CodeQL, dependency review, REUSE, + API/semver, all four mutation shards for all three products, and the manual Release Check + used by a candidate. +- Require the branch to be current before merge and prevent required checks from being + bypassed by ordinary maintainers. +- Confirm Dependabot has no open update pull request and code/dependency scanning has no + high or critical alert before approving a release candidate. + +## `release` environment + +- Restrict deployment branches to `main` only. +- Configure at least one required reviewer who is not the workflow initiator. +- Disable administrator/self-review bypass where the repository policy permits it. +- Do not store `CRATES_IO_TOKEN` during preparation. +- For the first publication only, add a narrowly scoped crates.io token immediately before + approval, then remove and revoke it after Trusted Publishing is verified. + +The manual [Release workflow](workflows/release.yml) names this environment and requires an +exact candidate SHA, successful Release Check run ID, version, authentication mode, and +confirmation phrase. Merging workflow files cannot trigger publication. + +## Handoff record + +Before the first irreversible action, record outside the repository: + +- repository administrator who verified these controls; +- required reviewer identity; +- verification timestamp; +- candidate commit SHA and successful Release Check run ID; and +- the result of checking crates.io names, existing tags/releases, open dependency PRs, and + high/critical alerts. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22ab41e..369f8ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,8 @@ # alternatives JLReq records; complete JIS X 4051 conformance is not claimed). # # Layout under test: -# - jlreq dependency-free no_std + alloc public library. +# - jlreq std high-level font/shaping/layout facade (MSRV 1.88). +# - jlreq-core dependency-free no_std + alloc composition core (MSRV 1.85). # - jlreq-conformance binary-only protocol runner and sample engine. # - xtask repository policy checks shared by Just and CI. # - engines/ocaml an independent reference engine, outside the Cargo workspace, @@ -303,7 +304,7 @@ jobs: conform-racket: # The independent Racket reference engine (engines/racket/README.md), on the same # terms as conform-ocaml: it shares no code with the Rust engine, builds its tables - # from spec/ rather than from crates/jlreq/src/generated/, and is gated by the + # from spec/ rather than from crates/jlreq-core/src/generated/, and is gated by the # cumulative suite engines/racket/milestones/CURRENT points at rather than by the # whole one. name: conform-racket (independent Racket engine) @@ -370,7 +371,7 @@ jobs: - uses: crate-ci/typos@8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0 msrv: - name: msrv (workspace 1.85) + name: msrv (facade 1.88; core/tooling 1.85) runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/mutants.yml b/.github/workflows/mutants.yml index 91ecc06..7ab0150 100644 --- a/.github/workflows/mutants.yml +++ b/.github/workflows/mutants.yml @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2026 jlreq contributors # SPDX-License-Identifier: MIT OR Apache-2.0 -# Weekly/manual runs cover both handwritten products in four shards. Pull-request smoke +# Weekly/manual runs cover all three handwritten Rust products in four shards. Pull-request smoke # mutation belongs to CI so its result feeds the single required aggregation gate there. # Generated tables and five exact, proof-backed equivalent mutants are the only configured # exclusions and are pinned in docs/mutation-ledger.toml; a missed or timed-out mutant fails @@ -22,14 +22,22 @@ concurrency: jobs: full: - name: full (${{ matrix.crate }}, ${{ matrix.shard }}) + name: full (${{ matrix.crate }}, shard ${{ matrix.shard.display }}/4) runs-on: ubuntu-latest timeout-minutes: 90 strategy: fail-fast: false matrix: - crate: [jlreq, jlreq-conformance] - shard: [1, 2, 3, 4] + crate: [jlreq, jlreq-core, jlreq-conformance] + shard: + - index: 0 + display: 1 + - index: 1 + display: 2 + - index: 2 + display: 3 + - index: 3 + display: 4 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable @@ -37,11 +45,11 @@ jobs: with: tool: just@1.50.0,cargo-mutants@27.1.0,nextest@0.9.140 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - - run: just mutants '${{ matrix.crate }}' '${{ matrix.shard }}/4' + - run: just mutants '${{ matrix.crate }}' '${{ matrix.shard.index }}/4' - if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: mutants-${{ matrix.crate }}-${{ matrix.shard }}-of-4 + name: mutants-${{ matrix.crate }}-${{ matrix.shard.display }}-of-4 path: mutants.out/ if-no-files-found: error retention-days: 14 diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index f713d0d..b12e266 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -55,6 +55,65 @@ jobs: - name: run every non-publishing release gate run: opam exec -- just release-check + crates: + name: crate archives, checksums, SBOMs, and provenance + needs: acceptance + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read + id-token: write + attestations: write + artifact-metadata: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + with: + tool: just@1.50.0,cargo-cyclonedx@0.5.9 + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + - name: build and offline-verify all crate archives + run: just package + - name: generate one CycloneDX SBOM per crate + run: |- + for package in jlreq-core jlreq jlreq-conformance; do + cargo cyclonedx --format json --spec-version 1.5 --license-strict --all-features \ + --manifest-path "crates/$package/Cargo.toml" + sbom=$(find "crates/$package" -maxdepth 1 -name '*.cdx.json' -print -quit) + test -n "$sbom" + jq -e --arg package "$package" --arg version "0.1.0" ' + .bomFormat == "CycloneDX" and + .specVersion == "1.5" and + .metadata.component.name == $package and + .metadata.component.version == $version + ' "$sbom" + cp "$sbom" "target/dist/$package-0.1.0.cdx.json" + done + - name: attest jlreq-core archive and SBOM + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-path: target/dist/jlreq-core-0.1.0.crate + sbom-path: target/dist/jlreq-core-0.1.0.cdx.json + - name: attest jlreq archive and SBOM + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-path: target/dist/jlreq-0.1.0.crate + sbom-path: target/dist/jlreq-0.1.0.cdx.json + - name: attest jlreq-conformance archive and SBOM + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + with: + subject-path: target/dist/jlreq-conformance-0.1.0.crate + sbom-path: target/dist/jlreq-conformance-0.1.0.cdx.json + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: release-crates + path: |- + target/dist/*.crate + target/dist/*-crates.sha256 + target/dist/*.cdx.json + if-no-files-found: error + retention-days: 30 + binaries: name: binaries (${{ matrix.target }}) needs: acceptance diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 73d675c..48a0b84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -95,31 +95,45 @@ jobs: done just package just publish-dry-run + for package in jlreq-core jlreq jlreq-conformance; do + cmp "target/release-artifacts/$package-$VERSION.crate" \ + "target/package/$package-$VERSION.crate" + done - name: obtain Trusted Publishing token if: inputs.authentication == 'trusted-publishing' id: crates_io_auth uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1 - - name: publish jlreq with first-release token + - name: publish jlreq-core with first-release token if: inputs.authentication == 'initial-token' env: CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} - run: cargo publish --locked -p jlreq - - name: publish jlreq with Trusted Publishing + run: cargo publish --locked -p jlreq-core + - name: publish jlreq-core with Trusted Publishing if: inputs.authentication == 'trusted-publishing' env: CARGO_REGISTRY_TOKEN: ${{ steps.crates_io_auth.outputs.token }} - run: cargo publish --locked -p jlreq - - name: wait for jlreq in the crates.io index + run: cargo publish --locked -p jlreq-core + - name: wait for jlreq-core in the crates.io index run: |- for attempt in $(seq 1 30); do - if cargo info "jlreq@$VERSION" >/dev/null 2>&1; then + if cargo info "jlreq-core@$VERSION" >/dev/null 2>&1; then exit 0 fi - echo "jlreq $VERSION is not visible yet (attempt $attempt/30)" + echo "jlreq-core $VERSION is not visible yet (attempt $attempt/30)" sleep 10 done - echo "jlreq $VERSION was uploaded but did not become visible; inspect crates.io before retrying" >&2 + echo "jlreq-core $VERSION was uploaded but did not become visible; inspect crates.io before retrying" >&2 exit 1 + - name: publish jlreq with first-release token + if: inputs.authentication == 'initial-token' + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} + run: cargo publish --locked -p jlreq + - name: publish jlreq with Trusted Publishing + if: inputs.authentication == 'trusted-publishing' + env: + CARGO_REGISTRY_TOKEN: ${{ steps.crates_io_auth.outputs.token }} + run: cargo publish --locked -p jlreq - name: publish jlreq-conformance with first-release token if: inputs.authentication == 'initial-token' env: @@ -130,7 +144,7 @@ jobs: env: CARGO_REGISTRY_TOKEN: ${{ steps.crates_io_auth.outputs.token }} run: cargo publish --locked -p jlreq-conformance - - name: create tag and GitHub Release after both uploads + - name: create tag and GitHub Release after all three uploads run: |- awk -v version="$VERSION" ' $0 ~ "^## \\[" version "\\]" { capture = 1; next } @@ -143,8 +157,6 @@ jobs: git push origin "v$VERSION" gh release create "v$VERSION" \ target/release-artifacts/* \ - target/package/jlreq-"$VERSION".crate \ - target/package/jlreq-conformance-"$VERSION".crate \ --target "$CANDIDATE_COMMIT" \ --title "jlreq $VERSION" \ --notes-file target/release-notes.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 25f96f5..610664d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,159 +1,162 @@ # Architecture -This document describes the 0.1.0 implementation and its mechanically enforced invariants. +This document describes the prepared 0.1.0 implementation and the boundaries enforced by +the repository gates. -## Boundary in the text stack +## Three product layers ```text -font I/O + shaping + UAX #14 + bidi renderer / PDF / game engine - │ ▲ - └── shaped clusters + break opportunities ──┤ - │ - jlreq ───── placements + diagnostics +UTF-8 + explicit font bytes + │ + ▼ + jlreq (MSRV 1.88) + graphemes · fallback · bidi · shaping · line opportunities + │ validated pre-shaped clusters and constructs + ▼ + jlreq-core (MSRV 1.85, no_std + alloc, no dependencies) + JLReq classification · kinsoku · spacing · adjustment · composition + │ + ▼ + TextLayout: visual-order glyphs + owned fonts + diagnostics + │ + ▼ + caller renderer / PDF writer / game engine + + jlreq-conformance ── protocol-v1 black-box validation of any engine ``` -`jlreq` never loads a font, shapes a glyph, discovers a Unicode break opportunity, -resolves paragraph bidi, or draws. The caller supplies those answers. The library owns -Japanese normalization, spacing, construct behavior, line choice, and physical placement. +The layers have different users and different compatibility surfaces: -## Product boundary +1. `jlreq` is the high-level facade. It accepts text and font resources, hides Fontique, + HarfRust, ICU4X, and unicode-bidi types, and returns renderer-independent physical glyph + placement. +2. `jlreq-core` is the deterministic composition engine for callers that already own + shaping and Unicode preprocessing. Its former public items and result model remain + available under their new crate path and through `jlreq::core`. +3. `jlreq-conformance` is binary-only. It validates implementations through the existing + `jlreq.conformance/1` NDJSON protocol and never becomes an in-process dependency of + either library. -There are two public contracts: +`xtask`, `fuzz/`, and the independent implementations under `engines/` are repository +tooling rather than products. -1. `jlreq`, a dependency-free Edition 2024 library with MSRV 1.85 and `#![no_std]` plus - `alloc`; -2. `jlreq-conformance`, a binary-only product that communicates with any engine through - the versioned NDJSON protocol. +## High-level processing order -`xtask` is repository tooling and is not a product crate. `engines/` — independent -reference implementations of the protocol, starting with `engines/ocaml/` — is tooling in -the same sense: neither a Cargo workspace member nor a product, and out of scope for every -gate whose scope is the Cargo graph (`purity`, `api`, `direction`, `derive`, `generate`, -`deny`, `shear`, `msrv`). See -[docs/design/conformance.md](docs/design/conformance.md#independent-reference-engines) and -[ADR 0024](docs/adr/0024-independent-reference-engines.md). +The facade performs these steps in a fixed order: -## Logical pipeline +1. Split paragraphs while preserving every original UTF-8 byte range, including CRLF and + Unicode paragraph separators. +2. Itemize extended graphemes and script, then choose the first fallback face that covers + the whole grapheme or variation sequence. +3. Resolve paragraph bidi with UAX #9. +4. Shape font/script/direction runs with HarfRust and map glyph clusters back to source + ranges. +5. Merge UAX #14 opportunities, mandatory breaks, tabs, and explicitly prohibited breaks. +6. Lower typed inline structures and ask `jlreq-core` to apply kinsoku, punctuation + spacing, line adjustment, and exact composition. +7. Reorder each completed line visually and convert logical placement into horizontal or + vertical physical coordinates. -The implementation is one directional pipeline: +Automatic semantic classification is deliberately conservative: obvious decimal points, +digit separators, and sentence punctuation may be classified, but units, quantities, +formulae, ruby, and other authored meaning require `DocumentBuilder`. + +`layout` constructs a fresh engine for one call. `LayoutEngine` reuses parsed fonts, +shaper state, Unicode services, and core scratch allocation. Cache state is never part of +an answer, so the two paths are bit-identical and an engine remains reusable after a typed +failure. + +## Font and shaping boundary + +`FontLibrary` owns immutable font bytes and registers a TTF/OTF face or an indexed TTC +face with family and style metadata. Its primary face is both the first candidate and the +source of `.notdef`; an explicit fallback order controls all later candidates. Fallback is +performed on a complete extended grapheme. If no face covers it, the grapheme and source +range remain present, a primary `.notdef` is shaped, and a positioned +`font.missing-glyph` diagnostic is returned. + +Fontique-backed OS discovery is behind the disabled-by-default `system-fonts` feature. +Layout is cross-platform deterministic when the same explicit bytes, face indices, text, +and options are supplied. An OS collection may change or choose a different face and is +therefore outside that guarantee. + +OpenType feature tags and variation coordinates are facade values. No upstream crate type +crosses the public API. + +## Documents and results + +`DocumentBuilder` validates UTF-8 ranges, non-overlapping spans, explicit break controls, +and nine typed inline structures: ruby (mono, group, and jukugo), tate-chu-yoko, emphasis +dots, warichu, furawake, jidori, reference marks, scripts, and formulae. Annotation strings +are shaped by the same font, fallback, and bidi machinery as body text. + +`TextLayout` owns its source, lines, diagnostics, and every `FontResource` used by its +glyphs. Each `GlyphPlacement` has visual draw order, font and glyph IDs, original UTF-8 +range, physical origin, advance, offset, transform, and bidi level. `hit_test`, +`caret_rect`, and `selection_rects` use that same geometry for horizontal, vertical, +and mixed-direction text. + +The facade does not rasterize, draw, allocate GPU resources, or serialize a document. + +## Numeric and resource invariants + +Floating-point input is accepted only at public convenience boundaries. NaN, infinity, +negative values where forbidden, and values outside the representable range are rejected. +Valid values are immediately rounded to signed 26.6 fixed point. All shaping, composition, +result comparison, and physical conversion use those quantized values. + +`ResourceLimits` independently bounds input bytes, font count, total font bytes, +paragraphs, shaping runs, glyphs, constructs, and core operations. The core additionally +bounds clusters, break candidates, constructs, tab stops, and charged exact-search +transitions. A limit or validation failure returns no partial layout and mutates no +caller-owned object. Scratch and cache state remain valid for the next engine call. + +## Core module direction + +The dependency-free core retains its one-way implementation pipeline: ```text -model/style/limits → spec → normalize/rules → construct → compose → place → pipeline → API +model/style/limits → spec → normalize/rules → construct + → paragraph → compose/place → layout → public API ``` -The present source groups some adjacent stages into files, but ownership follows this -direction: - -- `model` owns caller-unit sizes, frames, writing modes, shaped clusters, and input errors; -- `style` owns the 22 typed decisions and dated profiles; -- `limits` owns deterministic composition resource bounds and their typed failure; -- `generated` contains reproducible tables, while `spec` gives them private queries; -- `normalize` validates shaped text and joins Appendix A two-code-point keys without losing original cluster - attribution; -- `construct` owns the nine opaque document structures; -- `paragraph` jointly validates breaks, constructs, tabs, and paragraph policy; -- `layout` owns renderer-facing read-only result views; -- `pipeline` performs private classification, spacing, construct lowering, optimal - composition, and placement; -- `lib` is the only API layer and re-exports exactly `docs/public-api.toml`. - -No classification, seam, adjustment stage, feasibility score, ladder, badness, or rule ID is -public. A caller builds `ShapedText`, validates a `Paragraph`, calls `compose`, and draws the -returned views; it never wires internal stages together. - -## Input invariants - -All public scalar geometry is bounded `i32` in the caller's unit. Intermediate sums and -costs use checked or saturating `i64`; floating point is rejected by the purity gate. The -private specification unit remains 1/720 em where specification fractions are needed. - -Every public source coordinate is a UTF-8 byte offset or range. `ShapedText::new` verifies: - -- exact, ordered, non-overlapping source coverage; -- UTF-8 boundaries; -- non-negative shaped advances; -- valid per-cluster sizes and explicit metric frames; and -- the distinction between a proportional ligature and a non-Latin cluster hiding multiple - Appendix A keys. - -Appendix A two-code-point keys may arrive split across shaping clusters. Normalization makes -the internal key indivisible while placements still point back to the original shaped -clusters. Breaks and constructs cannot split such a key. - -`ParagraphBuilder` jointly validates the line extent, indent, break kinds, nested/disjoint -construct ranges, ruby runs, line-tab stops, widow policy, alignment, and writing mode. -`compose` returns a complete exact layout or a typed resource error. Unsatisfied fit and -quality constraints are represented by placements plus stable diagnostics; exhausting a -caller-visible resource limit is atomic and returns no partial layout. - -## Composition and placement - -The only search policy is exact whole-paragraph optimization. A prepared paragraph caches -cluster ordinals, construct ownership, legal breaks, mandatory partitions, widths, and -adjustment capacities. Mandatory breaks partition dynamic programming; prefix/range -queries make ordinary edges constant-time, while tabs and annotation structures charge -work proportional to the special elements they touch. Integer lower bounds prune only -provably dominated edges. There is no approximate or first-fit fallback. - -`CompositionLimits` bounds clusters, break candidates, constructs, tab stops, and charged -search transitions. Defaults are 65,536 clusters and break candidates, 4,096 constructs -and tab stops, and 8,000,000 transitions. These bounds make memory and CPU refusal -deterministic for hostile input while leaving ordinary 10,000-cluster paragraphs well -inside the transition budget. - -Tabs take part in measurement and placement rather than being a second line API. Their -alignment is expressed on the logical inline axis, so the same stops work in horizontal and -vertical writing. - -`Layout`, `Line`, `ClusterPlacement`, and `Attachment` have private fields and read-only -accessors. A placement identifies the original cluster or construct ordinal, byte range, -logical inline/block coordinates, advance, size, frame, local writing mode, and transform. -This is sufficient to draw vertical proportional glyphs and tate-chu-yoko without -reshaping. - -## Style compatibility - -Every alternative in `spec/derived/questions.tsv` maps one-to-one to a dedicated -`#[non_exhaustive]` enum in `jlreq::style`. `StyleBuilder::build` rejects contradictory -combinations. Generic string settings, public `Question`/`Choice` values, and internal rule -IDs are excluded. - -`Style::default()` is permanently `Style::jlreq_2020()`. A future JLReq revision adds a new -dated profile; it does not alter an existing profile. `docs/public-api.toml` maps all 22 enum -names and counts back to generated specification data, and the API gate compares that -mapping in both directions. - -## Diagnostics and conformance - -An `InputError` means the caller supplied an invalid representation. A `Diagnostic` means a -validated paragraph was placed but a stable observable condition should be reported. Only -the diagnostic code, severity, input range, and JLReq reference are contracted; private -rule sequences and adjustment steps are not. - -The process protocol deliberately describes only inputs and observable outputs. It never -asks an external implementation to expose classes, internal seams, or algorithm stages. -The protocol and specification identifiers are mandatory on every message so an old engine -cannot accidentally be judged against a new suite. +All core source coordinates are UTF-8 byte offsets or ranges; geometry is bounded integer +caller units. `ShapedText` validates ordered complete source coverage, cluster boundaries, +advances, sizes, and metric frames. `ParagraphBuilder` jointly validates line extent, +breaks, nested/disjoint constructs, ruby associations, tab stops, widow policy, alignment, +and writing mode. Composition returns a complete exact `Layout` or a typed error. It +never switches to approximate first-fit behavior. + +The 22 JLReq 2020 policy choices remain dedicated enums under +`jlreq_core::style` (also `jlreq::core::style`). `Style::default()` remains identical +to `Style::jlreq_2020()`; a future specification revision adds a dated profile instead of +changing an existing one. + +## Conformance and specification identity + +The process protocol describes only observable input and output, not internal classes or +algorithm stages. Every message carries `jlreq.conformance/1` and +`jlreq-2020-08-11+unicode-17.0.0`. Moving the Rust core did not change either identifier. +The conformance inventory remains 100 covered rules out of 106, with the other six +explicitly classified in the ledger. ## Mechanical enforcement -- `purity`: no `std`, I/O, font dependency, floating point, or undeclared dependency edge - in the core; -- `api`: 0.1.0 exports and the 22 typed Style mappings; -- `direction`: the private module graph follows the declared one-way layers; -- `placeholder`: no unwritten body or lint suppression in core code; -- `derive`, `generate`, `attest`: reproducible specification derivation and transcription - provenance; -- `conform`: every observable inventoried rule has a protocol-v1 black-box case, and every - excluded rule has an evidence-backed editorial/non-observable classification; -- `repository`: packages remain publishable at `0.1.0` while external release actions stay - disabled, stable code documentation matches product literals, tracked UTF-8 files use - LF, and every local Markdown link resolves; -- `coverage`: handwritten product code stays above 90% line and 85% region coverage; -- `mutants`: generated artifacts are ledgered out and no handwritten mutant is missed or - times out; -- normal Rust tests: invalid input, all style choices, all constructs in both directions, - paragraph search, placement, and protocol behavior. - -The gate rejects additions and removals from the public surface unless -`docs/public-api.toml` changes deliberately. +- `purity`: `jlreq-core` has no dependencies, `std`, I/O, fonts, or floating point; + facade and conformance dependencies are explicit and closed. +- `api` and `semver`: the facade and core 0.1.x exports match + `docs/public-api.toml`; later patches compare both public libraries to the registry. +- `direction` and `placeholder`: core modules follow the declared graph and contain no + unfinished body or lint suppression. +- `derive`, `generate`, and `attest`: specification data and generated core tables are + reproducible and provenance-bound. +- `conform`: protocol schema, cases, inventory, and specification identifiers agree. +- `coverage`, `mutants`, and fuzzing: all handwritten products are exercised; generated + exclusions and equivalent mutations are individually ledgered. +- `package`: all three archives are extracted into isolated Cargo homes after a locked + fetch, then built, tested, documented, and installed offline. +- `repository`, REUSE, deny, CodeQL, actionlint, and zizmor hold publication state, + licensing, dependency policy, links, workflow pinning, and security hygiene. + +No gate uploads a crate, writes a tag, creates a GitHub Release, chooses a date, or stores a +credential. diff --git a/CHANGELOG.md b/CHANGELOG.md index f22f409..c9ffba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,3 @@ - - # Changelog All notable user-facing changes are documented here. The format follows @@ -13,37 +7,50 @@ chronology is archived in [DEVELOPMENT-HISTORY.md](DEVELOPMENT-HISTORY.md). ## [Unreleased] -These are the completed 0.1.0 release notes. Publication will replace this heading with the -approved release date; no crate upload, tag, or GitHub Release has occurred yet. +These are the completed 0.1.0 release-candidate notes. The publication date is deliberately +unset. No crate upload, tag, or GitHub Release has occurred. ### Added -- A dependency-free `no_std + alloc` `jlreq` library for exact integer Japanese paragraph - composition over caller-shaped UTF-8 clusters. -- Horizontal and vertical placement, nine inline construct types, mono/group/jukugo ruby, - tabs, widow control, diagnostics, and 22 typed JLReq 2020 style choices. -- Deterministic `CompositionLimits`, reusable `Composer`, and typed `ComposeError` with - stable code, resource, limit, and observed-count fields. -- The binary-only `jlreq-conformance` protocol-v1 runner, built-in suite, JSON Schema, and - sample engine, with streaming transport, bounded input/output, inactivity timeout, and - order-independent response matching by unique case ID. -- Independent OCaml and Racket engines plus ten generated three-way censuses containing - 122,199 cases with zero expected differences. -- Reproducible specification generation and attestation, API/error-code controls, coverage, - mutation, fuzzing, package, MSRV, `no_std`, WASM, and release-artifact gates. +- `jlreq`, a high-level MSRV 1.88 Rust facade that turns UTF-8 text and in-memory + TTF/OTF/TTC data into renderer-independent visual-order glyph placements. +- `FontLibrary`, one-shot `layout`, reusable `LayoutEngine`, typed `LayoutOptions`, + explicit family/style/fallback configuration, OpenType features and variations, and an + opt-in `system-fonts` feature. +- Grapheme and variation-sequence fallback, UAX #9 bidi, HarfRust shaping, UAX #14 line + opportunities, paragraph preservation, tabs, horizontal/vertical physical placement, + Latin rotation, and tate-chu-yoko. +- `DocumentBuilder` spans, explicit break controls, mono/group/jukugo ruby, emphasis, + warichu, furawake, jidori, reference marks, scripts, and formulae; annotation strings are + shaped automatically. +- `TextLayout`, `TextLine`, and `GlyphPlacement` with owned font resources, original + UTF-8 ranges, 26.6 geometry, transforms and bidi levels, plus hit testing, caret + rectangles, and selection rectangles. +- Typed high-level errors, positioned missing-glyph diagnostics, configurable limits for + input/font bytes, fonts, paragraphs, runs, glyphs, constructs, and core operations, and + atomic failure with engine reuse. +- `jlreq-conformance`, the binary-only protocol-v1 runner and sample engine, plus + independent OCaml and Racket engines and generated three-way censuses. +- Reproducible specification generation, API/semver controls, coverage, mutation, fuzz, + CodeQL, dependency/license, isolated offline package, SBOM, checksum, and provenance + gates. ### Changed -- The final package and binary names are `jlreq`, `jlreq-conformance`, and - `jlreq-sample-engine`; older experimental multi-crate names are not supported. -- `compose` and `Composer::compose` return `Result`. Callers must - handle deterministic resource refusal; successful values are always complete exact - layouts. There is no infallible compatibility wrapper. +- The dependency-free pre-shaped composition library is now `jlreq-core` (MSRV 1.85, + `no_std + alloc`); the same low-level surface is also accessible as `jlreq::core`. +- Public-library responsibilities are separated from the conformance process contract. + Fontique 0.11.1, HarfRust 0.13.3, ICU4X segmenter 2.3.0, and unicode-bidi 0.3.18 are + facade implementation dependencies and do not enter the core. +- Release packaging produces and offline-verifies three crate archives. Future publication + order is `jlreq-core`, registry visibility, then `jlreq` and + `jlreq-conformance`. ### Security -- Exact search is charged against a transition budget; parser/message/suite/case limits, - concurrent stderr draining, bounded stderr retention, watchdog termination, and process - cleanup prevent untrusted input or engines from causing unbounded work or pipe deadlock. +- Malformed fonts and arbitrary text are explicit threat-model inputs. Finite-range checks + precede deterministic 26.6 quantization, and bounded work returns no partial result. +- The conformance runner bounds messages, suites, case counts, stderr retention, inactivity, + and child-process cleanup. [Unreleased]: https://github.com/P4suta/jlreq/compare/v0.1.0...HEAD diff --git a/Cargo.lock b/Cargo.lock index 86e8cba..d0a6a29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,12 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "core_maths" version = "0.1.1" @@ -48,6 +54,30 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-test-data" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8250a122865fc22377a26c0bb273bc4a69914409b4cf4a9b4cd7d7c6c5b1a0d7" +dependencies = [ + "font-types", +] + [[package]] name = "font-types" version = "0.12.4" @@ -57,71 +87,103 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "fontique" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6688bc1294fe7117d788937b6c53480169b29c566954af490830d4c09da9516a" +dependencies = [ + "hashbrown", + "linebender_resource_handle", + "memmap2", + "objc2", + "objc2-core-foundation", + "objc2-core-text", + "objc2-foundation", + "parlance", + "read-fonts 0.41.0", + "roxmltree", + "smallvec", + "windows", + "windows-core", + "yeslogic-fontconfig-sys", +] + [[package]] name = "harfrust" -version = "0.13.0" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddc6e68be24399b1a9d489f71fd43f6954558294882234c90a322b857e10c63" +checksum = "948d0741125ba89cd3e1c23e5642415b6ade7e1d29d67ba25fb925b533e989d6" dependencies = [ "bitflags", "bytemuck", - "read-fonts", + "once_cell", + "read-fonts 0.43.3", "smallvec", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locale" -version = "2.1.1" +name = "icu_locale_core" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_locale_data", - "icu_provider", - "potential_utf", + "displaydoc", + "litemap", + "serde", "tinystr", + "writeable", "zerovec", ] [[package]] -name = "icu_locale_core" -version = "2.1.1" +name = "icu_locale_fallback" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" dependencies = [ - "displaydoc", - "litemap", - "serde", + "icu_locale_core", + "icu_locale_fallback_data", + "icu_provider", + "potential_utf", "tinystr", - "writeable", "zerovec", ] [[package]] -name = "icu_locale_data" -version = "2.1.2" +name = "icu_locale_fallback_data" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831" +checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", @@ -136,25 +198,26 @@ dependencies = [ [[package]] name = "icu_segmenter" -version = "2.1.2" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a807a7488f3f758629ae86d99d9d30dce24da2fb2945d74c80a4f4a62c71db73" +checksum = "82d07aafccd67af15d02512a6adf5896fbc5ed00f2e99b471d2efa14016db3db" dependencies = [ "core_maths", "icu_collections", - "icu_locale", + "icu_locale_fallback", "icu_provider", "icu_segmenter_data", "potential_utf", + "smallvec", "utf8_iter", "zerovec", ] [[package]] name = "icu_segmenter_data" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ebbb7321d9e21d25f5660366cb6c08201d0175898a3a6f7a41ee9685af21c80" +checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad" [[package]] name = "itoa" @@ -165,6 +228,15 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jlreq" version = "0.1.0" +dependencies = [ + "font-test-data", + "fontique", + "harfrust", + "icu_segmenter", + "jlreq-core", + "rwml-fonts", + "unicode-bidi", +] [[package]] name = "jlreq-conformance" @@ -172,16 +244,42 @@ version = "0.1.0" dependencies = [ "harfrust", "icu_segmenter", - "jlreq", + "jlreq-core", "serde_json", ] +[[package]] +name = "jlreq-core" +version = "0.1.0" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + [[package]] name = "litemap" version = "0.8.2" @@ -194,12 +292,77 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "parlance" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6937eda350acc1a5d05872c3cbf99fe78619c269096e2be3d4a350058639d5" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + [[package]] name = "potential_utf" version = "0.1.5" @@ -231,15 +394,41 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.43.2" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "315132758c032b03a4d3a3ada9289d5608f2ff16c91157888afc8c8e8e9bb209" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" dependencies = [ "bytemuck", "font-types", "once_cell", ] +[[package]] +name = "read-fonts" +version = "0.43.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005c8acf251756c478b0bf402885bfd88a1476020c4c7e6060edc1aa68da38ea" +dependencies = [ + "bytemuck", + "font-types", + "once_cell", +] + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rwml-fonts" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf94ed9173a29f5326edc98180b3df5e4e6bbbdfa3e8edb8b4ca60f6ec1165f6" + [[package]] name = "serde" version = "1.0.229" @@ -330,15 +519,21 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "serde_core", "zerovec", ] +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -351,16 +546,137 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "xtask" version = "0.1.0" +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "yoke" version = "0.8.3" @@ -407,9 +723,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "zerovec", @@ -417,9 +733,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "serde", "yoke", @@ -429,13 +745,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1aa6c36..463bbd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,12 @@ [workspace] resolver = "3" -members = ["crates/jlreq", "crates/jlreq-conformance", "xtask"] +members = [ + "crates/jlreq", + "crates/jlreq-core", + "crates/jlreq-conformance", + "xtask", +] # cargo-fuzz requires nightly and a sanitizer runtime. exclude = ["fuzz"] diff --git a/DEVELOPMENT-HISTORY.md b/DEVELOPMENT-HISTORY.md index 7adb424..28acd54 100644 --- a/DEVELOPMENT-HISTORY.md +++ b/DEVELOPMENT-HISTORY.md @@ -16,7 +16,7 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). closes [#26](https://github.com/P4suta/jlreq/issues/26) — the first coordinate at which two reference engines disagreed with *each other* rather than one lagging the other two, and the one the entry below left out of the census on purpose. - `crates/jlreq/src/pipeline.rs` offered `reduction_sites` every interior boundary of a + `crates/jlreq-core/src/pipeline.rs` offered `reduction_sites` every interior boundary of a stacked structure, the seam included, so a line carrying `※〈〉あ※` at a three-em measure divided its 250 units of arrears over the one boundary it was composed from and one inside the block that carries nothing: it gave back 125, ended 125 units over its @@ -103,13 +103,13 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). tate-chu-yoko run, which runs across it, or a warichu's and a furawake's sublines, which run beside it — is not a sign of the line: it takes no stop, sets the advance it was shaped with, and never chooses §3.6.3's cut, the structure's *first* character included. - Two coordinates where `crates/jlreq/src/pipeline.rs` contradicted itself are closed by + Two coordinates where `crates/jlreq-core/src/pipeline.rs` contradicted itself are closed by this, both found by the independent OCaml and Racket engines and filed as [#12](https://github.com/P4suta/jlreq/issues/12) and [#13](https://github.com/P4suta/jlreq/issues/13). A sign that *opened* a tate-chu-yoko run ended the line — which only §3.6.3's fourth case does, and only to a sign of the line — and was then set as a member of the run on the next line; `validate_breaks` in - `crates/jlreq/src/paragraph.rs` no longer offers §3.6.3's cut there. Inside a warichu or a + `crates/jlreq-core/src/paragraph.rs` no longer offers §3.6.3's cut there. Inside a warichu or a furawake, `apply_tabs` stepped a tab cursor once per character of the block while the line set the block at one position, so the *next* sign's advance was measured from a cursor that was not where the sign stood; the tab cursor and the placement cursor are now one @@ -135,7 +135,7 @@ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). documentation URLs. `SPECIFICATION` (`jlreq-2020-08-11+unicode-17.0.0`), `Style::jlreq_2020` and the `jlreq-2020` profile are JLReq revision identifiers and are unchanged. The copyright line is an input the generation ledger hashes, so all ten `spec/derived/*.tsv` - files, all ten `crates/jlreq/src/generated/*.rs` modules and `data/manifest.toml` were + files, all ten `crates/jlreq-core/src/generated/*.rs` modules and `data/manifest.toml` were regenerated by `just derive` and `just generate`. Recorded in [ADR 0023](docs/adr/0023-the-project-is-named-jlreq.md), which amends [ADR 0022](docs/adr/0022-unified-public-crate-and-process-conformance.md) in name only — diff --git a/Justfile b/Justfile index d92a1c5..936dcfa 100644 --- a/Justfile +++ b/Justfile @@ -8,11 +8,11 @@ set windows-shell := ["powershell.exe", "-NoLogo", "-NoProfile", "-Command"] export RUSTDOCFLAGS := "-D warnings" # The layout core must stay free of std, I/O, and font access (docs/adr/0001). -core_crates := "-p jlreq" +core_crates := "-p jlreq-core" -# Mutation testing covers both handwritten Rust products. Generated tables and repository +# Mutation testing covers all three handwritten Rust products. Generated tables and repository # tooling have independent generation/attestation gates. -mutant_crates := "-p jlreq -p jlreq-conformance" +mutant_crates := "-p jlreq -p jlreq-core -p jlreq-conformance" # A developer must be able to inspect an archive before committing; CI packages a clean # checkout and therefore deliberately omits Cargo's dirty-tree escape hatch. @@ -74,6 +74,11 @@ census_summary := "docs/generated/conformance-summary.md" default: @just --list +# Convert the completed Unreleased notes into a dated release only after maintainers choose +# the real publication date. This edits CHANGELOG.md but never uploads, tags, or releases. +finalize-release date: + sh scripts/finalize-release.sh {{ quote(date) }} + # Format the workspace. fmt: cargo fmt --all @@ -96,16 +101,16 @@ lint: # which nextest currently executes. test: cargo nextest run --workspace --all-features - cargo test -p jlreq --lib pipeline::tests::ten_thousand_cluster_standard_paragraph_stays_below_the_search_budget -- --ignored --exact - cargo test --release -p jlreq --lib pipeline::tests::zero_width_pathological_paragraph_stops_at_the_default_search_budget -- --ignored --exact + cargo test -p jlreq-core --lib pipeline::tests::ten_thousand_cluster_standard_paragraph_stays_below_the_search_budget -- --ignored --exact + cargo test --release -p jlreq-core --lib pipeline::tests::zero_width_pathological_paragraph_stops_at_the_default_search_budget -- --ignored --exact cargo test -p jlreq-conformance --test transport --all-features cargo test --workspace --doc --all-features # Run the complete test suite with the non-fail-fast CI profile. test-ci: cargo nextest run --profile ci --workspace --all-features - cargo test -p jlreq --lib pipeline::tests::ten_thousand_cluster_standard_paragraph_stays_below_the_search_budget -- --ignored --exact - cargo test --release -p jlreq --lib pipeline::tests::zero_width_pathological_paragraph_stops_at_the_default_search_budget -- --ignored --exact + cargo test -p jlreq-core --lib pipeline::tests::ten_thousand_cluster_standard_paragraph_stays_below_the_search_budget -- --ignored --exact + cargo test --release -p jlreq-core --lib pipeline::tests::zero_width_pathological_paragraph_stops_at_the_default_search_budget -- --ignored --exact cargo test -p jlreq-conformance --test transport --all-features cargo test --workspace --doc --all-features @@ -113,19 +118,21 @@ test-ci: doc: cargo doc --workspace --all-features --no-deps -# Build and verify both public crate archives. The temporary crates.io patch lets Cargo verify -# the exact-version inter-crate dependency before jlreq has actually been uploaded. +# Build and verify all three public crate archives. Temporary crates.io patches validate the +# exact-version core dependency before the initial release reaches the registry. package: - cargo package -p jlreq --locked {{package_dirty}} - cargo package -p jlreq-conformance --locked {{package_dirty}} --offline --config 'patch.crates-io.jlreq.path="crates/jlreq"' + cargo package -p jlreq-core --locked {{package_dirty}} + cargo package -p jlreq --locked {{package_dirty}} --offline --config .cargo/initial-release-patch.toml + cargo package -p jlreq-conformance --locked {{package_dirty}} --offline --config .cargo/initial-release-patch.toml sh scripts/verify-crates.sh # Ask Cargo to execute its complete crates.io publication preflight while retaining the # upload locally. The patch validates jlreq-conformance before the first jlreq release has # appeared in the index; published metadata still carries only version 0.1.0. publish-dry-run: - cargo publish --dry-run --locked {{package_dirty}} -p jlreq - cargo publish --dry-run --locked {{package_dirty}} -p jlreq-conformance --config 'patch.crates-io.jlreq.path="crates/jlreq"' + cargo publish --dry-run --locked {{package_dirty}} -p jlreq-core + cargo publish --dry-run --locked {{package_dirty}} -p jlreq --config .cargo/initial-release-patch.toml + cargo publish --dry-run --locked {{package_dirty}} -p jlreq-conformance --config .cargo/initial-release-patch.toml # Compile no-default, every individual feature, and representative feature pairs. feature-matrix: @@ -150,6 +157,7 @@ fuzz-check: {{ if os() == "windows" { "cargo +nightly check --manifest-path fuzz/Cargo.toml --bins" } else { "just _fuzz-target input_validation 30" } }} {{ if os() == "windows" { "cargo +nightly check --manifest-path fuzz/Cargo.toml --bins" } else { "just _fuzz-target composition 30" } }} {{ if os() == "windows" { "cargo +nightly check --manifest-path fuzz/Cargo.toml --bins" } else { "just _fuzz-target protocol_parser 30" } }} + {{ if os() == "windows" { "cargo +nightly check --manifest-path fuzz/Cargo.toml --bins" } else { "just _fuzz-target high_level_layout 30" } }} # The install-action cargo-fuzz binary is itself built for musl. cargo-fuzz 0.13.2 # otherwise mistakes that build triple for the fuzz target, but ASan requires the @@ -158,6 +166,7 @@ fuzz-check-linux-ci: just _fuzz-target-linux input_validation 30 just _fuzz-target-linux composition 30 just _fuzz-target-linux protocol_parser 30 + just _fuzz-target-linux high_level_layout 30 # A single bounded fuzz target. Runtime corpora are disposable target/ state; only # fuzz/seeds is reviewed and committed. @@ -178,14 +187,16 @@ fuzz-scheduled: just _fuzz-target-linux input_validation 900 just _fuzz-target-linux composition 900 just _fuzz-target-linux protocol_parser 900 + just _fuzz-target-linux high_level_layout 900 # Each handwritten product must independently stay above both release thresholds. Generated # tables, test fixtures, xtask, and independent engines are covered by their own gates. The # transport regression deliberately kills its stalled synthetic engine, so LLVM may see that # one incomplete profile; `all` still rejects a run in which no valid profile can be merged. coverage: - cargo llvm-cov -p jlreq --all-features --ignore-filename-regex '(/src/generated/|/tests/)' --fail-under-lines 90 --fail-under-regions 85 --summary-only - cargo llvm-cov -p jlreq-conformance --all-features --exclude-from-report jlreq --ignore-filename-regex '(/tests/)' --failure-mode all --fail-under-lines 90 --fail-under-regions 85 --summary-only + cargo llvm-cov -p jlreq-core --all-features --ignore-filename-regex '(/src/generated/|/tests/)' --fail-under-lines 90 --fail-under-regions 85 --summary-only + cargo llvm-cov -p jlreq --all-features --exclude-from-report jlreq-core --ignore-filename-regex '(/tests/)' --fail-under-lines 90 --fail-under-regions 85 --summary-only + cargo llvm-cov -p jlreq-conformance --all-features --exclude-from-report jlreq-core --ignore-filename-regex '(/tests/)' --failure-mode all --fail-under-lines 90 --fail-under-regions 85 --summary-only # Reject std, I/O, and font dependencies in the layout core (docs/adr/0001). purity: @@ -195,13 +206,13 @@ purity: placeholder: cargo run --quiet -p xtask -- placeholder -# Hold jlreq to the exact 0.1.0 export surface and all 22 typed Style mappings. +# Hold jlreq and jlreq-core to the exact 0.1.0 export surface and all typed Style mappings. api: cargo run --quiet -p xtask -- api # Before the initial release, hold the local 0.1.0 control in both directions. For every -# later 0.1.x candidate, additionally compare the complete rustdoc API with the latest -# published jlreq release and reject patch-incompatible changes. +# later 0.1.x candidate, additionally compare both public libraries' complete rustdoc APIs +# with their latest published releases and reject patch-incompatible changes. semver: sh scripts/check-semver.sh @@ -270,20 +281,21 @@ actionlint: # Validate every repository-owned POSIX shell entry point, including release packaging and # the three-engine census driver. shellcheck: - shellcheck engines/census-all.sh scripts/*.sh + shellcheck engines/census-all.sh scripts/check-semver.sh scripts/finalize-release.sh scripts/package-binaries.sh scripts/run-mutation-smoke.sh scripts/verify-crates.sh scripts/verify-mutation-ledger.sh scripts/verify-release-state.sh # Reject high-severity GitHub Actions and Dependabot security findings without # granting the auditor network or repository credentials. zizmor: zizmor --offline --persona regular --min-severity high . -# Verify every workspace crate at the shared declared MSRV. +# Verify each workspace crate at its own declared MSRV (facade 1.88, core 1.85). msrv: cargo msrv verify --path crates/jlreq + cargo msrv verify --path crates/jlreq-core cargo msrv verify --path crates/jlreq-conformance cargo msrv verify --path xtask -# Mutation-test both handwritten products, or one package/shard when supplied. Generated +# Mutation-test all handwritten products, or one package/shard when supplied. Generated # table and exact equivalent-mutant exclusions are pinned in docs/mutation-ledger.toml. Any # missed or timed-out mutant makes cargo-mutants, and therefore this gate, fail. # @@ -299,7 +311,7 @@ msrv: # `no_std` boundary — see the milestone report for the per-crate rate. mutants crate="" shard="": sh scripts/verify-mutation-ledger.sh - cargo mutants {{ if crate == "" { mutant_crates } else { "-p " + crate } }} {{ if shard == "" { "" } else { "--shard " + shard } }} --test-tool cargo --minimum-test-timeout 120 --no-times --colors=never -j 4 + cargo mutants {{ if crate == "" { mutant_crates } else { "-p " + crate } }} {{ if shard == "" { "" } else { "--shard " + shard } }} --all-features --test-tool cargo --minimum-test-timeout 120 --no-times --colors=never -j 4 # Pull requests exercise only mutations in the changed Rust surface; weekly and release # workflows run the complete sharded gate above. @@ -531,5 +543,6 @@ release-check: just publish-dry-run just census-all just mutants jlreq + just mutants jlreq-core just mutants jlreq-conformance sh scripts/verify-release-state.sh diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..137069b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..d817195 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 7c1f30d..b1fdf3e 100644 --- a/README.md +++ b/README.md @@ -1,153 +1,182 @@ # jlreq -`jlreq` is a Japanese line-composition engine for already-shaped text. It accepts UTF-8 -source text, caller-unit cluster advances, break opportunities, and document structures; -it returns lines, placements, attachments, and stable diagnostics. +`jlreq` lays out Japanese and mixed-script UTF-8 text from in-memory fonts. Give it text, +font bytes, a font size, and a line extent; it returns visual-order glyphs with physical +coordinates, source ranges, transforms, bidi levels, and the font resources needed to draw +them. -The public surface is deliberately limited to: +The 0.1.0 workspace has three deliberately separate products: -- the dependency-free `jlreq` Rust library (`no_std + alloc`), and -- the language-independent `jlreq-conformance` process protocol. - -Font loading, shaping, UAX #14 segmentation, bidi resolution, rasterization, and drawing -remain the caller's responsibility. +| Product | Shortest path for | +| --- | --- | +| `jlreq` | applications that want shaping, fallback, bidi, line breaking, and composition in one call | +| `jlreq-core` | engines that already have shaped clusters and require dependency-free `no_std + alloc` composition | +| `jlreq-conformance` | implementations validating protocol-v1 behavior against the black-box suite | -This tree is prepared as version 0.1.0: both crate archives, binaries, release metadata, -and verification workflows can be produced without publishing. No crate upload, tag, or -GitHub Release is performed by the preparation gates. Within 0.1.x, the public Rust surface -recorded in `docs/public-api.toml`, protocol v1, stable error codes, and MSRV 1.85 are -compatibility contracts. +This tree is prepared up to the last reversible step before publication. It creates and +verifies three crate archives, six target-specific conformance binary archives, checksums, +SBOMs, and attestations in CI, but it does not upload a crate, choose the release date, +create `v0.1.0`, or create a GitHub Release. ## Quick start -```rust -use jlreq::{Break, Cluster, Frame, Paragraph, ShapedText, Size, Style}; - -let source = "日本語組版"; -let clusters = source.char_indices().map(|(start, ch)| { - Cluster::new(start..start + ch.len_utf8(), 1_000) -}); -let text = ShapedText::new(source, Size::square(1_000)?, Frame::FullEm, clusters)?; -let paragraph = Paragraph::builder(text, 4_000) - .breaks(source.char_indices().skip(1).map(|(at, _)| Break::allowed(at))) - .build()?; -let layout = jlreq::compose(¶graph, &Style::book_2020()) - .expect("this small paragraph is within the default resource limits"); -assert_eq!(layout.lines().len(), 2); -# Ok::<(), jlreq::InputError>(()) +This same example is the `jlreq` crate's compiled doctest: + +```rust,no_run +# fn quick_start(font_bytes: Vec) -> Result<(), jlreq::LayoutError> { +use jlreq::{FontLibrary, LayoutOptions}; + +let mut fonts = FontLibrary::new(); +fonts.register_font(font_bytes)?; + +let options = LayoutOptions::try_new(240.0, 16.0)?; +let layout = jlreq::layout("日本語組版 — draw-ready glyphs", &fonts, options)?; + +for glyph in layout.glyphs() { + let font = &layout.fonts()[glyph.font_id().get() as usize]; + renderer_draw( + font.bytes(), + font.face_index(), + glyph.glyph_id(), + glyph.origin(), + glyph.transform(), + ); +} +# fn renderer_draw( +# _: &[u8], _: u32, _: u32, _: jlreq::Point, _: jlreq::GlyphTransform, +# ) {} +# Ok(()) +# } ``` -All input ranges are UTF-8 byte ranges. `ShapedText` owns the source and clusters; -`ParagraphBuilder` validates ranges, breaks, tabs, writing mode, widow control, and inline -constructs once. Composition returns a complete exact `Layout` or a typed `ComposeError`. -It never returns a partial layout, approximates a placement, or silently falls back to -first-fit. Fit conditions that remain valid but cannot be improved, such as an overfull -line, still produce a complete layout with a stable diagnostic. - -Use `Composer` instead of the root `compose` function when composing repeatedly; it reuses -its search scratch space without lending it to the returned `Layout`, supports explicit -`CompositionLimits`, and remains reusable after a resource error. See the executable -[`minimal`](crates/jlreq/examples/minimal.rs), -[`Composer`](crates/jlreq/examples/composer.rs), and -[`vertical`](crates/jlreq/examples/vertical.rs) examples. The -[`reference_integration`](crates/jlreq-conformance/tests/reference_integration.rs) test -connects ICU4X byte break offsets and HarfRust glyph clusters at the intended caller seam. - -## Scope - -The library implements one paragraph pipeline: - -```text -normalize → classify/space → lower constructs → optimize breaks → place +`TextLayout` owns every `FontResource` referenced by its glyphs. A renderer therefore needs +only the returned layout: use `font_id` to select bytes and TTC face, draw `glyph_id` at the +physical origin plus offsets, and apply `GlyphTransform`. Rasterization, GPU upload, PDF +encoding, and drawing are intentionally outside the crate. + +Use `LayoutEngine` instead of `jlreq::layout` for batches. It reuses parsed fonts, shaping +data, Unicode services, and core-composer scratch space, remains reusable after an error, +and produces bit-identical results to the one-shot call. + +## Fonts, fallback, vertical text, and bidi + +`FontLibrary::register_face` records a TTF/OTF or a TTC face with family and style metadata. +The primary face supplies `.notdef`; `set_fallback_order` sets an explicit priority. The +first font that covers an entire extended grapheme (including a variation sequence) wins. +If none does, the source range is retained, the primary `.notdef` is emitted, and +`font.missing-glyph` is reported as a diagnostic. + +```rust,no_run +# fn configure(japanese: Vec, latin: Vec) -> Result<(), jlreq::LayoutError> { +use jlreq::{BaseDirection, FontLibrary, LayoutOptions, WritingMode}; + +let mut fonts = FontLibrary::new(); +let jp = fonts.register_font(japanese)?; +let latin = fonts.register_font(latin)?; +fonts.set_primary(jp)?; +fonts.set_fallback_order([jp, latin])?; + +let options = LayoutOptions::try_new(320.0, 16.0)? + .writing_mode(WritingMode::VerticalRl) + .base_direction(BaseDirection::Auto); +let layout = jlreq::layout("日本語 Latin العربية", &fonts, options)?; +assert!(layout.glyphs().all(|glyph| !glyph.source_range().is_empty())); +# Ok(()) +# } ``` -It accepts nine named inline constructs: ruby, tate-chu-yoko, emphasis dots, warichu, -furawake, jidori, reference marks, script complexes, and formulae. Ruby has explicit mono, -group, and jukugo runs. Horizontal and vertical paragraphs share the same logical inline -model; placements include the local writing mode and transform needed by a renderer. - -The 22 alternative points derived from JLReq 2020 are dedicated enums in -`jlreq::style`. There is no public generic question/choice vocabulary and no public rule -identifier. `Style::default()` is permanently equal to `Style::jlreq_2020()`; dated book, -magazine, newspaper, and JIS-reading profiles are also available. +Horizontal and vertical results use physical coordinates. Latin rotation, upright CJK, +tate-chu-yoko, per-line UAX #9 visual reordering, hit testing, caret rectangles, and +selection rectangles therefore require no logical-to-physical conversion in the caller. +OpenType features and variable-font axes can be set globally or per span. The optional +`system-fonts` feature adds Fontique-backed OS discovery; only explicit bytes carry the +cross-platform determinism guarantee. -The specification identifier is -`jlreq-2020-08-11+unicode-17.0.0`. This includes the alternatives JLReq records from JIS X -4051; it is not a claim of complete JIS X 4051 conformance. +## Typed documents and all nine inline constructs -## Language-independent conformance +`DocumentBuilder` accepts UTF-8 byte ranges. It also controls span family/style/language, +mandatory and prohibited breaks, and nine JLReq structures. Ruby, emphasis marks, +reference marks, and script annotations are ordinary strings and are shaped automatically. -`jlreq-conformance` speaks NDJSON with an external engine process. Every message carries: - -```json -{"protocol":"jlreq.conformance/1","spec":"jlreq-2020-08-11+unicode-17.0.0","id":"..."} -``` - -The fixed commands and exit codes are: - -```text -jlreq-conformance list [SUITE.ndjson] -jlreq-conformance validate [SUITE.ndjson|-] -jlreq-conformance run ENGINE [SUITE.ndjson] - -0 all cases conform / input validates -1 one or more result differences -2 input, protocol, or engine error +| Structure | Builder method | +| --- | --- | +| mono/group/jukugo ruby | `mono_ruby`, `group_ruby`, `jukugo_ruby`, or explicit `ruby` runs | +| 縦中横 | `tate_chu_yoko` | +| 圏点 | `emphasis_dots` | +| 割注 | `warichu` | +| 振分け | `furawake` | +| 字取り | `jidori` | +| 合印 | `reference_mark` | +| 添字 | `script` | +| 数式 | `formula` | + +```rust,no_run +# fn document(font_bytes: Vec) -> Result<(), jlreq::LayoutError> { +use jlreq::{DocumentBuilder, FontLibrary, LayoutOptions, ScriptPosition}; + +let text = "漢字12 注記 振分 字取 * H2O x+y"; +let mut doc = DocumentBuilder::new(text); +doc.group_ruby(0..6, "かんじ")?; +doc.tate_chu_yoko(6..8)?; +doc.emphasis_dots(9..15, '・')?; +doc.warichu(16..22)?; +doc.furawake(23..29, 2, 1.0)?; +doc.jidori(30..36, 4)?; +doc.reference_mark(37..38, "※")?; +doc.script(39..42, "2", ScriptPosition::Subscript)?; +doc.formula(43..46)?; + +let mut fonts = FontLibrary::new(); +fonts.register_font(font_bytes)?; +let layout = jlreq::layout_document( + &doc.build()?, + &fonts, + LayoutOptions::try_new(240.0, 16.0)?, +)?; +assert!(layout.glyphs().count() > 0); +# Ok(()) +# } ``` -All commands accept `--help`, `--version`, `--verbose`, `--timeout-seconds`, -`--max-message-bytes`, `--max-suite-bytes`, and `--max-cases`. Defaults are 30 seconds -without communication, 1 MiB per message, 256 MiB per suite, and 200,000 cases. Requests -and responses stream concurrently; responses may arrive in any order and are matched by a -unique `id`. Duplicate, unknown, missing, or extra responses are protocol errors. +The example ranges are illustrative; production code should derive ranges from the actual +string. Building is atomic: invalid relationships yield `LayoutError`, and layout either +returns a complete `TextLayout` or no result. -The package contains no library target. Its committed JSON Schema, built-in suite, and -`jlreq-sample-engine` executable form an end-to-end protocol example. See -[`docs/design/conformance.md`](docs/design/conformance.md). - -[`crates/jlreq-conformance/tests/reference_integration.rs`](crates/jlreq-conformance/tests/reference_integration.rs) -keeps direct ICU4X line-break-byte-offset and HarfRust glyph-cluster adapters compiling and -running. Both dependencies are test-only; neither is a `jlreq` dependency or feature. - -## Repository layout - -| Path | Role | -| --- | --- | -| `crates/jlreq` | the only public Rust library | -| `crates/jlreq-conformance` | binary-only black-box runner and sample engine | -| `xtask` | specification generation and architectural gates | -| `spec/`, `data/` | vendored specification inputs, derived data, and provenance | -| `engines/` | independent, non-product reference engines that speak protocol v1 | +## Low-level composition -## Independent reference engines +Applications that already shape text can depend on `jlreq-core` directly, or reach the +same API through `jlreq::core`. It preserves the pre-0.1.0 composition model: callers +provide UTF-8 cluster ranges, integer advances, break opportunities, and typed constructs; +the core applies JLReq classification, kinsoku, spacing, adjustment, exact line search, and +placement. It has no dependencies, no I/O, no font parser, and supports `no_std + alloc` +on MSRV 1.85. The facade uses MSRV 1.88. -Protocol independence is only a claim until a second implementation tests it. -[`engines/ocaml/`](engines/ocaml/README.md) is a from-scratch OCaml implementation of -protocol v1, built directly from `spec/` and barred from reading `crates/jlreq/src/`; -`engines/racket/` follows the same rule. Neither is a Cargo workspace member or a product; -see [`docs/design/conformance.md`](docs/design/conformance.md#independent-reference-engines) -and [ADR 0024](docs/adr/0024-independent-reference-engines.md). +See the executable [`minimal`](crates/jlreq-core/examples/minimal.rs), +[`Composer`](crates/jlreq-core/examples/composer.rs), and +[`vertical`](crates/jlreq-core/examples/vertical.rs) examples for that layer. -## Development +## Conformance and development -Development is test-first: write the observable failure, verify Red, implement the smallest -coherent behavior, verify Green, then refactor under the gates. +`jlreq-conformance` is a binary-only NDJSON runner. Protocol messages retain +`jlreq.conformance/1` and specification identifier +`jlreq-2020-08-11+unicode-17.0.0`. The committed suite and independent OCaml/Racket engines +keep the current 100/106 conformance-rule ledger and generated 122,199-case censuses. +See [the protocol design](docs/design/conformance.md). ```sh just check # formatting, lint, architecture, provenance, and repository hygiene -just test # workspace tests plus doctests -just ci # all practical CI checks, including no_std and WASM -cargo run -p jlreq-conformance -- list +just test # workspace tests and doctests +just package # isolated offline verification of all three .crate files +just release-check # every reversible acceptance gate; never publishes ``` -The 0.1.0 names and release-line contract are tracked in -[`docs/public-api.toml`](docs/public-api.toml). The network-free API gate checks missing and -extra exports plus all 22 typed Style mappings. Starting with the next 0.1.x candidate, the -required semver job also compares the complete rustdoc API with the latest published jlreq -release. Stable error and diagnostic codes are listed in -[`docs/error-codes.md`](docs/error-codes.md). +The exact 0.1.x public API is frozen in +[`docs/public-api.toml`](docs/public-api.toml); stable failures and diagnostics are in +[`docs/error-codes.md`](docs/error-codes.md). Architecture, resource bounds, and the release +handoff are documented in [ARCHITECTURE.md](ARCHITECTURE.md), [SECURITY.md](SECURITY.md), +and [docs/RELEASING.md](docs/RELEASING.md). ## License -Dual-licensed under [MIT](LICENSES/MIT.txt) or [Apache-2.0](LICENSES/Apache-2.0.txt), at -your option. The repository is [REUSE](https://reuse.software/)-compliant. +Dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option. The +repository is [REUSE](https://reuse.software/)-compliant. diff --git a/REUSE.toml b/REUSE.toml index 9753ebe..a548f60 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -15,6 +15,8 @@ path = [ ".gitignore", "fuzz/.gitignore", ".github/CODEOWNERS", + "LICENSE-MIT", + "LICENSE-APACHE", ] precedence = "aggregate" SPDX-FileCopyrightText = "2026 jlreq contributors" diff --git a/ROADMAP.md b/ROADMAP.md index 185381b..d18b475 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,27 +2,25 @@ ## Current status: 0.1.0 prepared, not published -Both product packages and their release artifacts are ready for 0.1.0. The public Rust API, -stable error codes, protocol v1, integer placement, MSRV 1.85, and `no_std + alloc` boundary -are the 0.1.x compatibility line. Preparation deliberately does not upload crates, create a -tag or GitHub Release, configure a Trusted Publisher, or change branch protection. +The three product packages are implemented: -The release has a deliberately small product boundary: +- `jlreq`: high-level text/font-to-glyph layout on MSRV 1.88; +- `jlreq-core`: dependency-free `no_std + alloc` composition on MSRV 1.85; and +- `jlreq-conformance`: protocol-v1 black-box validation. -- one dependency-free `no_std + alloc` Rust library; -- one validated paragraph composition pipeline for caller-shaped UTF-8 clusters; -- nine inline constructs, horizontal and vertical placement, tabs, widow control, and - whole-paragraph optimization; -- 22 typed JLReq 2020 Style choices; and -- a language-independent black-box conformance protocol. +The code, documentation, CI, package isolation, release workflows, API snapshot, checksum, +SBOM, and attestation paths are prepared. Publication date, credentials, uploads, tag, and +GitHub Release remain deliberately unperformed. -The working specification identifier is `jlreq-2020-08-11+unicode-17.0.0`. Complete JIS X -4051 conformance is not claimed; the implementation covers only alternatives JLReq records. +The specification identifier remains `jlreq-2020-08-11+unicode-17.0.0`. The current +conformance inventory remains 100/106; six editorial or non-observable entries are +explicitly ledgered. Complete JIS X 4051 conformance is not claimed beyond the alternatives +JLReq records. ## Implemented workstreams These headings preserve ownership keys used by the conformance ledger. “Implemented” means -the current tests exercise the workstream; it does not mean stable or released. +the current tests exercise the workstream; it does not imply publication. ## M0 — Classification and specification data @@ -36,44 +34,49 @@ the current tests exercise the workstream; it does not mean stable or released. ## M5 — Vertical composition +## High-level text integration + +The facade preserves source ranges while splitting paragraphs, itemizes graphemes/scripts, +performs grapheme-wide fallback, resolves UAX #9, shapes font/script/direction runs, +combines UAX #14 and authored break controls, calls the core, and emits per-line visual +order in physical coordinates. Explicit font bytes are the reproducibility boundary. + ## Independent reference engines -[`engines/ocaml/`](engines/ocaml/README.md) is a from-scratch OCaml implementation of the -conformance protocol, gated on a milestone sequence of its own -(`engines/ocaml/milestones/`) that is unrelated to the Rust workstream numbers above. It -advanced toward the built-in suite one disjoint milestone per pull request and now claims -milestone 9, the last one: all eighty-nine cases answer bit for bit, so `just ocaml-gate` -and `just conform-ocaml` are the same run and the required CI job holds the engine to the -whole suite. Ten synthetic censuses agree with the Rust engine across 122,199 further -requests, and the twenty-six observable policies the exercise turned up — rules two engines -must share to pass the same case, stated in no sentence of JLReq and no file under `docs/` — -are listed in `engines/ocaml/README.md` and are candidates for `docs/decisions/`. -[`engines/racket/`](engines/racket/README.md) independently implements the same complete -protocol surface. The generated census summary, rather than prose copied by hand, records -all ten census counts and all three pairwise zero-difference results. See -[the summary](docs/generated/conformance-summary.md) and +The OCaml and Racket engines independently implement the complete protocol surface from +`spec/` and public conformance documents. Their generated ten-kind census covers 122,199 +requests and records zero differences among the three implementations. See +[the generated summary](docs/generated/conformance-summary.md) and [ADR 0024](docs/adr/0024-independent-reference-engines.md). ## Publication-only work remaining -- Choose the release date and move the completed `Unreleased` notes to `0.1.0`. -- With explicit maintainer approval, upload `jlreq`, wait for the crates.io index, then - upload `jlreq-conformance`. -- Configure crates.io Trusted Publishing after that required first manual publication. -- Create the `v0.1.0` tag and GitHub Release from the already verified artifacts. -- Apply any desired external branch-protection settings separately. +- Select an actual date and run `just finalize-release YYYY-MM-DD`. +- Have a repository administrator verify the recorded release-environment, reviewer, + branch, required-check, and full-SHA Action settings. +- Run the successful manual `Release check` workflow for the finalized candidate and + verify its three crate archives plus six binary archives, checksums, SBOMs, and + attestations. +- Supply a narrowly scoped token only for the first publication, upload `jlreq-core`, + wait for registry visibility, then upload `jlreq` and `jlreq-conformance`. +- Only after all uploads succeed, create `v0.1.0` and the GitHub Release; configure + Trusted Publishing and revoke the initial token. ## Release-line invariants - `Style::default()` remains identical to `Style::jlreq_2020()`. -- A future JLReq revision adds a dated profile and specification identifier rather than - reinterpreting an existing profile. -- The conformance protocol uses a new `jlreq.conformance/N` identifier for incompatible - envelope or body changes. -- Integer layout results remain bit-identical across supported targets. +- Explicit identical font bytes, face indices, text, and options produce bit-identical + quantized results on supported systems; system-font selection is not covered. +- The core remains dependency-free `no_std + alloc`; facade dependency types remain + private. +- The conformance protocol uses a new `jlreq.conformance/N` identifier for an incompatible + envelope or body. +- Resource refusal is atomic and never returns a partial layout. ## Permanent non-goals -Font I/O, shaping, UAX #14 discovery, bidi resolution, rasterization, and drawing stay -outside the library. These belong to integrations that feed shaped clusters and break -opportunities into `jlreq` and consume its renderer-ready placements. +- rasterization, canvas/PDF/GPU drawing, and renderer ownership; +- a CLI or JSON layout service beyond the conformance validator; +- a WASM-specific facade, website, or hosted service; and +- semantic guessing of units, quantities, formulae, or annotations that an author can mark + explicitly with `DocumentBuilder`. diff --git a/SECURITY.md b/SECURITY.md index a8f0842..70b678f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,44 +1,88 @@ # Security Policy -## Scope +## Scope and threat model -jlreq performs pure computation over in-memory text. It does not read files, open -network connections, execute code, or link native libraries, and the core is `no_std` -([ADR 0001](docs/adr/0001-no-std-no-io-no-font-in-core.md)). The realistic threat is -untrusted text reaching the layout engine: a panic, an unbounded allocation, or -non-termination on adversarial input is a security issue here, because callers embed this -in servers and document pipelines. +`jlreq` is intended for servers, editors, and document pipelines that may receive +attacker-controlled text and font bytes. Both inputs are untrusted. A panic, hang, +out-of-bounds access, integer overflow, unbounded allocation, or partial result presented as +complete is a security defect. + +The facade parses TTF/OTF/TTC data, itemizes arbitrary Unicode, resolves bidi, shapes runs, +segments lines, and lowers typed structures. The dependency-free core consumes the +validated pre-shaped representation and performs exact composition. The conformance runner +also launches an explicitly supplied engine process and exchanges bounded NDJSON. Specifically in scope: -- Panics on any `&str` input, including malformed sequences, degenerate advances, and - pathological ruby or nesting depth -- Unbounded memory growth or non-termination driven by input size or content -- Integer overflow producing incorrect placement rather than a defined error +- malformed font directories, tables, offsets, TTC indices, variation data, and glyph data; +- arbitrary valid UTF-8, including empty input, controls, combining marks, variation + selectors, deeply alternating bidi text, long unbreakable spans, and many paragraphs; +- invalid UTF-8 byte ranges or crossing typed document structures supplied through the + Rust API; +- NaN, infinity, negative or out-of-range geometry, and arithmetic overflow after + quantization; +- resource exhaustion through input bytes, font bytes/count, paragraphs, shaping runs, + glyphs, constructs, break candidates, tabs, or exact-search transitions; +- protocol messages, suites, stderr, child-process lifetime, and response cardinality. -Composition has deterministic caller-configurable bounds for clusters, break candidates, -constructs, tab stops, and charged search transitions. The protocol runner separately -bounds message and suite bytes, case count, retained stderr, and inactivity time; a refusal -through one of these controls is expected behavior, not a partial result. +Rasterizers and renderers are outside this repository. A visual preference where JLReq +permits alternatives is a conformance or design issue, not a vulnerability. -Out of scope: a layout result you disagree with. Where JLReq permits alternatives, use an -issue or a conformance case. +## Bounds and atomic failure -## Reporting +`LayoutOptions::limits` accepts `ResourceLimits` for: + +| Resource | Default maximum | +| --- | ---: | +| input UTF-8 bytes | 16 MiB | +| registered font faces | 256 | +| total font bytes | 512 MiB | +| paragraphs | 65,536 | +| shaping runs | 262,144 | +| produced glyphs | 1,000,000 | +| typed constructs | 4,096 | +| charged core operations | 8,000,000 | + +The core separately bounds clusters and break candidates at 65,536 each, constructs and tab +stops at 4,096 each, and exact-search transitions at 8,000,000 by default. Callers should +lower these limits for latency-sensitive services. + +All public floating-point values are validated and immediately quantized to signed 26.6 +fixed point. Invalid fonts, invalid options, invalid documents, and exceeded limits return a +typed `LayoutError` and no `TextLayout`. Core composition likewise returns no partial +`Layout`. Reusable engines retain only valid immutable caches and scratch capacity, so an +error cannot poison the next request. -Report privately through GitHub's ["Report a vulnerability"][advisories] flow rather than a -public issue. Include the input, the advances supplied, and the observed behavior. +Fallback never drops input. When no face covers a grapheme, the primary `.notdef`, original +source range, and `font.missing-glyph` diagnostic remain in the complete result. + +The conformance runner defaults to 1 MiB per message, 256 MiB per suite, 200,000 cases, a +30-second inactivity timeout, bounded retained stderr, concurrent pipe draining, watchdog +termination, and child cleanup. + +## Assurance + +Tests cover valid fixture fonts, malformed font bytes, arbitrary text/options, resource +limits, failure recovery, and deterministic one-shot/reused results. Fuzz targets exercise +both valid-font arbitrary-text layout and malformed font registration/layout. Coverage, +mutation testing, CodeQL, `cargo deny`, REUSE, package isolation, and SHA-pinned workflow +audits are release gates. + +## Reporting -Expect an acknowledgement within seven days. +Report privately through GitHub's [Report a vulnerability][advisories] flow, not a public +issue. Include the smallest reproducing text/font if disclosure is safe, the options and +limits used, target and Rust versions, and observed behavior. Expect acknowledgement within +seven days. ## Supported versions -The prepared 0.1.x line receives security fixes. Before the first publication, reports -against the prepared 0.1.0 tree and `main` follow the same policy. +The prepared 0.1.x line receives security fixes. Before first publication, reports against +the prepared 0.1.0 tree and `main` follow the same policy. | Version | Supported | | --- | --- | | `0.1.x` | Supported | -| `< 0.1.0` development snapshots | Unsupported | +| earlier development snapshots | Unsupported | [advisories]: https://github.com/P4suta/jlreq/security/advisories/new diff --git a/clippy.toml b/clippy.toml index 45477d7..486f9f8 100644 --- a/clippy.toml +++ b/clippy.toml @@ -45,8 +45,8 @@ disallowed-macros = [ { path = "lazy_static::lazy_static", reason = "use core::cell::OnceCell or std::sync::LazyLock" }, ] disallowed-types = [ - { path = "once_cell::sync::Lazy", reason = "use std::sync::LazyLock (stable since 1.80)" }, - { path = "once_cell::sync::OnceCell", reason = "use std::sync::OnceLock (stable since 1.70)" }, + { path = "once_cell::sync::Lazy", reason = "use std::sync::LazyLock (stable since 1.80)", allow-invalid = true }, + { path = "once_cell::sync::OnceCell", reason = "use std::sync::OnceLock (stable since 1.70)", allow-invalid = true }, # Line composition must be reproducible: identical input yields byte-identical # output on every platform, so iteration order can never be incidental. { path = "std::collections::HashMap", reason = "non-deterministic iteration; use BTreeMap or a sorted Vec" }, diff --git a/crates/jlreq-conformance/Cargo.toml b/crates/jlreq-conformance/Cargo.toml index 171b9b1..e96f4d2 100644 --- a/crates/jlreq-conformance/Cargo.toml +++ b/crates/jlreq-conformance/Cargo.toml @@ -42,12 +42,12 @@ path = "tests/transport.rs" harness = false [dependencies] -jlreq = { version = "0.1.0", path = "../jlreq" } +jlreq-core = { version = "0.1.0", path = "../jlreq-core" } serde_json = "1.0" [dev-dependencies] -harfrust = "=0.13.0" -icu_segmenter = "=2.1.2" +harfrust = "=0.13.3" +icu_segmenter = "=2.3.0" [lints] workspace = true diff --git a/crates/jlreq-conformance/src/main.rs b/crates/jlreq-conformance/src/main.rs index 97b2f5b..11cd223 100644 --- a/crates/jlreq-conformance/src/main.rs +++ b/crates/jlreq-conformance/src/main.rs @@ -24,7 +24,7 @@ use serde_json::{Map, Value}; use validation::{validate_request, validate_response}; const PROTOCOL: &str = "jlreq.conformance/1"; -const SPEC: &str = jlreq::SPECIFICATION; +const SPEC: &str = jlreq_core::SPECIFICATION; const BUILTIN_SUITE: &str = include_str!("../suite.ndjson"); #[cfg(test)] const PROTOCOL_SCHEMA: &str = include_str!("../protocol.schema.json"); diff --git a/crates/jlreq-conformance/src/sample_engine.rs b/crates/jlreq-conformance/src/sample_engine.rs index 972cb76..6ae5e4e 100644 --- a/crates/jlreq-conformance/src/sample_engine.rs +++ b/crates/jlreq-conformance/src/sample_engine.rs @@ -9,7 +9,7 @@ use std::{ process::ExitCode, }; -use jlreq::{ +use jlreq_core::{ Alignment, Break, Cluster, ClusterRole, Construct, CoordinateTransform, Frame, Paragraph, Ruby, RubyKind, RubyRun, ShapedText, Size, Style, TabAlignment, TabStop, Widow, WritingMode, style::{ @@ -24,7 +24,7 @@ use jlreq::{ use serde_json::{Map, Value, json}; const PROTOCOL: &str = "jlreq.conformance/1"; -const SPEC: &str = jlreq::SPECIFICATION; +const SPEC: &str = jlreq_core::SPECIFICATION; const MAX_MESSAGE_BYTES: usize = 1024 * 1024; const MAX_STREAM_BYTES: usize = 256 * 1024 * 1024; const MAX_MESSAGES: usize = 200_000; @@ -85,7 +85,7 @@ fn run_stream( .get("request") .ok_or_else(|| "request is required".to_owned())?; let (paragraph, style) = parse_request(request)?; - let layout = jlreq::compose(¶graph, &style) + let layout = jlreq_core::compose(¶graph, &style) .map_err(|error| format!("{}: {}", error.code(), error))?; serde_json::to_writer( &mut *output, @@ -592,7 +592,7 @@ fn choice(name: &str, value: &str, choices: &[(&str, T)]) -> Result Value { +fn layout_json(layout: &jlreq_core::Layout) -> Value { json!({ "lines": layout.lines().iter().map(|line| json!({ "range": [line.range().start, line.range().end], @@ -602,8 +602,8 @@ fn layout_json(layout: &jlreq::Layout) -> Value { "block_extent": line.block_extent(), "clusters": line.clusters().iter().map(|placement| json!({ "origin": match placement.origin() { - jlreq::PlacementOrigin::Cluster(ordinal) => json!({"cluster": ordinal}), - jlreq::PlacementOrigin::Construct(ordinal) => json!({"construct": ordinal}), + jlreq_core::PlacementOrigin::Cluster(ordinal) => json!({"cluster": ordinal}), + jlreq_core::PlacementOrigin::Construct(ordinal) => json!({"construct": ordinal}), _ => json!({"unknown": true}), }, "range": [placement.range().start, placement.range().end], @@ -630,9 +630,9 @@ fn layout_json(layout: &jlreq::Layout) -> Value { "diagnostics": layout.diagnostics().iter().map(|diagnostic| json!({ "code": diagnostic.code(), "severity": match diagnostic.severity() { - jlreq::Severity::Info => "info", - jlreq::Severity::Warning => "warning", - jlreq::Severity::Error => "error", + jlreq_core::Severity::Info => "info", + jlreq_core::Severity::Warning => "warning", + jlreq_core::Severity::Error => "error", _ => "unknown", }, "range": diagnostic.range().map(|range| [range.start, range.end]), @@ -677,11 +677,11 @@ fn parse_size(value: &Value) -> Result { .map_err(|error| render_input_error(&error)) } -fn render_input_error(error: &jlreq::InputError) -> String { +fn render_input_error(error: &jlreq_core::InputError) -> String { format!("{}: {}", error.code(), error.message()) } -fn render_style_error(error: jlreq::style::StyleError) -> String { +fn render_style_error(error: jlreq_core::style::StyleError) -> String { format!("{}: {}", error.code(), error.message()) } diff --git a/crates/jlreq-conformance/tests/reference_integration.rs b/crates/jlreq-conformance/tests/reference_integration.rs index 5a4e652..35c6c4e 100644 --- a/crates/jlreq-conformance/tests/reference_integration.rs +++ b/crates/jlreq-conformance/tests/reference_integration.rs @@ -9,7 +9,7 @@ use std::collections::BTreeMap; use harfrust::GlyphBuffer; use icu_segmenter::{LineSegmenter, options::LineBreakOptions}; -use jlreq::{Break, Cluster, Frame, Paragraph, ShapedText, Size, Style}; +use jlreq_core::{Break, Cluster, Frame, Paragraph, ShapedText, Size, Style}; fn clusters_from_harfrust( source: &str, @@ -84,7 +84,7 @@ fn icu4x_byte_offsets_feed_breaks_without_conversion() { .build() .expect("ICU4X offsets are accepted verbatim"); assert_eq!( - jlreq::compose(¶graph, &Style::default()) + jlreq_core::compose(¶graph, &Style::default()) .expect("composition succeeds") .lines() .len(), diff --git a/crates/jlreq-core/Cargo.toml b/crates/jlreq-core/Cargo.toml new file mode 100644 index 0000000..dd597ed --- /dev/null +++ b/crates/jlreq-core/Cargo.toml @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: 2026 jlreq contributors +# +# SPDX-License-Identifier: MIT OR Apache-2.0 + +[package] +name = "jlreq-core" +description = "A dependency-free no_std Japanese line-composition engine for pre-shaped text" +keywords = ["japanese", "typesetting", "jlreq", "text-layout", "no-std"] +categories = ["text-processing", "internationalization", "no-std"] +readme = "README.md" +documentation = "https://docs.rs/jlreq-core" +version.workspace = true +edition.workspace = true +rust-version = "1.85" +license.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +include = [ + "src/**", + "tests/**", + "examples/**", + "Cargo.toml", + "README.md", + "LICENSE-MIT", + "LICENSE-APACHE", +] + +[lints] +workspace = true diff --git a/crates/jlreq-core/LICENSE-APACHE b/crates/jlreq-core/LICENSE-APACHE new file mode 100644 index 0000000..137069b --- /dev/null +++ b/crates/jlreq-core/LICENSE-APACHE @@ -0,0 +1,73 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/crates/jlreq-core/LICENSE-MIT b/crates/jlreq-core/LICENSE-MIT new file mode 100644 index 0000000..d817195 --- /dev/null +++ b/crates/jlreq-core/LICENSE-MIT @@ -0,0 +1,18 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT +LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/crates/jlreq-core/README.md b/crates/jlreq-core/README.md new file mode 100644 index 0000000..11210d6 --- /dev/null +++ b/crates/jlreq-core/README.md @@ -0,0 +1,43 @@ + + +# jlreq-core + +`jlreq-core` 0.1.0 is a dependency-free `no_std + alloc` Japanese line-composition engine for +already-shaped text. Callers provide UTF-8 byte ranges, cluster advances, and line-break +opportunities; the core returns integer logical placements without loading fonts, shaping, +running bidi, rendering, or discovering UAX #14 breaks. + +```rust +use jlreq_core::{Break, Cluster, Frame, Paragraph, ShapedText, Size, Style}; + +let source = "日本語組版"; +let clusters = source.char_indices().map(|(start, ch)| { + Cluster::new(start..start + ch.len_utf8(), 1_000) +}); +let text = ShapedText::new(source, Size::square(1_000)?, Frame::FullEm, clusters)?; +let paragraph = Paragraph::builder(text, 4_000) + .breaks(source.char_indices().skip(1).map(|(at, _)| Break::allowed(at))) + .build()?; +let layout = jlreq_core::compose(¶graph, &Style::book_2020()) + .expect("this small paragraph is within the default resource limits"); + +assert_eq!(layout.lines().len(), 2); +# Ok::<(), jlreq_core::InputError>(()) +``` + +Composition returns either a complete exact `Layout` or a typed `ComposeError`; it never +returns a partial layout or silently changes search strategy. `CompositionLimits` bounds +clusters, break candidates, constructs, tab stops, and exact-search transitions. A +`Composer` retains scratch allocation across calls and remains reusable after an error. + +The packaged, executable examples cover [minimal composition](examples/minimal.rs), +[Composer reuse and a resource refusal](examples/composer.rs), and +[vertical placement](examples/vertical.rs). The repository's +[ICU4X + HarfRust integration test](https://github.com/P4suta/jlreq/blob/main/crates/jlreq-conformance/tests/reference_integration.rs) +shows the intended segmentation/shaping boundary. See the +[repository guide](https://github.com/P4suta/jlreq) for scope and protocol details, or run +`cargo doc -p jlreq-core --open` for the API reference. diff --git a/crates/jlreq/examples/composer.rs b/crates/jlreq-core/examples/composer.rs similarity index 94% rename from crates/jlreq/examples/composer.rs rename to crates/jlreq-core/examples/composer.rs index 7fcc476..5745389 100644 --- a/crates/jlreq/examples/composer.rs +++ b/crates/jlreq-core/examples/composer.rs @@ -3,12 +3,12 @@ //! Reuse one composer, handle a typed resource error, and continue composing afterward. -use jlreq::{ +use jlreq_core::{ Cluster, Composer, CompositionLimits, CompositionResource, Frame, Paragraph, ShapedText, Size, Style, }; -fn paragraph(source: &str) -> Result { +fn paragraph(source: &str) -> Result { let clusters = source.char_indices().map(|(start, character)| { Cluster::new(start..start.saturating_add(character.len_utf8()), 1_000) }); diff --git a/crates/jlreq/examples/minimal.rs b/crates/jlreq-core/examples/minimal.rs similarity index 84% rename from crates/jlreq/examples/minimal.rs rename to crates/jlreq-core/examples/minimal.rs index be22875..b7e535e 100644 --- a/crates/jlreq/examples/minimal.rs +++ b/crates/jlreq-core/examples/minimal.rs @@ -3,7 +3,7 @@ //! Minimal horizontal composition with explicit legal break positions. -use jlreq::{Break, Cluster, Frame, Paragraph, ShapedText, Size, Style}; +use jlreq_core::{Break, Cluster, Frame, Paragraph, ShapedText, Size, Style}; fn main() -> Result<(), Box> { let source = "日本語組版"; @@ -19,7 +19,7 @@ fn main() -> Result<(), Box> { .map(|(offset, _)| Break::allowed(offset)), ) .build()?; - let layout = jlreq::compose(¶graph, &Style::book_2020())?; + let layout = jlreq_core::compose(¶graph, &Style::book_2020())?; assert_eq!(layout.lines().len(), 2); Ok(()) diff --git a/crates/jlreq/examples/vertical.rs b/crates/jlreq-core/examples/vertical.rs similarity index 80% rename from crates/jlreq/examples/vertical.rs rename to crates/jlreq-core/examples/vertical.rs index 74ea594..1fc5b3f 100644 --- a/crates/jlreq/examples/vertical.rs +++ b/crates/jlreq-core/examples/vertical.rs @@ -3,7 +3,9 @@ //! Compose a vertical line and inspect the Latin cluster's coordinate transform. -use jlreq::{Cluster, CoordinateTransform, Frame, Paragraph, ShapedText, Size, Style, WritingMode}; +use jlreq_core::{ + Cluster, CoordinateTransform, Frame, Paragraph, ShapedText, Size, Style, WritingMode, +}; fn main() -> Result<(), Box> { let source = "縦A"; @@ -14,7 +16,7 @@ fn main() -> Result<(), Box> { let paragraph = Paragraph::builder(text, 4_000) .writing_mode(WritingMode::VerticalRl) .build()?; - let layout = jlreq::compose(¶graph, &Style::jlreq_2020())?; + let layout = jlreq_core::compose(¶graph, &Style::jlreq_2020())?; assert_eq!( layout.lines()[0].clusters()[1].transform(), diff --git a/crates/jlreq/src/construct.rs b/crates/jlreq-core/src/construct.rs similarity index 100% rename from crates/jlreq/src/construct.rs rename to crates/jlreq-core/src/construct.rs diff --git a/crates/jlreq/src/generated.rs b/crates/jlreq-core/src/generated.rs similarity index 100% rename from crates/jlreq/src/generated.rs rename to crates/jlreq-core/src/generated.rs diff --git a/crates/jlreq/src/generated/appendix_a.rs b/crates/jlreq-core/src/generated/appendix_a.rs similarity index 99% rename from crates/jlreq/src/generated/appendix_a.rs rename to crates/jlreq-core/src/generated/appendix_a.rs index a82ec06..0c2efc9 100644 --- a/crates/jlreq/src/generated/appendix_a.rs +++ b/crates/jlreq-core/src/generated/appendix_a.rs @@ -10,10 +10,10 @@ //! specification will not carry it forward (ADR 0009). //! //! - Source: `spec/derived/appendix-a.tsv` -//! - Source SHA-256: `d47f6bf7a6d51d20c4e86419c35ef5ccaaf0c4baa9fb4ace55a0990e8360734c` +//! - Source SHA-256: `f671a4b9daae12d52ddb8fdbbdf7b75f8882da3f57491d4dc553d07477d2e22c` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/classes.rs`, `xtask/src/generate.rs` -//! - Generator SHA-256: `64c173c676c3d099077b59c02d707c9a3d470534f1ac5425d9eab6d1c1e78cdb` +//! - Generator SHA-256: `085b66191503c549cb51ba66e6ad248c982bebc64f23e3a013031ab4d76718dd` //! - Entries: 1686 /// The longest key Appendix A enumerates, in code points. diff --git a/crates/jlreq/src/generated/folding.rs b/crates/jlreq-core/src/generated/folding.rs similarity index 98% rename from crates/jlreq/src/generated/folding.rs rename to crates/jlreq-core/src/generated/folding.rs index 2725599..a431ab9 100644 --- a/crates/jlreq/src/generated/folding.rs +++ b/crates/jlreq-core/src/generated/folding.rs @@ -10,10 +10,10 @@ //! specification will not carry it forward (ADR 0009). //! //! - Source: `spec/derived/folding.tsv` -//! - Source SHA-256: `4e8abc525815527522e36bad9410d0f7fa8733d29010f8a518e92fbc233cefaf` +//! - Source SHA-256: `46eedd68ef0dc757f33f057a142df1e45cef2eb9bc8de025b5611cfc7267eddb` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/classes.rs`, `xtask/src/generate.rs` -//! - Generator SHA-256: `64c173c676c3d099077b59c02d707c9a3d470534f1ac5425d9eab6d1c1e78cdb` +//! - Generator SHA-256: `085b66191503c549cb51ba66e6ad248c982bebc64f23e3a013031ab4d76718dd` //! - Entries: 226 use super::appendix_a::{FRAME_FULL_EM, FRAME_HALF_EM}; diff --git a/crates/jlreq/src/generated/ideograph.rs b/crates/jlreq-core/src/generated/ideograph.rs similarity index 92% rename from crates/jlreq/src/generated/ideograph.rs rename to crates/jlreq-core/src/generated/ideograph.rs index 42cddfc..c422ade 100644 --- a/crates/jlreq/src/generated/ideograph.rs +++ b/crates/jlreq-core/src/generated/ideograph.rs @@ -10,10 +10,10 @@ //! specification will not carry it forward (ADR 0009). //! //! - Source: `spec/derived/ideographs.tsv` -//! - Source SHA-256: `cf61e33039cc37974f3e017e6381e94873df3e55701199a71877b18bbd01886d` +//! - Source SHA-256: `4faa81a44f8f497198975ff8dad10e843f4da629fee83bb7786f12b61958b0ba` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/classes.rs`, `xtask/src/generate.rs` -//! - Generator SHA-256: `64c173c676c3d099077b59c02d707c9a3d470534f1ac5425d9eab6d1c1e78cdb` +//! - Generator SHA-256: `085b66191503c549cb51ba66e6ad248c982bebc64f23e3a013031ab4d76718dd` //! - Entries: 16 /// One range of code points the Unicode Character Database gives diff --git a/crates/jlreq/src/generated/script.rs b/crates/jlreq-core/src/generated/script.rs similarity index 94% rename from crates/jlreq/src/generated/script.rs rename to crates/jlreq-core/src/generated/script.rs index 7e96310..eb1d7c1 100644 --- a/crates/jlreq/src/generated/script.rs +++ b/crates/jlreq-core/src/generated/script.rs @@ -10,10 +10,10 @@ //! specification will not carry it forward (ADR 0009). //! //! - Source: `spec/derived/scripts.tsv` -//! - Source SHA-256: `3b7c3d2e4e2f35912137c2fef54928c4cbf42f850bcd2483175ec11f5c78bfd1` +//! - Source SHA-256: `eee68d14c9afa914a501001e4da84bc649efa42012650cc20280a2bf4de2e36b` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/classes.rs`, `xtask/src/generate.rs` -//! - Generator SHA-256: `64c173c676c3d099077b59c02d707c9a3d470534f1ac5425d9eab6d1c1e78cdb` +//! - Generator SHA-256: `085b66191503c549cb51ba66e6ad248c982bebc64f23e3a013031ab4d76718dd` //! - Entries: 22 /// The `Script=Hiragana` tag. diff --git a/crates/jlreq/src/generated/table1.rs b/crates/jlreq-core/src/generated/table1.rs similarity index 99% rename from crates/jlreq/src/generated/table1.rs rename to crates/jlreq-core/src/generated/table1.rs index 638fc0d..b8baa29 100644 --- a/crates/jlreq/src/generated/table1.rs +++ b/crates/jlreq-core/src/generated/table1.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `02736559abf8f4be082781304b9291dbf8765ab95c8d07c630128153cce2332f` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `c8974d1fe3f413864bf20ad59a6418cea98202fb0ab5d5fe26364566b408f9fa` +//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` //! - Entries: 841 use crate::spec::{RawHang, RawSpacingCell, RawTerm, em}; diff --git a/crates/jlreq/src/generated/table2.rs b/crates/jlreq-core/src/generated/table2.rs similarity index 99% rename from crates/jlreq/src/generated/table2.rs rename to crates/jlreq-core/src/generated/table2.rs index 2fc4962..c664a5c 100644 --- a/crates/jlreq/src/generated/table2.rs +++ b/crates/jlreq-core/src/generated/table2.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `3e93d1104a5c730bc9eca01880ef989520b7c3ebb1ef98833be0424a44edbd66` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `c8974d1fe3f413864bf20ad59a6418cea98202fb0ab5d5fe26364566b408f9fa` +//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` //! - Entries: 784 use crate::spec::RawBreakCell; diff --git a/crates/jlreq/src/generated/table3.rs b/crates/jlreq-core/src/generated/table3.rs similarity index 99% rename from crates/jlreq/src/generated/table3.rs rename to crates/jlreq-core/src/generated/table3.rs index b3fec47..8524064 100644 --- a/crates/jlreq/src/generated/table3.rs +++ b/crates/jlreq-core/src/generated/table3.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `ca550d5323625d71d583ecd27006e5ec0d7c569814e4dfdd392593bc9aa43222` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `c8974d1fe3f413864bf20ad59a6418cea98202fb0ab5d5fe26364566b408f9fa` +//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` //! - Entries: 841 use crate::spec::{RawRangedCell, em}; diff --git a/crates/jlreq/src/generated/table4.rs b/crates/jlreq-core/src/generated/table4.rs similarity index 99% rename from crates/jlreq/src/generated/table4.rs rename to crates/jlreq-core/src/generated/table4.rs index a67f15b..6a9747f 100644 --- a/crates/jlreq/src/generated/table4.rs +++ b/crates/jlreq-core/src/generated/table4.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `f8274c63edd9a561f2d951ac2831f731da7db89f22d0a58c5ae533fe5d6d2a65` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `c8974d1fe3f413864bf20ad59a6418cea98202fb0ab5d5fe26364566b408f9fa` +//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` //! - Entries: 841 use crate::spec::{RawRangedCell, em}; diff --git a/crates/jlreq/src/generated/table5.rs b/crates/jlreq-core/src/generated/table5.rs similarity index 99% rename from crates/jlreq/src/generated/table5.rs rename to crates/jlreq-core/src/generated/table5.rs index bac40c1..db4ab2a 100644 --- a/crates/jlreq/src/generated/table5.rs +++ b/crates/jlreq-core/src/generated/table5.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `785b6275731e54b8b235efabfb9418b9e766145c5f59cdf99cda8a9e018c3519` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `c8974d1fe3f413864bf20ad59a6418cea98202fb0ab5d5fe26364566b408f9fa` +//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` //! - Entries: 841 use crate::spec::{RawRangedCell, em}; diff --git a/crates/jlreq/src/generated/table6.rs b/crates/jlreq-core/src/generated/table6.rs similarity index 99% rename from crates/jlreq/src/generated/table6.rs rename to crates/jlreq-core/src/generated/table6.rs index ad8652e..d2f134c 100644 --- a/crates/jlreq/src/generated/table6.rs +++ b/crates/jlreq-core/src/generated/table6.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `e63166631b11e80c00adff7e1bf3126be347e95133e74112310307fff1f27ac9` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `c8974d1fe3f413864bf20ad59a6418cea98202fb0ab5d5fe26364566b408f9fa` +//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` //! - Entries: 784 use crate::spec::{RawRangedCell, em}; diff --git a/crates/jlreq/src/layout.rs b/crates/jlreq-core/src/layout.rs similarity index 100% rename from crates/jlreq/src/layout.rs rename to crates/jlreq-core/src/layout.rs diff --git a/crates/jlreq-core/src/lib.rs b/crates/jlreq-core/src/lib.rs new file mode 100644 index 0000000..96c6f9c --- /dev/null +++ b/crates/jlreq-core/src/lib.rs @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Japanese line composition for already-shaped text. +//! +//! `jlreq-core` owns Japanese spacing, line selection, inline constructs, and physical +//! placement. Font loading, shaping, Unicode line segmentation, bidi resolution, and +//! rendering stay with the caller. Every input position is a UTF-8 byte offset. +//! +//! ``` +//! use jlreq_core::{Break, Cluster, Frame, Paragraph, ShapedText, Size, Style}; +//! +//! let source = "日本語組版"; +//! let clusters = source.char_indices().map(|(start, ch)| { +//! let end = start + ch.len_utf8(); +//! Cluster::new(start..end, 1_000) +//! }); +//! let text = ShapedText::new(source, Size::square(1_000)?, Frame::FullEm, clusters)?; +//! let paragraph = Paragraph::builder(text, 4_000) +//! .breaks(source.char_indices().skip(1).map(|(at, _)| Break::allowed(at))) +//! .build()?; +//! let layout = jlreq_core::compose(¶graph, &Style::book_2020()) +//! .expect("small paragraph is within the default resource limits"); +//! +//! assert_eq!(layout.lines().len(), 2); +//! # Ok::<(), jlreq_core::InputError>(()) +//! ``` + +#![no_std] + +extern crate alloc; + +mod construct; +mod generated; +mod layout; +mod limits; +mod model; +mod normalize; +mod paragraph; +mod pipeline; +mod spec; +pub mod style; + +pub use construct::{Construct, Ruby, RubyKind, RubyRun}; +pub use layout::{ + Attachment, ClusterPlacement, CoordinateTransform, Diagnostic, Layout, Line, PlacementOrigin, + Severity, +}; +pub use limits::{ComposeError, CompositionLimits, CompositionResource}; +pub use model::{Cluster, ClusterRole, Frame, InputError, ShapedText, Size, WritingMode}; +pub use paragraph::{Alignment, Break, Paragraph, ParagraphBuilder, TabAlignment, TabStop, Widow}; +pub use pipeline::Composer; +pub use style::{Style, StyleBuilder, StyleError}; + +/// The stable specification identifier implemented by this release line. +pub const SPECIFICATION: &str = "jlreq-2020-08-11+unicode-17.0.0"; + +/// Compose one validated paragraph with a fresh scratch allocator. +/// +/// Use Composer when composing repeatedly so its temporary buffers can be reused. +pub fn compose(paragraph: &Paragraph, style: &Style) -> Result { + Composer::new().compose(paragraph, style) +} diff --git a/crates/jlreq/src/limits.rs b/crates/jlreq-core/src/limits.rs similarity index 100% rename from crates/jlreq/src/limits.rs rename to crates/jlreq-core/src/limits.rs diff --git a/crates/jlreq/src/model.rs b/crates/jlreq-core/src/model.rs similarity index 100% rename from crates/jlreq/src/model.rs rename to crates/jlreq-core/src/model.rs diff --git a/crates/jlreq/src/normalize.rs b/crates/jlreq-core/src/normalize.rs similarity index 100% rename from crates/jlreq/src/normalize.rs rename to crates/jlreq-core/src/normalize.rs diff --git a/crates/jlreq/src/paragraph.rs b/crates/jlreq-core/src/paragraph.rs similarity index 100% rename from crates/jlreq/src/paragraph.rs rename to crates/jlreq-core/src/paragraph.rs diff --git a/crates/jlreq/src/pipeline.rs b/crates/jlreq-core/src/pipeline.rs similarity index 100% rename from crates/jlreq/src/pipeline.rs rename to crates/jlreq-core/src/pipeline.rs diff --git a/crates/jlreq/src/spec.rs b/crates/jlreq-core/src/spec.rs similarity index 100% rename from crates/jlreq/src/spec.rs rename to crates/jlreq-core/src/spec.rs diff --git a/crates/jlreq/src/style.rs b/crates/jlreq-core/src/style.rs similarity index 100% rename from crates/jlreq/src/style.rs rename to crates/jlreq-core/src/style.rs diff --git a/crates/jlreq/tests/public_api.rs b/crates/jlreq-core/tests/public_api.rs similarity index 97% rename from crates/jlreq/tests/public_api.rs rename to crates/jlreq-core/tests/public_api.rs index 5e3c806..79a57bd 100644 --- a/crates/jlreq/tests/public_api.rs +++ b/crates/jlreq-core/tests/public_api.rs @@ -4,7 +4,7 @@ //! Black-box acceptance tests for the intentionally small public API. -use jlreq::{ +use jlreq_core::{ Alignment, Break, Cluster, ClusterRole, Composer, CompositionLimits, CompositionResource, Construct, CoordinateTransform, Frame, Paragraph, Ruby, RubyKind, RubyRun, ShapedText, Size, Style, TabAlignment, TabStop, Widow, WritingMode, @@ -20,11 +20,11 @@ use jlreq::{ macro_rules! compose_ok { ($paragraph:expr, $style:expr) => { - jlreq::compose($paragraph, $style).expect("fixture stays within composition limits") + jlreq_core::compose($paragraph, $style).expect("fixture stays within composition limits") }; } -fn shaped(source: &str, frame: Frame, advance: i32) -> Result { +fn shaped(source: &str, frame: Frame, advance: i32) -> Result { let clusters = source.char_indices().map(|(start, character)| { Cluster::new(start..start.saturating_add(character.len_utf8()), advance) }); @@ -149,14 +149,14 @@ fn jidori_fills_declared_cells_and_keeps_its_outer_boundary_separate() { assert_eq!( line.clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 3_000, 4_000] ); assert_eq!( line.clusters() .iter() - .map(jlreq::ClusterPlacement::advance) + .map(jlreq_core::ClusterPlacement::advance) .collect::>(), [3_000, 1_000, 1_000] ); @@ -400,7 +400,7 @@ fn appendix_a_opening_brackets_are_not_limited_to_a_handwritten_subset() { assert_eq!( line.clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), vec![0, 1_500, 2_500] ); @@ -431,7 +431,7 @@ fn sentence_medial_dividing_mark_choice_requires_the_declared_role() { compose_ok!(¶graph, &style).lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>() }; @@ -483,7 +483,7 @@ fn sentence_terminator_space_is_inserted_and_withdrawn_at_a_wrap() { layout.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_000, 3_000], "a sentence-final dividing mark carries a fixed one-em following space" @@ -610,7 +610,7 @@ fn table_one_spaces_japanese_and_western_text_by_the_referents_em() { ( line.clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect(), line.inline_extent(), ) @@ -655,7 +655,7 @@ fn contextual_decimal_punctuation_withdraws_its_ordinary_space() { ( line.clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect(), line.inline_extent(), ) @@ -718,7 +718,7 @@ fn western_word_space_collapses_only_at_true_line_edges() { breaks: impl IntoIterator, alignment: Alignment, line_extent: i32, - ) -> jlreq::Layout { + ) -> jlreq_core::Layout { let clusters = source.char_indices().map(|(start, character)| { Cluster::new( start..start.saturating_add(character.len_utf8()), @@ -827,14 +827,14 @@ fn warichu_builds_two_balanced_sublines_and_can_straddle_main_lines() { assert_eq!( line.clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), vec![0, 1_000, 1_500, 1_000, 1_500, 2_000] ); assert_eq!( line.clusters() .iter() - .map(jlreq::ClusterPlacement::block) + .map(jlreq_core::ClusterPlacement::block) .collect::>(), if mode == WritingMode::HorizontalTb { vec![0, 0, 0, 500, 500, 0] @@ -895,8 +895,8 @@ fn warichu_builds_two_balanced_sublines_and_can_straddle_main_lines() { layout .lines() .iter() - .flat_map(jlreq::Line::clusters) - .map(jlreq::ClusterPlacement::range) + .flat_map(jlreq_core::Line::clusters) + .map(jlreq_core::ClusterPlacement::range) .collect::>(), (0..10).map(|start| start..start + 1).collect::>() ); @@ -944,7 +944,7 @@ fn the_reduction_ladder_does_not_reach_the_seam_between_a_warichu_s_sublines() { .expect("valid seam fixture") } - fn compose_at(measure: i32) -> jlreq::Layout { + fn compose_at(measure: i32) -> jlreq_core::Layout { let paragraph = Paragraph::builder(note(), measure) .constructs([Construct::warichu(3..12)]) .build() @@ -958,7 +958,7 @@ fn the_reduction_ladder_does_not_reach_the_seam_between_a_warichu_s_sublines() { natural.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), vec![0, 1_250, 1_750, 1_250, 2_250], "the seam's own amount stands nowhere on the line even where nothing adjusts" @@ -1014,14 +1014,14 @@ fn furawake_aligns_declared_sublines_and_never_becomes_an_outer_break() { assert_eq!( line.clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), vec![0, 0, 1_000] ); assert_eq!( line.clusters() .iter() - .map(jlreq::ClusterPlacement::block) + .map(jlreq_core::ClusterPlacement::block) .collect::>(), if mode == WritingMode::HorizontalTb { vec![-600, 600, 600] @@ -1091,7 +1091,7 @@ fn formula_spacing_width_and_breaks_follow_math_token_context() { layout.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::advance) + .map(jlreq_core::ClusterPlacement::advance) .collect::>(), vec![750, 1_250, 500, 1_000, 500], "display equations have quarter-em equality spacing and solid operators" @@ -1120,7 +1120,7 @@ fn formula_spacing_width_and_breaks_follow_math_token_context() { layout .lines() .iter() - .map(jlreq::Line::range) + .map(jlreq_core::Line::range) .collect::>(), vec![0..2, 2..8], "a feasible break before an equality symbol precedes one before an operator" @@ -1264,7 +1264,7 @@ fn group_ruby_distribution_changes_leading_and_interior_shares() { compose_ok!(¶graph, &style).lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>() }; @@ -1342,7 +1342,7 @@ fn group_ruby_longer_than_base_distributes_the_base_by_the_selected_method() { jis.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [250, 1_750], "JIS distributes the surplus at a 1:2:1 leading/interior/trailing ratio" @@ -1351,7 +1351,7 @@ fn group_ruby_longer_than_base_distributes_the_base_by_the_selected_method() { jis.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>(), [0, 500, 1_000, 1_500, 2_000, 2_500] ); @@ -1361,7 +1361,7 @@ fn group_ruby_longer_than_base_distributes_the_base_by_the_selected_method() { flush.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 2_000], "flush aligns both ends and puts the whole surplus between base characters" @@ -1370,7 +1370,7 @@ fn group_ruby_longer_than_base_distributes_the_base_by_the_selected_method() { flush.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>(), [0, 500, 1_000, 1_500, 2_000, 2_500] ); @@ -1407,7 +1407,7 @@ fn ruby_kinds_preserve_base_associations_and_break_semantics() { let mono_inline: Vec<_> = mono_layout.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect(); assert_eq!(mono_inline, [250, 1_000, 1_500]); @@ -1415,7 +1415,7 @@ fn ruby_kinds_preserve_base_associations_and_break_semantics() { let jukugo_inline: Vec<_> = jukugo_layout.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect(); assert_eq!(jukugo_inline, mono_inline); @@ -1423,7 +1423,7 @@ fn ruby_kinds_preserve_base_associations_and_break_semantics() { let group_inline: Vec<_> = group_layout.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect(); assert_eq!(group_inline, [84, 751, 1_417]); @@ -1526,7 +1526,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { forward.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_000, 2_000, 3_000], "F.2 prefers overhanging the following base and then the following kana" @@ -1535,7 +1535,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { forward.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>(), [1_000, 1_500, 2_000, 2_500, 3_000] ); @@ -1555,7 +1555,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { backward.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_000, 2_000, 3_000], "F.2 falls back to the preceding permitted character when the following one forbids overhang" @@ -1564,7 +1564,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { backward.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>(), [500, 1_000, 1_500, 2_000, 2_500] ); @@ -1585,7 +1585,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { expanded.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_250, 2_500, 3_500], "F.3 splits the remaining ruby-character width around the run with three ruby characters" @@ -1594,7 +1594,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { expanded.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>(), [1_000, 1_500, 2_000, 2_500, 3_000] ); @@ -1616,7 +1616,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { four_ruby.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_000, 2_250, 3_500], "F.4's four-ruby example assigns one ruby-character width around only the eligible base" @@ -1625,7 +1625,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { four_ruby.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>(), [1_000, 1_500, 2_000, 2_500, 3_000] ); @@ -1645,7 +1645,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { line_head.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_500, 2_500], "F.3 keeps both starts flush at line head and puts the whole assigned space after the first base" @@ -1654,7 +1654,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { line_head.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>(), [0, 500, 1_000, 1_500, 2_000] ); @@ -1674,7 +1674,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { line_end.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_000, 2_500], "F.3 keeps both ends flush at line end and puts the whole assigned space before the final base" @@ -1683,7 +1683,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { line_end.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>(), [1_000, 1_500, 2_000, 2_500, 3_000] ); @@ -1707,7 +1707,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { proportional.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_322, 3_072, 4_500, 5_500], "F.3 apportions the total expansion in the 3:4 solid-reading-length ratio and sends the integer remainder leading" @@ -1716,7 +1716,7 @@ fn phonetic_jukugo_follows_runs_before_expanding_eligible_base_gaps() { proportional.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>(), [ 1_000, 1_500, 2_000, 2_500, 3_000, 3_500, 4_000, 4_500, 5_000 @@ -1752,7 +1752,7 @@ fn long_ruby_respects_neighbor_and_indent_overhang_budgets() { layout.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_250, 2_750, 4_000], "a long group ruby distributes the base at the JIS 1:2:1 ratio" @@ -1762,7 +1762,7 @@ fn long_ruby_respects_neighbor_and_indent_overhang_budgets() { layout.lines()[0] .attachments() .iter() - .map(jlreq::Attachment::inline) + .map(jlreq_core::Attachment::inline) .collect::>(), [1_000, 1_500, 2_000, 2_500, 3_000, 3_500] ); @@ -1878,7 +1878,7 @@ fn long_ruby_respects_neighbor_and_indent_overhang_budgets() { fixpoint .lines() .iter() - .map(jlreq::Line::range) + .map(jlreq_core::Line::range) .collect::>(), [0..3, 3..6, 6..12], "break search remeasures the ruby after each candidate changes its neighbors" @@ -1911,7 +1911,7 @@ fn construct_run_boundaries_expand_at_third_order_but_not_inside_one_run() { compose_ok!(¶graph, &Style::default()).lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect() } @@ -1983,7 +1983,7 @@ fn construct_run_boundaries_expand_at_third_order_but_not_inside_one_run() { same_tcy.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 0, 1_250], "members of one tate-chu-yoko run share an inline item and cannot expand internally" @@ -2020,7 +2020,7 @@ fn construct_run_boundaries_expand_at_third_order_but_not_inside_one_run() { mixed.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_100, 2_250, 3_750], "the outer second-order site takes 250 first, then third-order shares split the remaining 250 as 100 and 150" @@ -2561,7 +2561,7 @@ fn reduction_uses_lower_priority_stages_only_after_earlier_ones() { let positions: Vec<_> = layout.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect(); assert_eq!(positions, [0, 1_000, 2_000, 3_400]); @@ -2698,7 +2698,7 @@ fn hanging_punctuation_closes_only_the_shortfall_reduction_leaves() { hanging.lines()[0] .clusters() .iter() - .map(jlreq::ClusterPlacement::inline) + .map(jlreq_core::ClusterPlacement::inline) .collect::>(), [0, 1_000], "hanging changes line accounting, not the shaped placements" @@ -2806,7 +2806,7 @@ fn composer_reuses_scratch_without_borrowing_the_returned_layout() { .breaks([Break::allowed(3)]) .build() .expect("valid paragraph"); - let mut composer = jlreq::Composer::new(); + let mut composer = jlreq_core::Composer::new(); let first = composer .compose(¶graph, &Style::default()) .expect("composition succeeds"); @@ -2820,9 +2820,9 @@ fn composer_reuses_scratch_without_borrowing_the_returned_layout() { #[test] fn composition_limits_are_typed_atomic_and_reusable() { fn assert_error() {} - assert_error::(); - assert_error::(); - assert_error::(); + assert_error::(); + assert_error::(); + assert_error::(); let defaults = CompositionLimits::default(); assert_eq!(defaults.max_clusters(), 65_536); diff --git a/crates/jlreq/Cargo.toml b/crates/jlreq/Cargo.toml index 474db16..28e6eea 100644 --- a/crates/jlreq/Cargo.toml +++ b/crates/jlreq/Cargo.toml @@ -4,14 +4,14 @@ [package] name = "jlreq" -description = "A no_std Japanese line-composition engine for pre-shaped text" -keywords = ["japanese", "typesetting", "jlreq", "text-layout", "no-std"] -categories = ["text-processing", "internationalization", "no-std"] +description = "Draw-ready Japanese text layout from UTF-8 text and font data" +keywords = ["japanese", "typesetting", "jlreq", "text-layout", "shaping"] +categories = ["text-processing", "internationalization", "graphics"] readme = "README.md" documentation = "https://docs.rs/jlreq" version.workspace = true edition.workspace = true -rust-version.workspace = true +rust-version = "1.88" license.workspace = true authors.workspace = true repository.workspace = true @@ -26,5 +26,25 @@ include = [ "LICENSE-APACHE", ] +[features] +default = [] +system-fonts = ["dep:fontique"] + +[dependencies] +harfrust = { version = "=0.13.3", default-features = true } +icu_segmenter = { version = "=2.3.0", default-features = true } +jlreq-core = { version = "0.1.0", path = "../jlreq-core" } +unicode-bidi = { version = "=0.3.18", default-features = true } + +[dependencies.fontique] +version = "=0.11.1" +default-features = false +features = ["system"] +optional = true + +[dev-dependencies] +font-test-data = "=0.9.1" +rwml-fonts = "=0.1.3" + [lints] workspace = true diff --git a/crates/jlreq/README.md b/crates/jlreq/README.md index c536724..8c384e4 100644 --- a/crates/jlreq/README.md +++ b/crates/jlreq/README.md @@ -1,43 +1,48 @@ - - # jlreq -`jlreq` 0.1.0 is a dependency-free `no_std + alloc` Japanese line-composition engine for -already-shaped text. Callers provide UTF-8 byte ranges, cluster advances, and line-break -opportunities; jlreq returns integer logical placements without loading fonts, shaping, -running bidi, rendering, or discovering UAX #14 breaks. +`jlreq` turns UTF-8 text, in-memory TTF/OTF/TTC data, a font size, and a line extent into +renderer-independent, draw-ready glyph placements. It performs grapheme-aware font +fallback, HarfRust shaping, UAX #9 bidi resolution, UAX #14 line segmentation, and +deterministic Japanese composition through [`jlreq-core`](https://crates.io/crates/jlreq-core). ```rust -use jlreq::{Break, Cluster, Frame, Paragraph, ShapedText, Size, Style}; - -let source = "日本語組版"; -let clusters = source.char_indices().map(|(start, ch)| { - Cluster::new(start..start + ch.len_utf8(), 1_000) -}); -let text = ShapedText::new(source, Size::square(1_000)?, Frame::FullEm, clusters)?; -let paragraph = Paragraph::builder(text, 4_000) - .breaks(source.char_indices().skip(1).map(|(at, _)| Break::allowed(at))) - .build()?; -let layout = jlreq::compose(¶graph, &Style::book_2020()) - .expect("this small paragraph is within the default resource limits"); - -assert_eq!(layout.lines().len(), 2); -# Ok::<(), jlreq::InputError>(()) +# fn quick_start(font_bytes: Vec) -> Result<(), jlreq::LayoutError> { +use jlreq::{FontLibrary, LayoutOptions}; + +let mut fonts = FontLibrary::new(); +fonts.register_font(font_bytes)?; + +let options = LayoutOptions::try_new(240.0, 16.0)?; +let layout = jlreq::layout("日本語組版 — draw-ready glyphs", &fonts, options)?; + +for glyph in layout.glyphs() { + let font = &layout.fonts()[glyph.font_id().get() as usize]; + renderer_draw( + font.bytes(), + font.face_index(), + glyph.glyph_id(), + glyph.origin(), + glyph.transform(), + ); +} +# fn renderer_draw( +# _: &[u8], _: u32, _: u32, _: jlreq::Point, _: jlreq::GlyphTransform, +# ) {} +# Ok(()) +# } ``` -Composition returns either a complete exact `Layout` or a typed `ComposeError`; it never -returns a partial layout or silently changes search strategy. `CompositionLimits` bounds -clusters, break candidates, constructs, tab stops, and exact-search transitions. A -`Composer` retains scratch allocation across calls and remains reusable after an error. - -The packaged, executable examples cover [minimal composition](examples/minimal.rs), -[Composer reuse and a resource refusal](examples/composer.rs), and -[vertical placement](examples/vertical.rs). The repository's -[ICU4X + HarfRust integration test](https://github.com/P4suta/jlreq/blob/main/crates/jlreq-conformance/tests/reference_integration.rs) -shows the intended segmentation/shaping boundary. See the -[repository guide](https://github.com/P4suta/jlreq) for scope and protocol details, or run -`cargo doc -p jlreq --open` for the API reference. +`TextLayout` owns every font resource referenced by its glyphs, so it can be passed to a +renderer without retaining `FontLibrary`. Glyphs preserve the original UTF-8 byte range, +physical origin, advances and offsets, transform, bidi level, and visual draw order. +Rasterization, drawing, PDF serialization, and GPU integration remain renderer concerns. + +Use `LayoutEngine` for repeated work, `DocumentBuilder` for spans, explicit break control, +ruby and the other eight typed inline constructs, and `jlreq::core` when text is already +shaped and the caller wants exact control over clusters and break opportunities. + +The optional `system-fonts` feature exposes Fontique-backed OS font discovery. Layout from +explicit font bytes is deterministic across supported operating systems; selection from a +changing system collection is intentionally outside that guarantee. + +Licensed under MIT or Apache-2.0, at your option. diff --git a/crates/jlreq/examples/quick_start.rs b/crates/jlreq/examples/quick_start.rs new file mode 100644 index 0000000..dadadff --- /dev/null +++ b/crates/jlreq/examples/quick_start.rs @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Load an explicit font and print the size of a draw-ready layout. + +use std::error::Error; + +use jlreq::{FontLibrary, LayoutOptions}; + +fn main() -> Result<(), Box> { + let path = std::env::args() + .nth(1) + .ok_or("pass a TTF, OTF, or TTC path")?; + let mut fonts = FontLibrary::new(); + fonts.register_font(std::fs::read(path)?)?; + let layout = jlreq::layout( + "日本語組版 — draw-ready glyphs", + &fonts, + LayoutOptions::try_new(240.0, 16.0)?, + )?; + println!( + "{} line(s), {} glyph(s), {} diagnostic(s)", + layout.lines().len(), + layout.glyphs().count(), + layout.diagnostics().len() + ); + Ok(()) +} diff --git a/crates/jlreq/src/document.rs b/crates/jlreq/src/document.rs new file mode 100644 index 0000000..80b4e29 --- /dev/null +++ b/crates/jlreq/src/document.rs @@ -0,0 +1,719 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::ops::Range; + +use crate::units::{non_negative, positive}; +use crate::{FontStyle, FontVariation, LayoutError, OpenTypeFeature, OptionKind}; + +/// Conservative semantic classification that cannot always be inferred from text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[non_exhaustive] +pub enum TextRole { + /// Ordinary prose. + #[default] + Text, + /// A numeral treated as one grouped Japanese numeral. + GroupedNumeral, + /// A unit-symbol character. + UnitSymbol, + /// A quantity symbol. + QuantitySymbol, + /// Mathematical or chemical notation. + Formula, + /// A bracket delimiting warichu. + WarichuBracket, +} + +impl TextRole { + pub(crate) const fn core(self) -> jlreq_core::ClusterRole { + match self { + Self::Text => jlreq_core::ClusterRole::Text, + Self::GroupedNumeral => jlreq_core::ClusterRole::GroupedNumeral, + Self::UnitSymbol => jlreq_core::ClusterRole::UnitSymbol, + Self::QuantitySymbol => jlreq_core::ClusterRole::QuantitySymbol, + Self::Formula => jlreq_core::ClusterRole::Formula, + Self::WarichuBracket => jlreq_core::ClusterRole::WarichuBracket, + } + } +} + +/// Typed style applied to a UTF-8 byte range. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub struct SpanStyle { + pub(crate) families: Vec, + pub(crate) font_style: FontStyle, + pub(crate) font_size: Option, + pub(crate) language: Option, + pub(crate) features: Vec, + pub(crate) variations: Vec, + pub(crate) role: TextRole, +} + +impl SpanStyle { + /// Default span, inheriting size/language and using library fallback order. + #[must_use] + pub const fn new() -> Self { + Self { + families: Vec::new(), + font_style: FontStyle::new(400, 100, crate::FontSlant::Normal), + font_size: None, + language: None, + features: Vec::new(), + variations: Vec::new(), + role: TextRole::Text, + } + } + + /// Add a preferred family ahead of normal fallback. + #[must_use] + pub fn family(mut self, family: impl Into) -> Self { + self.families.push(family.into()); + self + } + + /// Set requested family matching attributes. + #[must_use] + pub const fn font_style(mut self, style: FontStyle) -> Self { + self.font_style = style; + self + } + + /// Override the main font size for this span. + pub fn font_size(mut self, value: f32) -> Result { + self.font_size = Some(positive(value, OptionKind::SpanFontSize)?); + Ok(self) + } + + /// Override shaping language for this span. + pub fn language(mut self, value: impl Into) -> Result { + let value = value.into(); + if value.is_empty() + || value.len() > 63 + || !value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-') + { + return Err(LayoutError::invalid_option( + OptionKind::Language, + "language must be a non-empty ASCII language tag of at most 63 bytes", + )); + } + self.language = Some(value); + Ok(self) + } + + /// Add a span-specific OpenType feature. + #[must_use] + pub fn feature(mut self, value: OpenTypeFeature) -> Self { + self.features.push(value); + self + } + + /// Add a span-specific variable-font axis value. + #[must_use] + pub fn variation(mut self, value: FontVariation) -> Self { + self.variations.push(value); + self + } + + /// Assert a semantic classification instead of relying on conservative inference. + #[must_use] + pub const fn role(mut self, value: TextRole) -> Self { + self.role = value; + self + } +} + +impl Default for SpanStyle { + fn default() -> Self { + Self::new() + } +} + +/// Relationship between ruby base and annotation. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum RubyKind { + /// Each base cluster receives an associated reading run. + Mono, + /// One reading belongs to the full base. + Group, + /// Reading runs form a compound word. + Jukugo, +} + +impl RubyKind { + pub(crate) const fn core(self) -> jlreq_core::RubyKind { + match self { + Self::Mono => jlreq_core::RubyKind::Mono, + Self::Group => jlreq_core::RubyKind::Group, + Self::Jukugo => jlreq_core::RubyKind::Jukugo, + } + } +} + +/// Explicit association inside mono or jukugo ruby. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RubyRun { + base: Range, + annotation: Range, +} + +impl RubyRun { + /// Associate a document base range with a range in the annotation string. + #[must_use] + pub const fn new(base: Range, annotation: Range) -> Self { + Self { base, annotation } + } + + /// Document base range. + #[must_use] + pub fn base(&self) -> Range { + self.base.clone() + } + + /// Annotation-local range. + #[must_use] + pub fn annotation(&self) -> Range { + self.annotation.clone() + } +} + +/// Placement side for an automatically shaped script annotation. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum ScriptPosition { + /// Superscript-side placement. + Superscript, + /// Subscript-side placement. + Subscript, +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum DocumentConstruct { + Ruby { + kind: RubyKind, + base: Range, + annotation: String, + runs: Vec, + }, + TateChuYoko(Range), + Emphasis { + range: Range, + mark: char, + }, + Warichu(Range), + Furawake { + range: Range, + columns: u16, + line_gap: i32, + }, + Jidori { + range: Range, + cells: u16, + }, + ReferenceMark { + range: Range, + mark: String, + }, + Script { + range: Range, + annotation: String, + position: ScriptPosition, + }, + Formula(Range), +} + +impl DocumentConstruct { + pub(crate) fn range(&self) -> Range { + match self { + Self::Ruby { base, .. } => base.clone(), + Self::TateChuYoko(range) + | Self::Warichu(range) + | Self::Formula(range) + | Self::Emphasis { range, .. } + | Self::Furawake { range, .. } + | Self::Jidori { range, .. } + | Self::ReferenceMark { range, .. } + | Self::Script { range, .. } => range.clone(), + } + } +} + +/// Validated styled text and typed inline constructs. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub struct Document { + pub(crate) text: String, + pub(crate) spans: Vec<(Range, SpanStyle)>, + pub(crate) constructs: Vec, + pub(crate) mandatory_breaks: Vec, + pub(crate) prohibited_breaks: Vec, +} + +impl Document { + /// Original UTF-8 source. + #[must_use] + pub fn text(&self) -> &str { + &self.text + } + + /// Number of typed inline constructs. + #[must_use] + pub const fn construct_count(&self) -> usize { + self.constructs.len() + } +} + +/// Incrementally builds a [`Document`] while preserving UTF-8 ranges. +#[derive(Debug, Clone)] +pub struct DocumentBuilder { + text: String, + spans: Vec<(Range, SpanStyle)>, + constructs: Vec, + mandatory_breaks: Vec, + prohibited_breaks: Vec, +} + +impl DocumentBuilder { + /// Start a document around owned UTF-8 text. + #[must_use] + pub fn new(text: impl Into) -> Self { + Self { + text: text.into(), + spans: Vec::new(), + constructs: Vec::new(), + mandatory_breaks: Vec::new(), + prohibited_breaks: Vec::new(), + } + } + + /// Add a non-overlapping span style. + pub fn span( + &mut self, + range: Range, + style: SpanStyle, + ) -> Result<&mut Self, LayoutError> { + self.validate_non_empty_range(&range, "document.invalid-span-range")?; + if self + .spans + .iter() + .any(|(other, _)| ranges_overlap(other, &range)) + { + return Err(LayoutError::invalid_document( + "document.overlapping-spans", + Some(range), + )); + } + self.spans.push((range, style)); + Ok(self) + } + + /// Require a break at a UTF-8 boundary inside a paragraph. + pub fn mandatory_break(&mut self, offset: usize) -> Result<&mut Self, LayoutError> { + self.validate_offset(offset, "document.invalid-break")?; + self.mandatory_breaks.push(offset); + Ok(self) + } + + /// Remove an otherwise automatic UAX #14 opportunity. + pub fn prohibit_break(&mut self, offset: usize) -> Result<&mut Self, LayoutError> { + self.validate_offset(offset, "document.invalid-break")?; + self.prohibited_breaks.push(offset); + Ok(self) + } + + /// Add ruby with explicit or automatically derived associations. + pub fn ruby( + &mut self, + kind: RubyKind, + base: Range, + annotation: impl Into, + runs: I, + ) -> Result<&mut Self, LayoutError> + where + I: IntoIterator, + { + self.validate_non_empty_range(&base, "document.invalid-construct-range")?; + let annotation = annotation.into(); + if annotation.is_empty() { + return Err(LayoutError::invalid_document( + "document.empty-ruby-annotation", + Some(base), + )); + } + self.constructs.push(DocumentConstruct::Ruby { + kind, + base, + annotation, + runs: runs.into_iter().collect(), + }); + Ok(self) + } + + /// Add automatically associated group ruby. + pub fn group_ruby( + &mut self, + base: Range, + annotation: impl Into, + ) -> Result<&mut Self, LayoutError> { + self.ruby(RubyKind::Group, base, annotation, []) + } + + /// Add automatically partitioned mono ruby. + pub fn mono_ruby( + &mut self, + base: Range, + annotation: impl Into, + ) -> Result<&mut Self, LayoutError> { + self.ruby(RubyKind::Mono, base, annotation, []) + } + + /// Add automatically associated jukugo ruby. + pub fn jukugo_ruby( + &mut self, + base: Range, + annotation: impl Into, + ) -> Result<&mut Self, LayoutError> { + self.ruby(RubyKind::Jukugo, base, annotation, []) + } + + /// Keep a range horizontal in vertical text. + pub fn tate_chu_yoko(&mut self, range: Range) -> Result<&mut Self, LayoutError> { + self.push_range_construct(range, DocumentConstruct::TateChuYoko) + } + + /// Repeat an emphasis mark alongside every base cluster. + pub fn emphasis_dots( + &mut self, + range: Range, + mark: char, + ) -> Result<&mut Self, LayoutError> { + self.validate_non_empty_range(&range, "document.invalid-construct-range")?; + self.constructs + .push(DocumentConstruct::Emphasis { range, mark }); + Ok(self) + } + + /// Add an inline cutting note (割注). + pub fn warichu(&mut self, range: Range) -> Result<&mut Self, LayoutError> { + self.push_range_construct(range, DocumentConstruct::Warichu) + } + + /// Distribute a range over aligned sublines (振分け). + pub fn furawake( + &mut self, + range: Range, + columns: u16, + line_gap: f32, + ) -> Result<&mut Self, LayoutError> { + self.validate_non_empty_range(&range, "document.invalid-construct-range")?; + if columns < 2 { + return Err(LayoutError::invalid_document( + "document.invalid-furawake-columns", + Some(range), + )); + } + self.constructs.push(DocumentConstruct::Furawake { + range, + columns, + line_gap: non_negative(line_gap, OptionKind::ConstructGeometry)?, + }); + Ok(self) + } + + /// Fit a range in a fixed number of full-em cells (字取り). + pub fn jidori(&mut self, range: Range, cells: u16) -> Result<&mut Self, LayoutError> { + self.validate_non_empty_range(&range, "document.invalid-construct-range")?; + if cells == 0 { + return Err(LayoutError::invalid_document( + "document.invalid-jidori-cells", + Some(range), + )); + } + self.constructs + .push(DocumentConstruct::Jidori { range, cells }); + Ok(self) + } + + /// Attach an automatically shaped reference mark (合印). + pub fn reference_mark( + &mut self, + range: Range, + mark: impl Into, + ) -> Result<&mut Self, LayoutError> { + self.validate_non_empty_range(&range, "document.invalid-construct-range")?; + let mark = mark.into(); + if mark.is_empty() { + return Err(LayoutError::invalid_document( + "document.empty-reference-mark", + Some(range), + )); + } + self.constructs + .push(DocumentConstruct::ReferenceMark { range, mark }); + Ok(self) + } + + /// Attach automatically shaped superscript or subscript text. + pub fn script( + &mut self, + range: Range, + annotation: impl Into, + position: ScriptPosition, + ) -> Result<&mut Self, LayoutError> { + self.validate_non_empty_range(&range, "document.invalid-construct-range")?; + let annotation = annotation.into(); + if annotation.is_empty() { + return Err(LayoutError::invalid_document( + "document.empty-script-annotation", + Some(range), + )); + } + self.constructs.push(DocumentConstruct::Script { + range, + annotation, + position, + }); + Ok(self) + } + + /// Mark a pre-existing range as mathematical content. + pub fn formula(&mut self, range: Range) -> Result<&mut Self, LayoutError> { + self.push_range_construct(range, DocumentConstruct::Formula) + } + + /// Validate cross-field relationships and finish the document atomically. + pub fn build(mut self) -> Result { + self.spans.sort_by_key(|(range, _)| range.start); + self.constructs.sort_by_key(|construct| { + let range = construct.range(); + (range.start, range.end) + }); + self.mandatory_breaks.sort_unstable(); + self.mandatory_breaks.dedup(); + self.prohibited_breaks.sort_unstable(); + self.prohibited_breaks.dedup(); + if let Some(offset) = self + .mandatory_breaks + .iter() + .find(|offset| self.prohibited_breaks.binary_search(offset).is_ok()) + { + return Err(LayoutError::invalid_document( + "document.conflicting-break", + Some(*offset..*offset), + )); + } + for construct in &self.constructs { + if let DocumentConstruct::Ruby { + kind, + base, + annotation, + runs, + } = construct + { + validate_ruby_runs(&self.text, *kind, base, annotation, runs)?; + } + } + Ok(Document { + text: self.text, + spans: self.spans, + constructs: self.constructs, + mandatory_breaks: self.mandatory_breaks, + prohibited_breaks: self.prohibited_breaks, + }) + } + + fn validate_non_empty_range( + &self, + range: &Range, + code: &'static str, + ) -> Result<(), LayoutError> { + if range.start >= range.end + || range.end > self.text.len() + || !self.text.is_char_boundary(range.start) + || !self.text.is_char_boundary(range.end) + { + return Err(LayoutError::invalid_document(code, Some(range.clone()))); + } + Ok(()) + } + + fn validate_offset(&self, offset: usize, code: &'static str) -> Result<(), LayoutError> { + if offset == 0 || offset >= self.text.len() || !self.text.is_char_boundary(offset) { + return Err(LayoutError::invalid_document(code, Some(offset..offset))); + } + Ok(()) + } + + fn push_range_construct( + &mut self, + range: Range, + constructor: fn(Range) -> DocumentConstruct, + ) -> Result<&mut Self, LayoutError> { + self.validate_non_empty_range(&range, "document.invalid-construct-range")?; + self.constructs.push(constructor(range)); + Ok(self) + } +} + +fn validate_ruby_runs( + text: &str, + kind: RubyKind, + base: &Range, + annotation: &str, + runs: &[RubyRun], +) -> Result<(), LayoutError> { + if runs.is_empty() { + return Ok(()); + } + if kind == RubyKind::Group && runs.len() != 1 { + return Err(LayoutError::invalid_document( + "document.group-ruby-run-count", + Some(base.clone()), + )); + } + let mut base_cursor = base.start; + let mut annotation_cursor = 0; + for run in runs { + if run.base.start != base_cursor + || run.base.end > base.end + || run.base.start >= run.base.end + || !text.is_char_boundary(run.base.start) + || !text.is_char_boundary(run.base.end) + || run.annotation.start != annotation_cursor + || run.annotation.end > annotation.len() + || run.annotation.start >= run.annotation.end + || !annotation.is_char_boundary(run.annotation.start) + || !annotation.is_char_boundary(run.annotation.end) + { + return Err(LayoutError::invalid_document( + "document.invalid-ruby-run", + Some(run.base.clone()), + )); + } + base_cursor = run.base.end; + annotation_cursor = run.annotation.end; + } + if base_cursor != base.end || annotation_cursor != annotation.len() { + return Err(LayoutError::invalid_document( + "document.incomplete-ruby-runs", + Some(base.clone()), + )); + } + Ok(()) +} + +fn ranges_overlap(left: &Range, right: &Range) -> bool { + left.start < right.end && right.start < left.end +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{FontSlant, OpenTypeTag}; + + fn ruby_error( + text: &str, + kind: RubyKind, + base: Range, + annotation: &str, + runs: &[RubyRun], + ) -> &'static str { + validate_ruby_runs(text, kind, &base, annotation, runs) + .expect_err("the run must be rejected") + .code() + } + + #[test] + fn span_style_builders_preserve_every_supplied_value_and_language_boundary() { + let feature = OpenTypeFeature::new(OpenTypeTag::try_new("liga").unwrap(), 0); + let variation = + FontVariation::try_new(OpenTypeTag::try_new("wght").unwrap(), 525.0).unwrap(); + let font_style = FontStyle::new(525, 90, FontSlant::Italic); + let style = SpanStyle::new() + .family("first") + .family("second") + .font_style(font_style) + .font_size(17.25) + .unwrap() + .language("ja-Latn-JP") + .unwrap() + .feature(feature) + .variation(variation) + .role(TextRole::Formula); + + assert_eq!(style.families, ["first", "second"]); + assert_eq!(style.font_style, font_style); + assert_eq!(style.font_size, Some(17 * 64 + 16)); + assert_eq!(style.language.as_deref(), Some("ja-Latn-JP")); + assert_eq!(style.features, [feature]); + assert_eq!(style.variations, [variation]); + assert_eq!(style.role, TextRole::Formula); + + assert!(SpanStyle::new().language("a".repeat(63)).is_ok()); + assert!(SpanStyle::new().language("a".repeat(64)).is_err()); + assert!(SpanStyle::new().language("a".repeat(65)).is_err()); + assert!(SpanStyle::new().language("").is_err()); + assert!(SpanStyle::new().language("ja_JP").is_err()); + } + + #[test] + fn ruby_run_validation_reports_each_malformed_component() { + let complete = vec![RubyRun::new(0..2, 0..2), RubyRun::new(2..4, 2..4)]; + assert!(validate_ruby_runs("abcd", RubyKind::Mono, &(0..4), "wxyz", &complete).is_ok()); + assert_eq!( + ruby_error("abcd", RubyKind::Group, 0..4, "wxyz", &complete), + "document.group-ruby-run-count" + ); + + let invalid_cases = [ + ("abc", 0..2, "x", RubyRun::new(1..2, 0..1)), + ("abc", 0..2, "x", RubyRun::new(0..3, 0..1)), + ("abc", 0..2, "xy", RubyRun::new(0..0, 0..1)), + ("éA", 1..3, "x", RubyRun::new(1..3, 0..1)), + ("éA", 0..1, "x", RubyRun::new(0..1, 0..1)), + ("ab", 0..2, "xy", RubyRun::new(0..2, 1..2)), + ("ab", 0..2, "xy", RubyRun::new(0..2, 0..3)), + ("ab", 0..2, "xy", RubyRun::new(0..2, 0..0)), + ("ab", 0..2, "éA", RubyRun::new(0..2, 1..3)), + ("ab", 0..2, "éA", RubyRun::new(0..2, 0..1)), + ]; + for (text, base, annotation, run) in invalid_cases { + assert_eq!( + ruby_error(text, RubyKind::Mono, base, annotation, &[run]), + "document.invalid-ruby-run" + ); + } + + assert_eq!( + ruby_error( + "abcd", + RubyKind::Mono, + 0..4, + "x", + &[RubyRun::new(0..2, 0..1)], + ), + "document.incomplete-ruby-runs" + ); + assert_eq!( + ruby_error( + "ab", + RubyKind::Mono, + 0..2, + "xy", + &[RubyRun::new(0..2, 0..1)], + ), + "document.incomplete-ruby-runs" + ); + } + + #[test] + fn overlap_uses_half_open_range_edges() { + assert!(ranges_overlap(&(0..2), &(1..3))); + assert!(!ranges_overlap(&(0..1), &(1..2))); + assert!(!ranges_overlap(&(1..2), &(0..1))); + } +} diff --git a/crates/jlreq/src/engine.rs b/crates/jlreq/src/engine.rs new file mode 100644 index 0000000..8078399 --- /dev/null +++ b/crates/jlreq/src/engine.rs @@ -0,0 +1,2207 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::collections::{BTreeMap, BTreeSet}; +use std::fmt; +use std::ops::Range; +use std::sync::Arc; + +use harfrust::{ + Direction, Feature, Language, ShapeOptions, ShaperData, ShaperInstance, UnicodeBuffer, + Variation, +}; +use icu_segmenter::{GraphemeClusterSegmenter, LineSegmenter, options::LineBreakOptions}; +use unicode_bidi::{BidiInfo, Level, ParagraphBidiInfo}; + +use crate::document::{DocumentConstruct, TextRole}; +use crate::{ + Alignment, AnnotationSource, BaseDirection, Diagnostic, DiagnosticSeverity, Document, + DocumentBuilder, FontId, FontLibrary, FontResource, FontStyle, FontVariation, GlyphPlacement, + GlyphTransform, LayoutError, LayoutOptions, OpenTypeFeature, Point, Resource, SpanStyle, + TextLayout, TextLine, WritingMode, +}; + +struct FontCache { + bytes: Arc<[u8]>, + face_index: u32, + shaper_data: ShaperData, +} + +struct ShapeRequest<'a> { + source: &'a str, + range: Range, + resource: &'a FontResource, + size: i32, + direction: Direction, + language: &'a str, + features: &'a [OpenTypeFeature], + variations: &'a [FontVariation], +} + +struct PrepareRequest<'a> { + source: &'a str, + global_offset: usize, + spans: &'a [(Range, SpanStyle)], + fonts: &'a FontLibrary, + options: &'a LayoutOptions, + diagnostic_range: Option>, +} + +/// Reusable high-level layout engine. +/// +/// Font parsing and shaping caches are retained between calls. Returned layouts never borrow +/// the engine, and an error leaves it immediately reusable. +pub struct LayoutEngine { + fonts: BTreeMap, + composer: jlreq_core::Composer, +} + +impl fmt::Debug for LayoutEngine { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("LayoutEngine") + .field("cached_fonts", &self.fonts.len()) + .finish_non_exhaustive() + } +} + +impl Default for LayoutEngine { + fn default() -> Self { + Self::new() + } +} + +impl LayoutEngine { + /// Build an empty reusable engine. + #[must_use] + pub fn new() -> Self { + Self { + fonts: BTreeMap::new(), + composer: jlreq_core::Composer::new(), + } + } + + /// Shape, compose, reorder, and physically place plain UTF-8 text. + pub fn layout( + &mut self, + text: &str, + fonts: &FontLibrary, + options: LayoutOptions, + ) -> Result { + let document = DocumentBuilder::new(text).build()?; + self.layout_document(&document, fonts, options) + } + + /// Shape, compose, reorder, and physically place a typed document. + pub fn layout_document( + &mut self, + document: &Document, + fonts: &FontLibrary, + options: LayoutOptions, + ) -> Result { + // The public API intentionally takes an owned option set so callers can configure and + // submit it in one expression. Moving it through this single-element container makes + // that ownership boundary explicit while the pipeline borrows the stable value. + let options = [options]; + let options = &options[0]; + validate_call(document, fonts, options)?; + if document.text.is_empty() { + return Ok(TextLayout { + source: String::new(), + lines: Vec::new(), + fonts: Vec::new(), + diagnostics: Vec::new(), + writing_mode: options.writing_mode, + }); + } + + let segments = paragraph_segments(&document.text); + check_limit( + Resource::Paragraphs, + options.limits.paragraphs, + segments.len(), + )?; + let mut call = CallState::new(options); + let mut lines = Vec::new(); + let mut block_offset = 0_i32; + + for segment in segments { + let content = &document.text[segment.content.clone()]; + if content.is_empty() { + let origin = match options.writing_mode { + WritingMode::HorizontalTb => Point::from_fixed(0, block_offset), + WritingMode::VerticalRl => Point::from_fixed(block_offset, 0), + }; + lines.push(TextLine { + range: segment.content.clone(), + origin, + inline_extent: 0, + block_extent: options.font_size, + writing_mode: options.writing_mode, + glyphs: Vec::new(), + }); + block_offset = advance_block( + block_offset, + options.font_size.saturating_add(options.line_gap), + options.writing_mode, + ); + continue; + } + + let prepared = self.prepare_text( + PrepareRequest { + source: content, + global_offset: segment.content.start, + spans: &document.spans, + fonts, + options, + diagnostic_range: None, + }, + &mut call, + )?; + let shaped = prepared.to_core(content, options.font_size)?; + let (constructs, attachments) = self.lower_constructs( + document, + &segment.content, + &prepared, + fonts, + options, + &mut call, + )?; + let breaks = + collect_breaks(document, &segment.content, content, &prepared, &constructs); + let tabs = collect_tab_stops(content, options)?; + let paragraph = jlreq_core::Paragraph::builder(shaped, options.line_extent) + .breaks(breaks) + .constructs(constructs) + .tab_stops(tabs) + .alignment(options.alignment.core()) + .writing_mode(options.writing_mode.core()) + .build()?; + + let core_limits = jlreq_core::CompositionLimits::default() + .with_max_clusters(options.limits.glyphs) + .with_max_break_candidates(options.limits.runs) + .with_max_constructs(options.limits.constructs) + .with_max_tab_stops(options.limits.constructs) + .with_max_search_transitions(options.limits.core_operations); + self.composer.set_limits(core_limits); + let core_layout = self + .composer + .compose(¶graph, &options.style) + .map_err(map_core_resource_error)?; + + for diagnostic in core_layout.diagnostics() { + call.diagnostics.push(Diagnostic { + code: diagnostic.code(), + // Keep every public core severity distinct at the facade boundary. + // The helper is independently tested even when a particular composer + // release does not currently emit every declared severity. + severity: diagnostic_severity(diagnostic.severity()), + range: diagnostic.range().map(|range| { + range.start.saturating_add(segment.content.start) + ..range.end.saturating_add(segment.content.start) + }), + message: "the core composer produced a recoverable layout diagnostic", + jlreq: Some(diagnostic.jlreq()), + }); + } + + let paragraph_lines = map_core_lines( + &core_layout, + &prepared, + &attachments, + segment.content.start, + block_offset, + options, + ); + let consumed = paragraph_lines.len().max(1); + lines.extend(paragraph_lines); + let line_count = i32::try_from(consumed).unwrap_or(i32::MAX); + let block_advance = line_count + .saturating_mul(options.font_size.saturating_add(options.line_gap).max(0)); + block_offset = advance_block(block_offset, block_advance, options.writing_mode); + } + + call.diagnostics.sort_by_key(|diagnostic| { + diagnostic + .range + .as_ref() + .map_or((usize::MAX, usize::MAX), |range| (range.start, range.end)) + }); + let retained_fonts = call + .used_fonts + .iter() + .filter_map(|id| fonts.get(*id).cloned()) + .collect(); + Ok(TextLayout { + source: document.text.clone(), + lines, + fonts: retained_fonts, + diagnostics: call.diagnostics, + writing_mode: options.writing_mode, + }) + } + + fn ensure_cache(&mut self, resource: &FontResource) -> Result<(), LayoutError> { + let needs_replacement = self.fonts.get(&resource.id()).is_none_or(|cached| { + cached.face_index != resource.face_index() + || !Arc::ptr_eq(&cached.bytes, &resource.bytes) + }); + if needs_replacement { + let font = harfrust::FontRef::from_index(resource.bytes(), resource.face_index()) + .map_err(|_| LayoutError::invalid_font(resource.face_index()))?; + let shaper_data = ShaperData::new(&font); + self.fonts.insert( + resource.id(), + FontCache { + bytes: resource.bytes.clone(), + face_index: resource.face_index(), + shaper_data, + }, + ); + } + Ok(()) + } + + fn shape_font(&mut self, request: ShapeRequest<'_>) -> Result, LayoutError> { + let ShapeRequest { + source, + range, + resource, + size, + direction, + language, + features, + variations, + } = request; + self.ensure_cache(resource)?; + let cached = self + .fonts + .get(&resource.id()) + .ok_or_else(|| LayoutError::invalid_font(resource.face_index()))?; + let font = harfrust::FontRef::from_index(&cached.bytes, cached.face_index) + .map_err(|_| LayoutError::invalid_font(cached.face_index))?; + let variations: Vec<_> = variations + .iter() + .map(|variation| Variation { + tag: harfrust::Tag::new(&variation.tag().bytes()), + value: variation.value(), + }) + .collect(); + let instance = ShaperInstance::from_variations(&font, variations); + let shaper = cached + .shaper_data + .shaper(&font) + .instance(Some(&instance)) + .build(); + let mut buffer = UnicodeBuffer::new(); + for (relative, character) in source[range.clone()].char_indices() { + let cluster = u32::try_from(range.start.saturating_add(relative)).map_err(|_| { + LayoutError::resource(Resource::InputBytes, u32::MAX as usize, source.len()) + })?; + buffer.add(character, cluster); + } + buffer.set_direction(direction); + if let Some(language) = Language::new(language) { + buffer.set_language(language); + } + buffer.guess_segment_properties(); + let features: Vec<_> = features + .iter() + .map(|feature| { + Feature::new( + harfrust::Tag::new(&feature.tag().bytes()), + feature.value(), + .., + ) + }) + .collect(); + let glyphs = shaper.shape( + buffer, + ShapeOptions::new().scale(Some(size)).features(&features), + ); + Ok(glyphs + .glyph_infos() + .iter() + .zip(glyphs.glyph_positions()) + .map(|(info, position)| RawGlyph { + font_id: resource.id(), + glyph_id: info.glyph_id, + cluster: info.cluster as usize, + x_advance: position.x_advance, + y_advance: position.y_advance, + x_offset: position.x_offset, + y_offset: position.y_offset, + }) + .collect()) + } + + fn prepare_text( + &mut self, + request: PrepareRequest<'_>, + call: &mut CallState, + ) -> Result { + let PrepareRequest { + source, + global_offset, + spans, + fonts, + options, + diagnostic_range, + } = request; + if source.is_empty() { + return Ok(PreparedText { + clusters: Vec::new(), + }); + } + let base_level = match options.base_direction { + BaseDirection::Auto => None, + BaseDirection::LeftToRight => Some(Level::ltr()), + BaseDirection::RightToLeft => Some(Level::rtl()), + }; + let bidi = ParagraphBidiInfo::new(source, base_level); + let boundaries: Vec<_> = GraphemeClusterSegmenter::new() + .segment_str(source) + .collect(); + let mut graphemes = Vec::with_capacity(boundaries.len().saturating_sub(1)); + for pair in boundaries.windows(2) { + let range = pair[0]..pair[1]; + let global = + range.start.saturating_add(global_offset)..range.end.saturating_add(global_offset); + let effective = effective_style(&global, spans, options)?; + let level = bidi + .levels + .get(range.start) + .copied() + .unwrap_or(bidi.paragraph_level); + let script = script_class(&source[range.clone()]); + let direction = shape_direction(options.writing_mode, level, script); + let is_tab = &source[range.clone()] == "\t"; + let (font_id, missing) = if is_tab { + (fonts.primary().ok_or(LayoutError::NoFonts)?, false) + } else { + self.select_font(source, range.clone(), fonts, &effective, direction)? + }; + if missing { + call.diagnostics.push(Diagnostic { + code: "font.missing-glyph", + severity: DiagnosticSeverity::Warning, + range: Some(diagnostic_range.clone().unwrap_or(global)), + message: "no fallback face covers the complete grapheme; primary .notdef was retained", + jlreq: None, + }); + } + graphemes.push(GraphemeItem { + range, + level, + script, + direction, + font_id, + effective, + is_tab, + }); + } + + let mut clusters = Vec::new(); + let mut index = 0; + while index < graphemes.len() { + if graphemes[index].is_tab { + let item = &graphemes[index]; + clusters.push(PreparedCluster { + range: item.range.clone(), + advance: 0, + size: item.effective.size, + frame: jlreq_core::Frame::Proportional, + role: None, + bidi_level: item.level.number(), + glyphs: Vec::new(), + }); + index = index.saturating_add(1); + continue; + } + let start = index; + index = index.saturating_add(1); + while index < graphemes.len() + && !graphemes[index].is_tab + && graphemes[index].same_run(&graphemes[start]) + { + index = index.saturating_add(1); + } + call.charge_run()?; + let first = &graphemes[start]; + let run_range = first.range.start..graphemes[index.saturating_sub(1)].range.end; + let resource = fonts + .get(first.font_id) + .ok_or_else(|| LayoutError::invalid_document("font.unknown-id", None))?; + let raw = self.shape_font(ShapeRequest { + source, + range: run_range.clone(), + resource, + size: first.effective.size, + direction: first.direction, + language: &first.effective.language, + features: &first.effective.features, + variations: &first.effective.variations, + })?; + call.used_fonts.insert(first.font_id); + call.charge_glyphs(raw.len())?; + clusters.extend(aggregate_run( + source, + run_range, + &raw, + first.effective.size, + first.effective.role, + first.level.number(), + first.direction, + )); + } + Ok(PreparedText { clusters }) + } + + fn select_font( + &mut self, + source: &str, + range: Range, + fonts: &FontLibrary, + style: &EffectiveStyle, + direction: Direction, + ) -> Result<(FontId, bool), LayoutError> { + for id in fonts.ordered_candidates(&style.families, style.font_style) { + let resource = fonts + .get(id) + .ok_or_else(|| LayoutError::invalid_document("font.unknown-id", None))?; + let glyphs = self.shape_font(ShapeRequest { + source, + range: range.clone(), + resource, + size: style.size, + direction, + language: &style.language, + features: &style.features, + variations: &style.variations, + })?; + if !glyphs.is_empty() && glyphs.iter().all(|glyph| glyph.glyph_id != 0) { + return Ok((id, false)); + } + } + Ok((fonts.primary().ok_or(LayoutError::NoFonts)?, true)) + } + + fn lower_constructs( + &mut self, + document: &Document, + paragraph_range: &Range, + prepared: &PreparedText, + fonts: &FontLibrary, + options: &LayoutOptions, + call: &mut CallState, + ) -> Result<(Vec, Vec), LayoutError> { + let mut constructs = Vec::new(); + let mut attachments = Vec::new(); + for (global_ordinal, construct) in document.constructs.iter().enumerate() { + let global_range = construct.range(); + if !ranges_overlap(&global_range, paragraph_range) { + continue; + } + if global_range.start < paragraph_range.start || global_range.end > paragraph_range.end + { + return Err(LayoutError::invalid_document( + "document.construct-crosses-paragraph", + Some(global_range), + )); + } + let local_range = global_range.start.saturating_sub(paragraph_range.start) + ..global_range.end.saturating_sub(paragraph_range.start); + let local_ordinal = constructs.len(); + match construct { + DocumentConstruct::Ruby { + kind, + annotation, + runs, + .. + } => { + let annotation_options = annotation_options(options); + let annotation_prepared = self.prepare_text( + PrepareRequest { + source: annotation, + global_offset: 0, + spans: &[], + fonts, + options: &annotation_options, + diagnostic_range: Some(global_range.clone()), + }, + call, + )?; + let shaped = + annotation_prepared.to_core(annotation, annotation_options.font_size)?; + let core_runs = ruby_runs( + *kind, + &local_range, + paragraph_range.start, + runs, + prepared, + &annotation_prepared, + annotation.len(), + )?; + let ruby = + jlreq_core::Ruby::new(kind.core(), local_range.clone(), shaped, core_runs)?; + constructs.push(jlreq_core::Construct::ruby(ruby)); + attachments.push(AttachmentShape { + local_ordinal, + global_ordinal, + base: global_range, + prepared: annotation_prepared, + }); + }, + DocumentConstruct::TateChuYoko(_) => { + constructs.push(jlreq_core::Construct::tate_chu_yoko(local_range)); + }, + DocumentConstruct::Emphasis { mark, .. } => { + constructs.push(jlreq_core::Construct::emphasis_dots(local_range, *mark)); + let mark_text = mark.to_string(); + let annotation_options = annotation_options(options); + let mark_prepared = self.prepare_text( + PrepareRequest { + source: &mark_text, + global_offset: 0, + spans: &[], + fonts, + options: &annotation_options, + diagnostic_range: Some(global_range.clone()), + }, + call, + )?; + attachments.push(AttachmentShape { + local_ordinal, + global_ordinal, + base: global_range, + prepared: mark_prepared, + }); + }, + DocumentConstruct::Warichu(_) => { + constructs.push(jlreq_core::Construct::warichu(local_range)); + }, + DocumentConstruct::Furawake { + columns, line_gap, .. + } => { + constructs.push(jlreq_core::Construct::furawake( + local_range, + *columns, + *line_gap, + )); + }, + DocumentConstruct::Jidori { cells, .. } => { + constructs.push(jlreq_core::Construct::jidori(local_range, *cells)); + }, + DocumentConstruct::ReferenceMark { mark, .. } => { + let annotation_options = annotation_options(options); + let annotation_prepared = self.prepare_text( + PrepareRequest { + source: mark, + global_offset: 0, + spans: &[], + fonts, + options: &annotation_options, + diagnostic_range: Some(global_range.clone()), + }, + call, + )?; + let shaped = annotation_prepared.to_core(mark, annotation_options.font_size)?; + constructs.push(jlreq_core::Construct::reference_mark(local_range, shaped)); + attachments.push(AttachmentShape { + local_ordinal, + global_ordinal, + base: global_range, + prepared: annotation_prepared, + }); + }, + DocumentConstruct::Script { annotation, .. } => { + let annotation_options = annotation_options(options); + let annotation_prepared = self.prepare_text( + PrepareRequest { + source: annotation, + global_offset: 0, + spans: &[], + fonts, + options: &annotation_options, + diagnostic_range: Some(global_range.clone()), + }, + call, + )?; + let shaped = + annotation_prepared.to_core(annotation, annotation_options.font_size)?; + constructs.push(jlreq_core::Construct::script(local_range, shaped)); + attachments.push(AttachmentShape { + local_ordinal, + global_ordinal, + base: global_range, + prepared: annotation_prepared, + }); + }, + DocumentConstruct::Formula(_) => { + constructs.push(jlreq_core::Construct::formula(local_range)); + }, + } + } + Ok((constructs, attachments)) + } +} + +#[derive(Debug)] +struct CallState { + runs: usize, + glyphs: usize, + max_runs: usize, + max_glyphs: usize, + used_fonts: BTreeSet, + diagnostics: Vec, +} + +impl CallState { + fn new(options: &LayoutOptions) -> Self { + Self { + runs: 0, + glyphs: 0, + max_runs: options.limits.runs, + max_glyphs: options.limits.glyphs, + used_fonts: BTreeSet::new(), + diagnostics: Vec::new(), + } + } + + fn charge_run(&mut self) -> Result<(), LayoutError> { + self.runs = self.runs.saturating_add(1); + check_limit(Resource::Runs, self.max_runs, self.runs) + } + + fn charge_glyphs(&mut self, amount: usize) -> Result<(), LayoutError> { + self.glyphs = self.glyphs.saturating_add(amount); + check_limit(Resource::Glyphs, self.max_glyphs, self.glyphs) + } +} + +#[derive(Debug, Clone, PartialEq)] +struct EffectiveStyle { + families: Vec, + font_style: FontStyle, + size: i32, + language: String, + features: Vec, + variations: Vec, + role: TextRole, +} + +#[derive(Debug)] +struct GraphemeItem { + range: Range, + level: Level, + script: ScriptClass, + direction: Direction, + font_id: FontId, + effective: EffectiveStyle, + is_tab: bool, +} + +impl GraphemeItem { + fn same_run(&self, other: &Self) -> bool { + self.font_id == other.font_id + && self.level.is_rtl() == other.level.is_rtl() + && self.script == other.script + && self.direction == other.direction + && self.effective == other.effective + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ScriptClass { + Japanese, + Latin, + Rtl, + Emoji, + Other, +} + +#[derive(Debug, Clone)] +struct RawGlyph { + font_id: FontId, + glyph_id: u32, + cluster: usize, + x_advance: i32, + y_advance: i32, + x_offset: i32, + y_offset: i32, +} + +impl RawGlyph { + fn inline_advance(&self, direction: Direction) -> i32 { + match direction { + Direction::TopToBottom | Direction::BottomToTop => self.y_advance.abs(), + _ => self.x_advance.abs(), + } + } +} + +#[derive(Debug, Clone)] +struct PreparedCluster { + range: Range, + advance: i32, + size: i32, + frame: jlreq_core::Frame, + role: Option, + bidi_level: u8, + glyphs: Vec, +} + +#[derive(Debug, Clone)] +struct PreparedText { + clusters: Vec, +} + +impl PreparedText { + fn to_core( + &self, + source: &str, + default_size: i32, + ) -> Result { + let size = jlreq_core::Size::square(default_size)?; + let mut clusters = Vec::with_capacity(self.clusters.len()); + for cluster in &self.clusters { + let local_size = jlreq_core::Size::square(cluster.size)?; + let mut core = jlreq_core::Cluster::new(cluster.range.clone(), cluster.advance) + .with_size(local_size) + .with_frame(cluster.frame); + if let Some(role) = cluster.role { + core = core.with_role(role); + } + clusters.push(core); + } + Ok(jlreq_core::ShapedText::new( + source, + size, + jlreq_core::Frame::FullEm, + clusters, + )?) + } + + fn is_boundary(&self, offset: usize, source_len: usize) -> bool { + offset == 0 + || offset == source_len + || self + .clusters + .iter() + .any(|cluster| cluster.range.start == offset) + } +} + +#[derive(Debug, Clone)] +struct AttachmentShape { + local_ordinal: usize, + global_ordinal: usize, + base: Range, + prepared: PreparedText, +} + +#[derive(Debug, Clone)] +struct ParagraphSegment { + content: Range, +} + +fn validate_call( + document: &Document, + fonts: &FontLibrary, + options: &LayoutOptions, +) -> Result<(), LayoutError> { + if fonts.is_empty() { + return Err(LayoutError::NoFonts); + } + check_limit( + Resource::InputBytes, + options.limits.input_bytes, + document.text.len(), + )?; + check_limit(Resource::Fonts, options.limits.fonts, fonts.len())?; + let font_bytes = fonts + .fonts() + .iter() + .fold(0_usize, |sum, font| sum.saturating_add(font.bytes().len())); + check_limit(Resource::FontBytes, options.limits.font_bytes, font_bytes)?; + check_limit( + Resource::Constructs, + options.limits.constructs, + document.constructs.len(), + ) +} + +fn check_limit(resource: Resource, limit: usize, observed: usize) -> Result<(), LayoutError> { + if observed > limit { + Err(LayoutError::resource(resource, limit, observed)) + } else { + Ok(()) + } +} + +fn map_core_resource_error(error: jlreq_core::ComposeError) -> LayoutError { + let Some(resource) = high_level_resource(error.resource()) else { + return LayoutError::CoreComposition(error); + }; + LayoutError::resource(resource, error.limit(), error.observed()) +} + +fn high_level_resource(resource: jlreq_core::CompositionResource) -> Option { + match resource { + jlreq_core::CompositionResource::Clusters => Some(Resource::Glyphs), + jlreq_core::CompositionResource::BreakCandidates => Some(Resource::Runs), + jlreq_core::CompositionResource::Constructs | jlreq_core::CompositionResource::TabStops => { + Some(Resource::Constructs) + }, + jlreq_core::CompositionResource::SearchTransitions => Some(Resource::CoreOperations), + _ => None, + } +} + +fn effective_style( + global: &Range, + spans: &[(Range, SpanStyle)], + options: &LayoutOptions, +) -> Result { + let mut selected = None; + for (range, style) in spans { + if ranges_overlap(range, global) { + if range.start > global.start || range.end < global.end { + return Err(LayoutError::invalid_document( + "document.span-splits-grapheme", + Some(global.clone()), + )); + } + selected = Some(style); + break; + } + } + let mut result = EffectiveStyle { + families: Vec::new(), + font_style: FontStyle::default(), + size: options.font_size, + language: options.language.clone(), + features: options.features.clone(), + variations: options.variations.clone(), + role: TextRole::Text, + }; + if let Some(style) = selected { + result.families.clone_from(&style.families); + result.font_style = style.font_style; + result.size = style.font_size.unwrap_or(options.font_size); + if let Some(language) = &style.language { + result.language.clone_from(language); + } + result.features.extend_from_slice(&style.features); + result.variations.extend_from_slice(&style.variations); + result.role = style.role; + } + Ok(result) +} + +fn aggregate_run( + source: &str, + range: Range, + glyphs: &[RawGlyph], + size: i32, + role: TextRole, + bidi_level: u8, + direction: Direction, +) -> Vec { + let mut starts: Vec<_> = glyphs + .iter() + .map(|glyph| glyph.cluster) + .filter(|start| range.contains(start)) + .collect(); + starts.push(range.start); + starts.sort_unstable(); + starts.dedup(); + let mut result = Vec::with_capacity(starts.len()); + for (ordinal, start) in starts.iter().copied().enumerate() { + let end = starts + .get(ordinal.saturating_add(1)) + .copied() + .unwrap_or(range.end); + let members: Vec<_> = glyphs + .iter() + .filter(|glyph| glyph.cluster == start) + .cloned() + .collect(); + let advance = members.iter().fold(0_i32, |sum, glyph| { + sum.saturating_add(glyph.inline_advance(direction)) + }); + let piece = &source[start..end]; + result.push(PreparedCluster { + range: start..end, + advance, + size, + frame: frame_for(piece), + role: classify_role(source, start..end, role), + bidi_level, + glyphs: members, + }); + } + result +} + +fn frame_for(piece: &str) -> jlreq_core::Frame { + if piece.chars().count() == 1 + && piece + .chars() + .next() + .is_some_and(|character| is_japanese(character) || is_emoji(character)) + { + jlreq_core::Frame::FullEm + } else { + jlreq_core::Frame::Proportional + } +} + +fn classify_role( + source: &str, + range: Range, + asserted: TextRole, +) -> Option { + if asserted != TextRole::Text { + return Some(asserted.core()); + } + let mut characters = source[range.clone()].chars(); + let character = characters.next()?; + if characters.next().is_some() { + return None; + } + let previous = source[..range.start].chars().next_back(); + let next = source[range.end..].chars().next(); + if matches!(character, '.' | '.' | '・') + && previous.is_some_and(char::is_numeric) + && next.is_some_and(char::is_numeric) + { + return Some(jlreq_core::ClusterRole::DecimalPoint); + } + if matches!(character, ',' | ',' | '、') + && previous.is_some_and(char::is_numeric) + && next.is_some_and(char::is_numeric) + { + return Some(jlreq_core::ClusterRole::DigitGroupSeparator); + } + if matches!(character, '!' | '?' | '!' | '?') { + return Some(if source[range.end..].trim().is_empty() { + jlreq_core::ClusterRole::SentenceTerminator + } else { + jlreq_core::ClusterRole::SentenceMedial + }); + } + None +} + +fn script_class(text: &str) -> ScriptClass { + for character in text.chars() { + let value = character as u32; + if is_japanese(character) { + return ScriptClass::Japanese; + } + if is_emoji(character) { + return ScriptClass::Emoji; + } + if matches!(value, 0x0590..=0x08ff | 0xfb1d..=0xfdff | 0xfe70..=0xfeff) { + return ScriptClass::Rtl; + } + if character.is_ascii_alphanumeric() || matches!(value, 0x00c0..=0x024f | 0x1e00..=0x1eff) { + return ScriptClass::Latin; + } + } + ScriptClass::Other +} + +fn is_japanese(character: char) -> bool { + matches!( + character as u32, + 0x2e80..=0x2fff + | 0x3000..=0x30ff + | 0x31f0..=0x31ff + | 0x3400..=0x4dbf + | 0x4e00..=0x9fff + | 0xf900..=0xfaff + | 0x20000..=0x323af + ) +} + +fn is_emoji(character: char) -> bool { + matches!(character as u32, 0x1f000..=0x1faff | 0x2600..=0x27bf) +} + +fn shape_direction(mode: WritingMode, level: Level, script: ScriptClass) -> Direction { + if mode == WritingMode::VerticalRl + && matches!(script, ScriptClass::Japanese | ScriptClass::Emoji) + { + Direction::TopToBottom + } else if level.is_rtl() { + Direction::RightToLeft + } else { + Direction::LeftToRight + } +} + +fn paragraph_segments(text: &str) -> Vec { + let mut result = Vec::new(); + let mut start = 0; + let mut indices = text.char_indices().peekable(); + while let Some((offset, character)) = indices.next() { + let mut separator_end = offset.saturating_add(character.len_utf8()); + let separator = match character { + '\r' => { + if indices.peek().is_some_and(|(_, next)| *next == '\n') + && let Some((next_offset, next)) = indices.next() + { + separator_end = next_offset.saturating_add(next.len_utf8()); + } + true + }, + '\n' | '\u{2028}' | '\u{2029}' => true, + _ => false, + }; + if separator { + result.push(ParagraphSegment { + content: start..offset, + }); + start = separator_end; + } + } + result.push(ParagraphSegment { + content: start..text.len(), + }); + result +} + +fn collect_breaks( + document: &Document, + paragraph_range: &Range, + source: &str, + prepared: &PreparedText, + constructs: &[jlreq_core::Construct], +) -> Vec { + let prohibited: BTreeSet<_> = document + .prohibited_breaks + .iter() + .filter(|offset| paragraph_range.contains(offset)) + .map(|offset| offset.saturating_sub(paragraph_range.start)) + .collect(); + let mandatory: BTreeSet<_> = document + .mandatory_breaks + .iter() + .filter(|offset| paragraph_range.contains(offset)) + .map(|offset| offset.saturating_sub(paragraph_range.start)) + .collect(); + let inside_construct = |offset: usize| { + constructs.iter().any(|construct| { + let range = construct.range(); + range.start < offset && offset < range.end + }) + }; + let mut breaks = BTreeMap::new(); + for offset in LineSegmenter::new_auto(LineBreakOptions::default()).segment_str(source) { + if automatic_break_allowed( + offset, + source.len(), + prepared.is_boundary(offset, source.len()), + prohibited.contains(&offset), + inside_construct(offset), + ) { + breaks.insert(offset, false); + } + } + for offset in mandatory { + if prepared.is_boundary(offset, source.len()) { + breaks.insert(offset, true); + } + } + breaks + .into_iter() + .map(|(offset, required)| { + if required { + jlreq_core::Break::mandatory(offset) + } else { + jlreq_core::Break::allowed(offset) + } + }) + .collect() +} + +fn automatic_break_allowed( + offset: usize, + source_len: usize, + is_cluster_boundary: bool, + is_prohibited: bool, + is_inside_construct: bool, +) -> bool { + offset > 0 + && offset < source_len + && is_cluster_boundary + && !is_prohibited + && !is_inside_construct +} + +fn collect_tab_stops( + source: &str, + options: &LayoutOptions, +) -> Result, LayoutError> { + if !source.contains('\t') { + return Ok(Vec::new()); + } + let interval = options + .font_size + .saturating_mul(i32::from(options.tab_width)); + let mut position = interval; + let mut result = Vec::new(); + while position < options.line_extent && result.len() < options.limits.constructs { + result.push(jlreq_core::TabStop::new( + position, + jlreq_core::TabAlignment::Start, + )?); + position = position.saturating_add(interval); + } + Ok(result) +} + +fn annotation_options(options: &LayoutOptions) -> LayoutOptions { + let mut result = options.clone(); + result.font_size = (options.font_size / 2).max(1); + result.alignment = Alignment::Start; + result +} + +fn ruby_runs( + kind: crate::RubyKind, + local_base: &Range, + paragraph_offset: usize, + declared: &[crate::RubyRun], + base: &PreparedText, + annotation: &PreparedText, + annotation_len: usize, +) -> Result, LayoutError> { + if !declared.is_empty() { + return Ok(declared + .iter() + .map(|run| { + let base = run.base(); + jlreq_core::RubyRun::new( + base.start.saturating_sub(paragraph_offset) + ..base.end.saturating_sub(paragraph_offset), + run.annotation(), + ) + }) + .collect()); + } + if kind != crate::RubyKind::Mono { + return Ok(vec![jlreq_core::RubyRun::new( + local_base.clone(), + 0..annotation_len, + )]); + } + let base_clusters: Vec<_> = base + .clusters + .iter() + .filter(|cluster| { + local_base.start <= cluster.range.start && cluster.range.end <= local_base.end + }) + .collect(); + if base_clusters.len() != annotation.clusters.len() || base_clusters.is_empty() { + return Err(LayoutError::invalid_document( + "document.mono-ruby-cluster-count", + Some( + local_base.start.saturating_add(paragraph_offset) + ..local_base.end.saturating_add(paragraph_offset), + ), + )); + } + Ok(base_clusters + .iter() + .zip(&annotation.clusters) + .map(|(base, annotation)| { + jlreq_core::RubyRun::new(base.range.clone(), annotation.range.clone()) + }) + .collect()) +} + +fn map_core_lines( + layout: &jlreq_core::Layout, + prepared: &PreparedText, + attachments: &[AttachmentShape], + global_offset: usize, + block_offset: i32, + options: &LayoutOptions, +) -> Vec { + let mut result = Vec::with_capacity(layout.lines().len()); + for (line_index, line) in layout.lines().iter().enumerate() { + let mut cells = Vec::new(); + let mut used = BTreeSet::new(); + for placement in line.clusters() { + let range = placement.range(); + let cluster_indices = placement_cluster_indices(placement.origin(), prepared, &range); + let cluster_indices: Vec<_> = cluster_indices + .into_iter() + .filter(|index| used.insert(*index)) + .collect(); + if cluster_indices.is_empty() { + continue; + } + let level = prepared.clusters[cluster_indices[0]].bidi_level; + cells.push(Cell { + clusters: cluster_indices, + block: placement.block(), + advance: placement.advance().max(0), + level, + transform: core_transform(placement.transform()), + }); + } + let levels: Vec<_> = cells + .iter() + .map(|cell| Level::new(cell.level).unwrap_or_else(|_| Level::ltr())) + .collect(); + let visual = BidiInfo::reorder_visual(&levels); + let mut cursor = line.inline_origin(); + let mut glyphs = Vec::new(); + for visual_index in visual { + let cell = &cells[visual_index]; + let mut cluster_cursor = 0_i32; + let indices = logical_cluster_order(cell.clusters.clone(), cell.level); + for cluster_index in indices { + let cluster = &prepared.clusters[cluster_index]; + let mut glyph_cursor = 0_i32; + for raw in &cluster.glyphs { + glyphs.push(place_raw_glyph( + raw, + cluster, + PlacementContext { + source_range: global_offset.saturating_add(cluster.range.start) + ..global_offset.saturating_add(cluster.range.end), + annotation: None, + inline: cursor + .saturating_add(cluster_cursor) + .saturating_add(glyph_cursor), + block: adjusted_block(cell.block, line_index, block_offset, options), + transform: cell.transform, + writing_mode: options.writing_mode, + }, + )); + glyph_cursor = glyph_cursor.saturating_add(raw.inline_advance( + direction_from_geometry(raw, options.writing_mode, cell.transform), + )); + } + cluster_cursor = cluster_cursor.saturating_add(cluster.advance); + } + cursor = cursor.saturating_add(cell.advance.max(cluster_cursor)); + } + append_attachments( + &mut glyphs, + line, + attachments, + line_index, + block_offset, + options, + ); + let physical_origin = match options.writing_mode { + WritingMode::HorizontalTb => Point::from_fixed( + line.inline_origin(), + adjusted_block(line.block_origin(), line_index, block_offset, options), + ), + WritingMode::VerticalRl => Point::from_fixed( + adjusted_block(line.block_origin(), line_index, block_offset, options), + line.inline_origin(), + ), + }; + result.push(TextLine { + range: line.range().start.saturating_add(global_offset) + ..line.range().end.saturating_add(global_offset), + origin: physical_origin, + inline_extent: line.inline_extent(), + block_extent: line.block_extent(), + writing_mode: options.writing_mode, + glyphs, + }); + } + result +} + +fn placement_cluster_indices( + origin: jlreq_core::PlacementOrigin, + prepared: &PreparedText, + range: &Range, +) -> Vec { + match origin { + jlreq_core::PlacementOrigin::Cluster(index) => vec![index], + jlreq_core::PlacementOrigin::Construct(_) => prepared + .clusters + .iter() + .enumerate() + .filter(|(_, cluster)| ranges_overlap(&cluster.range, range)) + .map(|(index, _)| index) + .collect(), + _ => Vec::new(), + } +} + +fn logical_cluster_order(mut indices: Vec, level: u8) -> Vec { + if level % 2 == 1 { + indices.reverse(); + } + indices +} + +#[derive(Debug)] +struct Cell { + clusters: Vec, + block: i32, + advance: i32, + level: u8, + transform: GlyphTransform, +} + +struct PlacementContext { + source_range: Range, + annotation: Option, + inline: i32, + block: i32, + transform: GlyphTransform, + writing_mode: WritingMode, +} + +fn place_raw_glyph( + raw: &RawGlyph, + cluster: &PreparedCluster, + placement: PlacementContext, +) -> GlyphPlacement { + let PlacementContext { + source_range, + annotation, + inline, + block, + transform, + writing_mode, + } = placement; + let horizontal = + writing_mode == WritingMode::HorizontalTb || transform == GlyphTransform::TateChuYoko; + let (x, y, advance_x, advance_y, offset_x, offset_y) = if horizontal { + ( + inline, + block.saturating_add(cluster.size), + raw.x_advance.abs(), + 0, + raw.x_offset, + raw.y_offset.saturating_neg(), + ) + } else { + ( + block, + inline, + 0, + raw.y_advance.abs().max(raw.x_advance.abs()), + raw.x_offset, + raw.y_offset.saturating_neg(), + ) + }; + GlyphPlacement { + font_id: raw.font_id, + glyph_id: raw.glyph_id, + source_range, + annotation, + x, + y, + advance_x, + advance_y, + offset_x, + offset_y, + font_size: cluster.size, + transform, + bidi_level: cluster.bidi_level, + writing_mode, + } +} + +fn append_attachments( + glyphs: &mut Vec, + line: &jlreq_core::Line, + shapes: &[AttachmentShape], + line_index: usize, + block_offset: i32, + options: &LayoutOptions, +) { + for attachment in line.attachments() { + let Some(shape) = shapes + .iter() + .find(|shape| shape.local_ordinal == attachment.construct()) + else { + continue; + }; + let requested = attachment.range(); + let clusters: Vec<_> = if requested.is_empty() { + shape.prepared.clusters.iter().collect() + } else { + shape + .prepared + .clusters + .iter() + .filter(|cluster| ranges_overlap(&cluster.range, &requested)) + .collect() + }; + let transform = core_transform(attachment.transform()); + let mut inline = attachment.inline(); + for cluster in clusters { + let mut glyph_cursor = 0_i32; + for raw in &cluster.glyphs { + glyphs.push(place_raw_glyph( + raw, + cluster, + PlacementContext { + source_range: shape.base.clone(), + annotation: Some(AnnotationSource::new( + shape.global_ordinal, + cluster.range.clone(), + )), + inline: inline.saturating_add(glyph_cursor), + block: adjusted_block( + attachment.block(), + line_index, + block_offset, + options, + ), + transform, + writing_mode: options.writing_mode, + }, + )); + glyph_cursor = glyph_cursor.saturating_add(raw.inline_advance( + direction_from_geometry(raw, options.writing_mode, transform), + )); + } + inline = inline.saturating_add(cluster.advance); + } + } +} + +fn adjusted_block( + core_block: i32, + line_index: usize, + block_offset: i32, + options: &LayoutOptions, +) -> i32 { + let gap = i32::try_from(line_index) + .unwrap_or(i32::MAX) + .saturating_mul(options.line_gap); + match options.writing_mode { + WritingMode::HorizontalTb => block_offset.saturating_add(core_block).saturating_add(gap), + WritingMode::VerticalRl => block_offset.saturating_add(core_block).saturating_sub(gap), + } +} + +fn core_transform(value: jlreq_core::CoordinateTransform) -> GlyphTransform { + match value { + jlreq_core::CoordinateTransform::RotateClockwise => GlyphTransform::RotateClockwise, + jlreq_core::CoordinateTransform::TateChuYoko => GlyphTransform::TateChuYoko, + _ => GlyphTransform::Identity, + } +} + +fn direction_from_geometry( + raw: &RawGlyph, + mode: WritingMode, + transform: GlyphTransform, +) -> Direction { + if mode == WritingMode::VerticalRl + && transform != GlyphTransform::TateChuYoko + && raw.y_advance != 0 + { + Direction::TopToBottom + } else { + Direction::LeftToRight + } +} + +fn advance_block(value: i32, amount: i32, mode: WritingMode) -> i32 { + match mode { + WritingMode::HorizontalTb => value.saturating_add(amount), + WritingMode::VerticalRl => value.saturating_sub(amount), + } +} + +fn ranges_overlap(left: &Range, right: &Range) -> bool { + left.start < right.end && right.start < left.end +} + +fn diagnostic_severity(value: jlreq_core::Severity) -> DiagnosticSeverity { + match value { + jlreq_core::Severity::Info => DiagnosticSeverity::Info, + jlreq_core::Severity::Error => DiagnosticSeverity::Error, + _ => DiagnosticSeverity::Warning, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + + fn fixture_fonts() -> (FontLibrary, FontId, FontId) { + let mut fonts = FontLibrary::new(); + let first = fonts + .register_face( + Arc::<[u8]>::from(font_test_data::NOTO_SANS_JP_CFF), + 0, + "Primary", + FontStyle::default(), + ) + .unwrap(); + let second = fonts + .register_face( + Arc::<[u8]>::from(font_test_data::TINOS_SUBSET), + 0, + "Secondary", + FontStyle::default(), + ) + .unwrap(); + (fonts, first, second) + } + + fn raw(font_id: FontId) -> RawGlyph { + RawGlyph { + font_id, + glyph_id: 37, + cluster: 0, + x_advance: -256, + y_advance: -384, + x_offset: 64, + y_offset: -128, + } + } + + fn cluster(range: Range, font_id: FontId, level: u8) -> PreparedCluster { + PreparedCluster { + range, + advance: 256, + size: 192, + frame: jlreq_core::Frame::Proportional, + role: None, + bidi_level: level, + glyphs: vec![raw(font_id)], + } + } + + fn effective(size: i32) -> EffectiveStyle { + EffectiveStyle { + families: vec!["Primary".into()], + font_style: FontStyle::default(), + size, + language: "und".into(), + features: Vec::new(), + variations: Vec::new(), + role: TextRole::Text, + } + } + + fn grapheme(font_id: FontId) -> GraphemeItem { + GraphemeItem { + range: 0..1, + level: Level::ltr(), + script: ScriptClass::Latin, + direction: Direction::LeftToRight, + font_id, + effective: effective(1024), + is_tab: false, + } + } + + #[test] + fn engine_debug_and_font_cache_observe_face_and_byte_identity() { + let (fonts, id, _) = fixture_fonts(); + let resource = fonts.get(id).unwrap().clone(); + let mut engine = LayoutEngine::new(); + assert!(format!("{engine:?}").contains("cached_fonts: 0")); + engine.ensure_cache(&resource).unwrap(); + assert!(format!("{engine:?}").contains("cached_fonts: 1")); + + let replacement = FontResource { + bytes: Arc::from(resource.bytes().to_vec()), + ..resource.clone() + }; + assert!(!Arc::ptr_eq(&resource.bytes, &replacement.bytes)); + engine.ensure_cache(&replacement).unwrap(); + assert!(Arc::ptr_eq( + &engine.fonts.get(&id).unwrap().bytes, + &replacement.bytes + )); + + let invalid_face = FontResource { + face_index: u32::MAX, + ..replacement + }; + assert!(engine.ensure_cache(&invalid_face).is_err()); + } + + #[test] + fn preparation_groups_only_identical_non_tab_runs() { + let (fonts, _, _) = fixture_fonts(); + let options = LayoutOptions::try_new(200.0, 16.0).unwrap(); + let mut engine = LayoutEngine::new(); + let mut call = CallState::new(&options); + let prepared = engine + .prepare_text( + PrepareRequest { + source: "AB", + global_offset: 0, + spans: &[], + fonts: &fonts, + options: &options, + diagnostic_range: None, + }, + &mut call, + ) + .unwrap(); + assert_eq!(call.runs, 1); + assert_eq!(prepared.clusters.len(), 2); + + let mut call = CallState::new(&options); + let prepared = engine + .prepare_text( + PrepareRequest { + source: "A\tB", + global_offset: 0, + spans: &[], + fonts: &fonts, + options: &options, + diagnostic_range: None, + }, + &mut call, + ) + .unwrap(); + assert_eq!(call.runs, 2); + assert_eq!(prepared.clusters.len(), 3); + assert!(prepared.clusters[1].glyphs.is_empty()); + } + + #[test] + fn call_charges_are_inclusive_and_accumulate() { + let mut options = LayoutOptions::try_new(100.0, 16.0).unwrap(); + options.limits.runs = 1; + options.limits.glyphs = 3; + let mut call = CallState::new(&options); + call.charge_run().unwrap(); + assert_eq!(call.runs, 1); + assert_eq!(call.charge_run().unwrap_err().code(), "limit.runs"); + call.charge_glyphs(2).unwrap(); + assert_eq!(call.glyphs, 2); + assert_eq!(call.charge_glyphs(2).unwrap_err().code(), "limit.glyphs"); + assert_eq!(call.glyphs, 4); + } + + #[test] + fn run_identity_checks_every_shaping_dimension() { + let (_, first, second) = fixture_fonts(); + let base = grapheme(first); + assert!(base.same_run(&grapheme(first))); + + let mut changed = grapheme(second); + assert!(!base.same_run(&changed)); + changed = grapheme(first); + changed.level = Level::rtl(); + assert!(!base.same_run(&changed)); + changed = grapheme(first); + changed.script = ScriptClass::Japanese; + assert!(!base.same_run(&changed)); + changed = grapheme(first); + changed.direction = Direction::RightToLeft; + assert!(!base.same_run(&changed)); + changed = grapheme(first); + changed.effective.size = 2048; + assert!(!base.same_run(&changed)); + } + + #[test] + fn raw_advances_and_prepared_boundaries_are_directional_and_half_open() { + let (_, id, _) = fixture_fonts(); + let glyph = raw(id); + assert_eq!(glyph.inline_advance(Direction::LeftToRight), 256); + assert_eq!(glyph.inline_advance(Direction::RightToLeft), 256); + assert_eq!(glyph.inline_advance(Direction::TopToBottom), 384); + assert_eq!(glyph.inline_advance(Direction::BottomToTop), 384); + + let prepared = PreparedText { + clusters: vec![cluster(2..4, id, 0), cluster(4..6, id, 0)], + }; + assert!(prepared.is_boundary(0, 8)); + assert!(prepared.is_boundary(8, 8)); + assert!(prepared.is_boundary(2, 8)); + assert!(prepared.is_boundary(4, 8)); + assert!(!prepared.is_boundary(1, 8)); + assert!(!prepared.is_boundary(6, 8)); + } + + #[test] + fn limits_core_resources_and_severities_map_without_collapsing() { + assert!(check_limit(Resource::Runs, 2, 2).is_ok()); + let error = check_limit(Resource::Runs, 2, 3).unwrap_err(); + assert_eq!(error.code(), "limit.runs"); + assert_eq!( + high_level_resource(jlreq_core::CompositionResource::Clusters), + Some(Resource::Glyphs) + ); + assert_eq!( + high_level_resource(jlreq_core::CompositionResource::BreakCandidates), + Some(Resource::Runs) + ); + assert_eq!( + high_level_resource(jlreq_core::CompositionResource::Constructs), + Some(Resource::Constructs) + ); + assert_eq!( + high_level_resource(jlreq_core::CompositionResource::TabStops), + Some(Resource::Constructs) + ); + assert_eq!( + high_level_resource(jlreq_core::CompositionResource::SearchTransitions), + Some(Resource::CoreOperations) + ); + assert_eq!( + diagnostic_severity(jlreq_core::Severity::Info), + DiagnosticSeverity::Info + ); + assert_eq!( + diagnostic_severity(jlreq_core::Severity::Warning), + DiagnosticSeverity::Warning + ); + assert_eq!( + diagnostic_severity(jlreq_core::Severity::Error), + DiagnosticSeverity::Error + ); + } + + #[test] + fn effective_style_rejects_both_sides_of_a_split_grapheme_and_merges_values() { + let feature = OpenTypeFeature::new(crate::OpenTypeTag::try_new("liga").unwrap(), 0); + let variation = + FontVariation::try_new(crate::OpenTypeTag::try_new("wght").unwrap(), 515.0).unwrap(); + let options = LayoutOptions::try_new(100.0, 16.0) + .unwrap() + .language("en") + .unwrap() + .feature(feature) + .variation(variation); + + let left = vec![(2..4, SpanStyle::new())]; + assert_eq!( + effective_style(&(1..3), &left, &options) + .unwrap_err() + .code(), + "document.span-splits-grapheme" + ); + let right = vec![(0..2, SpanStyle::new())]; + assert_eq!( + effective_style(&(1..3), &right, &options) + .unwrap_err() + .code(), + "document.span-splits-grapheme" + ); + + let span_feature = OpenTypeFeature::new(crate::OpenTypeTag::try_new("kern").unwrap(), 0); + let span_variation = + FontVariation::try_new(crate::OpenTypeTag::try_new("wdth").unwrap(), 90.0).unwrap(); + let span = SpanStyle::new() + .family("Secondary") + .font_style(FontStyle::new(600, 90, crate::FontSlant::Italic)) + .font_size(18.0) + .unwrap() + .language("ja") + .unwrap() + .feature(span_feature) + .variation(span_variation) + .role(TextRole::Formula); + let merged = effective_style(&(1..3), &[(0..4, span)], &options).unwrap(); + assert_eq!(merged.families, ["Secondary"]); + assert_eq!(merged.size, 18 * 64); + assert_eq!(merged.language, "ja"); + assert_eq!(merged.features, [feature, span_feature]); + assert_eq!(merged.variations, [variation, span_variation]); + assert_eq!(merged.role, TextRole::Formula); + } + + #[test] + fn frames_roles_scripts_and_shape_directions_cover_the_closed_tables() { + assert_eq!(frame_for("日"), jlreq_core::Frame::FullEm); + assert_eq!(frame_for("😀"), jlreq_core::Frame::FullEm); + assert_eq!(frame_for("A"), jlreq_core::Frame::Proportional); + assert_eq!(frame_for("日本"), jlreq_core::Frame::Proportional); + + assert_eq!( + classify_role("A", 0..1, TextRole::Formula), + Some(jlreq_core::ClusterRole::Formula) + ); + for text in ["1.2", "1.2", "1・2"] { + let start = text.chars().next().unwrap().len_utf8(); + let end = start + text[start..].chars().next().unwrap().len_utf8(); + assert_eq!( + classify_role(text, start..end, TextRole::Text), + Some(jlreq_core::ClusterRole::DecimalPoint) + ); + } + for text in ["1,2", "1,2", "1、2"] { + let start = text.chars().next().unwrap().len_utf8(); + let end = start + text[start..].chars().next().unwrap().len_utf8(); + assert_eq!( + classify_role(text, start..end, TextRole::Text), + Some(jlreq_core::ClusterRole::DigitGroupSeparator) + ); + } + assert_eq!( + classify_role("!", 0..1, TextRole::Text), + Some(jlreq_core::ClusterRole::SentenceTerminator) + ); + assert_eq!( + classify_role("! A", 0..1, TextRole::Text), + Some(jlreq_core::ClusterRole::SentenceMedial) + ); + assert_eq!(classify_role(".2", 0..1, TextRole::Text), None); + assert_eq!(classify_role("1.", 1..2, TextRole::Text), None); + assert_eq!(classify_role("1A2", 1..2, TextRole::Text), None); + assert_eq!(classify_role("A,2", 1..2, TextRole::Text), None); + assert_eq!(classify_role("12", 0..2, TextRole::Text), None); + assert_eq!(classify_role("", 0..0, TextRole::Text), None); + + assert_eq!(script_class("日"), ScriptClass::Japanese); + assert_eq!(script_class("😀"), ScriptClass::Emoji); + assert_eq!(script_class("ع"), ScriptClass::Rtl); + assert_eq!(script_class("A"), ScriptClass::Latin); + assert_eq!(script_class("é"), ScriptClass::Latin); + assert_eq!(script_class("\u{0301}"), ScriptClass::Other); + assert!(is_japanese('日')); + assert!(!is_japanese('A')); + assert!(is_emoji('😀')); + assert!(!is_emoji('A')); + + assert_eq!( + shape_direction(WritingMode::VerticalRl, Level::ltr(), ScriptClass::Japanese), + Direction::TopToBottom + ); + assert_eq!( + shape_direction(WritingMode::VerticalRl, Level::ltr(), ScriptClass::Emoji), + Direction::TopToBottom + ); + assert_eq!( + shape_direction( + WritingMode::HorizontalTb, + Level::rtl(), + ScriptClass::Japanese + ), + Direction::RightToLeft + ); + assert_eq!( + shape_direction(WritingMode::VerticalRl, Level::rtl(), ScriptClass::Latin), + Direction::RightToLeft + ); + assert_eq!( + shape_direction(WritingMode::HorizontalTb, Level::ltr(), ScriptClass::Latin), + Direction::LeftToRight + ); + } + + #[test] + fn paragraph_separators_keep_original_utf8_ranges() { + let text = "a\r\nb\rc\nd\u{2028}e\u{2029}f"; + let ranges: Vec<_> = paragraph_segments(text) + .into_iter() + .map(|segment| segment.content) + .collect(); + assert_eq!(ranges, [0..1, 3..4, 5..6, 7..8, 11..12, 15..16]); + assert_eq!( + paragraph_segments("\r\n") + .into_iter() + .map(|segment| segment.content) + .collect::>(), + [0..0, 2..2] + ); + } + + #[test] + fn automatic_and_explicit_break_filters_are_independent() { + assert!(automatic_break_allowed(3, 6, true, false, false)); + assert!(!automatic_break_allowed(0, 6, true, false, false)); + assert!(!automatic_break_allowed(6, 6, true, false, false)); + assert!(!automatic_break_allowed(3, 6, false, false, false)); + assert!(!automatic_break_allowed(3, 6, true, true, false)); + assert!(!automatic_break_allowed(3, 6, true, false, true)); + + let (_, id, _) = fixture_fonts(); + let source = "日本"; + let prepared = PreparedText { + clusters: vec![cluster(0..3, id, 0), cluster(3..6, id, 0)], + }; + let plain = DocumentBuilder::new(source).build().unwrap(); + let breaks = collect_breaks(&plain, &(0..6), source, &prepared, &[]); + assert_eq!( + breaks.iter().map(|item| item.offset()).collect::>(), + [3] + ); + assert!(!breaks[0].is_mandatory()); + + let mut prohibited = DocumentBuilder::new(source); + prohibited.prohibit_break(3).unwrap(); + assert!( + collect_breaks( + &prohibited.build().unwrap(), + &(0..6), + source, + &prepared, + &[] + ) + .is_empty() + ); + + let mut mandatory = DocumentBuilder::new(source); + mandatory.mandatory_break(3).unwrap(); + let breaks = collect_breaks(&mandatory.build().unwrap(), &(0..6), source, &prepared, &[]); + assert_eq!(breaks.len(), 1); + assert!(breaks[0].is_mandatory()); + + assert!( + collect_breaks( + &plain, + &(0..6), + source, + &prepared, + &[jlreq_core::Construct::tate_chu_yoko(0..6)] + ) + .is_empty() + ); + assert_eq!( + collect_breaks( + &plain, + &(0..6), + source, + &prepared, + &[jlreq_core::Construct::tate_chu_yoko(0..3)] + ) + .len(), + 1 + ); + assert_eq!( + collect_breaks( + &plain, + &(0..6), + source, + &prepared, + &[jlreq_core::Construct::tate_chu_yoko(3..6)] + ) + .len(), + 1 + ); + } + + #[test] + fn tab_stops_and_annotation_options_honor_exact_boundaries() { + let exact = LayoutOptions::try_new(64.0, 16.0) + .unwrap() + .tab_width(2) + .unwrap(); + assert_eq!( + collect_tab_stops("\t", &exact) + .unwrap() + .iter() + .map(|stop| stop.position()) + .collect::>(), + [2048] + ); + let bounded = LayoutOptions::try_new(100.0, 16.0) + .unwrap() + .tab_width(2) + .unwrap() + .limits(crate::ResourceLimits::default().with_max_constructs(1)); + assert_eq!(collect_tab_stops("\t", &bounded).unwrap().len(), 1); + assert!(collect_tab_stops("no tab", &bounded).unwrap().is_empty()); + + let source = LayoutOptions::try_new(101.0, 17.0) + .unwrap() + .alignment(Alignment::End); + let annotation = annotation_options(&source); + assert_eq!(annotation.font_size, 544); + assert_eq!(annotation.line_extent, source.line_extent); + assert_eq!(annotation.alignment, Alignment::Start); + } + + #[test] + fn ruby_run_lowering_handles_declared_group_and_filtered_mono_runs() { + let (_, id, _) = fixture_fonts(); + let base = PreparedText { + clusters: vec![ + cluster(0..1, id, 0), + cluster(1..2, id, 0), + cluster(2..3, id, 0), + cluster(3..4, id, 0), + cluster(4..5, id, 0), + ], + }; + let annotation = PreparedText { + clusters: vec![cluster(0..1, id, 0), cluster(1..2, id, 0)], + }; + let declared = [crate::RubyRun::new(12..13, 0..1)]; + let runs = ruby_runs( + crate::RubyKind::Mono, + &(2..3), + 10, + &declared, + &base, + &annotation, + 2, + ) + .unwrap(); + assert_eq!(runs[0].base(), 2..3); + assert_eq!(runs[0].annotation(), 0..1); + + let group = ruby_runs( + crate::RubyKind::Group, + &(2..4), + 0, + &[], + &base, + &annotation, + 2, + ) + .unwrap(); + assert_eq!(group.len(), 1); + assert_eq!(group[0].base(), 2..4); + assert_eq!(group[0].annotation(), 0..2); + + let mono = ruby_runs( + crate::RubyKind::Mono, + &(2..4), + 0, + &[], + &base, + &annotation, + 2, + ) + .unwrap(); + assert_eq!(mono.len(), 2); + assert_eq!(mono[0].base(), 2..3); + assert_eq!(mono[1].base(), 3..4); + } + + #[test] + fn placement_origins_bidi_order_and_physical_geometry_are_exact() { + let (_, id, _) = fixture_fonts(); + let prepared = PreparedText { + clusters: vec![ + cluster(0..2, id, 0), + cluster(2..4, id, 1), + cluster(4..6, id, 0), + ], + }; + assert_eq!( + placement_cluster_indices(jlreq_core::PlacementOrigin::Cluster(2), &prepared, &(0..6)), + [2] + ); + assert_eq!( + placement_cluster_indices( + jlreq_core::PlacementOrigin::Construct(0), + &prepared, + &(1..5) + ), + [0, 1, 2] + ); + assert_eq!(logical_cluster_order(vec![0, 1, 2], 0), [0, 1, 2]); + assert_eq!(logical_cluster_order(vec![0, 1, 2], 1), [2, 1, 0]); + + let raw = raw(id); + let horizontal = place_raw_glyph( + &raw, + &prepared.clusters[0], + PlacementContext { + source_range: 0..2, + annotation: None, + inline: 320, + block: 640, + transform: GlyphTransform::Identity, + writing_mode: WritingMode::HorizontalTb, + }, + ); + assert_eq!(horizontal.geometry_26_6(), (320, 832, 256, 0, 64, 128)); + let vertical = place_raw_glyph( + &raw, + &prepared.clusters[0], + PlacementContext { + source_range: 0..2, + annotation: None, + inline: 320, + block: 640, + transform: GlyphTransform::Identity, + writing_mode: WritingMode::VerticalRl, + }, + ); + assert_eq!(vertical.geometry_26_6(), (640, 320, 0, 384, 64, 128)); + let tate_chu_yoko = place_raw_glyph( + &raw, + &prepared.clusters[0], + PlacementContext { + source_range: 0..2, + annotation: None, + inline: 320, + block: 640, + transform: GlyphTransform::TateChuYoko, + writing_mode: WritingMode::VerticalRl, + }, + ); + assert_eq!(tate_chu_yoko.geometry_26_6(), (320, 832, 256, 0, 64, 128)); + } + + #[test] + fn block_and_direction_helpers_distinguish_every_geometry_branch() { + let horizontal = LayoutOptions::try_new(100.0, 16.0) + .unwrap() + .line_gap(2.0) + .unwrap(); + assert_eq!(adjusted_block(10, 3, 20, &horizontal), 414); + let vertical = horizontal.clone().writing_mode(WritingMode::VerticalRl); + assert_eq!(adjusted_block(10, 3, 20, &vertical), -354); + + let (_, id, _) = fixture_fonts(); + let with_vertical_advance = raw(id); + assert_eq!( + direction_from_geometry( + &with_vertical_advance, + WritingMode::VerticalRl, + GlyphTransform::Identity + ), + Direction::TopToBottom + ); + assert_eq!( + direction_from_geometry( + &with_vertical_advance, + WritingMode::VerticalRl, + GlyphTransform::TateChuYoko + ), + Direction::LeftToRight + ); + assert_eq!( + direction_from_geometry( + &with_vertical_advance, + WritingMode::HorizontalTb, + GlyphTransform::Identity + ), + Direction::LeftToRight + ); + let mut without_vertical_advance = with_vertical_advance.clone(); + without_vertical_advance.y_advance = 0; + assert_eq!( + direction_from_geometry( + &without_vertical_advance, + WritingMode::VerticalRl, + GlyphTransform::RotateClockwise + ), + Direction::LeftToRight + ); + + assert_eq!(advance_block(100, 30, WritingMode::HorizontalTb), 130); + assert_eq!(advance_block(100, 30, WritingMode::VerticalRl), 70); + assert!(ranges_overlap(&(0..2), &(1..3))); + assert!(!ranges_overlap(&(0..1), &(1..2))); + assert!(!ranges_overlap(&(1..2), &(0..1))); + } +} diff --git a/crates/jlreq/src/error.rs b/crates/jlreq/src/error.rs new file mode 100644 index 0000000..8cdb15e --- /dev/null +++ b/crates/jlreq/src/error.rs @@ -0,0 +1,246 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use core::fmt; +use std::ops::Range; + +/// A validated option whose value was rejected. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum OptionKind { + /// Available inline length. + LineExtent, + /// Main text size. + FontSize, + /// Extra distance between lines. + LineGap, + /// OpenType variation coordinate. + Variation, + /// OpenType feature tag. + Feature, + /// Language tag. + Language, + /// Tab interval. + TabWidth, + /// A span-specific size. + SpanFontSize, + /// A construct-specific geometric value. + ConstructGeometry, +} + +/// A finite resource bounded by [`crate::ResourceLimits`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum Resource { + /// UTF-8 input bytes. + InputBytes, + /// Font resources made available to one layout. + Fonts, + /// Bytes across all registered fonts. + FontBytes, + /// Paragraphs in the input. + Paragraphs, + /// Shaping runs. + Runs, + /// Produced glyphs. + Glyphs, + /// Typed inline constructs. + Constructs, + /// Work charged by the deterministic core composer. + CoreOperations, +} + +/// Why high-level layout could not produce a complete result. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum LayoutError { + /// Font bytes or a TTC face index were invalid. + InvalidFont { + /// Index requested from a single font or collection. + face_index: u32, + }, + /// No font was registered. + NoFonts, + /// An option did not satisfy its public invariant. + InvalidOption { + /// The rejected option. + option: OptionKind, + /// Stable explanatory text. + message: &'static str, + }, + /// A UTF-8 range or typed document relationship was invalid. + InvalidDocument { + /// Stable machine-readable code. + code: &'static str, + /// Responsible source range, when applicable. + range: Option>, + }, + /// A declared resource maximum was exceeded. + ResourceLimit { + /// Resource that stopped processing. + resource: Resource, + /// Configured inclusive limit. + limit: usize, + /// Required or observed amount. + observed: usize, + }, + /// Validated high-level data could not be represented by the core input model. + CoreInput(jlreq_core::InputError), + /// The deterministic core composer exhausted a declared budget. + CoreComposition(jlreq_core::ComposeError), +} + +impl LayoutError { + pub(crate) const fn invalid_font(face_index: u32) -> Self { + Self::InvalidFont { face_index } + } + + pub(crate) const fn invalid_option(option: OptionKind, message: &'static str) -> Self { + Self::InvalidOption { option, message } + } + + pub(crate) fn invalid_document(code: &'static str, range: Option>) -> Self { + Self::InvalidDocument { code, range } + } + + pub(crate) const fn resource(resource: Resource, limit: usize, observed: usize) -> Self { + Self::ResourceLimit { + resource, + limit, + observed, + } + } + + /// Stable machine-readable error code. + #[must_use] + pub const fn code(&self) -> &'static str { + match self { + Self::InvalidFont { .. } => "font.invalid", + Self::NoFonts => "font.none-registered", + Self::InvalidOption { .. } => "layout.invalid-option", + Self::InvalidDocument { code, .. } => code, + Self::ResourceLimit { resource, .. } => match resource { + Resource::InputBytes => "limit.input-bytes", + Resource::Fonts => "limit.fonts", + Resource::FontBytes => "limit.font-bytes", + Resource::Paragraphs => "limit.paragraphs", + Resource::Runs => "limit.runs", + Resource::Glyphs => "limit.glyphs", + Resource::Constructs => "limit.constructs", + Resource::CoreOperations => "limit.core-operations", + }, + Self::CoreInput(error) => error.code(), + Self::CoreComposition(error) => error.code(), + } + } + + /// Responsible UTF-8 byte range, when one input range caused the error. + #[must_use] + pub fn range(&self) -> Option> { + match self { + Self::InvalidDocument { range, .. } => range.clone(), + Self::CoreInput(error) => error.range(), + _ => None, + } + } +} + +impl fmt::Display for LayoutError { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidFont { face_index } => { + write!( + formatter, + "invalid font data or TTC face index {face_index}" + ) + }, + Self::NoFonts => formatter.write_str("no fonts are registered"), + Self::InvalidOption { option, message } => { + write!(formatter, "invalid {option:?}: {message}") + }, + Self::InvalidDocument { code, .. } => write!(formatter, "invalid document ({code})"), + Self::ResourceLimit { + resource, + limit, + observed, + } => write!( + formatter, + "{resource:?} limit exceeded: limit {limit}, observed {observed}" + ), + Self::CoreInput(error) => error.fmt(formatter), + Self::CoreComposition(error) => error.fmt(formatter), + } + } +} + +impl std::error::Error for LayoutError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::CoreInput(error) => Some(error), + Self::CoreComposition(error) => Some(error), + _ => None, + } + } +} + +impl From for LayoutError { + fn from(value: jlreq_core::InputError) -> Self { + Self::CoreInput(value) + } +} + +impl From for LayoutError { + fn from(value: jlreq_core::ComposeError) -> Self { + Self::CoreComposition(value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::error::Error as _; + + fn core_input_error() -> jlreq_core::InputError { + jlreq_core::ShapedText::new( + "é", + jlreq_core::Size::square(64).unwrap(), + jlreq_core::Frame::FullEm, + [jlreq_core::Cluster::new(1..2, 64)], + ) + .expect_err("the cluster starts inside a UTF-8 scalar") + } + + fn core_compose_error() -> jlreq_core::ComposeError { + let shaped = jlreq_core::ShapedText::new( + "A", + jlreq_core::Size::square(64).unwrap(), + jlreq_core::Frame::FullEm, + [jlreq_core::Cluster::new(0..1, 64)], + ) + .unwrap(); + let paragraph = jlreq_core::Paragraph::builder(shaped, 64).build().unwrap(); + jlreq_core::Composer::with_limits( + jlreq_core::CompositionLimits::default().with_max_clusters(0), + ) + .compose(¶graph, &jlreq_core::Style::default()) + .expect_err("one cluster exceeds a zero cluster budget") + } + + #[test] + fn ranges_and_error_sources_are_preserved_for_every_wrapped_error() { + let document = LayoutError::invalid_document("document.invalid-span-range", Some(2..5)); + assert_eq!(document.range(), Some(2..5)); + assert!(document.source().is_none()); + + let input = LayoutError::from(core_input_error()); + assert_eq!(input.range(), Some(1..2)); + assert!(input.source().is_some()); + + let composition = LayoutError::from(core_compose_error()); + assert_eq!(composition.range(), None); + assert!(composition.source().is_some()); + + assert_eq!(LayoutError::NoFonts.range(), None); + assert!(LayoutError::NoFonts.source().is_none()); + } +} diff --git a/crates/jlreq/src/font.rs b/crates/jlreq/src/font.rs new file mode 100644 index 0000000..997828a --- /dev/null +++ b/crates/jlreq/src/font.rs @@ -0,0 +1,441 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::fmt; +use std::sync::Arc; + +use crate::LayoutError; + +/// Stable identifier assigned by a [`FontLibrary`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FontId(u32); + +impl FontId { + /// Numeric value suitable for renderer-side maps. + #[must_use] + pub const fn get(self) -> u32 { + self.0 + } +} + +/// Font slant requested by a span or recorded for a face. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[non_exhaustive] +pub enum FontSlant { + /// Upright design. + #[default] + Normal, + /// Designed italic face. + Italic, + /// Mechanically or explicitly oblique face. + Oblique, +} + +/// Family matching attributes kept independent of Fontique's public types. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub struct FontStyle { + weight: u16, + width: u16, + slant: FontSlant, +} + +impl FontStyle { + /// Build a style using CSS-like weight and percentage width values. + #[must_use] + pub const fn new(weight: u16, width: u16, slant: FontSlant) -> Self { + Self { + weight, + width, + slant, + } + } + + /// CSS-like weight, normally 100 through 900. + #[must_use] + pub const fn weight(self) -> u16 { + self.weight + } + + /// Percentage width where 100 is normal. + #[must_use] + pub const fn width(self) -> u16 { + self.width + } + + /// Requested slant. + #[must_use] + pub const fn slant(self) -> FontSlant { + self.slant + } +} + +impl Default for FontStyle { + fn default() -> Self { + Self::new(400, 100, FontSlant::Normal) + } +} + +/// Font bytes retained by a completed layout for renderer use. +#[derive(Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct FontResource { + pub(crate) id: FontId, + pub(crate) bytes: Arc<[u8]>, + pub(crate) face_index: u32, + pub(crate) family: String, + pub(crate) style: FontStyle, +} + +impl fmt::Debug for FontResource { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("FontResource") + .field("id", &self.id) + .field("byte_len", &self.bytes.len()) + .field("face_index", &self.face_index) + .field("family", &self.family) + .field("style", &self.style) + .finish() + } +} + +impl FontResource { + /// Library identifier referenced by glyph placements. + #[must_use] + pub const fn id(&self) -> FontId { + self.id + } + + /// Original TTF, OTF, or TTC bytes. + #[must_use] + pub fn bytes(&self) -> &[u8] { + &self.bytes + } + + /// Face index inside the original bytes. + #[must_use] + pub const fn face_index(&self) -> u32 { + self.face_index + } + + /// Registered family label. + #[must_use] + pub fn family(&self) -> &str { + &self.family + } + + /// Registered style label. + #[must_use] + pub const fn style(&self) -> FontStyle { + self.style + } +} + +/// Ordered, in-memory font faces used for primary selection and fallback. +#[derive(Clone, Default)] +pub struct FontLibrary { + fonts: Vec, + primary: Option, + fallback_order: Vec, +} + +impl fmt::Debug for FontLibrary { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("FontLibrary") + .field("fonts", &self.fonts) + .field("primary", &self.primary) + .field("fallback_order", &self.fallback_order) + .finish() + } +} + +impl FontLibrary { + /// Create an empty library. Layout requires at least one registered face. + #[must_use] + pub const fn new() -> Self { + Self { + fonts: Vec::new(), + primary: None, + fallback_order: Vec::new(), + } + } + + /// Register face zero with default family/style metadata. + pub fn register_font(&mut self, bytes: B) -> Result + where + B: Into>, + { + self.register_face(bytes, 0, "", FontStyle::default()) + } + + /// Alias for [`register_font`](Self::register_font), convenient in builder-style setup. + pub fn add_font(&mut self, bytes: B) -> Result + where + B: Into>, + { + self.register_font(bytes) + } + + /// Register one face from TTF, OTF, or TTC bytes. + pub fn register_face( + &mut self, + bytes: B, + face_index: u32, + family: impl Into, + style: FontStyle, + ) -> Result + where + B: Into>, + { + let bytes = bytes.into(); + let font = harfrust::FontRef::from_index(&bytes, face_index) + .map_err(|_| LayoutError::invalid_font(face_index))?; + // Constructing the cache touches the shaping-critical directory and metrics tables. + // It is intentionally discarded here; LayoutEngine owns reusable copies. + let _ = harfrust::ShaperData::new(&font); + + let ordinal = u32::try_from(self.fonts.len()) + .map_err(|_| LayoutError::invalid_document("font.too-many-ids", None))?; + let id = FontId(ordinal); + self.fonts.push(FontResource { + id, + bytes, + face_index, + family: family.into(), + style, + }); + self.fallback_order.push(id); + if self.primary.is_none() { + self.primary = Some(id); + } + Ok(id) + } + + /// Alias for [`register_face`](Self::register_face). + pub fn add_face( + &mut self, + bytes: B, + face_index: u32, + family: impl Into, + style: FontStyle, + ) -> Result + where + B: Into>, + { + self.register_face(bytes, face_index, family, style) + } + + /// Make a registered face the `.notdef` source and first fallback candidate. + pub fn set_primary(&mut self, id: FontId) -> Result<(), LayoutError> { + self.get(id) + .ok_or_else(|| LayoutError::invalid_document("font.unknown-id", None))?; + self.primary = Some(id); + Ok(()) + } + + /// Replace fallback priority after the primary face. + pub fn set_fallback_order(&mut self, ids: I) -> Result<(), LayoutError> + where + I: IntoIterator, + { + let mut next = Vec::new(); + for id in ids { + if self.get(id).is_none() { + return Err(LayoutError::invalid_document("font.unknown-id", None)); + } + if !next.contains(&id) { + next.push(id); + } + } + for font in &self.fonts { + if !next.contains(&font.id) { + next.push(font.id); + } + } + self.fallback_order = next; + Ok(()) + } + + /// Registered faces in identifier order. + #[must_use] + pub fn fonts(&self) -> &[FontResource] { + &self.fonts + } + + /// Number of registered faces. + #[must_use] + pub const fn len(&self) -> usize { + self.fonts.len() + } + + /// Whether no face has been registered. + #[must_use] + pub const fn is_empty(&self) -> bool { + self.fonts.is_empty() + } + + /// Primary face, if present. + #[must_use] + pub const fn primary(&self) -> Option { + self.primary + } + + /// Look up registration metadata. + #[must_use] + pub fn get(&self, id: FontId) -> Option<&FontResource> { + self.fonts.get(id.0 as usize).filter(|font| font.id == id) + } + + #[cfg(feature = "system-fonts")] + /// Resolve and copy one system family face through Fontique. + /// + /// System selection is intentionally opt-in and is not covered by the cross-platform + /// determinism guarantee. Once registered, the selected bytes are owned exactly like an + /// explicitly supplied font. + pub fn register_system_family( + &mut self, + family: &str, + style: FontStyle, + ) -> Result { + use fontique::{ + Collection, CollectionOptions, QueryStatus, SourceCache, SourceCacheOptions, + }; + + let mut collection = Collection::new(CollectionOptions { + shared: false, + system_fonts: true, + }); + let mut cache = SourceCache::new(SourceCacheOptions { shared: false }); + let mut selected = None; + { + let mut query = collection.query(&mut cache); + query.set_families([family]); + query.matches_with(|font| { + selected = Some((font.blob.as_ref().to_vec(), font.index)); + QueryStatus::Stop + }); + } + let (bytes, index) = selected + .ok_or_else(|| LayoutError::invalid_document("font.system-family-not-found", None))?; + self.register_face(bytes, index, family, style) + } + + pub(crate) fn ordered_candidates( + &self, + families: &[String], + requested: FontStyle, + ) -> Vec { + let mut result = Vec::with_capacity(self.fonts.len()); + for family in families { + let mut matching: Vec<_> = self + .fonts + .iter() + .filter(|font| font.family.eq_ignore_ascii_case(family)) + .collect(); + matching.sort_by_key(|font| { + let slant = usize::from(font.style.slant != requested.slant); + let weight = font.style.weight.abs_diff(requested.weight) as usize; + let width = font.style.width.abs_diff(requested.width) as usize; + (slant, weight, width, font.id) + }); + for font in matching { + if !result.contains(&font.id) { + result.push(font.id); + } + } + } + if let Some(primary) = self.primary + && !result.contains(&primary) + { + result.push(primary); + } + for id in &self.fallback_order { + if !result.contains(id) { + result.push(*id); + } + } + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn resource(id: u32, face_index: u32, family: &str, style: FontStyle) -> FontResource { + FontResource { + id: FontId(id), + bytes: Arc::from(vec![1_u8, 2, 3]), + face_index, + family: family.to_owned(), + style, + } + } + + fn fixture_library() -> FontLibrary { + let regular = FontStyle::new(400, 100, FontSlant::Normal); + let italic = FontStyle::new(500, 90, FontSlant::Italic); + FontLibrary { + fonts: vec![ + resource(0, 4, "Family", regular), + resource(1, 7, "Family", italic), + resource(2, 9, "Fallback", regular), + ], + primary: Some(FontId(0)), + fallback_order: vec![FontId(0), FontId(1), FontId(2)], + } + } + + #[test] + fn identifiers_resources_and_libraries_have_exact_observable_metadata() { + assert_eq!(FontId(42).get(), 42); + let font = resource( + 42, + 7, + "Distinct Family", + FontStyle::new(525, 91, FontSlant::Oblique), + ); + assert_eq!(font.id().get(), 42); + assert_eq!(font.face_index(), 7); + assert_eq!(font.bytes(), [1, 2, 3]); + assert_eq!(font.family(), "Distinct Family"); + let rendered = format!("{font:?}"); + assert!(rendered.contains("FontResource")); + assert!(rendered.contains("byte_len: 3")); + assert!(rendered.contains("face_index: 7")); + assert!(rendered.contains("Distinct Family")); + + let library = fixture_library(); + let rendered = format!("{library:?}"); + assert!(rendered.contains("FontLibrary")); + assert!(rendered.contains("fallback_order")); + assert!(rendered.contains("FontId(2)")); + } + + #[test] + fn fallback_order_deduplicates_and_appends_every_omitted_face() { + let mut library = fixture_library(); + library.set_fallback_order([FontId(2), FontId(2)]).unwrap(); + assert_eq!(library.fallback_order, [FontId(2), FontId(0), FontId(1)]); + } + + #[test] + fn candidate_matching_prefers_style_without_repeating_primary_or_fallback() { + let library = fixture_library(); + let requested = FontStyle::new(500, 90, FontSlant::Italic); + assert_eq!( + library.ordered_candidates(&["family".into(), "FAMILY".into()], requested), + [FontId(1), FontId(0), FontId(2)] + ); + assert_eq!( + library.ordered_candidates(&["Fallback".into()], FontStyle::default()), + [FontId(2), FontId(0), FontId(1)] + ); + assert_eq!( + library.ordered_candidates(&[], FontStyle::default()), + [FontId(0), FontId(1), FontId(2)] + ); + } +} diff --git a/crates/jlreq/src/lib.rs b/crates/jlreq/src/lib.rs index 0751e1d..c6bfc9e 100644 --- a/crates/jlreq/src/lib.rs +++ b/crates/jlreq/src/lib.rs @@ -1,64 +1,50 @@ // SPDX-FileCopyrightText: 2026 jlreq contributors -// // SPDX-License-Identifier: MIT OR Apache-2.0 -//! Japanese line composition for already-shaped text. -//! -//! jlreq owns Japanese spacing, line selection, inline constructs, and physical -//! placement. Font loading, shaping, Unicode line segmentation, bidi resolution, and -//! rendering stay with the caller. Every input position is a UTF-8 byte offset. -//! -//! ``` -//! use jlreq::{Break, Cluster, Frame, Paragraph, ShapedText, Size, Style}; -//! -//! let source = "日本語組版"; -//! let clusters = source.char_indices().map(|(start, ch)| { -//! let end = start + ch.len_utf8(); -//! Cluster::new(start..end, 1_000) -//! }); -//! let text = ShapedText::new(source, Size::square(1_000)?, Frame::FullEm, clusters)?; -//! let paragraph = Paragraph::builder(text, 4_000) -//! .breaks(source.char_indices().skip(1).map(|(at, _)| Break::allowed(at))) -//! .build()?; -//! let layout = jlreq::compose(¶graph, &Style::book_2020()) -//! .expect("small paragraph is within the default resource limits"); -//! -//! assert_eq!(layout.lines().len(), 2); -//! # Ok::<(), jlreq::InputError>(()) -//! ``` +#![doc = include_str!("../README.md")] -#![no_std] +/// Dependency-free `no_std + alloc` composition API for already-shaped text. +pub use jlreq_core as core; -extern crate alloc; +mod document; +mod engine; +mod error; +mod font; +mod options; +mod result; +mod units; -mod construct; -mod generated; -mod layout; -mod limits; -mod model; -mod normalize; -mod paragraph; -mod pipeline; -mod spec; -pub mod style; - -pub use construct::{Construct, Ruby, RubyKind, RubyRun}; -pub use layout::{ - Attachment, ClusterPlacement, CoordinateTransform, Diagnostic, Layout, Line, PlacementOrigin, - Severity, +pub use document::{ + Document, DocumentBuilder, RubyKind, RubyRun, ScriptPosition, SpanStyle, TextRole, +}; +pub use engine::LayoutEngine; +pub use error::{LayoutError, OptionKind, Resource}; +pub use font::{FontId, FontLibrary, FontResource, FontSlant, FontStyle}; +pub use options::{ + Alignment, BaseDirection, FontVariation, LayoutOptions, OpenTypeFeature, OpenTypeTag, + ResourceLimits, WritingMode, +}; +pub use result::{ + Affinity, AnnotationSource, Diagnostic, DiagnosticSeverity, GlyphPlacement, GlyphTransform, + HitTest, Point, Rect, TextLayout, TextLine, }; -pub use limits::{ComposeError, CompositionLimits, CompositionResource}; -pub use model::{Cluster, ClusterRole, Frame, InputError, ShapedText, Size, WritingMode}; -pub use paragraph::{Alignment, Break, Paragraph, ParagraphBuilder, TabAlignment, TabStop, Widow}; -pub use pipeline::Composer; -pub use style::{Style, StyleBuilder, StyleError}; - -/// The stable specification identifier implemented by this release line. -pub const SPECIFICATION: &str = "jlreq-2020-08-11+unicode-17.0.0"; -/// Compose one validated paragraph with a fresh scratch allocator. +/// Shape and lay out plain UTF-8 text with a fresh engine. /// -/// Use Composer when composing repeatedly so its temporary buffers can be reused. -pub fn compose(paragraph: &Paragraph, style: &Style) -> Result { - Composer::new().compose(paragraph, style) +/// Use [`LayoutEngine`] when processing repeatedly so font and shaper caches are reused. +pub fn layout( + text: &str, + fonts: &FontLibrary, + options: LayoutOptions, +) -> Result { + LayoutEngine::new().layout(text, fonts, options) +} + +/// Shape and lay out a typed document with a fresh engine. +pub fn layout_document( + document: &Document, + fonts: &FontLibrary, + options: LayoutOptions, +) -> Result { + LayoutEngine::new().layout_document(document, fonts, options) } diff --git a/crates/jlreq/src/options.rs b/crates/jlreq/src/options.rs new file mode 100644 index 0000000..865ff7b --- /dev/null +++ b/crates/jlreq/src/options.rs @@ -0,0 +1,469 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::units::{finite, non_negative, positive}; +use crate::{LayoutError, OptionKind}; + +/// Physical writing mode requested from the high-level pipeline. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[non_exhaustive] +pub enum WritingMode { + /// Inline progression is left-to-right and lines progress top-to-bottom. + #[default] + HorizontalTb, + /// Inline progression is top-to-bottom and lines progress right-to-left. + VerticalRl, +} + +impl WritingMode { + pub(crate) const fn core(self) -> jlreq_core::WritingMode { + match self { + Self::HorizontalTb => jlreq_core::WritingMode::HorizontalTb, + Self::VerticalRl => jlreq_core::WritingMode::VerticalRl, + } + } +} + +/// Line alignment in the available inline extent. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[non_exhaustive] +pub enum Alignment { + /// Align at inline start. + Start, + /// Center the occupied line. + Center, + /// Align at inline end. + End, + /// Apply JLReq adjustment to non-final lines. + #[default] + Justify, +} + +impl Alignment { + pub(crate) const fn core(self) -> jlreq_core::Alignment { + match self { + Self::Start => jlreq_core::Alignment::Start, + Self::Center => jlreq_core::Alignment::Center, + Self::End => jlreq_core::Alignment::End, + Self::Justify => jlreq_core::Alignment::Justify, + } + } +} + +/// Paragraph base-direction policy. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[non_exhaustive] +pub enum BaseDirection { + /// Resolve from the first strong character under UAX #9. + #[default] + Auto, + /// Force a left-to-right paragraph embedding level. + LeftToRight, + /// Force a right-to-left paragraph embedding level. + RightToLeft, +} + +/// A four-byte OpenType tag whose bytes are kept private. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct OpenTypeTag([u8; 4]); + +impl OpenTypeTag { + /// Validate an ASCII OpenType tag of exactly four bytes. + pub fn try_new(tag: &str) -> Result { + let bytes = tag.as_bytes(); + if bytes.len() != 4 || !bytes.iter().all(u8::is_ascii_graphic) { + return Err(LayoutError::invalid_option( + OptionKind::Feature, + "an OpenType tag must contain exactly four printable ASCII bytes", + )); + } + Ok(Self([bytes[0], bytes[1], bytes[2], bytes[3]])) + } + + /// Tag bytes in network order. + #[must_use] + pub const fn bytes(self) -> [u8; 4] { + self.0 + } +} + +/// One global OpenType feature setting. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct OpenTypeFeature { + tag: OpenTypeTag, + value: u32, +} + +impl OpenTypeFeature { + /// Create a feature value applied to the whole shaping run. + #[must_use] + pub const fn new(tag: OpenTypeTag, value: u32) -> Self { + Self { tag, value } + } + + /// Feature tag. + #[must_use] + pub const fn tag(self) -> OpenTypeTag { + self.tag + } + + /// Feature value. + #[must_use] + pub const fn value(self) -> u32 { + self.value + } +} + +/// One variable-font axis value, quantized at the public boundary. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct FontVariation { + tag: OpenTypeTag, + value: f32, +} + +impl FontVariation { + /// Validate and quantize a variation setting. + pub fn try_new(tag: OpenTypeTag, value: f32) -> Result { + Ok(Self { + tag, + value: finite(value, OptionKind::Variation)?, + }) + } + + /// Axis tag. + #[must_use] + pub const fn tag(self) -> OpenTypeTag { + self.tag + } + + /// Quantized user-space axis value. + #[must_use] + pub const fn value(self) -> f32 { + self.value + } +} + +/// Deterministic high-level resource limits for one call. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub struct ResourceLimits { + pub(crate) input_bytes: usize, + pub(crate) fonts: usize, + pub(crate) font_bytes: usize, + pub(crate) paragraphs: usize, + pub(crate) runs: usize, + pub(crate) glyphs: usize, + pub(crate) constructs: usize, + pub(crate) core_operations: usize, +} + +impl ResourceLimits { + /// Release defaults, usable in constants. + pub const DEFAULT: Self = Self { + input_bytes: 16 * 1024 * 1024, + fonts: 256, + font_bytes: 512 * 1024 * 1024, + paragraphs: 65_536, + runs: 262_144, + glyphs: 1_000_000, + constructs: 4_096, + core_operations: 8_000_000, + }; + + /// Maximum input bytes. + #[must_use] + pub const fn max_input_bytes(self) -> usize { + self.input_bytes + } + + /// Maximum registered fonts considered by a call. + #[must_use] + pub const fn max_fonts(self) -> usize { + self.fonts + } + + /// Maximum total bytes in registered fonts. + #[must_use] + pub const fn max_font_bytes(self) -> usize { + self.font_bytes + } + + /// Maximum paragraphs. + #[must_use] + pub const fn max_paragraphs(self) -> usize { + self.paragraphs + } + + /// Maximum shaping runs. + #[must_use] + pub const fn max_runs(self) -> usize { + self.runs + } + + /// Maximum produced glyphs. + #[must_use] + pub const fn max_glyphs(self) -> usize { + self.glyphs + } + + /// Maximum inline constructs. + #[must_use] + pub const fn max_constructs(self) -> usize { + self.constructs + } + + /// Maximum core operations. + #[must_use] + pub const fn max_core_operations(self) -> usize { + self.core_operations + } + + /// Replace the input-byte maximum. + #[must_use] + pub const fn with_max_input_bytes(mut self, value: usize) -> Self { + self.input_bytes = value; + self + } + + /// Replace the font-count maximum. + #[must_use] + pub const fn with_max_fonts(mut self, value: usize) -> Self { + self.fonts = value; + self + } + + /// Replace the font-byte maximum. + #[must_use] + pub const fn with_max_font_bytes(mut self, value: usize) -> Self { + self.font_bytes = value; + self + } + + /// Replace the paragraph maximum. + #[must_use] + pub const fn with_max_paragraphs(mut self, value: usize) -> Self { + self.paragraphs = value; + self + } + + /// Replace the run maximum. + #[must_use] + pub const fn with_max_runs(mut self, value: usize) -> Self { + self.runs = value; + self + } + + /// Replace the glyph maximum. + #[must_use] + pub const fn with_max_glyphs(mut self, value: usize) -> Self { + self.glyphs = value; + self + } + + /// Replace the construct maximum. + #[must_use] + pub const fn with_max_constructs(mut self, value: usize) -> Self { + self.constructs = value; + self + } + + /// Replace the core-operation maximum. + #[must_use] + pub const fn with_max_core_operations(mut self, value: usize) -> Self { + self.core_operations = value; + self + } +} + +impl Default for ResourceLimits { + fn default() -> Self { + Self::DEFAULT + } +} + +/// Validated controls for automatic shaping and line layout. +#[derive(Debug, Clone, PartialEq)] +#[non_exhaustive] +pub struct LayoutOptions { + pub(crate) line_extent: i32, + pub(crate) font_size: i32, + pub(crate) writing_mode: WritingMode, + pub(crate) alignment: Alignment, + pub(crate) style: jlreq_core::Style, + pub(crate) language: String, + pub(crate) base_direction: BaseDirection, + pub(crate) line_gap: i32, + pub(crate) tab_width: u16, + pub(crate) features: Vec, + pub(crate) variations: Vec, + pub(crate) limits: ResourceLimits, +} + +impl LayoutOptions { + /// Validate the two required floating-point inputs and quantize them to 26.6. + pub fn try_new(line_extent: f32, font_size: f32) -> Result { + Ok(Self { + line_extent: positive(line_extent, OptionKind::LineExtent)?, + font_size: positive(font_size, OptionKind::FontSize)?, + writing_mode: WritingMode::default(), + alignment: Alignment::default(), + style: jlreq_core::Style::default(), + language: "und".to_owned(), + base_direction: BaseDirection::default(), + line_gap: 0, + tab_width: 4, + features: Vec::new(), + variations: Vec::new(), + limits: ResourceLimits::default(), + }) + } + + /// Set horizontal or vertical writing. + #[must_use] + pub const fn writing_mode(mut self, value: WritingMode) -> Self { + self.writing_mode = value; + self + } + + /// Set line alignment. + #[must_use] + pub const fn alignment(mut self, value: Alignment) -> Self { + self.alignment = value; + self + } + + /// Set the complete low-level JLReq policy profile. + #[must_use] + pub fn style(mut self, value: jlreq_core::Style) -> Self { + self.style = value; + self + } + + /// Set a BCP 47/OpenType language tag. + pub fn language(mut self, value: impl Into) -> Result { + let value = value.into(); + if value.is_empty() + || value.len() > 63 + || !value + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-') + { + return Err(LayoutError::invalid_option( + OptionKind::Language, + "language must be a non-empty ASCII language tag of at most 63 bytes", + )); + } + self.language = value; + Ok(self) + } + + /// Set paragraph base direction. + #[must_use] + pub const fn base_direction(mut self, value: BaseDirection) -> Self { + self.base_direction = value; + self + } + + /// Set extra block-axis distance between adjacent lines. + pub fn line_gap(mut self, value: f32) -> Result { + self.line_gap = non_negative(value, OptionKind::LineGap)?; + Ok(self) + } + + /// Set the tab interval in space cells. + pub fn tab_width(mut self, value: u16) -> Result { + if value == 0 { + return Err(LayoutError::invalid_option( + OptionKind::TabWidth, + "tab width must be at least one space cell", + )); + } + self.tab_width = value; + Ok(self) + } + + /// Add one global OpenType feature. + #[must_use] + pub fn feature(mut self, value: OpenTypeFeature) -> Self { + self.features.push(value); + self + } + + /// Add one global variable-font axis value. + #[must_use] + pub fn variation(mut self, value: FontVariation) -> Self { + self.variations.push(value); + self + } + + /// Replace all high-level resource limits. + #[must_use] + pub const fn limits(mut self, value: ResourceLimits) -> Self { + self.limits = value; + self + } + + /// Available inline length after 26.6 quantization. + #[must_use] + pub fn line_extent(&self) -> f32 { + crate::units::to_f32(self.line_extent) + } + + /// Main text size after 26.6 quantization. + #[must_use] + pub fn font_size(&self) -> f32 { + crate::units::to_f32(self.font_size) + } + + /// Current writing mode. + #[must_use] + pub const fn writing_mode_value(&self) -> WritingMode { + self.writing_mode + } + + /// Current limits. + #[must_use] + pub const fn resource_limits(&self) -> ResourceLimits { + self.limits + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn release_resource_defaults_are_exact_byte_counts() { + let limits = ResourceLimits::default(); + assert_eq!(limits.max_input_bytes(), 16_777_216); + assert_eq!(limits.max_fonts(), 256); + assert_eq!(limits.max_font_bytes(), 536_870_912); + assert_eq!(limits.max_paragraphs(), 65_536); + assert_eq!(limits.max_runs(), 262_144); + assert_eq!(limits.max_glyphs(), 1_000_000); + assert_eq!(limits.max_constructs(), 4_096); + assert_eq!(limits.max_core_operations(), 8_000_000); + } + + #[test] + fn layout_language_accepts_exactly_the_documented_length_boundary() { + assert!( + LayoutOptions::try_new(100.0, 16.0) + .unwrap() + .language("a".repeat(63)) + .is_ok() + ); + assert!( + LayoutOptions::try_new(100.0, 16.0) + .unwrap() + .language("a".repeat(64)) + .is_err() + ); + assert!( + LayoutOptions::try_new(100.0, 16.0) + .unwrap() + .language("a".repeat(65)) + .is_err() + ); + } +} diff --git a/crates/jlreq/src/result.rs b/crates/jlreq/src/result.rs new file mode 100644 index 0000000..e0b873f --- /dev/null +++ b/crates/jlreq/src/result.rs @@ -0,0 +1,903 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::ops::Range; + +use crate::units::{finite, quantize, to_f32}; +use crate::{FontId, FontResource, LayoutError, OptionKind, WritingMode}; + +/// Physical point represented internally in deterministic 26.6 fixed point. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct Point { + x: i32, + y: i32, +} + +impl Point { + /// Validate and quantize a physical point. + pub fn try_new(x: f32, y: f32) -> Result { + let x = finite(x, OptionKind::ConstructGeometry)?; + let y = finite(y, OptionKind::ConstructGeometry)?; + Ok(Self { + x: quantize(x), + y: quantize(y), + }) + } + + pub(crate) const fn from_fixed(x: i32, y: i32) -> Self { + Self { x, y } + } + + /// Horizontal coordinate. + #[must_use] + pub fn x(self) -> f32 { + to_f32(self.x) + } + + /// Vertical coordinate. + #[must_use] + pub fn y(self) -> f32 { + to_f32(self.y) + } + + /// Raw 26.6 horizontal coordinate. + #[must_use] + pub const fn x_26_6(self) -> i32 { + self.x + } + + /// Raw 26.6 vertical coordinate. + #[must_use] + pub const fn y_26_6(self) -> i32 { + self.y + } +} + +/// Axis-aligned physical rectangle in deterministic 26.6 fixed point. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub struct Rect { + x: i32, + y: i32, + width: i32, + height: i32, +} + +impl Rect { + pub(crate) const fn from_fixed(x: i32, y: i32, width: i32, height: i32) -> Self { + Self { + x, + y, + width, + height, + } + } + + /// Left edge. + #[must_use] + pub fn x(self) -> f32 { + to_f32(self.x) + } + + /// Top edge. + #[must_use] + pub fn y(self) -> f32 { + to_f32(self.y) + } + + /// Width. + #[must_use] + pub fn width(self) -> f32 { + to_f32(self.width) + } + + /// Height. + #[must_use] + pub fn height(self) -> f32 { + to_f32(self.height) + } + + /// Raw 26.6 components `(x, y, width, height)`. + #[must_use] + pub const fn as_26_6(self) -> (i32, i32, i32, i32) { + (self.x, self.y, self.width, self.height) + } + + fn contains(self, point: Point) -> bool { + point.x >= self.x + && point.y >= self.y + && point.x <= self.x.saturating_add(self.width) + && point.y <= self.y.saturating_add(self.height) + } +} + +/// Local transform a renderer applies around a glyph origin. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[non_exhaustive] +pub enum GlyphTransform { + /// No rotation. + #[default] + Identity, + /// Rotate a horizontal glyph clockwise in vertical text. + RotateClockwise, + /// Keep a short horizontal run upright inside vertical text. + TateChuYoko, +} + +/// Annotation stream attribution for a glyph not stored in the main source string. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub struct AnnotationSource { + construct: usize, + range: Range, +} + +impl AnnotationSource { + pub(crate) const fn new(construct: usize, range: Range) -> Self { + Self { construct, range } + } + + /// Ordinal in the document's construct list. + #[must_use] + pub const fn construct(&self) -> usize { + self.construct + } + + /// UTF-8 byte range in the annotation string. + #[must_use] + pub fn range(&self) -> Range { + self.range.clone() + } +} + +/// One glyph in visual draw order with no renderer-specific handles. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct GlyphPlacement { + pub(crate) font_id: FontId, + pub(crate) glyph_id: u32, + pub(crate) source_range: Range, + pub(crate) annotation: Option, + pub(crate) x: i32, + pub(crate) y: i32, + pub(crate) advance_x: i32, + pub(crate) advance_y: i32, + pub(crate) offset_x: i32, + pub(crate) offset_y: i32, + pub(crate) font_size: i32, + pub(crate) transform: GlyphTransform, + pub(crate) bidi_level: u8, + pub(crate) writing_mode: WritingMode, +} + +impl GlyphPlacement { + /// Font resource identifier. + #[must_use] + pub const fn font_id(&self) -> FontId { + self.font_id + } + + /// OpenType glyph identifier. Zero is `.notdef`. + #[must_use] + pub const fn glyph_id(&self) -> u32 { + self.glyph_id + } + + /// Original document UTF-8 byte range responsible for this glyph. + #[must_use] + pub fn source_range(&self) -> Range { + self.source_range.clone() + } + + /// Alias for [`source_range`](Self::source_range). + #[must_use] + pub fn range(&self) -> Range { + self.source_range() + } + + /// Annotation-local attribution, if this glyph came from ruby or another attachment. + #[must_use] + pub const fn annotation(&self) -> Option<&AnnotationSource> { + self.annotation.as_ref() + } + + /// Physical glyph origin. + #[must_use] + pub const fn origin(&self) -> Point { + Point::from_fixed(self.x, self.y) + } + + /// Physical horizontal origin. + #[must_use] + pub fn x(&self) -> f32 { + to_f32(self.x) + } + + /// Physical vertical origin. + #[must_use] + pub fn y(&self) -> f32 { + to_f32(self.y) + } + + /// Shaped horizontal advance. + #[must_use] + pub fn advance_x(&self) -> f32 { + to_f32(self.advance_x) + } + + /// Shaped vertical advance. + #[must_use] + pub fn advance_y(&self) -> f32 { + to_f32(self.advance_y) + } + + /// Shaped horizontal offset. + #[must_use] + pub fn offset_x(&self) -> f32 { + to_f32(self.offset_x) + } + + /// Shaped vertical offset. + #[must_use] + pub fn offset_y(&self) -> f32 { + to_f32(self.offset_y) + } + + /// `(x, y, advance_x, advance_y, offset_x, offset_y)` in raw 26.6 units. + #[must_use] + pub const fn geometry_26_6(&self) -> (i32, i32, i32, i32, i32, i32) { + ( + self.x, + self.y, + self.advance_x, + self.advance_y, + self.offset_x, + self.offset_y, + ) + } + + /// Local renderer transform. + #[must_use] + pub const fn transform(&self) -> GlyphTransform { + self.transform + } + + /// Resolved UAX #9 embedding level. + #[must_use] + pub const fn bidi_level(&self) -> u8 { + self.bidi_level + } + + fn bounds(&self) -> Rect { + match self.writing_mode { + WritingMode::HorizontalTb => { + let width = self.advance_x.abs().max(1); + Rect::from_fixed( + self.x, + self.y.saturating_sub(self.font_size), + width, + self.font_size, + ) + }, + WritingMode::VerticalRl => { + let height = self.advance_y.abs().max(1); + Rect::from_fixed( + self.x.saturating_sub(self.font_size), + self.y, + self.font_size, + height, + ) + }, + } + } +} + +/// Severity of a recoverable layout diagnostic. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum DiagnosticSeverity { + /// Informational fallback or policy note. + Info, + /// Complete output whose quality or fit deserves attention. + Warning, + /// A requested constraint was not satisfied, but all input was preserved. + Error, +} + +/// Recoverable, positioned issue accompanying a complete layout. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct Diagnostic { + pub(crate) code: &'static str, + pub(crate) severity: DiagnosticSeverity, + pub(crate) range: Option>, + pub(crate) message: &'static str, + pub(crate) jlreq: Option<&'static str>, +} + +impl Diagnostic { + /// Stable machine-readable code. + #[must_use] + pub const fn code(&self) -> &'static str { + self.code + } + + /// Diagnostic severity. + #[must_use] + pub const fn severity(&self) -> DiagnosticSeverity { + self.severity + } + + /// Responsible document byte range. + #[must_use] + pub fn range(&self) -> Option> { + self.range.clone() + } + + /// Short stable English explanation. + #[must_use] + pub const fn message(&self) -> &'static str { + self.message + } + + /// JLReq address when this diagnostic originated in the core composer. + #[must_use] + pub const fn jlreq(&self) -> Option<&'static str> { + self.jlreq + } +} + +/// One physical line and its glyphs in visual draw order. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct TextLine { + pub(crate) range: Range, + pub(crate) origin: Point, + pub(crate) inline_extent: i32, + pub(crate) block_extent: i32, + pub(crate) writing_mode: WritingMode, + pub(crate) glyphs: Vec, +} + +impl TextLine { + /// Original document bytes assigned to this line. + #[must_use] + pub fn range(&self) -> Range { + self.range.clone() + } + + /// Physical line origin. + #[must_use] + pub const fn origin(&self) -> Point { + self.origin + } + + /// Occupied inline length. + #[must_use] + pub fn inline_extent(&self) -> f32 { + to_f32(self.inline_extent) + } + + /// Block-axis demand. + #[must_use] + pub fn block_extent(&self) -> f32 { + to_f32(self.block_extent) + } + + /// Glyphs in visual draw order, including automatically shaped annotations. + #[must_use] + pub fn glyphs(&self) -> &[GlyphPlacement] { + &self.glyphs + } + + /// Writing mode used for physical conversion. + #[must_use] + pub const fn writing_mode(&self) -> WritingMode { + self.writing_mode + } +} + +/// Which logical side of a UTF-8 position was selected. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum Affinity { + /// Position before the following cluster in logical order. + Upstream, + /// Position after the preceding cluster in logical order. + Downstream, +} + +/// Result of mapping a physical point back to source text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct HitTest { + byte_offset: usize, + affinity: Affinity, + inside: bool, +} + +impl HitTest { + /// UTF-8 byte boundary nearest the point. + #[must_use] + pub const fn byte_offset(self) -> usize { + self.byte_offset + } + + /// Logical edge selected at that boundary. + #[must_use] + pub const fn affinity(self) -> Affinity { + self.affinity + } + + /// Whether the point fell inside a glyph cell rather than merely nearest to it. + #[must_use] + pub const fn is_inside(self) -> bool { + self.inside + } +} + +/// Complete renderer-independent layout that owns every font resource it references. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct TextLayout { + pub(crate) source: String, + pub(crate) lines: Vec, + pub(crate) fonts: Vec, + pub(crate) diagnostics: Vec, + pub(crate) writing_mode: WritingMode, +} + +impl TextLayout { + /// Original unnormalized UTF-8 text. + #[must_use] + pub fn source(&self) -> &str { + &self.source + } + + /// Lines in paragraph order. + #[must_use] + pub fn lines(&self) -> &[TextLine] { + &self.lines + } + + /// Font resources sufficient to draw every returned glyph. + #[must_use] + pub fn fonts(&self) -> &[FontResource] { + &self.fonts + } + + /// Recoverable issues in source order. + #[must_use] + pub fn diagnostics(&self) -> &[Diagnostic] { + &self.diagnostics + } + + /// Iterate every glyph in global visual draw order. + pub fn glyphs(&self) -> impl Iterator { + self.lines.iter().flat_map(|line| line.glyphs.iter()) + } + + /// Map a physical point to the nearest logical UTF-8 boundary. + #[must_use] + pub fn hit_test(&self, point: Point) -> HitTest { + let mut best: Option<(&GlyphPlacement, i64, bool)> = None; + for glyph in self.glyphs().filter(|glyph| glyph.annotation.is_none()) { + let bounds = glyph.bounds(); + let inside = bounds.contains(point); + let dx = axis_distance(point.x, bounds.x, bounds.width); + let dy = axis_distance(point.y, bounds.y, bounds.height); + let distance = dx.saturating_mul(dx).saturating_add(dy.saturating_mul(dy)); + if best.is_none_or(|(_, old, old_inside)| better_hit(inside, distance, old_inside, old)) + { + best = Some((glyph, distance, inside)); + } + } + let Some((glyph, _, inside)) = best else { + return HitTest { + byte_offset: 0, + affinity: Affinity::Downstream, + inside: false, + }; + }; + let bounds = glyph.bounds(); + let after_visual_midpoint = is_after_visual_midpoint(point, bounds, glyph.writing_mode); + let logical_after = is_logically_after(after_visual_midpoint, glyph.bidi_level); + HitTest { + byte_offset: if logical_after { + glyph.source_range.end + } else { + glyph.source_range.start + }, + affinity: if logical_after { + Affinity::Downstream + } else { + Affinity::Upstream + }, + inside, + } + } + + /// Convenience wrapper around [`Self::hit_test`]. + pub fn hit_test_xy(&self, x: f32, y: f32) -> Result { + Ok(self.hit_test(Point::try_new(x, y)?)) + } + + /// Return a one-subpixel caret at the closest rendered edge of a UTF-8 boundary. + #[must_use] + pub fn caret_rect(&self, byte_offset: usize) -> Option { + if !is_valid_caret_offset(&self.source, byte_offset) { + return None; + } + for line in &self.lines { + let mut candidates = line + .glyphs + .iter() + .filter(|glyph| glyph.annotation.is_none()) + .filter(|glyph| glyph_touches_boundary(&glyph.source_range, byte_offset)); + if let Some(glyph) = candidates.next() { + let bounds = glyph.bounds(); + let at_start = glyph.source_range.start == byte_offset; + let visual_end = is_visual_end(at_start, glyph.bidi_level); + return Some(match glyph.writing_mode { + WritingMode::HorizontalTb => Rect::from_fixed( + if visual_end { + bounds.x.saturating_add(bounds.width) + } else { + bounds.x + }, + bounds.y, + 1, + bounds.height, + ), + WritingMode::VerticalRl => Rect::from_fixed( + bounds.x, + if visual_end { + bounds.y.saturating_add(bounds.height) + } else { + bounds.y + }, + bounds.width, + 1, + ), + }); + } + if line.range.start == byte_offset && line.glyphs.is_empty() { + return Some(match line.writing_mode { + WritingMode::HorizontalTb => Rect::from_fixed( + line.origin.x, + line.origin.y.saturating_sub(line.block_extent), + 1, + line.block_extent, + ), + WritingMode::VerticalRl => Rect::from_fixed( + line.origin.x.saturating_sub(line.block_extent), + line.origin.y, + line.block_extent, + 1, + ), + }); + } + } + None + } + + /// Return one merged selection rectangle per touched line. + #[must_use] + pub fn selection_rects(&self, range: Range) -> Vec { + if !is_valid_selection_range(&self.source, &range) { + return Vec::new(); + } + let mut result = Vec::new(); + for line in &self.lines { + let mut boxes = line + .glyphs + .iter() + .filter(|glyph| glyph.annotation.is_none()) + .filter(|glyph| ranges_overlap(&glyph.source_range, &range)) + .map(GlyphPlacement::bounds); + let Some(first) = boxes.next() else { + continue; + }; + let mut min_x = first.x; + let mut min_y = first.y; + let mut max_x = first.x.saturating_add(first.width); + let mut max_y = first.y.saturating_add(first.height); + for rect in boxes { + min_x = min_x.min(rect.x); + min_y = min_y.min(rect.y); + max_x = max_x.max(rect.x.saturating_add(rect.width)); + max_y = max_y.max(rect.y.saturating_add(rect.height)); + } + result.push(Rect::from_fixed( + min_x, + min_y, + max_x.saturating_sub(min_x), + max_y.saturating_sub(min_y), + )); + } + result + } +} + +fn axis_distance(value: i32, start: i32, extent: i32) -> i64 { + let end = start.saturating_add(extent); + let before = start.saturating_sub(value).max(0); + let after = value.saturating_sub(end).max(0); + i64::from(before.max(after)) +} + +fn better_hit(inside: bool, distance: i64, old_inside: bool, old_distance: i64) -> bool { + (inside && !old_inside) || (inside == old_inside && distance < old_distance) +} + +fn is_after_visual_midpoint(point: Point, bounds: Rect, mode: WritingMode) -> bool { + match mode { + WritingMode::HorizontalTb => point.x >= bounds.x.saturating_add(bounds.width / 2), + WritingMode::VerticalRl => point.y >= bounds.y.saturating_add(bounds.height / 2), + } +} + +fn is_logically_after(after_visual_midpoint: bool, bidi_level: u8) -> bool { + after_visual_midpoint ^ (bidi_level % 2 == 1) +} + +fn is_valid_caret_offset(source: &str, offset: usize) -> bool { + offset <= source.len() && source.is_char_boundary(offset) +} + +fn glyph_touches_boundary(range: &Range, offset: usize) -> bool { + range.start == offset || range.end == offset +} + +fn is_visual_end(at_start: bool, bidi_level: u8) -> bool { + at_start == (bidi_level % 2 == 1) +} + +fn is_valid_selection_range(source: &str, range: &Range) -> bool { + if range.start >= range.end { + return false; + } + if range.end > source.len() { + return false; + } + source.is_char_boundary(range.start) && source.is_char_boundary(range.end) +} + +fn ranges_overlap(left: &Range, right: &Range) -> bool { + left.start < right.end && right.start < left.end +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Arc; + + fn assert_float(actual: f32, expected: f32) { + assert_eq!(actual.to_bits(), expected.to_bits()); + } + + fn font_id() -> FontId { + let mut fonts = crate::FontLibrary::new(); + fonts + .register_font(Arc::<[u8]>::from(font_test_data::NOTO_SANS_JP_CFF)) + .unwrap() + } + + fn glyph(mode: WritingMode) -> GlyphPlacement { + GlyphPlacement { + font_id: font_id(), + glyph_id: 77, + source_range: 2..5, + annotation: Some(AnnotationSource::new(3, 7..11)), + x: 128, + y: 320, + advance_x: 256, + advance_y: -384, + offset_x: 96, + offset_y: -128, + font_size: 192, + transform: GlyphTransform::RotateClockwise, + bidi_level: 2, + writing_mode: mode, + } + } + + #[test] + fn rectangle_accessors_preserve_all_signed_fixed_components() { + let rect = Rect::from_fixed(128, -192, 256, 320); + assert_float(rect.x(), 2.0); + assert_float(rect.y(), -3.0); + assert_float(rect.width(), 4.0); + assert_float(rect.height(), 5.0); + assert_eq!(rect.as_26_6(), (128, -192, 256, 320)); + assert!(rect.contains(Point::from_fixed(128, -192))); + assert!(rect.contains(Point::from_fixed(384, 128))); + assert!(!rect.contains(Point::from_fixed(127, -192))); + assert!(!rect.contains(Point::from_fixed(128, 129))); + } + + #[test] + fn hit_distance_priority_and_both_writing_mode_midpoints_are_exact() { + assert_eq!(axis_distance(5, 10, 4), 5); + assert_eq!(axis_distance(10, 10, 4), 0); + assert_eq!(axis_distance(12, 10, 4), 0); + assert_eq!(axis_distance(14, 10, 4), 0); + assert_eq!(axis_distance(15, 10, 4), 1); + + assert!(better_hit(true, 100, false, 1)); + assert!(!better_hit(false, 1, true, 100)); + assert!(better_hit(false, 4, false, 5)); + assert!(!better_hit(false, 5, false, 5)); + assert!(!better_hit(false, 6, false, 5)); + assert!(better_hit(true, 4, true, 5)); + + let bounds = Rect::from_fixed(100, 200, 80, 120); + assert!(!is_after_visual_midpoint( + Point::from_fixed(139, 259), + bounds, + WritingMode::HorizontalTb + )); + assert!(is_after_visual_midpoint( + Point::from_fixed(140, 259), + bounds, + WritingMode::HorizontalTb + )); + assert!(!is_after_visual_midpoint( + Point::from_fixed(139, 259), + bounds, + WritingMode::VerticalRl + )); + assert!(is_after_visual_midpoint( + Point::from_fixed(139, 260), + bounds, + WritingMode::VerticalRl + )); + + assert!(!is_logically_after(false, 0)); + assert!(is_logically_after(true, 0)); + assert!(is_logically_after(false, 1)); + assert!(!is_logically_after(true, 1)); + assert!(!is_logically_after(false, 2)); + assert!(is_logically_after(false, 3)); + } + + #[test] + fn caret_and_selection_predicates_preserve_utf8_and_half_open_edges() { + let source = "éAB"; + assert!(is_valid_caret_offset(source, 0)); + assert!(!is_valid_caret_offset(source, 1)); + assert!(is_valid_caret_offset(source, 2)); + assert!(is_valid_caret_offset(source, source.len())); + assert!(!is_valid_caret_offset(source, source.len() + 1)); + assert!(glyph_touches_boundary(&(2..5), 2)); + assert!(glyph_touches_boundary(&(2..5), 5)); + assert!(!glyph_touches_boundary(&(2..5), 3)); + assert!(!is_visual_end(true, 0)); + assert!(is_visual_end(false, 0)); + assert!(is_visual_end(true, 1)); + assert!(!is_visual_end(false, 1)); + + assert!(is_valid_selection_range(source, &(0..2))); + assert!(!is_valid_selection_range(source, &(2..2))); + assert!(!is_valid_selection_range(source, &(0..source.len() + 1))); + assert!(!is_valid_selection_range(source, &(1..2))); + assert!(!is_valid_selection_range(source, &(0..1))); + assert!(ranges_overlap(&(2..5), &(1..3))); + assert!(!ranges_overlap(&(2..5), &(0..2))); + assert!(!ranges_overlap(&(2..5), &(5..6))); + } + + #[test] + fn glyph_and_annotation_accessors_preserve_draw_ready_geometry() { + let horizontal = glyph(WritingMode::HorizontalTb); + assert_eq!(horizontal.font_id().get(), 0); + assert_eq!(horizontal.glyph_id(), 77); + assert_eq!(horizontal.source_range(), 2..5); + assert_eq!(horizontal.range(), 2..5); + let annotation = horizontal.annotation().unwrap(); + assert_eq!(annotation.construct(), 3); + assert_eq!(annotation.range(), 7..11); + assert_eq!(horizontal.origin().x_26_6(), 128); + assert_eq!(horizontal.origin().y_26_6(), 320); + assert_float(horizontal.x(), 2.0); + assert_float(horizontal.y(), 5.0); + assert_float(horizontal.advance_x(), 4.0); + assert_float(horizontal.advance_y(), -6.0); + assert_float(horizontal.offset_x(), 1.5); + assert_float(horizontal.offset_y(), -2.0); + assert_eq!(horizontal.geometry_26_6(), (128, 320, 256, -384, 96, -128)); + assert_eq!(horizontal.transform(), GlyphTransform::RotateClockwise); + assert_eq!(horizontal.bidi_level(), 2); + assert_eq!(horizontal.bounds().as_26_6(), (128, 128, 256, 192)); + + let vertical = glyph(WritingMode::VerticalRl); + assert_eq!(vertical.bounds().as_26_6(), (-64, 320, 192, 384)); + } + + #[test] + fn diagnostic_line_hit_caret_and_selection_values_are_exact() { + let diagnostic = Diagnostic { + code: "test.code", + severity: DiagnosticSeverity::Error, + range: Some(2..5), + message: "test message", + jlreq: Some("3.1.2"), + }; + assert_eq!(diagnostic.code(), "test.code"); + assert_eq!(diagnostic.severity(), DiagnosticSeverity::Error); + assert_eq!(diagnostic.range(), Some(2..5)); + assert_eq!(diagnostic.message(), "test message"); + assert_eq!(diagnostic.jlreq(), Some("3.1.2")); + + let mut rendered = glyph(WritingMode::HorizontalTb); + rendered.annotation = None; + let line = TextLine { + range: 2..5, + origin: Point::from_fixed(32, 64), + inline_extent: 448, + block_extent: 192, + writing_mode: WritingMode::HorizontalTb, + glyphs: vec![rendered], + }; + assert_eq!(line.range(), 2..5); + assert_eq!(line.origin().x_26_6(), 32); + assert_eq!(line.origin().y_26_6(), 64); + assert_float(line.inline_extent(), 7.0); + assert_float(line.block_extent(), 3.0); + assert_eq!(line.writing_mode(), WritingMode::HorizontalTb); + assert_eq!(line.glyphs().len(), 1); + + let layout = TextLayout { + source: "abXYZq".to_owned(), + lines: vec![line], + fonts: Vec::new(), + diagnostics: vec![diagnostic], + writing_mode: WritingMode::HorizontalTb, + }; + let before = layout.hit_test(Point::from_fixed(160, 200)); + assert_eq!(before.byte_offset(), 2); + assert_eq!(before.affinity(), Affinity::Upstream); + assert!(before.is_inside()); + let after = layout.hit_test(Point::from_fixed(352, 200)); + assert_eq!(after.byte_offset(), 5); + assert_eq!(after.affinity(), Affinity::Downstream); + assert!(after.is_inside()); + assert_eq!(layout.caret_rect(2).unwrap().as_26_6(), (128, 128, 1, 192)); + assert_eq!(layout.caret_rect(5).unwrap().as_26_6(), (384, 128, 1, 192)); + assert_eq!( + layout.selection_rects(2..5)[0].as_26_6(), + (128, 128, 256, 192) + ); + assert!(layout.selection_rects(0..2).is_empty()); + assert!(layout.selection_rects(5..6).is_empty()); + + let mut rtl = layout.clone(); + rtl.lines[0].glyphs[0].bidi_level = 1; + assert_eq!(rtl.caret_rect(2).unwrap().as_26_6(), (384, 128, 1, 192)); + assert_eq!(rtl.caret_rect(5).unwrap().as_26_6(), (128, 128, 1, 192)); + + let empty_horizontal = TextLayout { + source: "A".into(), + lines: vec![TextLine { + range: 0..0, + origin: Point::from_fixed(320, 640), + inline_extent: 0, + block_extent: 192, + writing_mode: WritingMode::HorizontalTb, + glyphs: Vec::new(), + }], + fonts: Vec::new(), + diagnostics: Vec::new(), + writing_mode: WritingMode::HorizontalTb, + }; + assert_eq!( + empty_horizontal.caret_rect(0).unwrap().as_26_6(), + (320, 448, 1, 192) + ); + assert!(empty_horizontal.caret_rect(1).is_none()); + let mut empty_vertical = empty_horizontal; + empty_vertical.lines[0].writing_mode = WritingMode::VerticalRl; + assert_eq!( + empty_vertical.caret_rect(0).unwrap().as_26_6(), + (128, 640, 192, 1) + ); + } +} diff --git a/crates/jlreq/src/units.rs b/crates/jlreq/src/units.rs new file mode 100644 index 0000000..c9ebd5d --- /dev/null +++ b/crates/jlreq/src/units.rs @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{LayoutError, OptionKind}; + +pub(crate) const SUBPIXELS_PER_UNIT: f32 = 64.0; +// Kept below the first f32 integer precision cliff after 26.6 scaling, and well below the +// i32 geometry overflow boundary used by the composer. +const MAX_ABS_UNITS: f32 = 8_388_607.0; + +pub(crate) fn positive(value: f32, option: OptionKind) -> Result { + if !value.is_finite() || value <= 0.0 || value > MAX_ABS_UNITS { + return Err(LayoutError::invalid_option( + option, + "value must be finite, positive, and representable in 26.6 fixed point", + )); + } + Ok(quantize(value)) +} + +pub(crate) fn non_negative(value: f32, option: OptionKind) -> Result { + if !value.is_finite() || value < 0.0 || value > MAX_ABS_UNITS { + return Err(LayoutError::invalid_option( + option, + "value must be finite, non-negative, and representable in 26.6 fixed point", + )); + } + Ok(quantize(value)) +} + +pub(crate) fn finite(value: f32, option: OptionKind) -> Result { + if !value.is_finite() || value.abs() > MAX_ABS_UNITS { + return Err(LayoutError::invalid_option( + option, + "value must be finite and representable", + )); + } + Ok(to_f32(quantize(value))) +} + +pub(crate) fn quantize(value: f32) -> i32 { + rounded_f32_to_i32((value * SUBPIXELS_PER_UNIT).round()) +} + +pub(crate) fn to_f32(value: i32) -> f32 { + // Split at a power of two so both integer-to-float conversions use lossless `From` + // implementations and the final f32 rounding is platform-independent. + let high = i16::try_from(value.div_euclid(65_536)).unwrap_or_else(|_| { + if value.is_negative() { + i16::MIN + } else { + i16::MAX + } + }); + let low = u16::try_from(value.rem_euclid(65_536)).unwrap_or_default(); + f32::from(high).mul_add(65_536.0, f32::from(low)) / SUBPIXELS_PER_UNIT +} + +fn rounded_f32_to_i32(value: f32) -> i32 { + if value == 0.0 { + return 0; + } + let bits = value.to_bits(); + let negative = bits & 0x8000_0000 != 0; + let exponent = (bits >> 23) & 0xff; + if exponent < 127 { + return 0; + } + // The stored mantissa and the implicit leading bit are disjoint. Express the + // reconstruction without a bitwise `or`, whose `xor` mutation is equivalent + // for this mask and therefore cannot be distinguished by any input. + let significand = 0x0080_0000_u32.saturating_add(bits & 0x007f_ffff); + let magnitude = if exponent >= 150 { + significand + .checked_shl(exponent.saturating_sub(150)) + .unwrap_or(u32::MAX) + } else { + significand >> 150_u32.saturating_sub(exponent) + }; + let signed = i32::try_from(magnitude).unwrap_or(i32::MAX); + if negative { + signed.saturating_neg() + } else { + signed + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::OptionKind; + + #[test] + fn fixed_point_conversion_rounds_to_the_nearest_subpixel() { + assert_eq!(quantize(1.0), 64); + assert_eq!(quantize(-1.0), -64); + assert_eq!(quantize(1.007_812_5), 65); + assert_eq!(quantize(to_f32(65)), 65); + assert_eq!(quantize(to_f32(-65)), -65); + assert_eq!(rounded_f32_to_i32(1.0), 1); + assert_eq!(rounded_f32_to_i32(-1.0), -1); + } + + #[test] + fn validated_numeric_domains_include_their_exact_finite_ceiling() { + assert!(positive(MAX_ABS_UNITS, OptionKind::FontSize).is_ok()); + assert!(positive(MAX_ABS_UNITS + 1.0, OptionKind::FontSize).is_err()); + assert!(positive(0.0, OptionKind::FontSize).is_err()); + assert!(positive(f32::NAN, OptionKind::FontSize).is_err()); + + assert!(non_negative(MAX_ABS_UNITS, OptionKind::LineGap).is_ok()); + assert!(non_negative(MAX_ABS_UNITS + 1.0, OptionKind::LineGap).is_err()); + assert!(non_negative(0.0, OptionKind::LineGap).is_ok()); + assert!(non_negative(-1.0, OptionKind::LineGap).is_err()); + + assert!(finite(MAX_ABS_UNITS, OptionKind::Variation).is_ok()); + assert!(finite(-MAX_ABS_UNITS, OptionKind::Variation).is_ok()); + assert!(finite(MAX_ABS_UNITS + 1.0, OptionKind::Variation).is_err()); + assert!(finite(-(MAX_ABS_UNITS + 1.0), OptionKind::Variation).is_err()); + assert!(finite(f32::INFINITY, OptionKind::Variation).is_err()); + } +} diff --git a/crates/jlreq/tests/high_level.rs b/crates/jlreq/tests/high_level.rs new file mode 100644 index 0000000..36a6c47 --- /dev/null +++ b/crates/jlreq/tests/high_level.rs @@ -0,0 +1,821 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! End-to-end acceptance tests for the draw-ready high-level API. + +use std::error::Error; +use std::ops::Range; +use std::sync::Arc; + +use jlreq::{ + Affinity, Alignment, BaseDirection, DiagnosticSeverity, DocumentBuilder, FontLibrary, + FontSlant, FontStyle, FontVariation, GlyphTransform, LayoutEngine, LayoutError, LayoutOptions, + OpenTypeFeature, OpenTypeTag, Point, Resource, ResourceLimits, RubyKind, RubyRun, + ScriptPosition, SpanStyle, TextRole, WritingMode, +}; + +fn bytes(data: &'static [u8]) -> Arc<[u8]> { + Arc::from(data) +} + +fn fixture_fonts() -> Result<(FontLibrary, jlreq::FontId, jlreq::FontId), LayoutError> { + let mut fonts = FontLibrary::new(); + fonts.register_face( + bytes(font_test_data::NOTO_SANS_JP_CFF), + 0, + "Noto Sans JP", + FontStyle::default(), + )?; + let arabic = fonts.register_face( + bytes(rwml_fonts::noto_sans_arabic_subset()), + 0, + "Vazirmatn", + FontStyle::default(), + )?; + let emoji = fonts.register_face( + bytes(font_test_data::NOTO_COLOR_EMOJI_FLAGS), + 0, + "Noto Color Emoji", + FontStyle::default(), + )?; + fonts.register_face( + bytes(font_test_data::TINOS_SUBSET), + 0, + "Tinos", + FontStyle::default(), + )?; + Ok((fonts, arabic, emoji)) +} + +fn range_of(text: &str, needle: &str) -> Result, Box> { + let start = text + .find(needle) + .ok_or_else(|| std::io::Error::other(format!("{needle:?} is absent")))?; + Ok(start..start.saturating_add(needle.len())) +} + +fn expected_layout_error(result: Result) -> Result> { + match result { + Err(error) => Ok(error), + Ok(_) => Err(Box::new(std::io::Error::other( + "operation unexpectedly succeeded", + ))), + } +} + +#[test] +fn plain_horizontal_and_vertical_text_are_draw_ready() -> Result<(), Box> { + let (fonts, _, _) = fixture_fonts()?; + let text = "日本語A12"; + let horizontal = jlreq::layout(text, &fonts, LayoutOptions::try_new(240.0, 16.0)?)?; + assert!(!horizontal.lines().is_empty()); + assert!(!horizontal.fonts().is_empty()); + assert!(horizontal.glyphs().all(|glyph| { + let range = glyph.source_range(); + range.start <= range.end && range.end <= text.len() + })); + + let vertical = jlreq::layout( + text, + &fonts, + LayoutOptions::try_new(240.0, 16.0)?.writing_mode(WritingMode::VerticalRl), + )?; + assert!( + vertical + .glyphs() + .any(|glyph| glyph.transform() == GlyphTransform::RotateClockwise), + "Latin text is rotated in vertical composition" + ); + assert!(vertical.lines().iter().all(|line| { + line.writing_mode() == WritingMode::VerticalRl && line.origin().x_26_6() <= 0 + })); + Ok(()) +} + +#[test] +fn bidi_fallback_and_missing_glyphs_preserve_source_ranges() -> Result<(), Box> { + let (fonts, arabic, emoji) = fixture_fonts()?; + let text = "日本語 abc مرحبا 🇪🇨"; + let layout = jlreq::layout( + text, + &fonts, + LayoutOptions::try_new(640.0, 18.0)?.base_direction(BaseDirection::Auto), + )?; + assert!(layout.glyphs().any(|glyph| glyph.bidi_level() % 2 == 1)); + assert!(layout.glyphs().any(|glyph| glyph.font_id() == arabic)); + assert!(layout.glyphs().any(|glyph| glyph.font_id() == emoji)); + assert!( + layout + .glyphs() + .all(|glyph| glyph.source_range().end <= text.len()) + ); + + let missing_text = "\u{10ffff}"; + let missing = jlreq::layout(missing_text, &fonts, LayoutOptions::try_new(80.0, 16.0)?)?; + assert!( + missing + .diagnostics() + .iter() + .any(|diagnostic| diagnostic.code() == "font.missing-glyph") + ); + assert!(missing.glyphs().all(|glyph| glyph.source_range() == (0..4))); + Ok(()) +} + +#[test] +fn paragraph_breaks_tabs_and_empty_input_are_defined() -> Result<(), Box> { + let (fonts, _, _) = fixture_fonts()?; + let text = "A\r\nB\u{2028}C\tD\n"; + let layout = jlreq::layout(text, &fonts, LayoutOptions::try_new(160.0, 16.0)?)?; + assert!(layout.lines().len() >= 4); + assert!(layout.glyphs().all(|glyph| { + let piece = &text[glyph.source_range()]; + piece != "\r" && piece != "\n" && piece != "\u{2028}" + })); + + let empty = jlreq::layout("", &fonts, LayoutOptions::try_new(160.0, 16.0)?)?; + assert!(empty.lines().is_empty()); + assert_eq!(empty.glyphs().count(), 0); + Ok(()) +} + +#[test] +fn ttc_variations_and_features_reach_the_shaper() -> Result<(), Box> { + let mut collection = FontLibrary::new(); + collection.register_face( + bytes(font_test_data::ttc::TTC), + 0, + "Fixture TTC", + FontStyle::default(), + )?; + assert!( + collection + .register_face( + bytes(font_test_data::ttc::TTC), + u32::MAX, + "bad face", + FontStyle::default(), + ) + .is_err() + ); + + let mut variable = FontLibrary::new(); + variable.register_face( + bytes(font_test_data::VAZIRMATN_VAR), + 0, + "Vazirmatn", + FontStyle::default(), + )?; + let weight = FontVariation::try_new(OpenTypeTag::try_new("wght")?, 700.0)?; + let liga = OpenTypeFeature::new(OpenTypeTag::try_new("liga")?, 1); + let options = LayoutOptions::try_new(320.0, 18.0)? + .variation(weight) + .feature(liga); + let layout = jlreq::layout("A", &variable, options)?; + assert!(layout.glyphs().all(|glyph| glyph.glyph_id() != 0)); + Ok(()) +} + +#[test] +fn all_typed_constructs_and_ruby_modes_are_automatically_shaped() -> Result<(), Box> { + let (fonts, _, _) = fixture_fonts()?; + let text = "漢12強注割振字合上式"; + let mut builder = DocumentBuilder::new(text); + builder.group_ruby(range_of(text, "漢")?, "かん")?; + builder.tate_chu_yoko(range_of(text, "12")?)?; + builder.emphasis_dots(range_of(text, "強")?, '・')?; + builder.warichu(range_of(text, "注")?)?; + let furawake = range_of(text, "割振")?; + let furawake_split = furawake.start.saturating_add("割".len()); + builder.furawake(furawake, 2, 0.0)?; + builder.mandatory_break(furawake_split)?; + builder.jidori(range_of(text, "字")?, 2)?; + builder.reference_mark(range_of(text, "合")?, "*")?; + builder.script(range_of(text, "上")?, "2", ScriptPosition::Superscript)?; + builder.formula(range_of(text, "式")?)?; + let document = builder.build()?; + assert_eq!(document.construct_count(), 9); + let layout = jlreq::layout_document( + &document, + &fonts, + LayoutOptions::try_new(800.0, 20.0)?.writing_mode(WritingMode::VerticalRl), + )?; + assert!(layout.glyphs().any(|glyph| glyph.annotation().is_some())); + assert!( + layout + .glyphs() + .any(|glyph| glyph.transform() == GlyphTransform::TateChuYoko) + ); + + let mut mono = DocumentBuilder::new("漢"); + mono.mono_ruby(0..3, "字")?; + let mono = mono.build()?; + let mono_layout = jlreq::layout_document(&mono, &fonts, LayoutOptions::try_new(120.0, 16.0)?)?; + assert!( + mono_layout + .glyphs() + .any(|glyph| glyph.annotation().is_some()) + ); + + let mut jukugo = DocumentBuilder::new("漢"); + jukugo.jukugo_ruby(0..3, "字")?; + let jukugo = jukugo.build()?; + let jukugo_layout = + jlreq::layout_document(&jukugo, &fonts, LayoutOptions::try_new(120.0, 16.0)?)?; + assert!( + jukugo_layout + .glyphs() + .any(|glyph| glyph.annotation().is_some()) + ); + Ok(()) +} + +#[test] +fn limits_fail_atomically_and_engine_remains_reusable() -> Result<(), Box> { + assert!(FontLibrary::new().register_font([0_u8; 8]).is_err()); + assert!(LayoutOptions::try_new(f32::NAN, 16.0).is_err()); + assert!(LayoutOptions::try_new(100.0, f32::INFINITY).is_err()); + + let (fonts, _, _) = fixture_fonts()?; + let limited = LayoutOptions::try_new(200.0, 16.0)?.limits( + ResourceLimits::default() + .with_max_glyphs(1) + .with_max_core_operations(64), + ); + let mut engine = LayoutEngine::new(); + let failure = engine.layout("ABC", &fonts, limited); + assert!(matches!( + failure, + Err(LayoutError::ResourceLimit { + resource: Resource::Glyphs, + .. + }) + )); + + let recovered = engine.layout("A", &fonts, LayoutOptions::try_new(200.0, 16.0)?)?; + assert!(!recovered.lines().is_empty()); + let one_shot = jlreq::layout("A", &fonts, LayoutOptions::try_new(200.0, 16.0)?)?; + assert_eq!(recovered, one_shot); + Ok(()) +} + +#[test] +fn hit_caret_and_selection_round_trip_in_both_writing_modes() -> Result<(), Box> { + let (fonts, _, _) = fixture_fonts()?; + for mode in [WritingMode::HorizontalTb, WritingMode::VerticalRl] { + let layout = jlreq::layout( + "A日本語مرحبا", + &fonts, + LayoutOptions::try_new(420.0, 18.0)?.writing_mode(mode), + )?; + let first = layout + .glyphs() + .next() + .ok_or_else(|| std::io::Error::other("fixture produced no glyph"))?; + let hit = layout.hit_test(first.origin()); + assert!(hit.byte_offset() <= layout.source().len()); + assert!(layout.caret_rect(hit.byte_offset()).is_some()); + assert!(!layout.selection_rects(0..layout.source().len()).is_empty()); + } + Ok(()) +} + +#[test] +fn explicit_font_bytes_are_bit_identical_across_libraries() -> Result<(), Box> { + let mut first_fonts = FontLibrary::new(); + first_fonts.add_font(bytes(font_test_data::NOTO_SANS_JP_CFF))?; + let mut second_fonts = FontLibrary::new(); + second_fonts.add_font(bytes(font_test_data::NOTO_SANS_JP_CFF))?; + let first = jlreq::layout( + "同一フォント", + &first_fonts, + LayoutOptions::try_new(300.0, 16.0)?, + )?; + let second = jlreq::layout( + "同一フォント", + &second_fonts, + LayoutOptions::try_new(300.0, 16.0)?, + )?; + assert_eq!(first, second); + Ok(()) +} + +#[test] +fn public_configuration_and_font_metadata_are_observable() -> Result<(), Box> { + let mut fonts = FontLibrary::new(); + assert!(fonts.is_empty()); + assert_eq!(fonts.len(), 0); + assert!(fonts.primary().is_none()); + let style = FontStyle::new(550, 90, FontSlant::Italic); + assert_eq!( + (style.weight(), style.width(), style.slant()), + (550, 90, FontSlant::Italic) + ); + let primary = fonts.add_face(bytes(font_test_data::NOTO_SANS_JP_CFF), 0, "Primary", style)?; + let secondary = fonts.add_face( + bytes(font_test_data::TINOS_SUBSET), + 0, + "Secondary", + FontStyle::new(400, 100, FontSlant::Oblique), + )?; + assert!(!fonts.is_empty()); + assert_eq!(fonts.len(), 2); + assert_eq!(fonts.primary(), Some(primary)); + fonts.set_primary(secondary)?; + fonts.set_fallback_order([secondary, secondary, primary])?; + let resource = fonts + .get(primary) + .ok_or_else(|| std::io::Error::other("registered font disappeared"))?; + assert_eq!(resource.id(), primary); + assert_eq!(resource.face_index(), 0); + assert_eq!(resource.family(), "Primary"); + assert_eq!(resource.style(), style); + assert_eq!(resource.bytes(), font_test_data::NOTO_SANS_JP_CFF); + assert!(!format!("{resource:?} {fonts:?}").is_empty()); + + let mut foreign = FontLibrary::new(); + foreign.add_font(bytes(font_test_data::TINOS_SUBSET))?; + foreign.add_font(bytes(font_test_data::TINOS_SUBSET))?; + let unknown = foreign.add_font(bytes(font_test_data::TINOS_SUBSET))?; + assert!(fonts.get(unknown).is_none()); + assert_eq!( + expected_layout_error(fonts.set_primary(unknown))?.code(), + "font.unknown-id" + ); + assert_eq!( + expected_layout_error(fonts.set_fallback_order([unknown]))?.code(), + "font.unknown-id" + ); + + #[cfg(feature = "system-fonts")] + assert_eq!( + expected_layout_error(fonts.register_system_family( + "jlreq-family-that-cannot-exist-7f721b83", + FontStyle::default(), + ))? + .code(), + "font.system-family-not-found" + ); + Ok(()) +} + +#[test] +fn all_option_values_validate_quantize_and_reach_layout() -> Result<(), Box> { + let tag = OpenTypeTag::try_new("kern")?; + assert_eq!(tag.bytes(), *b"kern"); + assert!(OpenTypeTag::try_new("bad").is_err()); + assert!(OpenTypeTag::try_new("a\ncd").is_err()); + let feature = OpenTypeFeature::new(tag, 7); + assert_eq!((feature.tag(), feature.value()), (tag, 7)); + let variation = FontVariation::try_new(OpenTypeTag::try_new("wght")?, 650.125)?; + assert_eq!(variation.tag().bytes(), *b"wght"); + assert!((variation.value() - 650.125).abs() < f32::EPSILON); + assert!(FontVariation::try_new(tag, f32::NAN).is_err()); + + let limits = ResourceLimits::default() + .with_max_input_bytes(11) + .with_max_fonts(12) + .with_max_font_bytes(13) + .with_max_paragraphs(14) + .with_max_runs(15) + .with_max_glyphs(16) + .with_max_constructs(17) + .with_max_core_operations(18); + assert_eq!( + ( + limits.max_input_bytes(), + limits.max_fonts(), + limits.max_font_bytes(), + limits.max_paragraphs(), + limits.max_runs(), + limits.max_glyphs(), + limits.max_constructs(), + limits.max_core_operations(), + ), + (11, 12, 13, 14, 15, 16, 17, 18) + ); + + let options = LayoutOptions::try_new(100.125, 16.25)? + .alignment(Alignment::Center) + .style(jlreq::core::Style::book_2020()) + .language("ja-JP")? + .base_direction(BaseDirection::LeftToRight) + .line_gap(2.5)? + .tab_width(8)? + .feature(feature) + .variation(variation) + .limits(ResourceLimits::default()); + assert!((options.line_extent() - 100.125).abs() < f32::EPSILON); + assert!((options.font_size() - 16.25).abs() < f32::EPSILON); + assert_eq!(options.writing_mode_value(), WritingMode::HorizontalTb); + assert_eq!(options.resource_limits(), ResourceLimits::default()); + assert!(LayoutOptions::try_new(100.0, 16.0)?.language("").is_err()); + assert!( + LayoutOptions::try_new(100.0, 16.0)? + .language("ja_JP") + .is_err() + ); + assert!(LayoutOptions::try_new(100.0, 16.0)?.line_gap(-1.0).is_err()); + assert!(LayoutOptions::try_new(100.0, 16.0)?.tab_width(0).is_err()); + + let (fonts, _, _) = fixture_fonts()?; + for alignment in [ + Alignment::Start, + Alignment::Center, + Alignment::End, + Alignment::Justify, + ] { + let layout = jlreq::layout("ABC", &fonts, options.clone().alignment(alignment))?; + assert!(!layout.lines().is_empty()); + } + for direction in [BaseDirection::LeftToRight, BaseDirection::RightToLeft] { + let layout = jlreq::layout("A مرحبا", &fonts, options.clone().base_direction(direction))?; + assert!(!layout.lines().is_empty()); + } + Ok(()) +} + +#[test] +fn span_roles_and_document_validation_are_typed() -> Result<(), Box> { + let (fonts, _, _) = fixture_fonts()?; + let text = "ABCDEF"; + let feature = OpenTypeFeature::new(OpenTypeTag::try_new("liga")?, 0); + let variation = FontVariation::try_new(OpenTypeTag::try_new("wght")?, 500.0)?; + let roles = [ + TextRole::Text, + TextRole::GroupedNumeral, + TextRole::UnitSymbol, + TextRole::QuantitySymbol, + TextRole::Formula, + TextRole::WarichuBracket, + ]; + let mut builder = DocumentBuilder::new(text); + for (index, role) in roles.into_iter().enumerate() { + let mut style = SpanStyle::new().role(role); + if index == 0 { + style = style + .family("Noto Sans JP") + .font_style(FontStyle::new(500, 100, FontSlant::Normal)) + .font_size(17.0)? + .language("ja")? + .feature(feature) + .variation(variation); + } + builder.span(index..index.saturating_add(1), style)?; + } + builder.mandatory_break(2)?; + builder.prohibit_break(3)?; + let document = builder.build()?; + assert_eq!(document.text(), text); + let layout = jlreq::layout_document(&document, &fonts, LayoutOptions::try_new(200.0, 16.0)?)?; + assert!(!layout.lines().is_empty()); + assert!(SpanStyle::new().font_size(0.0).is_err()); + assert!(SpanStyle::new().language("ja_JP").is_err()); + + let mut invalid = DocumentBuilder::new("éA"); + assert_eq!( + expected_layout_error(invalid.span(1..2, SpanStyle::default()))?.code(), + "document.invalid-span-range" + ); + invalid.span(0..2, SpanStyle::default())?; + assert_eq!( + expected_layout_error(invalid.span(0..3, SpanStyle::default()))?.code(), + "document.overlapping-spans" + ); + assert_eq!( + expected_layout_error(invalid.mandatory_break(0))?.code(), + "document.invalid-break" + ); + assert_eq!( + expected_layout_error(invalid.prohibit_break(1))?.code(), + "document.invalid-break" + ); + + let mut conflicting = DocumentBuilder::new("AB"); + conflicting.mandatory_break(1)?; + conflicting.prohibit_break(1)?; + assert_eq!( + expected_layout_error(conflicting.build())?.code(), + "document.conflicting-break" + ); + Ok(()) +} + +#[test] +fn ruby_and_construct_validation_preserve_precise_codes() -> Result<(), Box> { + let run = RubyRun::new(0..3, 0..3); + assert_eq!(run.base(), 0..3); + assert_eq!(run.annotation(), 0..3); + let mut valid = DocumentBuilder::new("漢"); + valid.ruby(RubyKind::Jukugo, 0..3, "字", [run])?; + assert_eq!(valid.build()?.construct_count(), 1); + + let mut group = DocumentBuilder::new("AB"); + group.ruby( + RubyKind::Group, + 0..2, + "xy", + [RubyRun::new(0..1, 0..1), RubyRun::new(1..2, 1..2)], + )?; + assert_eq!( + expected_layout_error(group.build())?.code(), + "document.group-ruby-run-count" + ); + let mut invalid_run = DocumentBuilder::new("AB"); + invalid_run.ruby(RubyKind::Jukugo, 0..2, "xy", [RubyRun::new(1..2, 0..1)])?; + assert_eq!( + expected_layout_error(invalid_run.build())?.code(), + "document.invalid-ruby-run" + ); + let mut incomplete = DocumentBuilder::new("AB"); + incomplete.ruby(RubyKind::Jukugo, 0..2, "xy", [RubyRun::new(0..1, 0..1)])?; + assert_eq!( + expected_layout_error(incomplete.build())?.code(), + "document.incomplete-ruby-runs" + ); + + let mut invalid = DocumentBuilder::new("AB"); + assert_eq!( + expected_layout_error(invalid.formula(0..0))?.code(), + "document.invalid-construct-range" + ); + assert_eq!( + expected_layout_error(invalid.group_ruby(0..1, ""))?.code(), + "document.empty-ruby-annotation" + ); + assert_eq!( + expected_layout_error(invalid.reference_mark(0..1, ""))?.code(), + "document.empty-reference-mark" + ); + assert_eq!( + expected_layout_error(invalid.script(0..1, "", ScriptPosition::Subscript))?.code(), + "document.empty-script-annotation" + ); + assert_eq!( + expected_layout_error(invalid.furawake(0..1, 1, 0.0))?.code(), + "document.invalid-furawake-columns" + ); + assert_eq!( + expected_layout_error(invalid.jidori(0..1, 0))?.code(), + "document.invalid-jidori-cells" + ); + Ok(()) +} + +#[test] +fn cross_paragraph_grapheme_and_mono_ruby_fail_atomically() -> Result<(), Box> { + let (fonts, _, _) = fixture_fonts()?; + let mut crossing = DocumentBuilder::new("A\nB"); + crossing.group_ruby(0..3, "all")?; + let crossing = crossing.build()?; + assert_eq!( + expected_layout_error(jlreq::layout_document( + &crossing, + &fonts, + LayoutOptions::try_new(200.0, 16.0)?, + ))? + .code(), + "document.construct-crosses-paragraph" + ); + + let mut split = DocumentBuilder::new("e\u{301}"); + split.span(0..1, SpanStyle::default())?; + let split = split.build()?; + assert_eq!( + expected_layout_error(jlreq::layout_document( + &split, + &fonts, + LayoutOptions::try_new(200.0, 16.0)?, + ))? + .code(), + "document.span-splits-grapheme" + ); + + let mut mono = DocumentBuilder::new("漢字"); + mono.mono_ruby(0..6, "か")?; + let mono = mono.build()?; + assert_eq!( + expected_layout_error(jlreq::layout_document( + &mono, + &fonts, + LayoutOptions::try_new(200.0, 16.0)?, + ))? + .code(), + "document.mono-ruby-cluster-count" + ); + Ok(()) +} + +#[test] +fn every_high_level_resource_limit_has_a_stable_error() -> Result<(), Box> { + let (fonts, _, _) = fixture_fonts()?; + let cases = [ + ( + ResourceLimits::default().with_max_input_bytes(0), + Resource::InputBytes, + "limit.input-bytes", + ), + ( + ResourceLimits::default().with_max_fonts(0), + Resource::Fonts, + "limit.fonts", + ), + ( + ResourceLimits::default().with_max_font_bytes(0), + Resource::FontBytes, + "limit.font-bytes", + ), + ( + ResourceLimits::default().with_max_paragraphs(0), + Resource::Paragraphs, + "limit.paragraphs", + ), + ( + ResourceLimits::default().with_max_runs(0), + Resource::Runs, + "limit.runs", + ), + ( + ResourceLimits::default().with_max_glyphs(0), + Resource::Glyphs, + "limit.glyphs", + ), + ( + ResourceLimits::default().with_max_core_operations(0), + Resource::CoreOperations, + "limit.core-operations", + ), + ]; + for (limits, expected_resource, expected_code) in cases { + let error = expected_layout_error(jlreq::layout( + "A", + &fonts, + LayoutOptions::try_new(100.0, 16.0)?.limits(limits), + ))?; + assert!(matches!( + error, + LayoutError::ResourceLimit { resource, .. } if resource == expected_resource + )); + assert_eq!(error.code(), expected_code); + assert!(!error.to_string().is_empty()); + } + + let mut builder = DocumentBuilder::new("A"); + builder.formula(0..1)?; + let document = builder.build()?; + let error = expected_layout_error(jlreq::layout_document( + &document, + &fonts, + LayoutOptions::try_new(100.0, 16.0)? + .limits(ResourceLimits::default().with_max_constructs(0)), + ))?; + assert!(matches!( + error, + LayoutError::ResourceLimit { + resource: Resource::Constructs, + .. + } + )); + assert_eq!(error.code(), "limit.constructs"); + Ok(()) +} + +#[test] +fn error_codes_ranges_displays_and_sources_are_stable() -> Result<(), Box> { + let invalid_font = expected_layout_error(FontLibrary::new().register_font([0_u8; 8]))?; + let no_fonts = expected_layout_error(jlreq::layout( + "A", + &FontLibrary::new(), + LayoutOptions::try_new(100.0, 16.0)?, + ))?; + let invalid_option = expected_layout_error(LayoutOptions::try_new(-1.0, 16.0))?; + let mut invalid_document = DocumentBuilder::new("A"); + let invalid_document = expected_layout_error(invalid_document.group_ruby(0..1, ""))?; + let core_input = + expected_layout_error(jlreq::core::Size::square(0).map_err(LayoutError::from))?; + + let shaped = jlreq::core::ShapedText::new( + "A", + jlreq::core::Size::square(1_000)?, + jlreq::core::Frame::FullEm, + [jlreq::core::Cluster::new(0..1, 1_000)], + )?; + let paragraph = jlreq::core::Paragraph::builder(shaped, 1_000).build()?; + let mut composer = jlreq::core::Composer::with_limits( + jlreq::core::CompositionLimits::default().with_max_clusters(0), + ); + let core_composition = LayoutError::from( + composer + .compose(¶graph, &jlreq::core::Style::default()) + .err() + .ok_or_else(|| std::io::Error::other("core limit unexpectedly succeeded"))?, + ); + + let errors = [ + invalid_font, + no_fonts, + invalid_option, + invalid_document, + core_input, + core_composition, + ]; + for error in errors { + assert!(!error.code().is_empty()); + let _ = error.range(); + assert!(!error.to_string().is_empty()); + let _ = error.source(); + } + Ok(()) +} + +#[test] +fn renderer_geometry_and_interaction_accessors_are_complete() -> Result<(), Box> { + let (fonts, _, _) = fixture_fonts()?; + let point = Point::try_new(1.25, -2.5)?; + assert_eq!((point.x(), point.y()), (1.25, -2.5)); + assert_eq!(point.x_26_6(), 80); + assert_eq!(point.y_26_6(), -160); + assert!(Point::try_new(9_000_000.0, 0.0).is_err()); + assert!(Point::try_new(0.0, f32::INFINITY).is_err()); + + let mut builder = DocumentBuilder::new("漢 \u{10ffff}"); + builder.group_ruby(0..3, "かん")?; + let document = builder.build()?; + let layout = jlreq::layout_document(&document, &fonts, LayoutOptions::try_new(160.0, 16.0)?)?; + for line in layout.lines() { + let _ = ( + line.range(), + line.origin(), + line.inline_extent(), + line.block_extent(), + line.writing_mode(), + line.glyphs().len(), + ); + } + for glyph in layout.glyphs() { + let _ = ( + glyph.glyph_id(), + glyph.range(), + glyph.x(), + glyph.y(), + glyph.advance_x(), + glyph.advance_y(), + glyph.offset_x(), + glyph.offset_y(), + glyph.geometry_26_6(), + ); + if let Some(annotation) = glyph.annotation() { + let _ = (annotation.construct(), annotation.range()); + } + } + for diagnostic in layout.diagnostics() { + assert_eq!(diagnostic.severity(), DiagnosticSeverity::Warning); + let _ = (diagnostic.range(), diagnostic.message(), diagnostic.jlreq()); + } + + for test_point in [ + Point::try_new(-10_000.0, -10_000.0)?, + Point::try_new(10_000.0, 10_000.0)?, + Point::default(), + ] { + let hit = layout.hit_test(test_point); + assert!(hit.byte_offset() <= layout.source().len()); + assert!(matches!( + hit.affinity(), + Affinity::Upstream | Affinity::Downstream + )); + let _ = hit.is_inside(); + } + let _ = layout.hit_test_xy(0.0, 0.0)?; + assert!(layout.hit_test_xy(f32::NAN, 0.0).is_err()); + assert!( + layout + .caret_rect(layout.source().len().saturating_add(1)) + .is_none() + ); + assert!(layout.caret_rect(1).is_none()); + assert!(layout.selection_rects(0..0).is_empty()); + assert!( + layout + .selection_rects(0..layout.source().len().saturating_add(1)) + .is_empty() + ); + for rect in layout.selection_rects(0..layout.source().len()) { + let _ = ( + rect.x(), + rect.y(), + rect.width(), + rect.height(), + rect.as_26_6(), + ); + } + + let blank_line = jlreq::layout("\n", &fonts, LayoutOptions::try_new(100.0, 16.0)?)?; + assert!(blank_line.caret_rect(0).is_some()); + let empty = jlreq::layout("", &fonts, LayoutOptions::try_new(100.0, 16.0)?)?; + let empty_hit = empty.hit_test(Point::default()); + assert_eq!(empty_hit.byte_offset(), 0); + assert_eq!(empty_hit.affinity(), Affinity::Downstream); + assert!(!empty_hit.is_inside()); + Ok(()) +} diff --git a/data/manifest.toml b/data/manifest.toml index f4bf28d..b2014c6 100644 --- a/data/manifest.toml +++ b/data/manifest.toml @@ -11,7 +11,7 @@ [[file]] path = "ROADMAP.md" -sha256 = "8d2b11a5e4ae31c948c625a7f4c4828dbac3c68294f727fa480cc3edeff499e4" +sha256 = "0cec26f52f8bc7e5089da4757d57c878014031ee10fd22f6daba69659656f0de" [[file]] path = "crates/jlreq-conformance/protocol.schema.json" @@ -22,44 +22,44 @@ path = "crates/jlreq-conformance/suite.ndjson" sha256 = "8f8f70b5f193a140994c8ca42bde5e46a445f55465b6b098012f7ffd0ccbadcf" [[file]] -path = "crates/jlreq/src/generated/appendix_a.rs" -sha256 = "00761c3eb613d272f3713e9c6b71383540a273021971a2c22b615eed23f8959a" +path = "crates/jlreq-core/src/generated/appendix_a.rs" +sha256 = "0683a89eb19f28155274c1cf1b87846826c8073b2b877a27d7cb628e38588d36" [[file]] -path = "crates/jlreq/src/generated/folding.rs" -sha256 = "11ed0a1852127cadc94ca98efcc6c51a8daedfb33e10033b07abae6353cc5fe0" +path = "crates/jlreq-core/src/generated/folding.rs" +sha256 = "5d9f8286b5c8c081069d27da4b4c45c92670bc7542b7d973775ea7a934403cc7" [[file]] -path = "crates/jlreq/src/generated/ideograph.rs" -sha256 = "13e3bb408d7148add03d2348c1360f33cdd71a462f0f4291ad8e5f38af41aef8" +path = "crates/jlreq-core/src/generated/ideograph.rs" +sha256 = "52ecfcf55347f2e247df493156fe29c4855ed0858f753c227fe791535c57a4cc" [[file]] -path = "crates/jlreq/src/generated/script.rs" -sha256 = "ad15e49f34640d3f949ca7e2a3a3f64315ce68266102759051eb4fe12cace6af" +path = "crates/jlreq-core/src/generated/script.rs" +sha256 = "a7e6121bf9cfe63190c4bf3cfccde7b72f640580059fb30e42bf5f0a80fda361" [[file]] -path = "crates/jlreq/src/generated/table1.rs" -sha256 = "6de73a04480c3ee32ee59d7d23128aa82783dc0ccbde8559f3be17b77482105c" +path = "crates/jlreq-core/src/generated/table1.rs" +sha256 = "54f879f69b2b47cb816ada14c721a503894e307e7d43c61e796d4fa149933c3a" [[file]] -path = "crates/jlreq/src/generated/table2.rs" -sha256 = "4bb486fd05ae95896b728d26f1b14774a531fd83f7fec05e571530914d59a751" +path = "crates/jlreq-core/src/generated/table2.rs" +sha256 = "76e6e4d3f22f8cbe8b7243e3c1672e787db9c62a0940ac691ed6abc540efa753" [[file]] -path = "crates/jlreq/src/generated/table3.rs" -sha256 = "9427c738b22ee41090a52b9d2d3f0f1c82c1b072bc26660940ede1d394648176" +path = "crates/jlreq-core/src/generated/table3.rs" +sha256 = "d02226e7728d275b4ca60eb641e58bade4dd1a8be6e40b5b7aec864f7476a30c" [[file]] -path = "crates/jlreq/src/generated/table4.rs" -sha256 = "2cc7292a8b5bbeeaa04dc693c02e68012230f7c0aee10fdd3824f0f68f8bbd47" +path = "crates/jlreq-core/src/generated/table4.rs" +sha256 = "3b373bc7dda9b017d9a5e5f927e2fb281ef273e52cee4dea3f9676242bfc8a11" [[file]] -path = "crates/jlreq/src/generated/table5.rs" -sha256 = "ee099abbea8b724d9ffbb2fc5dfed43fa09fac21851d1cf67ff7ca5ce91ba3bb" +path = "crates/jlreq-core/src/generated/table5.rs" +sha256 = "594f6e4c0d1645c4e5ad1d5e38ea46fc1d2739704eb998c52c28b4f7700c8d2d" [[file]] -path = "crates/jlreq/src/generated/table6.rs" -sha256 = "0eeebc30219709e4cdc8d78f36981e0df9e90fe9097550f1ef636aec5fd4d6ea" +path = "crates/jlreq-core/src/generated/table6.rs" +sha256 = "e1ec1925685b6a71dc621cba2056f956d450a9c6e21888d294b360469be4fde1" [[file]] path = "docs/conformance-deferrals.toml" @@ -67,7 +67,7 @@ sha256 = "e75e3651a359f3886308b0a01867d18538d4e62423498f405c9b5e1e8c204221" [[file]] path = "docs/public-api.toml" -sha256 = "419582158ccc214d12a6f0258a75ed5eb78b905abfc0f0202026fc6f294a0517" +sha256 = "543682c99a71b5e2c90bb02f655c8b47dbbde700040f28a01aee783f5451b5fa" [[file]] path = "spec/PROVENANCE.toml" @@ -131,11 +131,11 @@ sha256 = "1d8d36d1f2a69b30c6817f3012a367fbab0efbd6455e0d2a1371b6fee40a36d6" [[file]] path = "spec/derived/appendix-a.tsv" -sha256 = "d47f6bf7a6d51d20c4e86419c35ef5ccaaf0c4baa9fb4ace55a0990e8360734c" +sha256 = "f671a4b9daae12d52ddb8fdbbdf7b75f8882da3f57491d4dc553d07477d2e22c" [[file]] path = "spec/derived/classes.tsv" -sha256 = "311fe44852e4f8057565d8b78a9976bcec628ef9ea199110d29832d0fb31120b" +sha256 = "44b454f16518b4f24c49b5ed4349a14ac9b5060ac7cc7b839c80d706dea40004" [[file]] path = "spec/derived/defects.tsv" @@ -143,11 +143,11 @@ sha256 = "1537dda983e5b4ba5a4ef0f8fd7984e7c00397a054cc6ad9080270e28f8bc169" [[file]] path = "spec/derived/folding.tsv" -sha256 = "4e8abc525815527522e36bad9410d0f7fa8733d29010f8a518e92fbc233cefaf" +sha256 = "46eedd68ef0dc757f33f057a142df1e45cef2eb9bc8de025b5611cfc7267eddb" [[file]] path = "spec/derived/ideographs.tsv" -sha256 = "cf61e33039cc37974f3e017e6381e94873df3e55701199a71877b18bbd01886d" +sha256 = "4faa81a44f8f497198975ff8dad10e843f4da629fee83bb7786f12b61958b0ba" [[file]] path = "spec/derived/notes.tsv" @@ -163,7 +163,7 @@ sha256 = "a54974732b8077af7ce1a4c2ff7e41d4772cdae07dd8a9f341650231f49f4cab" [[file]] path = "spec/derived/scripts.tsv" -sha256 = "3b7c3d2e4e2f35912137c2fef54928c4cbf42f850bcd2483175ec11f5c78bfd1" +sha256 = "eee68d14c9afa914a501001e4da84bc649efa42012650cc20280a2bf4de2e36b" [[file]] path = "spec/snapshot/index.html" @@ -183,7 +183,7 @@ sha256 = "2e1efc1dcb59c575eedf5ccae60f95229f706ee6d031835247d843c11d96470c" [[file]] path = "xtask/src/api.rs" -sha256 = "d2d3c985110fd526be7500fbfa75517b7ff0e986896faf0921f326c0cc5b442d" +sha256 = "0b0abd4208a79e9c41d1812bc84205919627af023d6d5aa01a126dc614d4b939" [[file]] path = "xtask/src/attest.rs" @@ -191,11 +191,11 @@ sha256 = "23be98adeefbac0b1a9f67ce69880a0fe9b51c1f42f205ba128e3018d3d322b9" [[file]] path = "xtask/src/classes.rs" -sha256 = "6b005096ce0174f4de51bb83e747fe5516e24281e878c0efae5f7b499bf5610a" +sha256 = "12b8b18651712bcecaf516e0df5c334d7b3e7469ccadfa5944bdd481156bc5ed" [[file]] path = "xtask/src/conform.rs" -sha256 = "b3456c6290e1f7bb10c8b140f97451dc9424e7e5c7b523ca0357eb190d2bcd09" +sha256 = "2ab65b535fda8ec9ceea6f00ecdb1661282c2f79cf27fd32338f1bf2cb2d8210" [[file]] path = "xtask/src/defects.rs" @@ -211,7 +211,7 @@ sha256 = "b4811f9b3fd18724d82f43c8a1ec82252e865c263c75872dd8a6b6ec9615747e" [[file]] path = "xtask/src/generate.rs" -sha256 = "81cc82c92a695f636510c901ace253cdd39a32bbc515312c945be8d7985064b3" +sha256 = "62aaa23967ad5fba4af39f4d836a49a4f09cabad151d6d6dca7295606a79cb11" [[file]] path = "xtask/src/inventory.rs" @@ -223,4 +223,4 @@ sha256 = "12e47247d7965060728f8482e5c17f8c5e6a83f3ef4f263a64b44cd56f77c3b7" [[file]] path = "xtask/src/spacing.rs" -sha256 = "4073acf8bcba3180e5a701d21d5b7fc1534475c12cc73360797faff13401d9a7" +sha256 = "da772cf4c1a9504568e040df2a04519ae5c119e91a51a36c671899df66c56a08" diff --git a/deny.toml b/deny.toml index c96dade..dcc5d91 100644 --- a/deny.toml +++ b/deny.toml @@ -19,7 +19,16 @@ ignore = [] [licenses] version = 2 confidence-threshold = 0.93 -allow = ["MIT", "Apache-2.0", "Unlicense"] +allow = [ + "MIT", + "Apache-2.0", + "Unlicense", + # ICU4X and Unicode-derived support crates. + "Unicode-3.0", + # Fontique's hash table and Unix dynamic font-discovery dependencies. + "Zlib", + "ISC", +] [bans] multiple-versions = "deny" @@ -30,7 +39,12 @@ highlight = "all" workspace-default-features = "allow" external-default-features = "allow" deny = [] -skip = [] +skip = [ + # Fontique 0.11.1 uses read-fonts 0.41 while HarfRust 0.13.3 uses 0.43. + { crate = "read-fonts@0.41.0", reason = "fixed upstream font-stack versions use distinct read-fonts APIs" }, + # ICU4X procedural macros remain on syn 2 while the current font stack uses syn 3. + { crate = "syn@2.0.119", reason = "fixed upstream ICU4X and font-stack versions span the syn 2/3 boundary" }, +] skip-tree = [] [sources] diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 2ed51d7..4a978a8 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -6,84 +6,98 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Release procedure -This guide separates reversible preparation from irreversible publication. Cargo documents -that a published version is permanent: it cannot be overwritten or deleted, and yanking -only prevents new resolutions. Always inspect `cargo package`/`cargo publish --dry-run` -output first ([Cargo publishing reference](https://doc.rust-lang.org/cargo/reference/publishing.html)). +This guide separates reversible preparation from irreversible publication. A crates.io +version cannot be overwritten or deleted; yanking only prevents new resolutions. Nothing +in `just release-check` publishes, tags, chooses a date, creates a GitHub Release, changes +repository settings, or stores a credential. ## Prepared state -`just release-check` is the single non-publishing acceptance command. It requires a clean -tracked tree and verifies generation/attestation/design gates, tests and doctests, coverage, -full mutation runs, all three reference engines and the 122,199-case census, MSRV 1.85, -`no_std`, WASM, the public API contract, package contents, extracted crate builds, and CLI -installation. At 0.1.0 the semver gate fixes the local baseline without contacting a -registry; later 0.1.x candidates are checked in patch mode against the latest published -jlreq release. All repository-owned shell entry points are checked by ShellCheck 0.11.0. -The manual -`Release check` workflow runs that command and builds the six target archives: - -- Linux x86_64 GNU and musl, and Linux AArch64 GNU; -- Windows x86_64 MSVC; and -- macOS x86_64 and AArch64. - -`just publish-dry-run` is part of that command and contacts crates.io for Cargo's complete -publication preflight, but Cargo retains both uploads locally. The conformance dry-run uses -the packaged local `jlreq` only because 0.1.0 is not in the index before the first release. - -The mutation step first verifies [the exclusion ledger](mutation-ledger.toml): all ten -generated table files have individual hashes, and each of the five proven-equivalent -mutants has one exact regex, source hash, and proof. Handwritten integrity checks are not -excluded. - -Each archive contains `jlreq-conformance`, `jlreq-sample-engine`, the repository README, -and both license texts. The workflow emits SHA-256 files and target-scoped CycloneDX 1.5 -JSON, validates its component, version, license, and target triple, and records GitHub -build-provenance and SBOM attestations. Verify downloaded artifacts with `sha256sum -c`; -after publication, consumers can additionally use `gh attestation verify`. - -## First publication checklist - -The first crates.io release cannot use Trusted Publishing. crates.io requires one manual -release before a Trusted Publisher can be configured -([Rust announcement](https://blog.rust-lang.org/2025/07/11/crates-io-development-update-2025-07/)). -The `Release` workflow therefore has an `initial-token` mode protected by the `release` -GitHub environment and an exact confirmation phrase. It is intentionally never triggered -by a push or tag. - -Before approving it: - -- [ ] Replace the `Unreleased` heading with `0.1.0` and the approved date; update the - comparison link. -- [ ] Confirm the candidate commit passed `Release check` and record that workflow run ID. -- [ ] Download its artifacts, verify every checksum and attestation, and inspect both - `.crate` archives. -- [ ] Confirm the `jlreq` and `jlreq-conformance` names and the 0.1.0 version have not - already been published. -- [ ] Store a narrowly scoped crates.io token in the `release` environment as - `CRATES_IO_TOKEN`, require environment approval, and enter the exact workflow - confirmation phrase. - -The workflow performs the irreversible sequence in this order: - -1. publish `jlreq` 0.1.0; -2. wait until that exact version is visible in the crates.io index; -3. publish `jlreq-conformance` 0.1.0; -4. create and push `v0.1.0`; and -5. create the GitHub Release from the already attested archives, checksums, SBOMs, and - `.crate` files. - -If Cargo times out while polling the index, check crates.io before retrying: the upload may -already be permanent. Never rerun a publish blindly. +`just release-check` is the complete local acceptance command. It requires a clean tracked +tree and runs generation/attestation/design gates, tests and doctests, line/region coverage, +all mutation shards, fuzz compilation, MSRVs (facade 1.88, core 1.85), core `no_std`, +WASM, semver/API checks, dependency/license checks, package extraction, and the independent +engine census. + +`just package` first performs `cargo fetch --locked` into an isolated Cargo home. It then +packages, extracts, builds, tests, documents, and where applicable installs these archives +offline: + +1. `jlreq-core-0.1.0.crate`; +2. `jlreq-0.1.0.crate`; and +3. `jlreq-conformance-0.1.0.crate`. + +The manual **Release check** workflow adds one CycloneDX SBOM and provenance attestation per +crate. Its six target jobs build archives for Linux x86_64 GNU/musl, Linux AArch64 GNU, +Windows x86_64 MSVC, and macOS x86_64/AArch64. Each target archive contains both +`jlreq-conformance` and `jlreq-sample-engine`, licenses, and README, with a target +checksum, SBOM, and attestations. + +After downloading artifacts, verify every `.sha256` in its directory. Use +`gh attestation verify` against the repository for the corresponding archives and SBOMs. + +## Repository settings before publication + +The versioned policy is [`.github/REPOSITORY-SETTINGS.md`](../.github/REPOSITORY-SETTINGS.md). +A repository administrator must confirm: + +- Actions are required to use full commit SHAs; +- the `release` environment permits deployment from `main` only, has at least one + required reviewer, and disallows self-review; +- required CI, CodeQL, dependency review, REUSE, API, mutation, and Release Check jobs are + green; and +- high/critical security alerts and open dependency-update PRs are zero. + +Create the environment without a crate token. Reviewer identity and organization settings +cannot be encoded safely in this repository and must be recorded during the handoff. + +## Finalize the candidate date + +Only after a date is approved, run: + +```sh +just finalize-release YYYY-MM-DD +``` + +The command validates an ISO calendar date, inserts `## [0.1.0] - YYYY-MM-DD` below a fresh +`[Unreleased]` heading, and writes the comparison links. Review and commit that change, +then run **Release check** on the exact full commit SHA. Do not use this command during +reversible preparation merely to invent a date. + +Record the successful workflow run ID. Download its artifacts; verify all checksums, +attestations, package contents, and the candidate commit. Confirm that none of the three +names/version pairs or `v0.1.0` already exists. + +## First publication + +The first release uses the manual **Release** workflow in `initial-token` mode, protected +by the `release` environment and its exact confirmation phrase. Immediately before the +approved run, add a narrowly scoped crates.io token as environment secret +`CRATES_IO_TOKEN`. Do not add a repository-level token. + +The workflow performs this irreversible sequence: + +1. publish `jlreq-core` at the candidate version; +2. poll until that exact core version is visible in the registry index; +3. publish `jlreq`; +4. publish `jlreq-conformance`; +5. only after all three uploads succeed, create and push the annotated version tag; and +6. create the GitHub Release from the already attested artifacts. + +The facade and conformance archive dependencies are patched to the extracted core only for +pre-publication offline verification; published manifests resolve the just-indexed +`jlreq-core`. + +If an upload or polling step times out, inspect crates.io before retrying. The upload may +already be permanent. Never blindly rerun the workflow. ## Trusted Publishing after 0.1.0 -After both first uploads succeed, configure each crate's Trusted Publisher for this -repository, `.github/workflows/release.yml`, and the `release` environment. Future releases -select `trusted-publishing`; the official -[`rust-lang/crates-io-auth-action`](https://github.com/rust-lang/crates-io-auth-action) -exchanges GitHub OIDC for a short-lived crates.io token and revokes it after the job. -Remove the long-lived `CRATES_IO_TOKEN` secret after verifying the OIDC path. +After all first uploads succeed, configure a Trusted Publisher for each of `jlreq-core`, +`jlreq`, and `jlreq-conformance`, restricted to this repository, +`.github/workflows/release.yml`, and the `release` environment. Verify a later dry run or +release through the OIDC path, then remove `CRATES_IO_TOKEN` and revoke the initial token. -Tag creation, GitHub Release creation, Trusted Publisher configuration, branch-protection -changes, and the two uploads are external mutations. None is part of `just release-check`. +Future releases choose `trusted-publishing`; the pinned crates.io auth action exchanges +GitHub OIDC for a short-lived token. The tag and GitHub Release still occur only after every +crate publication succeeds. diff --git a/docs/adr/0022-unified-public-crate-and-process-conformance.md b/docs/adr/0022-unified-public-crate-and-process-conformance.md index a1af359..bb80a78 100644 --- a/docs/adr/0022-unified-public-crate-and-process-conformance.md +++ b/docs/adr/0022-unified-public-crate-and-process-conformance.md @@ -1,6 +1,6 @@ # ADR-0022: one public Rust crate and one process conformance contract -- Status: accepted +- Status: superseded by [ADR 0025](0025-three-product-layers.md) - Date: 2026-08-14 - Supersedes the crate topology in [ADR 0015](0015-the-crate-graph-and-the-inline-line-seam.md) and its seam-carrier amendment in diff --git a/docs/adr/0023-the-project-is-named-jlreq.md b/docs/adr/0023-the-project-is-named-jlreq.md index fa09184..3f93616 100644 --- a/docs/adr/0023-the-project-is-named-jlreq.md +++ b/docs/adr/0023-the-project-is-named-jlreq.md @@ -3,8 +3,9 @@ - Status: accepted - Date: 2026-08-23 - Amends the naming in - [ADR 0022](0022-unified-public-crate-and-process-conformance.md); its crate topology — - one public library, one binary-only conformance package — is unchanged. + [ADR 0022](0022-unified-public-crate-and-process-conformance.md). Its original topology + was later superseded by [ADR 0025](0025-three-product-layers.md), while the names and + protocol identifier chosen here remain current. ## Context @@ -85,7 +86,7 @@ this decision. The workspace stays `publish = false` until a release decision is Every generated artifact changes, by design. `xtask/src/generate.rs` writes an SPDX header into each generated module, and `data/manifest.toml` records the SHA-256 of every generated file *and of the xtask sources that generated it*, while each `spec/derived/*.tsv` carries -its reader's digest and each `crates/jlreq/src/generated/*.rs` carries its generator's. One +its reader's digest and each `crates/jlreq-core/src/generated/*.rs` carries its generator's. One changed word in one copyright line therefore propagates through the whole digest ledger. That is the ledger working: a byte that decides an output is not allowed to change silently. All ten derived files, all ten generated modules, and the manifest were rewritten diff --git a/docs/adr/0024-independent-reference-engines.md b/docs/adr/0024-independent-reference-engines.md index bce265a..df74c87 100644 --- a/docs/adr/0024-independent-reference-engines.md +++ b/docs/adr/0024-independent-reference-engines.md @@ -38,8 +38,8 @@ the first one's source. starting with `engines/ocaml/` and followed by `engines/racket/`. Each is built from `spec/` and from the public protocol contract only (`crates/jlreq-conformance/protocol.schema.json`, `docs/design/conformance.md`, the sample -engine's wire vocabulary); each is barred from reading `crates/jlreq/src/` — `pipeline.rs` -above all — and from `crates/jlreq/src/generated/` or the xtask generators that write it. +engine's wire vocabulary); each is barred from reading `crates/jlreq-core/src/` — `pipeline.rs` +above all — and from `crates/jlreq-core/src/generated/` or the xtask generators that write it. The rule, the integer contract both engines follow, and the milestone mechanism are recorded in the new "Independent reference engines" section of `docs/design/conformance.md`, which is their single shared source rather than something @@ -95,7 +95,8 @@ reaches `9`. `engines/` is deliberately not a path to a released product. Neither engine gains a stable CLI contract, a version number, or a publish target; both stay tooling in service of the -`jlreq` and `jlreq-conformance` products ADR 0022 names, the same way `xtask` is tooling and +the `jlreq`, `jlreq-core`, and `jlreq-conformance` products ADR 0025 names, the same +way `xtask` is tooling and not a product crate. If a reference engine's design or its findings turn out to be broadly useful outside this repository, that is a separate, later decision — not a reason to gate one here on eventual portability. diff --git a/docs/adr/0025-three-product-layers.md b/docs/adr/0025-three-product-layers.md new file mode 100644 index 0000000..129655b --- /dev/null +++ b/docs/adr/0025-three-product-layers.md @@ -0,0 +1,53 @@ +# ADR-0025: separate the high-level facade from the composition core + +- Status: accepted +- Date: 2026-08-28 +- Supersedes the product topology in + [ADR 0022](0022-unified-public-crate-and-process-conformance.md). +- Retains the project, binary, and protocol names chosen by + [ADR 0023](0023-the-project-is-named-jlreq.md). + +## Context + +The single dependency-free `jlreq` library accepted only pre-shaped clusters and authored +break opportunities. That was an appropriate engine boundary, but it made the shortest +application path require font parsing, fallback, shaping, Unicode line segmentation, bidi, +cluster attribution, and renderer mapping before Japanese composition could begin. + +Those responsibilities cannot enter the dependency-free core without removing its useful +`no_std + alloc` boundary. Conformance also serves language-independent implementation +authors and should not acquire a facade dependency. + +Version 0.1.0 has not been published, so crate names and public entry points can still be +chosen for users rather than preserved for compatibility. + +## Decision + +The product has three layers: + +1. `jlreq` is the MSRV 1.88 high-level facade. It accepts UTF-8 text and explicit font + bytes, performs grapheme fallback, shaping, UAX #9, UAX #14, and physical placement, and + returns a self-contained draw-ready `TextLayout`. +2. The former low-level library becomes `jlreq-core`, preserving dependency-free + `no_std + alloc` composition and MSRV 1.85. It is also available as `jlreq::core`, + without flattening all low-level names into the facade root. +3. `jlreq-conformance` remains binary-only and retains + `jlreq.conformance/1` and the existing specification identifier. + +The facade uses Fontique for opt-in system discovery, HarfRust for shaping, ICU4X for line +and grapheme segmentation, and unicode-bidi for UAX #9. Upstream types stay private. +Explicit font bytes are the reproducibility boundary; OS font choice is not. + +Rendering, rasterization, GPU backends, a layout CLI/service, a WASM-specific facade, and a +website are outside the decision. + +## Consequences + +Ordinary callers can reach glyph placements from text and fonts without constructing +low-level clusters or breaks. Engine implementers retain the small deterministic core, and +protocol implementers retain a process boundary independent of Rust types. + +The package and release order is `jlreq-core`, registry visibility, then `jlreq` and +`jlreq-conformance`. API, semver, package, mutation, coverage, fuzz, license, SBOM, and +attestation gates cover all applicable layers. Publication itself remains a separate +explicit maintainer action. diff --git a/docs/decisions/README.md b/docs/decisions/README.md index e62f3f4..dd75bdb 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -47,22 +47,22 @@ belongs here rather than in an engine's README. | Reading | Question | Where it applies | | --- | --- | --- | -| [ambiguous-context](ambiguous-context.md) | Which class, when Appendix A names several and nothing separates them | [`spec` / `normalize`](../../crates/jlreq/src/normalize.rs) | -| [unlisted-code-point](unlisted-code-point.md) | Which class, when Appendix A lists the key nowhere | [`spec` / `normalize`](../../crates/jlreq/src/normalize.rs) | -| [compatibility-ideographs](compatibility-ideographs.md) | Whether a CJK Compatibility Ideograph is cl-19, and whether it is normalized first | [`spec`](../../crates/jlreq/src/spec.rs) | -| [grouped-numeral-qualification](grouped-numeral-qualification.md) | Whether the width or the job §A.24's Remarks cell names is what reaches cl-24 | [`spec`](../../crates/jlreq/src/spec.rs) | -| [adjustment-preference](adjustment-preference.md) | Where the non-ladder demerit components sit in paragraph optimization | [`pipeline`](../../crates/jlreq/src/pipeline.rs) | -| [european-numeral-by-code-point](european-numeral-by-code-point.md) | Whether §C.2 note 11's "European numeral" is a declared role or a fact read from the occurrence's own key | [`pipeline`](../../crates/jlreq/src/pipeline.rs) | -| [inseparable-character-kind](inseparable-character-kind.md) | What "of different kinds" means for two adjacent inseparable characters (cl-08) in §E.2 note 4 | [`pipeline`](../../crates/jlreq/src/pipeline.rs) | -| [specified-character-tab-alignment](specified-character-tab-alignment.md) | Whether §3.6.2's specified-character tab kind names its occurrence by `char` or by a caller-declared item | [`paragraph` / `pipeline`](../../crates/jlreq/src/paragraph.rs) | -| [sentence-medial-dividing-mark](sentence-medial-dividing-mark.md) | What §3.1.6's third Note leaves open about a sentence-medial cl-04 mark's quarter em: whose em it is, which coordinates it reaches, and what a line edge does to it | [`pipeline`](../../crates/jlreq/src/pipeline.rs) | -| [jukugo-ruby-unset-group](jukugo-ruby-unset-group.md) | What an occurrence with no declared group means for §C.2 note 8's base-and-ruby indivisibility | [`pipeline`](../../crates/jlreq/src/pipeline.rs) | -| [line-head-opening-bracket](line-head-opening-bracket.md) | What §3.1.5 pattern 2 and §B.2 note 17 leave open about the wrapped-line-head half em before an opening bracket: whose em it is and whether Appendix D reduces it | [`pipeline`](../../crates/jlreq/src/pipeline.rs) | -| [tolerance-exhaustion](tolerance-exhaustion.md) | What composition does when no complete arrangement meets its quality threshold | [`pipeline`](../../crates/jlreq/src/pipeline.rs) | -| [widow-threshold](widow-threshold.md) | What §3.5.4 leaves open about widow adjustment and an unsatisfiable threshold | [`paragraph` / `pipeline`](../../crates/jlreq/src/paragraph.rs) | -| [mono-ruby-separation-split](mono-ruby-separation-split.md) | How mono-ruby overhang surplus splits and how demands at a shared boundary combine | [`pipeline`](../../crates/jlreq/src/pipeline.rs) | -| [group-ruby-flush-single-character](group-ruby-flush-single-character.md) | What §3.3.6's `flush` method does for a group-ruby run of exactly one ruby character | [`pipeline`](../../crates/jlreq/src/pipeline.rs) | -| [jukugo-group-layout-distribution](jukugo-group-layout-distribution.md) | Which of §3.3.6's two methods §3.3.7¶2's `group` answer means, and whether `ruby.group_distribution` selects anything inside a jukugo compound | [`pipeline`](../../crates/jlreq/src/pipeline.rs) | +| [ambiguous-context](ambiguous-context.md) | Which class, when Appendix A names several and nothing separates them | [`spec` / `normalize`](../../crates/jlreq-core/src/normalize.rs) | +| [unlisted-code-point](unlisted-code-point.md) | Which class, when Appendix A lists the key nowhere | [`spec` / `normalize`](../../crates/jlreq-core/src/normalize.rs) | +| [compatibility-ideographs](compatibility-ideographs.md) | Whether a CJK Compatibility Ideograph is cl-19, and whether it is normalized first | [`spec`](../../crates/jlreq-core/src/spec.rs) | +| [grouped-numeral-qualification](grouped-numeral-qualification.md) | Whether the width or the job §A.24's Remarks cell names is what reaches cl-24 | [`spec`](../../crates/jlreq-core/src/spec.rs) | +| [adjustment-preference](adjustment-preference.md) | Where the non-ladder demerit components sit in paragraph optimization | [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) | +| [european-numeral-by-code-point](european-numeral-by-code-point.md) | Whether §C.2 note 11's "European numeral" is a declared role or a fact read from the occurrence's own key | [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) | +| [inseparable-character-kind](inseparable-character-kind.md) | What "of different kinds" means for two adjacent inseparable characters (cl-08) in §E.2 note 4 | [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) | +| [specified-character-tab-alignment](specified-character-tab-alignment.md) | Whether §3.6.2's specified-character tab kind names its occurrence by `char` or by a caller-declared item | [`paragraph` / `pipeline`](../../crates/jlreq-core/src/paragraph.rs) | +| [sentence-medial-dividing-mark](sentence-medial-dividing-mark.md) | What §3.1.6's third Note leaves open about a sentence-medial cl-04 mark's quarter em: whose em it is, which coordinates it reaches, and what a line edge does to it | [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) | +| [jukugo-ruby-unset-group](jukugo-ruby-unset-group.md) | What an occurrence with no declared group means for §C.2 note 8's base-and-ruby indivisibility | [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) | +| [line-head-opening-bracket](line-head-opening-bracket.md) | What §3.1.5 pattern 2 and §B.2 note 17 leave open about the wrapped-line-head half em before an opening bracket: whose em it is and whether Appendix D reduces it | [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) | +| [tolerance-exhaustion](tolerance-exhaustion.md) | What composition does when no complete arrangement meets its quality threshold | [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) | +| [widow-threshold](widow-threshold.md) | What §3.5.4 leaves open about widow adjustment and an unsatisfiable threshold | [`paragraph` / `pipeline`](../../crates/jlreq-core/src/paragraph.rs) | +| [mono-ruby-separation-split](mono-ruby-separation-split.md) | How mono-ruby overhang surplus splits and how demands at a shared boundary combine | [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) | +| [group-ruby-flush-single-character](group-ruby-flush-single-character.md) | What §3.3.6's `flush` method does for a group-ruby run of exactly one ruby character | [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) | +| [jukugo-group-layout-distribution](jukugo-group-layout-distribution.md) | Which of §3.3.6's two methods §3.3.7¶2's `group` answer means, and whether `ruby.group_distribution` selects anything inside a jukugo compound | [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) | Promoted from the second and third engines' observations: @@ -83,10 +83,10 @@ Promoted from the second and third engines' observations: | [jidori-inserted-space-locale-split](jidori-inserted-space-locale-split.md) | How many sides of an inserted space §3.7.3 opens, where its two renderings state opposite rules | `just census constructs` | Every reading in the second table applies to the layout round in -[`pipeline`](../../crates/jlreq/src/pipeline.rs) — except +[`pipeline`](../../crates/jlreq-core/src/pipeline.rs) — except [inexpressible-advance-remarks](inexpressible-advance-remarks.md) and [warichu-bracket-listing](warichu-bracket-listing.md), which apply to -[`spec`](../../crates/jlreq/src/spec.rs) — and to the corresponding round of +[`spec`](../../crates/jlreq-core/src/spec.rs) — and to the corresponding round of [`engines/ocaml/lib/`](../../engines/ocaml/lib/pipeline.ml) and of [`engines/racket/`](../../engines/racket/README.md). Each file names the first two by module; the two the third engine's convergence added name all three. diff --git a/docs/decisions/construct-break-refusal.md b/docs/decisions/construct-break-refusal.md index e3cad22..eafa47d 100644 --- a/docs/decisions/construct-break-refusal.md +++ b/docs/decisions/construct-break-refusal.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: what an engine does with a break the caller states inside an indivisible construct - Applies to: the feasibility round in - [`pipeline`](../../crates/jlreq/src/pipeline.rs), and the same round in + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), and the same round in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) - Standing: `Unstated` - JLReq: §C.2#13, §C.2#6, §C.2#8, §3.3.5, §3.3.6, §3.3.9, §3.7.1, §3.7.3, §3.7.4, §3.4.2, diff --git a/docs/decisions/expansion-ladder-scope.md b/docs/decisions/expansion-ladder-scope.md index 8c6490d..a0eb2df 100644 --- a/docs/decisions/expansion-ladder-scope.md +++ b/docs/decisions/expansion-ladder-scope.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: which coordinates §3.8.4's ladder asks its ceiling at, and which its fourth step re-levels - Applies to: the expansion ladder in - [`pipeline`](../../crates/jlreq/src/pipeline.rs), and the same ladder in + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), and the same ladder in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) - Standing: `Unstated` - JLReq: §3.8.4, §E.1, §E.2, ADR-0021 diff --git a/docs/decisions/group-ruby-flush-single-character.md b/docs/decisions/group-ruby-flush-single-character.md index c737fa9..d210020 100644 --- a/docs/decisions/group-ruby-flush-single-character.md +++ b/docs/decisions/group-ruby-flush-single-character.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: what `flush` does for a group-ruby run of exactly one ruby character - Applies to: the private ruby placement round in - [`pipeline`](../../crates/jlreq/src/pipeline.rs), selected by the typed + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), selected by the typed `GroupRubyDistribution::Flush` style setting. - Standing: `Unstated` - JLReq: §3.3.6 @@ -40,7 +40,7 @@ rather than by a special case: `flush`'s own leading offset is `InlineExtent::ZE never derived from `distribute`, and its own interior weights are built over the run's `count - 1` interior sites. At `count == 1` that is zero sites, so the `flush` weight vector in -[`pipeline.rs`](../../crates/jlreq/src/pipeline.rs) is empty and `proportional_shares` +[`pipeline.rs`](../../crates/jlreq-core/src/pipeline.rs) is empty and `proportional_shares` yields no shares at all. The run's one character is therefore placed exactly at the base's own start with nothing consumed from the surplus in either direction. diff --git a/docs/decisions/inexpressible-advance-remarks.md b/docs/decisions/inexpressible-advance-remarks.md index 897feb3..57c5c69 100644 --- a/docs/decisions/inexpressible-advance-remarks.md +++ b/docs/decisions/inexpressible-advance-remarks.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: what an Appendix A Remarks cell naming only an advance the format cannot express does -- Applies to: [`spec`](../../crates/jlreq/src/spec.rs), and the same lookup in +- Applies to: [`spec`](../../crates/jlreq-core/src/spec.rs), and the same lookup in [`engines/ocaml/lib/spec.ml`](../../engines/ocaml/lib/spec.ml) - Standing: `Unstated` - JLReq: §A.03, §A.24, §A.25, §A.26, §A.27, §3.9.2 diff --git a/docs/decisions/jidori-inserted-space-locale-split.md b/docs/decisions/jidori-inserted-space-locale-split.md index 1f63647..6ea1a5d 100644 --- a/docs/decisions/jidori-inserted-space-locale-split.md +++ b/docs/decisions/jidori-inserted-space-locale-split.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: how many sides of an inserted space §3.7.3 opens, where its two renderings differ - Applies to: the jidori round in - [`pipeline`](../../crates/jlreq/src/pipeline.rs), and the same round in + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), and the same round in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) - Standing: `Adjudicated`. This is a `divergent` permission in the sense `xtask/src/policy.rs` defines — the two renderings of one sentence do not state the same diff --git a/docs/decisions/jidori-room-and-solid-boundaries.md b/docs/decisions/jidori-room-and-solid-boundaries.md index 4efb9e8..43f463a 100644 --- a/docs/decisions/jidori-room-and-solid-boundaries.md +++ b/docs/decisions/jidori-room-and-solid-boundaries.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: what a jidori measures its room in, and where a run with no boundary left stands -- Applies to: the jidori round in [`pipeline`](../../crates/jlreq/src/pipeline.rs), and the +- Applies to: the jidori round in [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), and the same round in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) and [`engines/racket/structure.rkt`](../../engines/racket/structure.rkt) - Standing: `Unstated` diff --git a/docs/decisions/ornamented-complex-geometry.md b/docs/decisions/ornamented-complex-geometry.md index 623955a..ccac463 100644 --- a/docs/decisions/ornamented-complex-geometry.md +++ b/docs/decisions/ornamented-complex-geometry.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: what an ornamented character complex is centered on, and how many of them a run is - Applies to: the cl-21 placement round in - [`pipeline`](../../crates/jlreq/src/pipeline.rs), and the same round in + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), and the same round in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) - Standing: `Unstated` - JLReq: §3.3.9, §3.7.1, §B.2#9, §C.2#6, §E.2#5 diff --git a/docs/decisions/ruby-distribution-and-rounding.md b/docs/decisions/ruby-distribution-and-rounding.md index 59debc6..6a53fa9 100644 --- a/docs/decisions/ruby-distribution-and-rounding.md +++ b/docs/decisions/ruby-distribution-and-rounding.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: how a ruby run distributes its surplus, and how the odd unit rounds - Applies to: the ruby placement round in - [`pipeline`](../../crates/jlreq/src/pipeline.rs), and the same round in + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), and the same round in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) - Standing: `Unstated` - JLReq: §3.3.5, §3.3.6, §3.3.8, §F.2, §F.3 diff --git a/docs/decisions/ruby-overhang-permission.md b/docs/decisions/ruby-overhang-permission.md index cfdaf9c..3c67f10 100644 --- a/docs/decisions/ruby-overhang-permission.md +++ b/docs/decisions/ruby-overhang-permission.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: which space a ruby reading may hang over, where §3.3.8 and Table 1 differ - Applies to: the ruby overhang round in - [`pipeline`](../../crates/jlreq/src/pipeline.rs), and the same round in + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), and the same round in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) - Standing: `Unstated` - JLReq: §3.3.8, §3.3.3, §B.1, §B.2#7, §A.09, §A.10, §A.15, §A.16 diff --git a/docs/decisions/stacked-structure-geometry.md b/docs/decisions/stacked-structure-geometry.md index d9088c6..a81104e 100644 --- a/docs/decisions/stacked-structure-geometry.md +++ b/docs/decisions/stacked-structure-geometry.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: where a stacked structure divides, and what its own edges carry - Applies to: the warichu and furawake rounds in - [`pipeline`](../../crates/jlreq/src/pipeline.rs), and the same rounds in + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), and the same rounds in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) - Standing: `Unstated` - JLReq: §3.4.2, §3.4.3, §3.7.2, §B.2#13, §C.3 diff --git a/docs/decisions/tab-line-correspondence.md b/docs/decisions/tab-line-correspondence.md index 9ddaed7..11a3a7a 100644 --- a/docs/decisions/tab-line-correspondence.md +++ b/docs/decisions/tab-line-correspondence.md @@ -7,8 +7,8 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: what §3.6.3 corresponds a line's tab signs with, and what a sign with no stop does - Applies to: the tab round in - [`pipeline`](../../crates/jlreq/src/pipeline.rs) and - [`paragraph`](../../crates/jlreq/src/paragraph.rs), and the same round in + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs) and + [`paragraph`](../../crates/jlreq-core/src/paragraph.rs), and the same round in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) and [`engines/racket/compose.rkt`](../../engines/racket/compose.rkt) - Standing: `Unstated` @@ -220,5 +220,5 @@ the Racket engine reached the reading published here ([#19](https://github.com/P4suta/jlreq/issues/19)); until then they were deliberately left out, because a census is a gate and a gate that is red is not one. `engines/ocaml/test/test_pipeline.ml`, -[`crates/jlreq/tests/public_api.rs`](../../crates/jlreq/tests/public_api.rs) and +[`crates/jlreq-core/tests/public_api.rs`](../../crates/jlreq-core/tests/public_api.rs) and `engines/racket/tests/test-compose.rkt` pin the individual shapes as well. diff --git a/docs/decisions/tate-chu-yoko-spacing-sources.md b/docs/decisions/tate-chu-yoko-spacing-sources.md index 621f81a..aecb7af 100644 --- a/docs/decisions/tate-chu-yoko-spacing-sources.md +++ b/docs/decisions/tate-chu-yoko-spacing-sources.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: which of §3.2.5 and the matrices states the space beside a tate-chu-yoko run - Applies to: the cl-30 placement round in - [`pipeline`](../../crates/jlreq/src/pipeline.rs), and the same round in + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), and the same round in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) - Standing: `Adjudicated` for the first silence — §3.2.5's own prose and the table its own sentence points at do not state the same rule, which is the `contradictory` permission diff --git a/docs/decisions/unstated-alignment.md b/docs/decisions/unstated-alignment.md index 16c31eb..7f42ddc 100644 --- a/docs/decisions/unstated-alignment.md +++ b/docs/decisions/unstated-alignment.md @@ -7,7 +7,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: what a request that states no `alignment` asks for - Applies to: the line-adjustment round in - [`pipeline`](../../crates/jlreq/src/pipeline.rs), and the same round in + [`pipeline`](../../crates/jlreq-core/src/pipeline.rs), and the same round in [`engines/ocaml/lib/pipeline.ml`](../../engines/ocaml/lib/pipeline.ml) - Standing: `Unstated` - JLReq: §3.8.1, §3.5.3 diff --git a/docs/decisions/warichu-bracket-listing.md b/docs/decisions/warichu-bracket-listing.md index 2643c3d..4eb6fb2 100644 --- a/docs/decisions/warichu-bracket-listing.md +++ b/docs/decisions/warichu-bracket-listing.md @@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Reading: which keys the `warichu-bracket` role reaches cl-28 and cl-29 with -- Applies to: the classification round in [`spec`](../../crates/jlreq/src/spec.rs), and the +- Applies to: the classification round in [`spec`](../../crates/jlreq-core/src/spec.rs), and the same round in [`engines/ocaml/lib/spec.ml`](../../engines/ocaml/lib/spec.ml) and [`engines/racket/classes.rkt`](../../engines/racket/classes.rkt) - Standing: `Unstated` diff --git a/docs/design/api-spine.md b/docs/design/api-spine.md index 56ea8b3..fbbdfb8 100644 --- a/docs/design/api-spine.md +++ b/docs/design/api-spine.md @@ -1,151 +1,104 @@ # The 0.1.0 API spine -This is the human-readable contract for the only public Rust library, `jlreq`. The exact -directly exported names and the 22 Style mappings are machine-readable in +This is the human-readable contract for the two public Rust libraries. The exact facade and +core exports, together with all 22 core Style mappings, are frozen in [`docs/public-api.toml`](../public-api.toml) and checked in both directions by `xtask api`. -This document describes the 0.1.0 contract. It is both a mechanical design control and the -compatibility floor for 0.1.x. The old multi-crate API is not carried forward and has no -compatibility facade. - -Before the first publication, `just semver` enforces the local export and Style mapping in -`docs/public-api.toml`; there is no registry baseline to compare yet. For every later 0.1.x -candidate the same required gate uses `cargo-semver-checks` in patch mode against the latest -normal, non-yanked jlreq release. This rolling baseline protects APIs added by intermediate -0.1.x releases as well as the original 0.1.0 surface. - -## Principles - -- All positions and ranges in input text are UTF-8 byte coordinates. -- All public geometry is a bounded `i32` in the caller's unit. -- Inputs are already shaped. Font I/O, shaping, UAX #14, bidi, and rendering are out of - scope. -- `ParagraphBuilder::build` is the representation-validation boundary. Composition of a - validated paragraph returns either a complete exact layout or a typed resource error. -- Classification, spacing records, lowering seams, feasibility, ladders, badness, and rule - IDs are private. -- Public result types are read-only views with private fields. -- Every public type is `#[non_exhaustive]`; new detail may be added without changing an - existing outcome. - -## Normal path - -```rust -let text = ShapedText::new(source, Size::square(1_000)?, Frame::FullEm, clusters)?; -let paragraph = Paragraph::builder(text, 20_000) - .breaks(break_offsets.map(Break::allowed)) - .build()?; -let layout = jlreq::compose(¶graph, &Style::book_2020())?; - -for line in layout.lines() { - for placement in line.clusters() { - // Pass `placement` to the caller's renderer. - let _ = placement; - } -} -``` - -`Composer::compose` is the same operation with reusable search scratch space. The root -function is for one-off composition. - -## Model +## Facade: text and fonts in, drawable glyphs out -`Size` carries positive inline and block em lengths. `Frame` is an explicit `FullEm`, -`HalfEm`, or `Proportional` metrics interpretation. `WritingMode` is `HorizontalTb` or -`VerticalRl`. +The normal path is intentionally one call: -`Cluster::new(range, advance)` requires only an original-source byte range and shaped inline -advance. Builder methods may override size, frame, and `ClusterRole` for an occurrence. -`ShapedText::new(source, size, frame, clusters)` owns and validates the complete stream. +```rust,no_run +# fn layout(font_bytes: Vec) -> Result<(), jlreq::LayoutError> { +let mut fonts = jlreq::FontLibrary::new(); +fonts.register_font(font_bytes)?; +let options = jlreq::LayoutOptions::try_new(240.0, 16.0)?; +let layout = jlreq::layout("日本語組版", &fonts, options)?; -Normalization may join an Appendix A two-code-point key across multiple shaped clusters, -but it preserves attribution to those clusters. A non-proportional cluster may not hide -multiple keys; proportional Latin ligatures remain valid. - -## Paragraph - -`Paragraph::builder(text, line_extent)` accepts: +for glyph in layout.glyphs() { + let font = &layout.fonts()[glyph.font_id().get() as usize]; + let _draw = (font.bytes(), glyph.glyph_id(), glyph.origin()); +} +# Ok(()) +# } +``` -- ordinary, mandatory, and discretionary `Break` values; -- first-line indent and logical `Alignment`; -- `Widow` control; -- `TabStop` values with logical `TabAlignment`; -- `WritingMode`; and -- a sequence of opaque `Construct` values. +`LayoutOptions::try_new(line_extent, font_size)` validates and quantizes its two required +values. Builder methods set writing mode, alignment, core `Style`, language, base +direction, line gap, tab width, OpenType features and variations, and `ResourceLimits`. +`LayoutEngine` exposes the same plain-text and typed-document calls with reusable +internals. -The builder rejects invalid UTF-8/cluster boundaries, uncovered or overlapping clusters, -duplicate breaks and tab positions, crossing construct ranges, illegal breaks inside an -indivisible construct, invalid sizes, and invalid construct-specific counts. +`FontLibrary` registers owned memory fonts and TTC indices, family/style metadata, a +primary face, and ordered fallback. The optional `system-fonts` feature is the only OS +discovery surface. HarfRust, Fontique, ICU4X, and unicode-bidi types are private. -The paragraph end is an implicit mandatory break. The only search exposed by composition is -whole-paragraph optimization. +## Typed authored content -## Inline structures +`DocumentBuilder` adds non-overlapping `SpanStyle` ranges, mandatory and prohibited +breaks, and all nine inline structure families. Ruby can be mono, group, or jukugo, with +automatic or explicit `RubyRun` association. The builder shapes annotation strings for +ruby, emphasis, reference marks, and scripts; callers never manufacture low-level +annotation clusters. -`Construct` is opaque and has nine named constructors: +The builder validates byte boundaries and cross-field relationships before producing an +immutable `Document`. `layout_document` then returns a complete result or a typed error. -| Constructor | Structure | -| --- | --- | -| `ruby` | mono, group, or jukugo ruby | -| `tate_chu_yoko` | upright horizontal span in vertical writing | -| `emphasis_dots` | repeated emphasis marks | -| `warichu` | inline cutting note | -| `furawake` | distribution at declared breaks into an explicit number of sublines and line gap | -| `jidori` | fit into an explicit number of full-em cells | -| `reference_mark` | shaped reference mark attached to a base | -| `script` | shaped subscript/superscript complex | -| `formula` | shaped formula span with math-token break opportunities | +## Renderer-facing results -Ruby alone has public supporting types: `Ruby`, `RubyKind`, and `RubyRun`. The annotation is -another `ShapedText`; runs map base ranges to annotation ranges. Internal runs, -contributions, seams, and placement strategies are not public. +`TextLayout` owns: -## Style +- the original UTF-8 source; +- physical `TextLine` values in reading order; +- `GlyphPlacement` values in visual draw order; +- every referenced `FontResource`; and +- positioned, stable-code diagnostics. -`Style` is complete and immutable. `StyleBuilder` has one typed setter for each choice and -rejects contradictions at `build()`. Profiles are: +A glyph exposes font ID, glyph ID, source byte range, optional annotation attribution, +origin, advances, offsets, 26.6 geometry, transform, and bidi level. A line exposes source +range, physical origin and extents, writing mode, and its visual glyph slice. -- `jlreq_2020` and the permanently equivalent `default`; -- `book_2020`; -- `magazine_2020`; -- `newspaper_2020`; and -- `jis_reading_2020` (the alternatives JLReq records, not complete JIS X 4051 conformance). +`hit_test`, `caret_rect`, and `selection_rects` are defined over the same physical +geometry and support both writing modes and bidi. They never require the renderer to infer +logical ordering from glyph order. -The `jlreq::style` namespace contains the 22 dedicated choice enums. Their complete names -and specification paths live in `docs/public-api.toml`; generic `Question`, `Choice`, and -string-setting types are intentionally absent. +## Errors, diagnostics, and atomicity -## Results +`LayoutError` distinguishes invalid font data or TTC index, missing fonts, invalid +options, invalid typed document data, resource exhaustion, core input failure, and core +composition failure. Its `code()` and optional source range are stable; display prose may +improve. -`Layout::lines()` and `Layout::diagnostics()` return borrowed slices. A `Line` provides its -source range, logical origins, occupied inline extent, block demand, cluster placements, -and attachments. +Invalid fonts, options, ranges, and resource limits return no `TextLayout`. Missing glyphs +and overfull or widow conditions that still permit a complete answer are diagnostics. +Failures do not poison `LayoutEngine`; the next call is independent. -Each `ClusterPlacement` provides: +All public float values are checked for finiteness and range and quantized to 26.6 fixed +point before processing. Equivalent quantized inputs are intentionally equivalent. -- `PlacementOrigin` (source cluster or construct ordinal); -- original byte range; -- logical inline and block coordinates; -- placed advance, local size, and frame; and -- local writing mode and `CoordinateTransform`. +## Core: pre-shaped exact composition -This is enough for a renderer to draw proportional text in vertical writing and -tate-chu-yoko without reshaping. `Attachment` provides the equivalent geometry and -attribution for ruby, emphasis marks, reference marks, and script annotations. +`jlreq-core` is reachable directly and as `jlreq::core`. It accepts caller-shaped UTF-8 +clusters, integer advances, break opportunities, paragraph policy, and typed constructs. +Its `ShapedText`, `ParagraphBuilder`, `Style`, `CompositionLimits`, `Composer`, +`Layout`, placements, diagnostics, and typed errors retain the existing public behavior +under the new crate path. -`Diagnostic` is opaque. Only its stable code, `Severity`, optional input range, and JLReq -reference string are contractual. It exposes no internal rule sequence. +The core is dependency-free, `no_std + alloc`, MSRV 1.85, and contains no font I/O, +Unicode line segmenter, bidi implementation, rasterizer, or drawing backend. Its result +uses logical integer geometry; the facade maps that result to physical 26.6 glyph geometry. -## Errors and compatibility +## Compatibility -`InputError` means no valid paragraph could be built; its stable code and optional range are -for programs, while its message may improve. `StyleError` similarly exposes a stable -conflict code. `ComposeError` exposes a stable code, resource, limit, and observed count; -it never carries a partial layout. +Both libraries begin at 0.1.0. Within 0.1.x: -`Style::default()` never changes meaning. A new specification revision adds a dated profile -and a new specification identifier. The process protocol is versioned separately. +- directly exported names in `docs/public-api.toml` are preserved; +- `Style::default()` retains the JLReq 2020 reading; +- stable error and diagnostic codes retain their meanings; +- explicit font bytes and options retain deterministic output; +- the core keeps MSRV 1.85 and the facade keeps MSRV 1.88; and +- protocol v1 and the specification identifier remain separate compatibility contracts. -The 0.1.0 design has no compatibility layer for the former experimental API. After 0.1.0, -compatible 0.1.x releases preserve this surface and its stable codes; incompatible changes -require a semver-minor release while the major version is zero. +Before first publication the semver gate is network-free. Later 0.1.x candidates run +`cargo-semver-checks` in patch mode for both `jlreq-core` and `jlreq` against the latest +normal non-yanked registry release. diff --git a/docs/design/conformance.md b/docs/design/conformance.md index c56e5de..5169358 100644 --- a/docs/design/conformance.md +++ b/docs/design/conformance.md @@ -178,8 +178,8 @@ reasoning locally, so the contract is amended in exactly one place. **May not be read into a reference engine** — the Rust implementation: -- the layout logic under `crates/jlreq/src/` (`pipeline.rs` above all); -- `crates/jlreq/src/generated/`, and the xtask code generators that write it. +- the layout logic under `crates/jlreq-core/src/` (`pipeline.rs` above all); +- `crates/jlreq-core/src/generated/`, and the xtask code generators that write it. A reference engine builds its tables from `spec/derived/*.tsv` and `spec/captured/*.tsv` directly; it does not read the Rust-generated modules those files also produce. The property diff --git a/docs/design/generation.md b/docs/design/generation.md index b6d0175..ce44481 100644 --- a/docs/design/generation.md +++ b/docs/design/generation.md @@ -63,13 +63,13 @@ spec/ table1.ja.tsv transcribed from tables/table_ja2.pdf ... through table6 upstream/ gitignored; the PDFs, if a developer fetched them -crates/jlreq/src/generated/*.rs committed output of stage 2 +crates/jlreq-core/src/generated/*.rs committed output of stage 2 data/manifest.toml SHA-256 of every file the pipeline reads or writes docs/decisions/*.md this project's published readings of silences ``` The active stage-2 outputs are Appendix A membership, compatibility folding, ideograph and -script ranges, plus Tables 1 through 6, all under `crates/jlreq/src/generated/`. Rule and +script ranges, plus Tables 1 through 6, all under `crates/jlreq-core/src/generated/`. Rule and policy inventories remain derived TSV inputs for repository gates; internal runtime rule identifiers are deliberately not generated into the public library. @@ -85,7 +85,7 @@ asserts the known off-by-one so a corrected upstream fails loudly. **Stage 1**, `cargo run -p xtask -- derive`, reads `spec/snapshot/` and emits `spec/derived/*.tsv` and nothing else. -**Stage 2**, `cargo run -p xtask -- generate`, emits `crates/jlreq/src/generated/*.rs` from +**Stage 2**, `cargo run -p xtask -- generate`, emits `crates/jlreq-core/src/generated/*.rs` from `spec/derived/` and `spec/captured/`. Both are byte-identity gates, both are dependency-free, and both run in CI on every commit. diff --git a/docs/error-codes.md b/docs/error-codes.md index f04a567..2bf560b 100644 --- a/docs/error-codes.md +++ b/docs/error-codes.md @@ -7,10 +7,55 @@ SPDX-License-Identifier: MIT OR Apache-2.0 # Stable error and diagnostic codes The code is the compatibility key; prose messages may become clearer in patch releases. -`InputError` reports invalid caller data, `StyleError` reports contradictory settings, -`ComposeError` reports an atomic resource refusal, and `Diagnostic` describes a complete -layout that was necessarily degraded. `just repository` compares this table with every -literal in the handwritten product source in both directions. +`LayoutError` and facade diagnostics cover automatic text/font processing. +`jlreq_core::InputError`, `StyleError`, `ComposeError`, and core `Diagnostic` cover +pre-shaped composition. `just repository` compares this table with every literal in both +handwritten public-library sources in both directions. + +## High-level font and option errors + +| Code | Meaning | +| --- | --- | +| `font.invalid` | Font bytes or the selected TTC face index are invalid. | +| `font.none-registered` | Layout was requested without a registered face. | +| `font.unknown-id` | Primary or fallback configuration names an unregistered font ID. | +| `font.too-many-ids` | Registered faces cannot be represented by a public font ID. | +| `font.system-family-not-found` | Opt-in system discovery found no face for the requested family. | +| `layout.invalid-option` | A numeric, tag, language, tab, feature, or variation option violates its invariant. | + +## High-level document errors + +| Code | Meaning | +| --- | --- | +| `document.invalid-span-range` | A span range is empty, out of bounds, or not on UTF-8 boundaries. | +| `document.overlapping-spans` | Two span styles overlap. | +| `document.invalid-break` | An authored break offset is out of bounds or splits UTF-8. | +| `document.conflicting-break` | The same offset is both mandatory and prohibited. | +| `document.invalid-construct-range` | A typed structure has an invalid source range. | +| `document.empty-ruby-annotation` | Ruby annotation text is empty. | +| `document.invalid-ruby-run` | An explicit ruby association has an invalid range. | +| `document.group-ruby-run-count` | Explicit group ruby does not contain exactly one association. | +| `document.incomplete-ruby-runs` | Explicit ruby associations do not cover both base and annotation. | +| `document.empty-reference-mark` | A reference-mark annotation is empty. | +| `document.empty-script-annotation` | A subscript or superscript annotation is empty. | +| `document.invalid-furawake-columns` | Furawake requests fewer than two columns. | +| `document.invalid-jidori-cells` | Jidori requests zero cells. | +| `document.construct-crosses-paragraph` | One structure crosses a mandatory paragraph boundary. | +| `document.span-splits-grapheme` | A styled span endpoint splits an extended grapheme. | +| `document.mono-ruby-cluster-count` | Automatic mono ruby cannot map its base and annotation clusters one-to-one. | + +## High-level resource errors + +| Code | Meaning | +| --- | --- | +| `limit.input-bytes` | UTF-8 input exceeds the configured byte maximum. | +| `limit.fonts` | Registered faces exceed the configured count. | +| `limit.font-bytes` | Total registered font data exceeds the configured byte maximum. | +| `limit.paragraphs` | The input contains too many paragraphs. | +| `limit.runs` | Itemization would create too many shaping runs. | +| `limit.glyphs` | Shaping would produce too many glyphs. | +| `limit.constructs` | The typed document has too many inline structures. | +| `limit.core-operations` | Core composition exceeds the facade's operation budget. | ## Input errors @@ -70,5 +115,6 @@ literal in the handwritten product source in both directions. | Code | Meaning | | --- | --- | +| `font.missing-glyph` | No registered face covers a complete grapheme; the primary `.notdef` preserves its range. | | `layout.overfull` | A complete line remains wider than its measure after permitted adjustment. | | `layout.widow` | The complete layout cannot meet the requested final-line cluster minimum. | diff --git a/docs/mutation-ledger.toml b/docs/mutation-ledger.toml index 8ac7e68..f82d410 100644 --- a/docs/mutation-ledger.toml +++ b/docs/mutation-ledger.toml @@ -11,14 +11,14 @@ note = "Pre-0.1.0 audit baseline; release gates require both counts to be zero." [[release_result]] audited = "2026-08-25" -package = "jlreq" +package = "jlreq-core" tested = 1949 caught = 1490 unviable = 459 missed = 0 timeout = 0 equivalent_excluded = 5 -note = "Full 0.1.0 release-candidate run after restoring handwritten generated.rs integrity checks to mutation scope." +note = "Full 0.1.0 core release-candidate run after restoring handwritten generated.rs integrity checks to mutation scope." [[release_result]] audited = "2026-08-25" @@ -32,71 +32,71 @@ equivalent_excluded = 0 note = "Full 0.1.0 release-candidate run; no conformance-source or equivalent-mutant exclusions." [[exclusion]] -path = "crates/jlreq/src/generated/appendix_a.rs" -sha256 = "00761c3eb613d272f3713e9c6b71383540a273021971a2c22b615eed23f8959a" +path = "crates/jlreq-core/src/generated/appendix_a.rs" +sha256 = "0683a89eb19f28155274c1cf1b87846826c8073b2b877a27d7cb628e38588d36" kind = "generated" provenance = "data/manifest.toml" reason = "Generated Appendix A table; just generate-check and just attest verify it byte-for-byte." [[exclusion]] -path = "crates/jlreq/src/generated/folding.rs" -sha256 = "11ed0a1852127cadc94ca98efcc6c51a8daedfb33e10033b07abae6353cc5fe0" +path = "crates/jlreq-core/src/generated/folding.rs" +sha256 = "5d9f8286b5c8c081069d27da4b4c45c92670bc7542b7d973775ea7a934403cc7" kind = "generated" provenance = "data/manifest.toml" reason = "Generated width-folding table; just generate-check and just attest verify it byte-for-byte." [[exclusion]] -path = "crates/jlreq/src/generated/ideograph.rs" -sha256 = "13e3bb408d7148add03d2348c1360f33cdd71a462f0f4291ad8e5f38af41aef8" +path = "crates/jlreq-core/src/generated/ideograph.rs" +sha256 = "52ecfcf55347f2e247df493156fe29c4855ed0858f753c227fe791535c57a4cc" kind = "generated" provenance = "data/manifest.toml" reason = "Generated Unified_Ideograph ranges; just generate-check and just attest verify them byte-for-byte." [[exclusion]] -path = "crates/jlreq/src/generated/script.rs" -sha256 = "ad15e49f34640d3f949ca7e2a3a3f64315ce68266102759051eb4fe12cace6af" +path = "crates/jlreq-core/src/generated/script.rs" +sha256 = "a7e6121bf9cfe63190c4bf3cfccde7b72f640580059fb30e42bf5f0a80fda361" kind = "generated" provenance = "data/manifest.toml" reason = "Generated kana-script ranges; just generate-check and just attest verify them byte-for-byte." [[exclusion]] -path = "crates/jlreq/src/generated/table1.rs" -sha256 = "6de73a04480c3ee32ee59d7d23128aa82783dc0ccbde8559f3be17b77482105c" +path = "crates/jlreq-core/src/generated/table1.rs" +sha256 = "54f879f69b2b47cb816ada14c721a503894e307e7d43c61e796d4fa149933c3a" kind = "generated" provenance = "data/manifest.toml" reason = "Generated JLReq Table 1 cells; just generate-check and just attest verify them byte-for-byte." [[exclusion]] -path = "crates/jlreq/src/generated/table2.rs" -sha256 = "4bb486fd05ae95896b728d26f1b14774a531fd83f7fec05e571530914d59a751" +path = "crates/jlreq-core/src/generated/table2.rs" +sha256 = "76e6e4d3f22f8cbe8b7243e3c1672e787db9c62a0940ac691ed6abc540efa753" kind = "generated" provenance = "data/manifest.toml" reason = "Generated JLReq Table 2 cells; just generate-check and just attest verify them byte-for-byte." [[exclusion]] -path = "crates/jlreq/src/generated/table3.rs" -sha256 = "9427c738b22ee41090a52b9d2d3f0f1c82c1b072bc26660940ede1d394648176" +path = "crates/jlreq-core/src/generated/table3.rs" +sha256 = "d02226e7728d275b4ca60eb641e58bade4dd1a8be6e40b5b7aec864f7476a30c" kind = "generated" provenance = "data/manifest.toml" reason = "Generated JLReq Table 3 cells; just generate-check and just attest verify them byte-for-byte." [[exclusion]] -path = "crates/jlreq/src/generated/table4.rs" -sha256 = "2cc7292a8b5bbeeaa04dc693c02e68012230f7c0aee10fdd3824f0f68f8bbd47" +path = "crates/jlreq-core/src/generated/table4.rs" +sha256 = "3b373bc7dda9b017d9a5e5f927e2fb281ef273e52cee4dea3f9676242bfc8a11" kind = "generated" provenance = "data/manifest.toml" reason = "Generated JLReq Table 4 cells; just generate-check and just attest verify them byte-for-byte." [[exclusion]] -path = "crates/jlreq/src/generated/table5.rs" -sha256 = "ee099abbea8b724d9ffbb2fc5dfed43fa09fac21851d1cf67ff7ca5ce91ba3bb" +path = "crates/jlreq-core/src/generated/table5.rs" +sha256 = "594f6e4c0d1645c4e5ad1d5e38ea46fc1d2739704eb998c52c28b4f7700c8d2d" kind = "generated" provenance = "data/manifest.toml" reason = "Generated JLReq Table 5 cells; just generate-check and just attest verify them byte-for-byte." [[exclusion]] -path = "crates/jlreq/src/generated/table6.rs" -sha256 = "0eeebc30219709e4cdc8d78f36981e0df9e90fe9097550f1ef636aec5fd4d6ea" +path = "crates/jlreq-core/src/generated/table6.rs" +sha256 = "e1ec1925685b6a71dc621cba2056f956d450a9c6e21888d294b360469be4fde1" kind = "generated" provenance = "data/manifest.toml" reason = "Generated JLReq Table 6 cells; just generate-check and just attest verify them byte-for-byte." @@ -106,31 +106,31 @@ reason = "Generated JLReq Table 6 cells; just generate-check and just attest ver # only these five names. [[equivalent]] -mutant = "crates/jlreq/src/generated.rs:35:5: replace | with ^" -exclude_re = '^crates/jlreq/src/generated[.]rs:35:5: replace [|] with \^$' +mutant = "crates/jlreq-core/src/generated.rs:35:5: replace | with ^" +exclude_re = '^crates/jlreq-core/src/generated[.]rs:35:5: replace [|] with \^$' source_sha256 = "d4621a5ba23aa7a94090267395d431a5e6f5817e65539aca3fe2d026a84aedb8" proof = "FRAME_FULL_EM is bit 0 and FRAME_HALF_EM is bit 1, so the accumulated operand and the added bit are disjoint; OR and XOR are identical." [[equivalent]] -mutant = "crates/jlreq/src/generated.rs:36:5: replace | with ^" -exclude_re = '^crates/jlreq/src/generated[.]rs:36:5: replace [|] with \^$' +mutant = "crates/jlreq-core/src/generated.rs:36:5: replace | with ^" +exclude_re = '^crates/jlreq-core/src/generated[.]rs:36:5: replace [|] with \^$' source_sha256 = "d4621a5ba23aa7a94090267395d431a5e6f5817e65539aca3fe2d026a84aedb8" proof = "FRAME_THIRD_EM is bit 2 and does not overlap the accumulated bits 0-1; OR and XOR are identical." [[equivalent]] -mutant = "crates/jlreq/src/generated.rs:37:5: replace | with ^" -exclude_re = '^crates/jlreq/src/generated[.]rs:37:5: replace [|] with \^$' +mutant = "crates/jlreq-core/src/generated.rs:37:5: replace | with ^" +exclude_re = '^crates/jlreq-core/src/generated[.]rs:37:5: replace [|] with \^$' source_sha256 = "d4621a5ba23aa7a94090267395d431a5e6f5817e65539aca3fe2d026a84aedb8" proof = "FRAME_QUARTER_EM is bit 3 and does not overlap the accumulated bits 0-2; OR and XOR are identical." [[equivalent]] -mutant = "crates/jlreq/src/generated.rs:38:5: replace | with ^" -exclude_re = '^crates/jlreq/src/generated[.]rs:38:5: replace [|] with \^$' +mutant = "crates/jlreq-core/src/generated.rs:38:5: replace | with ^" +exclude_re = '^crates/jlreq-core/src/generated[.]rs:38:5: replace [|] with \^$' source_sha256 = "d4621a5ba23aa7a94090267395d431a5e6f5817e65539aca3fe2d026a84aedb8" proof = "FRAME_PROPORTIONAL is bit 4 and does not overlap the accumulated bits 0-3; OR and XOR are identical." [[equivalent]] -mutant = "crates/jlreq/src/generated.rs:55:41: replace < with <= in ascends" -exclude_re = '^crates/jlreq/src/generated[.]rs:55:41: replace < with <= in ascends$' +mutant = "crates/jlreq-core/src/generated.rs:55:41: replace < with <= in ascends" +exclude_re = '^crates/jlreq-core/src/generated[.]rs:55:41: replace < with <= in ascends$' source_sha256 = "d4621a5ba23aa7a94090267395d431a5e6f5817e65539aca3fe2d026a84aedb8" proof = "This return is dominated by before.key[position] != after.key[position]; for unequal integers, < and <= have the same truth value." diff --git a/docs/public-api.toml b/docs/public-api.toml index b59191b..94a3d27 100644 --- a/docs/public-api.toml +++ b/docs/public-api.toml @@ -2,9 +2,10 @@ # # SPDX-License-Identifier: MIT OR Apache-2.0 -# Public names released by jlreq 0.1.0. The `api` gate checks this list in both directions; +# Public names released by the jlreq 0.1.0 facade and core. The `api` gate checks this list in both directions; # every compatible 0.1.x release must preserve it. The `semver` gate reads the release -# contract below and compares later 0.1.x candidates with the latest published jlreq. +# contract below and compares later 0.1.x candidates with the latest published jlreq-core +# and jlreq releases. # Implementation modules remain private. baseline_version = "0.1.0" @@ -12,6 +13,49 @@ compatible_series = "0.1" [[module]] path = "jlreq" +items = [ + "Affinity", + "Alignment", + "AnnotationSource", + "BaseDirection", + "Diagnostic", + "DiagnosticSeverity", + "Document", + "DocumentBuilder", + "FontId", + "FontLibrary", + "FontResource", + "FontSlant", + "FontStyle", + "FontVariation", + "GlyphPlacement", + "GlyphTransform", + "HitTest", + "LayoutEngine", + "LayoutError", + "LayoutOptions", + "OpenTypeFeature", + "OpenTypeTag", + "OptionKind", + "Point", + "Rect", + "Resource", + "ResourceLimits", + "RubyKind", + "RubyRun", + "ScriptPosition", + "SpanStyle", + "TextLayout", + "TextLine", + "TextRole", + "WritingMode", + "core", + "layout", + "layout_document", +] + +[[module]] +path = "jlreq_core" items = [ "Alignment", "Attachment", @@ -52,7 +96,7 @@ items = [ ] [[module]] -path = "jlreq::style" +path = "jlreq_core::style" items = [ "AdjustmentPreference", "AmbiguousContext", diff --git a/engines/ocaml/README.md b/engines/ocaml/README.md index 56d631e..5c3b53c 100644 --- a/engines/ocaml/README.md +++ b/engines/ocaml/README.md @@ -235,7 +235,7 @@ This is the point of the exercise, so it is worth stating precisely. **May not be read into this tree** — the Rust implementation: - the layout logic in `crates/jlreq/src/` (`pipeline.rs` above all); -- `crates/jlreq/src/generated/`, and the xtask code generators that write it. +- `crates/jlreq-core/src/generated/`, and the xtask code generators that write it. Where the two engines disagree, the disagreement is settled by returning to JLReq and to `spec/`, and recorded in `docs/decisions/`. It is never settled by reading @@ -247,7 +247,7 @@ this second implementation is for. The exception the rule leaves open is a policy that is *observable* — two engines have to agree on it to pass the same case — and stated in no sentence of JLReq and -no file under `docs/`. Those are read from `crates/jlreq/src/pipeline.rs`, never +no file under `docs/`. Those are read from `crates/jlreq-core/src/pipeline.rs`, never transcribed. Twenty-six of them were found while this engine was written: two from §3.8.4's diff --git a/engines/racket/README.md b/engines/racket/README.md index ff833df..acf3fe0 100644 --- a/engines/racket/README.md +++ b/engines/racket/README.md @@ -209,7 +209,7 @@ OCaml one is already finished and green on all eighty-nine cases. **May not be read into this tree:** - the layout logic under `crates/jlreq/src/` (`pipeline.rs` above all), and - `crates/jlreq/src/generated/`; + `crates/jlreq-core/src/generated/`; - **the bodies of `engines/ocaml/lib/`**, `pipeline.ml` and `spec.ml` above all. A line-by-line translation of a finished engine is not an independent implementation: it would agree with the OCaml engine exactly where the OCaml diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 99bef48..fdf3a18 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -8,6 +8,32 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "cc" version = "1.4.2" @@ -26,12 +52,50 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "find-msvc-tools" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" +[[package]] +name = "font-test-data" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8250a122865fc22377a26c0bb273bc4a69914409b4cf4a9b4cd7d7c6c5b1a0d7" +dependencies = [ + "font-types", +] + +[[package]] +name = "font-types" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64eb721ca85a34323425f4041adc5d82704d3782d5f8f03793bc012419dce23" +dependencies = [ + "bytemuck", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -43,6 +107,107 @@ dependencies = [ "r-efi", ] +[[package]] +name = "harfrust" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "948d0741125ba89cd3e1c23e5642415b6ade7e1d29d67ba25fb925b533e989d6" +dependencies = [ + "bitflags", + "bytemuck", + "once_cell", + "read-fonts", + "smallvec", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" +dependencies = [ + "icu_locale_core", + "icu_locale_fallback_data", + "icu_provider", + "potential_utf", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locale_fallback_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "serde", + "stable_deref_trait", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_segmenter" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d07aafccd67af15d02512a6adf5896fbc5ed00f2e99b471d2efa14016db3db" +dependencies = [ + "core_maths", + "icu_collections", + "icu_locale_fallback", + "icu_provider", + "icu_segmenter_data", + "potential_utf", + "smallvec", + "utf8_iter", + "zerovec", +] + +[[package]] +name = "icu_segmenter_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad" + [[package]] name = "itoa" version = "1.0.18" @@ -52,12 +217,24 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jlreq" version = "0.1.0" +dependencies = [ + "harfrust", + "icu_segmenter", + "jlreq-core", + "unicode-bidi", +] + +[[package]] +name = "jlreq-core" +version = "0.1.0" [[package]] name = "jlreq-fuzz" version = "0.1.0" dependencies = [ + "font-test-data", "jlreq", + "jlreq-core", "libfuzzer-sys", "serde_json", ] @@ -88,12 +265,41 @@ dependencies = [ "cc", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + [[package]] name = "memchr" version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "serde_core", + "writeable", + "zerovec", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -118,6 +324,17 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "read-fonts" +version = "0.43.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005c8acf251756c478b0bf402885bfd88a1476020c4c7e6060edc1aa68da38ea" +dependencies = [ + "bytemuck", + "font-types", + "once_cell", +] + [[package]] name = "serde" version = "1.0.229" @@ -125,6 +342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -144,7 +362,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.4", ] [[package]] @@ -166,6 +384,29 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "3.0.4" @@ -177,12 +418,129 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "zmij" version = "1.0.23" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index fd97933..cdc2c55 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -13,7 +13,9 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "=0.4.13" +font-test-data = "=0.9.1" jlreq = { path = "../crates/jlreq" } +jlreq-core = { path = "../crates/jlreq-core" } serde_json = "1.0" [[bin]] @@ -37,5 +39,12 @@ test = false doc = false bench = false +[[bin]] +name = "high_level_layout" +path = "fuzz_targets/high_level_layout.rs" +test = false +doc = false +bench = false + [workspace] members = ["."] diff --git a/fuzz/fuzz_targets/composition.rs b/fuzz/fuzz_targets/composition.rs index 18ff219..b895789 100644 --- a/fuzz/fuzz_targets/composition.rs +++ b/fuzz/fuzz_targets/composition.rs @@ -6,7 +6,7 @@ use std::hint::black_box; -use jlreq::{ +use jlreq_core::{ Alignment, Break, Cluster, Composer, CompositionLimits, Construct, Frame, Paragraph, Ruby, RubyKind, RubyRun, ShapedText, Size, Style, TabAlignment, TabStop, Widow, WritingMode, }; diff --git a/fuzz/fuzz_targets/high_level_layout.rs b/fuzz/fuzz_targets/high_level_layout.rs new file mode 100644 index 0000000..41b4a3d --- /dev/null +++ b/fuzz/fuzz_targets/high_level_layout.rs @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#![no_main] + +use std::hint::black_box; +use std::sync::Arc; + +use jlreq::{BaseDirection, FontLibrary, LayoutEngine, LayoutOptions, ResourceLimits, WritingMode}; +use libfuzzer_sys::fuzz_target; + +const MAX_TEXT_BYTES: usize = 16 * 1024; + +fuzz_target!(|data: &[u8]| { + let controls = data.get(..8).unwrap_or(data); + let body = data.get(controls.len()..).unwrap_or_default(); + let text_bytes = body.get(..body.len().min(MAX_TEXT_BYTES)).unwrap_or_default(); + let text = String::from_utf8_lossy(text_bytes); + let byte = |index: usize| controls.get(index).copied().unwrap_or_default(); + let line_extent = match byte(0) % 5 { + 0 => f32::NAN, + 1 => f32::INFINITY, + 2 => 0.0, + 3 => 1.0, + _ => f32::from(byte(1)).mul_add(8.0, 16.0), + }; + let font_size = match byte(2) % 4 { + 0 => f32::NEG_INFINITY, + 1 => 0.0, + 2 => 1.0, + _ => f32::from(byte(3)).mul_add(0.25, 4.0), + }; + let Ok(options) = LayoutOptions::try_new(line_extent, font_size) else { + return; + }; + let options = options + .writing_mode(if byte(4) & 1 == 0 { + WritingMode::HorizontalTb + } else { + WritingMode::VerticalRl + }) + .base_direction(match byte(5) % 3 { + 0 => BaseDirection::Auto, + 1 => BaseDirection::LeftToRight, + _ => BaseDirection::RightToLeft, + }) + .limits( + ResourceLimits::default() + .with_max_input_bytes(MAX_TEXT_BYTES) + .with_max_fonts(4) + .with_max_font_bytes(2 * 1024 * 1024) + .with_max_paragraphs(512) + .with_max_runs(16 * 1024) + .with_max_glyphs(64 * 1024) + .with_max_constructs(512) + .with_max_core_operations(250_000), + ); + + let mut valid_fonts = FontLibrary::new(); + if valid_fonts + .register_font(Arc::<[u8]>::from(font_test_data::NOTO_SANS_JP_CFF)) + .is_ok() + { + let mut engine = LayoutEngine::new(); + let _ = black_box(engine.layout(&text, &valid_fonts, options.clone())); + let _ = black_box(engine.layout("再利用", &valid_fonts, options.clone())); + } + + let mut arbitrary_fonts = FontLibrary::new(); + if arbitrary_fonts + .register_font(Arc::<[u8]>::from(data)) + .is_ok() + { + let _ = black_box(jlreq::layout(&text, &arbitrary_fonts, options)); + } +}); diff --git a/fuzz/fuzz_targets/input_validation.rs b/fuzz/fuzz_targets/input_validation.rs index 314ab84..0a3f918 100644 --- a/fuzz/fuzz_targets/input_validation.rs +++ b/fuzz/fuzz_targets/input_validation.rs @@ -6,7 +6,7 @@ use std::hint::black_box; -use jlreq::{ +use jlreq_core::{ Alignment, Break, Cluster, Construct, Frame, Paragraph, Ruby, RubyKind, RubyRun, ShapedText, Size, Style, TabAlignment, TabStop, Widow, WritingMode, }; diff --git a/fuzz/seeds/high_level_layout/basic b/fuzz/seeds/high_level_layout/basic new file mode 100644 index 0000000..01900c7 --- /dev/null +++ b/fuzz/seeds/high_level_layout/basic @@ -0,0 +1 @@ +日本語 Arabic 123 diff --git a/release-plz.toml b/release-plz.toml index a8716d9..4fcae9e 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -14,16 +14,30 @@ git_release_enable = false changelog_update = false [[package]] -name = "jlreq" +name = "jlreq-core" version_group = "jlreq" changelog_update = false changelog_path = "./CHANGELOG.md" -changelog_include = ["jlreq", "jlreq-conformance"] +changelog_include = ["jlreq-core", "jlreq", "jlreq-conformance"] git_tag_enable = false git_tag_name = "v{{ version }}" git_release_enable = false git_release_draft = false +[[package]] +name = "jlreq" +version_group = "jlreq" +changelog_update = false +changelog_path = "./CHANGELOG.md" +changelog_include = ["jlreq-core", "jlreq", "jlreq-conformance"] +git_tag_enable = false +git_release_enable = false + [[package]] name = "jlreq-conformance" version_group = "jlreq" +changelog_update = false +changelog_path = "./CHANGELOG.md" +changelog_include = ["jlreq-core", "jlreq", "jlreq-conformance"] +git_tag_enable = false +git_release_enable = false diff --git a/scripts/check-semver.sh b/scripts/check-semver.sh index 18d5bc7..85a0209 100644 --- a/scripts/check-semver.sh +++ b/scripts/check-semver.sh @@ -20,9 +20,21 @@ contract_value() { baseline_version=$(contract_value baseline_version) compatible_series=$(contract_value compatible_series) -package_id=$(cargo pkgid -p jlreq) -current_version=${package_id##*#} -current_version=${current_version##*@} + +package_version() { + package_id=$(cargo pkgid -p "$1") + version=${package_id##*#} + printf '%s\n' "${version##*@}" +} + +current_version=$(package_version jlreq) +for package in jlreq-core jlreq-conformance; do + version=$(package_version "$package") + if [ "$version" != "$current_version" ]; then + echo "semver: $package $version does not match jlreq $current_version" >&2 + exit 2 + fi +done case "$baseline_version" in "$compatible_series".*) ;; @@ -35,7 +47,7 @@ esac case "$current_version" in "$baseline_version" | "$compatible_series".*) ;; *) - echo "semver: jlreq $current_version is outside the $compatible_series.x contract in $control" >&2 + echo "semver: workspace products at $current_version are outside the $compatible_series.x contract in $control" >&2 echo "semver: review and update the release-line policy before continuing" >&2 exit 2 ;; @@ -55,25 +67,29 @@ if [ -n "${JLREQ_SEMVER_BASELINE_ROOT:-}" ]; then # The current tree's documentation warnings are already denied by `just doc`. Do not # make a later compiler's new warning in an immutable registry baseline masquerade as # a semantic-versioning failure. - RUSTDOCFLAGS='' cargo semver-checks check-release \ - --manifest-path crates/jlreq/Cargo.toml \ - --package jlreq \ - --baseline-root "$JLREQ_SEMVER_BASELINE_ROOT" \ - --release-type patch \ - --all-features + for package in jlreq-core jlreq; do + RUSTDOCFLAGS='' cargo semver-checks check-release \ + --manifest-path "crates/$package/Cargo.toml" \ + --package "$package" \ + --baseline-root "$JLREQ_SEMVER_BASELINE_ROOT" \ + --release-type patch \ + --all-features + done exit 0 fi if [ "$current_version" = "$baseline_version" ]; then - echo "semver: $current_version is the initial baseline; registry comparison begins with the next $compatible_series.x candidate" + echo "semver: all three products are at the initial $current_version baseline; registry comparison for jlreq-core and jlreq begins with the next $compatible_series.x candidate" exit 0 fi # For later 0.1.x candidates cargo-semver-checks resolves the latest normal, non-yanked # crates.io version. Comparing with the latest release also protects API added in an # intermediate patch release, not only the original 0.1.0 surface. -RUSTDOCFLAGS='' cargo semver-checks check-release \ - --manifest-path crates/jlreq/Cargo.toml \ - --package jlreq \ - --release-type patch \ - --all-features +for package in jlreq-core jlreq; do + RUSTDOCFLAGS='' cargo semver-checks check-release \ + --manifest-path "crates/$package/Cargo.toml" \ + --package "$package" \ + --release-type patch \ + --all-features +done diff --git a/scripts/finalize-release.sh b/scripts/finalize-release.sh new file mode 100644 index 0000000..42db4d8 --- /dev/null +++ b/scripts/finalize-release.sh @@ -0,0 +1,100 @@ +#!/bin/sh +# SPDX-FileCopyrightText: 2026 jlreq contributors +# SPDX-License-Identifier: MIT OR Apache-2.0 + +set -eu + +if [ "$#" -ne 1 ]; then + echo "usage: $0 YYYY-MM-DD" >&2 + exit 2 +fi + +release_date=$1 +case "$release_date" in + [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ;; + *) + echo "finalize-release: date must be YYYY-MM-DD" >&2 + exit 2 + ;; +esac + +if ! awk -v date="$release_date" ' +BEGIN { + split(date, part, "-") + year = part[1] + 0 + month = part[2] + 0 + day = part[3] + 0 + leap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) + days[1] = 31 + days[2] = leap ? 29 : 28 + days[3] = 31 + days[4] = 30 + days[5] = 31 + days[6] = 30 + days[7] = 31 + days[8] = 31 + days[9] = 30 + days[10] = 31 + days[11] = 30 + days[12] = 31 + exit !(year >= 2000 && month >= 1 && month <= 12 && day >= 1 && day <= days[month]) +} +'; then + echo "finalize-release: $release_date is not a valid calendar date" >&2 + exit 2 +fi + +root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +changelog=$root/CHANGELOG.md +version=$(sed -n 's/^version = "\([^"]*\)"$/\1/p' "$root/Cargo.toml" | head -n 1) +test -n "$version" || { + echo "finalize-release: could not read workspace version" >&2 + exit 2 +} + +unreleased_count=$(grep -Fxc '## [Unreleased]' "$changelog" || true) +if [ "$unreleased_count" -ne 1 ]; then + echo "finalize-release: CHANGELOG.md must contain exactly one Unreleased heading" >&2 + exit 2 +fi +if grep -Fq "## [$version]" "$changelog" || grep -Fq "[$version]:" "$changelog"; then + echo "finalize-release: $version is already finalized in CHANGELOG.md" >&2 + exit 2 +fi + +scratch=$(mktemp "$root/CHANGELOG.md.finalize.XXXXXX") +cleanup() { + rm -f "$scratch" +} +trap cleanup EXIT HUP INT TERM + +awk -v version="$version" -v date="$release_date" ' +$0 == "## [Unreleased]" { + print + print "" + print "Nothing yet." + print "" + print "## [" version "] - " date + skip_blank = 1 + next +} +skip_blank && $0 == "" { + skip_blank = 0 + next +} +skip_blank { + skip_blank = 0 +} +$0 ~ /^\[Unreleased\]:/ { + print "[Unreleased]: https://github.com/P4suta/jlreq/compare/v" version "...HEAD" + print "[" version "]: https://github.com/P4suta/jlreq/releases/tag/v" version + next +} +{ + print +} +' "$changelog" >"$scratch" + +mv "$scratch" "$changelog" +trap - EXIT HUP INT TERM +echo "finalize-release: prepared $version for $release_date; review and commit CHANGELOG.md" diff --git a/scripts/run-mutation-smoke.sh b/scripts/run-mutation-smoke.sh index efb71a0..1d68d67 100644 --- a/scripts/run-mutation-smoke.sh +++ b/scripts/run-mutation-smoke.sh @@ -26,7 +26,8 @@ if [ ! -s "$diff_file" ]; then exit 0 fi -cargo mutants -p jlreq -p jlreq-conformance --in-diff "$diff_file" \ +cargo mutants -p jlreq -p jlreq-core -p jlreq-conformance --in-diff "$diff_file" \ + --all-features \ --test-tool cargo \ --minimum-test-timeout 120 \ --no-times \ diff --git a/scripts/verify-crates.sh b/scripts/verify-crates.sh index 293bb84..a6af402 100755 --- a/scripts/verify-crates.sh +++ b/scripts/verify-crates.sh @@ -1,69 +1,88 @@ -#!/bin/sh +#!/usr/bin/env sh # SPDX-FileCopyrightText: 2026 jlreq contributors # SPDX-License-Identifier: MIT OR Apache-2.0 set -eu root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) -version=0.1.0 -package_dir=$root/target/package +version=$(sed -n 's/^version = "\([^"]*\)"$/\1/p' "$root/Cargo.toml" | head -n 1) +test -n "$version" || { + echo "could not read workspace version" >&2 + exit 1 +} + audit=$(mktemp -d "${TMPDIR:-/tmp}/jlreq-crates.XXXXXX") -trap 'rm -r "$audit"' EXIT HUP INT TERM +trap 'rm -rf "$audit"' EXIT HUP INT TERM +mkdir -p "$audit/cargo-home" "$audit/install" -for package in jlreq jlreq-conformance; do - archive=$package_dir/$package-$version.crate +# Populate a brand-new Cargo home from the frozen workspace lockfile before any archive is +# opened. Every package check below is offline, so a passing run cannot borrow the developer's +# registry/index cache or hide a newly introduced dependency. +CARGO_HOME="$audit/cargo-home" cargo fetch --locked --manifest-path "$root/Cargo.toml" + +for package in jlreq-core jlreq jlreq-conformance; do + archive="$root/target/package/$package-$version.crate" test -f "$archive" || { - echo "missing crate archive: $archive" >&2 + echo "missing package archive: $archive" >&2 exit 1 } - size=$(wc -c < "$archive" | tr -d ' ') - test "$size" -le 5242880 || { - echo "$archive exceeds the 5 MiB release limit ($size bytes)" >&2 - exit 1 - } - if tar -tzf "$archive" | grep -Eq '(^/|(^|/)\.\.(/|$))'; then - echo "$archive contains an unsafe path" >&2 - exit 1 - fi - for required in Cargo.toml README.md LICENSE-MIT LICENSE-APACHE; do - tar -tzf "$archive" | grep -Fx "$package-$version/$required" >/dev/null || { - echo "$archive omits $required" >&2 + tar -xzf "$archive" -C "$audit" + for required in Cargo.toml Cargo.toml.orig README.md LICENSE-MIT LICENSE-APACHE; do + test -f "$audit/$package-$version/$required" || { + echo "$package archive omits $required" >&2 exit 1 } done - tar -xzf "$archive" -C "$audit" done -for required in protocol.schema.json suite.ndjson; do - test -f "$audit/jlreq-conformance-$version/$required" || { - echo "jlreq-conformance archive omits $required" >&2 - exit 1 - } -done +core="$audit/jlreq-core-$version" +facade="$audit/jlreq-$version" +conformance="$audit/jlreq-conformance-$version" +core_patch="patch.crates-io.jlreq-core.path=\"$core\"" -cargo test --manifest-path "$audit/jlreq-$version/Cargo.toml" --all-targets --offline -cargo test --manifest-path "$audit/jlreq-$version/Cargo.toml" --doc --offline +CARGO_HOME="$audit/cargo-home" cargo test --manifest-path "$core/Cargo.toml" \ + --all-targets --offline +CARGO_HOME="$audit/cargo-home" cargo test --manifest-path "$core/Cargo.toml" \ + --doc --offline -patch="patch.crates-io.jlreq.path=\"$audit/jlreq-$version\"" -cargo test --manifest-path "$audit/jlreq-conformance-$version/Cargo.toml" --all-targets \ - --offline --config "$patch" -# A binary-only package has no Cargo doctest target. Building its rustdoc proves that the -# package documentation target itself is complete and valid in the extracted archive. -cargo doc --manifest-path "$audit/jlreq-conformance-$version/Cargo.toml" --no-deps \ - --offline --config "$patch" -cargo install --path "$audit/jlreq-conformance-$version" --root "$audit/install" \ - --locked --offline --config "$patch" +CARGO_HOME="$audit/cargo-home" cargo test --manifest-path "$facade/Cargo.toml" \ + --all-targets --all-features --offline --config "$core_patch" +CARGO_HOME="$audit/cargo-home" cargo test --manifest-path "$facade/Cargo.toml" \ + --doc --all-features --offline --config "$core_patch" +CARGO_HOME="$audit/cargo-home" cargo doc --manifest-path "$facade/Cargo.toml" \ + --no-deps --all-features --offline --config "$core_patch" + +CARGO_HOME="$audit/cargo-home" cargo test --manifest-path "$conformance/Cargo.toml" \ + --all-targets --all-features --offline --config "$core_patch" +CARGO_HOME="$audit/cargo-home" cargo doc --manifest-path "$conformance/Cargo.toml" \ + --no-deps --all-features --offline --config "$core_patch" +CARGO_HOME="$audit/cargo-home" cargo install --path "$conformance" --root "$audit/install" \ + --locked --offline --config "$core_patch" suffix= -case $(uname -s) in - MINGW*|MSYS*|CYGWIN*) suffix=.exe ;; -esac +if [ "${OS:-}" = Windows_NT ]; then + suffix=.exe +fi "$audit/install/bin/jlreq-conformance$suffix" --version | grep -Fx \ "jlreq-conformance $version" >/dev/null "$audit/install/bin/jlreq-conformance$suffix" --help >/dev/null "$audit/install/bin/jlreq-sample-engine$suffix" --help >/dev/null 2>&1 || { - status=$? - test "$status" -eq 2 + echo "installed sample engine did not accept --help" >&2 + exit 1 } -echo "crate archives verified: jlreq $version and jlreq-conformance $version" +dist="$root/target/dist" +mkdir -p "$dist" +for package in jlreq-core jlreq jlreq-conformance; do + cp "$root/target/package/$package-$version.crate" "$dist/" +done +( + cd "$dist" + sha256sum \ + "jlreq-core-$version.crate" \ + "jlreq-$version.crate" \ + "jlreq-conformance-$version.crate" >"jlreq-$version-crates.sha256" + sha256sum --check --strict "jlreq-$version-crates.sha256" +) + +echo "crate archives verified offline from an isolated Cargo home: jlreq-core, jlreq, jlreq-conformance $version" diff --git a/scripts/verify-mutation-ledger.sh b/scripts/verify-mutation-ledger.sh index c224ada..acc1e19 100644 --- a/scripts/verify-mutation-ledger.sh +++ b/scripts/verify-mutation-ledger.sh @@ -117,7 +117,7 @@ if grep -Eq '^glob = ' "$ledger"; then fail "generated exclusions must name individual paths, not a broad glob" fi -find crates/jlreq/src/generated -maxdepth 1 -type f -name '*.rs' -print | +find crates/jlreq-core/src/generated -maxdepth 1 -type f -name '*.rs' -print | LC_ALL=C sort >"$scratch/generated-files.txt" cut -f 1 "$scratch/exclusions.tsv" | LC_ALL=C sort >"$scratch/excluded-files.txt" if ! cmp -s "$scratch/generated-files.txt" "$scratch/excluded-files.txt"; then @@ -142,10 +142,10 @@ while IFS="$tab" read -r path expected kind provenance reason; do test "$actual" = "$expected" || fail "$path changed: expected $expected, observed $actual" done <"$scratch/exclusions.tsv" -if ! grep -Fqx 'exclude_globs = ["crates/jlreq/src/generated/**"]' "$config"; then +if ! grep -Fqx 'exclude_globs = ["crates/jlreq-core/src/generated/**"]' "$config"; then fail "$config must exclude the generated table directory exactly" fi -if grep -Fq '"crates/jlreq/src/generated.rs"' "$config"; then +if grep -Fq '"crates/jlreq-core/src/generated.rs"' "$config"; then fail "$config excludes the handwritten generated.rs integrity checks" fi diff --git a/scripts/verify-release-state.sh b/scripts/verify-release-state.sh index d72723c..3e8377c 100755 --- a/scripts/verify-release-state.sh +++ b/scripts/verify-release-state.sh @@ -14,20 +14,41 @@ test -f docs/error-codes.md test -f docs/mutation-ledger.toml test -f .github/workflows/release.yml test -f .github/workflows/release-check.yml +test -f .github/REPOSITORY-SETTINGS.md +test -f LICENSE-MIT +test -f LICENSE-APACHE grep -F 'version = "0.1.0"' Cargo.toml >/dev/null -grep -F 'jlreq = { version = "0.1.0", path = "../jlreq" }' \ +grep -F 'jlreq-core = { version = "0.1.0", path = "../jlreq-core" }' \ + crates/jlreq/Cargo.toml >/dev/null +grep -F 'jlreq-core = { version = "0.1.0", path = "../jlreq-core" }' \ crates/jlreq-conformance/Cargo.toml >/dev/null -if grep -F 'publish = false' crates/jlreq/Cargo.toml crates/jlreq-conformance/Cargo.toml; then +if grep -F 'publish = false' crates/jlreq/Cargo.toml crates/jlreq-core/Cargo.toml \ + crates/jlreq-conformance/Cargo.toml; then echo "release crates must be publishable" >&2 exit 1 fi + +for package in jlreq-core jlreq jlreq-conformance; do + test -f "target/dist/$package-0.1.0.crate" || { + echo "missing verified prerelease archive for $package" >&2 + exit 1 + } +done +( + cd target/dist + sha256sum --check --strict jlreq-0.1.0-crates.sha256 +) +test -z "$(git tag --list v0.1.0)" || { + echo "v0.1.0 tag must not exist during prerelease preparation" >&2 + exit 1 +} if grep -Eq '^git_(tag|release)_enable = true$' release-plz.toml; then echo "release-plz must remain externally inert during preparation" >&2 exit 1 fi -git diff --exit-code -- crates/jlreq/src/generated data/manifest.toml \ +git diff --exit-code -- crates/jlreq-core/src/generated data/manifest.toml \ docs/generated/conformance-summary.md echo "0.1.0 release state is internally consistent; no publication was performed" diff --git a/spec/derived/appendix-a.tsv b/spec/derived/appendix-a.tsv index ffd7ba3..cd05641 100644 --- a/spec/derived/appendix-a.tsv +++ b/spec/derived/appendix-a.tsv @@ -10,7 +10,7 @@ # Source: spec/snapshot/index.html # Source SHA-256: cfb1def5bf1594d59080a26d8d03c2670bb3116490ab57ef6e131b166a7c832f # Reader: xtask/src/classes.rs, xtask/src/derive.rs -# Reader SHA-256: 673b41ade1f0288a4217fb6569825225aeae5ce15032aa613602083e3d223982 +# Reader SHA-256: aa04cdfeac9951ff411834cab798953c48116aeabdfee532265b930dbbc57f1b class key remark-en remark-ja cl-01 2018 used in horizontal composition 横組で使用 diff --git a/spec/derived/classes.tsv b/spec/derived/classes.tsv index 35c2912..27f8f11 100644 --- a/spec/derived/classes.tsv +++ b/spec/derived/classes.tsv @@ -10,7 +10,7 @@ # Source: spec/snapshot/index.html # Source SHA-256: cfb1def5bf1594d59080a26d8d03c2670bb3116490ab57ef6e131b166a7c832f # Reader: xtask/src/classes.rs, xtask/src/derive.rs -# Reader SHA-256: 673b41ade1f0288a4217fb6569825225aeae5ce15032aa613602083e3d223982 +# Reader SHA-256: aa04cdfeac9951ff411834cab798953c48116aeabdfee532265b930dbbc57f1b class name_en name_ja enumeration cl-01 Opening brackets 始め括弧類 A.1 diff --git a/spec/derived/folding.tsv b/spec/derived/folding.tsv index 4ca42a9..5b30cbe 100644 --- a/spec/derived/folding.tsv +++ b/spec/derived/folding.tsv @@ -10,7 +10,7 @@ # Source: spec/snapshot/ucd/UnicodeData.txt # Source SHA-256: 2e1efc1dcb59c575eedf5ccae60f95229f706ee6d031835247d843c11d96470c # Reader: xtask/src/classes.rs, xtask/src/derive.rs -# Reader SHA-256: 673b41ade1f0288a4217fb6569825225aeae5ce15032aa613602083e3d223982 +# Reader SHA-256: aa04cdfeac9951ff411834cab798953c48116aeabdfee532265b930dbbc57f1b source target frame 3000 0020 full-em diff --git a/spec/derived/ideographs.tsv b/spec/derived/ideographs.tsv index f734138..cfe4087 100644 --- a/spec/derived/ideographs.tsv +++ b/spec/derived/ideographs.tsv @@ -10,7 +10,7 @@ # Source: spec/snapshot/ucd/PropList.txt # Source SHA-256: 130dcddcaadaf071008bdfce1e7743e04fdfbc910886f017d9f9ac931d8c64dd # Reader: xtask/src/classes.rs, xtask/src/derive.rs -# Reader SHA-256: 673b41ade1f0288a4217fb6569825225aeae5ce15032aa613602083e3d223982 +# Reader SHA-256: aa04cdfeac9951ff411834cab798953c48116aeabdfee532265b930dbbc57f1b first last 3400 4DBF diff --git a/spec/derived/scripts.tsv b/spec/derived/scripts.tsv index 2fd9fa5..83a4c46 100644 --- a/spec/derived/scripts.tsv +++ b/spec/derived/scripts.tsv @@ -10,7 +10,7 @@ # Source: spec/snapshot/ucd/Scripts.txt # Source SHA-256: 9f5e50d3abaee7d6ce09480f325c706f485ae3240912527e651954d2d6b035bf # Reader: xtask/src/classes.rs, xtask/src/derive.rs -# Reader SHA-256: 673b41ade1f0288a4217fb6569825225aeae5ce15032aa613602083e3d223982 +# Reader SHA-256: aa04cdfeac9951ff411834cab798953c48116aeabdfee532265b930dbbc57f1b script first last Hiragana 3041 3096 diff --git a/typos.toml b/typos.toml index 724b185..f5dba92 100644 --- a/typos.toml +++ b/typos.toml @@ -29,6 +29,8 @@ Nd = "Nd" # General_Category=Nd, the decimal digits [default.extend-identifiers] # The 7-Zip bare-output switch used when auditing Windows release archives. ba = "ba" +# Registered OpenType variation axis tag (width). +wdth = "wdth" [files] extend-exclude = [ diff --git a/xtask/src/api.rs b/xtask/src/api.rs index 59c0d03..238f1fe 100644 --- a/xtask/src/api.rs +++ b/xtask/src/api.rs @@ -81,7 +81,7 @@ //! a `pub use` re-exports it. That is the outer bound and it fails closed — a `pub` type is //! one export line away from an adopter's hands, so holding it to the frozen shape now is //! what keeps the shape from being decided by that line. The 0.1.0 surface governed by the -//! active path is the sole `jlreq` library and the `jlreq::style` namespace, compared +//! active path covers the `jlreq` facade plus the `jlreq_core` root and style namespace, compared //! exactly with `docs/public-api.toml`. use std::collections::{BTreeMap, BTreeSet}; @@ -94,7 +94,7 @@ use crate::shared::{self, Gate}; /// The `api` gate, as the dispatcher sees it. pub(crate) const GATE: Gate = Gate { name: "api", - purpose: "jlreq matches docs/public-api.toml and its 22 typed Style mappings exactly", + purpose: "jlreq and jlreq-core match docs/public-api.toml and all typed Style mappings exactly", reference: concat!( "docs/adr/0012-outcome-and-detail-compatibility.md ", "and docs/design/api-spine.md" @@ -132,7 +132,7 @@ fn run(arguments: &[String]) -> io::Result> { } check_policy_space(&root, &mut violations)?; check_zero_one_zero_allowlist(&root, &mut violations)?; - println!("api: checked the sole public Rust crate against docs/public-api.toml"); + println!("api: checked both public Rust libraries against docs/public-api.toml"); Ok(violations) } @@ -143,7 +143,7 @@ fn run(arguments: &[String]) -> io::Result> { /// One public module whose directly exported item names are frozen for 0.1.0. #[derive(Debug)] struct AllowedModule { - /// `jlreq` for the crate root, or a public module path such as `jlreq::style`. + /// A crate root, or a public module path such as `jlreq_core::style`. path: String, /// Every directly nameable item in that module, in no significant order. items: BTreeSet, @@ -154,7 +154,7 @@ struct AllowedModule { struct StyleChoiceMapping { /// Stable dotted path from `spec/derived/questions.tsv`. question: String, - /// Public enum in `jlreq::style`. + /// Public enum in `jlreq_core::style`. rust_type: String, /// Number of choices the dated specification records. count: usize, @@ -255,7 +255,7 @@ fn check_style_choice_mappings( } if !style_items.contains(&mapping.rust_type) { violations.push(format!( - "docs/public-api.toml maps `{}` to `{}`, which jlreq::style does not export", + "docs/public-api.toml maps `{}` to `{}`, which jlreq_core::style does not export", mapping.question, mapping.rust_type )); } @@ -316,25 +316,38 @@ fn check_allowed_items(allowed: &AllowedModule, source: &str) -> Vec { violations } -/// Hold the only public Rust crate to the root and style-module names frozen for 0.1.0. +/// Hold both public Rust crates to the root and style-module names frozen for 0.1.0. fn check_zero_one_zero_allowlist(root: &Path, violations: &mut Vec) -> io::Result<()> { let modules = allowed_modules(root)?; for module in &modules { - let relative = match module.path.as_str() { - "jlreq" => "lib.rs".to_owned(), - path if path.starts_with("jlreq::") => { + let (crate_directory, relative) = match module.path.as_str() { + "jlreq" => ("jlreq", "lib.rs".to_owned()), + path if path.starts_with("jlreq::") => ( + "jlreq", format!( "{}.rs", path.trim_start_matches("jlreq::").replace("::", "/") - ) - }, + ), + ), + "jlreq_core" => ("jlreq-core", "lib.rs".to_owned()), + path if path.starts_with("jlreq_core::") => ( + "jlreq-core", + format!( + "{}.rs", + path.trim_start_matches("jlreq_core::").replace("::", "/") + ), + ), path => { return Err(malformed(format!( - "docs/public-api.toml names `{path}`; the only public Rust crate is `jlreq`" + "docs/public-api.toml names `{path}`; public Rust crates are `jlreq` and `jlreq_core`" ))); }, }; - let source_path = root.join("crates").join("jlreq").join("src").join(relative); + let source_path = root + .join("crates") + .join(crate_directory) + .join("src") + .join(relative); let source = fs::read_to_string(source_path)?; violations.extend(check_allowed_items(module, &source)); } @@ -2622,9 +2635,11 @@ fn check_policy_space(root: &Path, violations: &mut Vec) -> io::Result<( let modules = allowed_modules(root)?; let style_items = modules .iter() - .find(|module| module.path == "jlreq::style") + .find(|module| module.path == "jlreq_core::style") .map(|module| &module.items) - .ok_or_else(|| malformed("docs/public-api.toml has no `jlreq::style` module".to_owned()))?; + .ok_or_else(|| { + malformed("docs/public-api.toml has no `jlreq_core::style` module".to_owned()) + })?; let derived = derived_questions(root)?.ok_or_else(|| { malformed(format!( "{POLICY_SPACE} does not exist, so the typed Style mapping cannot be checked" diff --git a/xtask/src/classes.rs b/xtask/src/classes.rs index 37901cb..a6de75b 100644 --- a/xtask/src/classes.rs +++ b/xtask/src/classes.rs @@ -1375,7 +1375,7 @@ pub(crate) const APPENDIX_A_TABLE: Unit = Unit { pub(crate) const UNIFIED_APPENDIX_A_TABLE: Unit = Unit { input: "spec/derived/appendix-a.tsv", generator: &["xtask/src/classes.rs"], - output: "crates/jlreq/src/generated/appendix_a.rs", + output: "crates/jlreq-core/src/generated/appendix_a.rs", summary: "Appendix A: every enumerated key, the classes naming it, and the Remarks cell.", emit: emit_appendix_a, }; @@ -1403,7 +1403,7 @@ pub(crate) const IDEOGRAPH_TABLE: Unit = Unit { pub(crate) const UNIFIED_IDEOGRAPH_TABLE: Unit = Unit { input: "spec/derived/ideographs.tsv", generator: &["xtask/src/classes.rs"], - output: "crates/jlreq/src/generated/ideograph.rs", + output: "crates/jlreq-core/src/generated/ideograph.rs", summary: "The members of cl-19 that §A.19's table deliberately does not list.", emit: emit_ideograph, }; @@ -1421,7 +1421,7 @@ pub(crate) const FOLDING_TABLE: Unit = Unit { pub(crate) const UNIFIED_FOLDING_TABLE: Unit = Unit { input: "spec/derived/folding.tsv", generator: &["xtask/src/classes.rs"], - output: "crates/jlreq/src/generated/folding.rs", + output: "crates/jlreq-core/src/generated/folding.rs", summary: "The Wide and Narrow decompositions: the only folding §A's preamble permits.", emit: emit_folding, }; @@ -1439,7 +1439,7 @@ pub(crate) const SCRIPT_TABLE: Unit = Unit { pub(crate) const UNIFIED_SCRIPT_TABLE: Unit = Unit { input: "spec/derived/scripts.tsv", generator: &["xtask/src/classes.rs"], - output: "crates/jlreq/src/generated/script.rs", + output: "crates/jlreq-core/src/generated/script.rs", summary: "The two kana scripts §C.2 note 3's small-kana fallback reads.", emit: emit_script, }; diff --git a/xtask/src/conform.rs b/xtask/src/conform.rs index a0bd7a6..3ae5100 100644 --- a/xtask/src/conform.rs +++ b/xtask/src/conform.rs @@ -68,7 +68,7 @@ const QUESTIONS_INVENTORY: &str = "spec/derived/questions.tsv"; /// and never a rule a case may cover. const ANCHORS_INVENTORY: &str = "spec/derived/anchors.tsv"; /// The crate that declares the fixed-point denominator. -const UNIT_CRATE: &str = "crates/jlreq/src"; +const UNIT_CRATE: &str = "crates/jlreq-core/src"; /// Validate the conformance suite and report one message per malformed thing. fn run(arguments: &[String]) -> io::Result> { diff --git a/xtask/src/direction.rs b/xtask/src/direction.rs index b635285..fd9b5cd 100644 --- a/xtask/src/direction.rs +++ b/xtask/src/direction.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 -//! Enforce the one-way module graph inside the unified library. +//! Enforce the one-way module graph inside the dependency-free composition core. use std::collections::{BTreeMap, BTreeSet}; use std::fs; @@ -13,7 +13,7 @@ use crate::shared::{self, Gate}; pub(crate) const GATE: Gate = Gate { name: "direction", - purpose: "jlreq's private modules depend only on the declared earlier pipeline layers", + purpose: "jlreq-core's private modules depend only on declared earlier pipeline layers", reference: "ARCHITECTURE.md", run, }; @@ -101,7 +101,7 @@ fn run(arguments: &[String]) -> io::Result> { } let root = shared::workspace_root()? .join("crates") - .join("jlreq") + .join("jlreq-core") .join("src"); let mut sources = BTreeMap::new(); for path in shared::rust_sources(&root)? { @@ -144,7 +144,7 @@ fn check_graph(sources: &BTreeMap) -> Vec { for module in sources.keys() { if !declared.contains(module.as_str()) { violations.push(format!( - "jlreq::{module} has no row in the module graph; update ARCHITECTURE.md and \ + "jlreq_core::{module} has no row in the module graph; update ARCHITECTURE.md and \ xtask/src/direction.rs together" )); } @@ -152,14 +152,14 @@ fn check_graph(sources: &BTreeMap) -> Vec { for layer in LAYERS { let Some(source) = sources.get(layer.name) else { violations.push(format!( - "the module graph names jlreq::{}, but no source module exists", + "the module graph names jlreq_core::{}, but no source module exists", layer.name )); continue; }; if layer.name != "lib" && source.contains("use crate::{") { violations.push(format!( - "jlreq::{} uses a crate-root grouped import; private dependencies must name \ + "jlreq_core::{} uses a crate-root grouped import; private dependencies must name \ their source module so this gate can check their direction", layer.name )); @@ -167,7 +167,7 @@ fn check_graph(sources: &BTreeMap) -> Vec { for dependency in module_references(source, &declared) { if dependency != layer.name && !layer.may_depend_on.contains(&dependency.as_str()) { violations.push(format!( - "jlreq::{from} depends on jlreq::{dependency}; its row permits {allowed}", + "jlreq_core::{from} depends on jlreq_core::{dependency}; its row permits {allowed}", from = layer.name, allowed = if layer.may_depend_on.is_empty() { "no private modules".to_owned() @@ -218,7 +218,7 @@ mod tests { assert!( found .iter() - .any(|message| message.contains("model depends on jlreq::pipeline")), + .any(|message| message.contains("model depends on jlreq_core::pipeline")), "{found:#?}" ); } diff --git a/xtask/src/generate.rs b/xtask/src/generate.rs index ef8463d..8e867c5 100644 --- a/xtask/src/generate.rs +++ b/xtask/src/generate.rs @@ -7,7 +7,7 @@ //! Stage 1 is the `derive` gate, which reads the vendored snapshot and emits //! `spec/derived/*.tsv`. Stage 2 is this module: it turns the tab-separated files under //! `spec/derived/` and `spec/captured/` into the Rust modules committed at -//! `crates/jlreq/src/generated/*.rs`. Tab-separated text needs no dependency to read, which is +//! `crates/jlreq-core/src/generated/*.rs`. Tab-separated text needs no dependency to read, which is //! what keeps this program's empty dependency table intact, and the SHA-256 below is //! hand-written for the same reason. //! @@ -304,7 +304,7 @@ fn run(arguments: &[String]) -> io::Result> { println!( "{NAME}: examined {units} active generation unit(s), {modules} module(s) under \ - crates/jlreq/src/{GENERATED_DIRECTORY}/ and {entries} digest(s) recorded \ + crates/jlreq-core/src/{GENERATED_DIRECTORY}/ and {entries} digest(s) recorded \ in {MANIFEST_PATH}", units = UNITS.len(), modules = found.len(), diff --git a/xtask/src/purity.rs b/xtask/src/purity.rs index 69331b9..3545488 100644 --- a/xtask/src/purity.rs +++ b/xtask/src/purity.rs @@ -4,10 +4,11 @@ //! The `purity` gate. //! -//! Checks the invariants around the sole public library and its conformance product: +//! Checks the invariants around the facade, dependency-free core, and conformance product: //! -//! - `jlreq` has no normal dependencies and remains `#![no_std]` plus `alloc`; -//! - `jlreq-conformance` depends only on the library and its product/test tooling; +//! - `jlreq-core` has no dependencies and remains `#![no_std]` plus `alloc`; +//! - `jlreq` depends only on the selected shaping/segmentation stack and the core; +//! - `jlreq-conformance` depends only on the core and its product/test tooling; //! - core source names neither floating-point types nor floating-point literals; //! - manifests match the two-node product graph in `docs/design/api-spine.md`. //! @@ -65,12 +66,24 @@ impl Adjacency { /// The product graph, transcribed rather than derived from the manifests it checks. const CRATE_GRAPH: &[Adjacency] = &[ Adjacency { - crate_name: "jlreq", + crate_name: "jlreq-core", may_depend_on: &[], }, + Adjacency { + crate_name: "jlreq", + may_depend_on: &[ + "font-test-data", + "fontique", + "harfrust", + "icu_segmenter", + "jlreq-core", + "rwml-fonts", + "unicode-bidi", + ], + }, Adjacency { crate_name: "jlreq-conformance", - may_depend_on: &["jlreq", "serde_json", "harfrust", "icu_segmenter"], + may_depend_on: &["jlreq-core", "serde_json", "harfrust", "icu_segmenter"], }, ]; @@ -83,8 +96,10 @@ const CRATE_GRAPH: &[Adjacency] = &[ /// explicitly. Its manifest is therefore read here. The name is verified against the /// manifest rather than assumed from the path, so this pair cannot rot into applying one /// crate's row to another. -const NON_CORE_GRAPH_MEMBERS: &[(&str, &str)] = - &[("jlreq-conformance", "crates/jlreq-conformance")]; +const NON_CORE_GRAPH_MEMBERS: &[(&str, &str)] = &[ + ("jlreq", "crates/jlreq"), + ("jlreq-conformance", "crates/jlreq-conformance"), +]; /// One type in the retired cross-crate seam model, retained for parser regression tests. #[derive(Debug)] @@ -113,7 +128,7 @@ const SEAM_OWNER: &str = "jlreq-unit"; /// The crate root, as `shared::relative_name` spells it. const LIB: &str = "lib.rs"; -/// Production has no cross-crate seam: all layout layers are private modules of `jlreq`. +/// Production has no retired cross-crate seam; the public facade/core boundary uses the core API. const SEAM: &[SeamType] = &[]; /// One core crate's sources, read once and stripped to code. @@ -1133,26 +1148,35 @@ mod tests { #[test] fn a_crate_may_declare_only_what_its_row_permits() { let mut violations = Vec::new(); - check_row("jlreq", "[dependencies]\n", &mut violations); + check_row("jlreq-core", "[dependencies]\n", &mut violations); assert!(violations.is_empty(), "found {violations:?}"); } #[test] fn the_unified_products_have_explicit_dependency_rows() { - let library = CRATE_GRAPH + let core = CRATE_GRAPH .iter() - .find(|row| row.crate_name == "jlreq") - .expect("the unified public library has a graph row"); + .find(|row| row.crate_name == "jlreq-core") + .expect("the dependency-free core has a graph row"); assert!( - library.may_depend_on.is_empty(), - "the no_std public library has no external dependencies" + core.may_depend_on.is_empty(), + "the no_std core has no external dependencies" ); + let facade = CRATE_GRAPH + .iter() + .find(|row| row.crate_name == "jlreq") + .expect("the high-level facade has a graph row"); + assert!(facade.may_depend_on.contains(&"jlreq-core")); + assert!(facade.may_depend_on.contains(&"harfrust")); + assert!(facade.may_depend_on.contains(&"icu_segmenter")); + assert!(facade.may_depend_on.contains(&"unicode-bidi")); + let runner = CRATE_GRAPH .iter() .find(|row| row.crate_name == "jlreq-conformance") .expect("the binary-only conformance product has a graph row"); - assert!(runner.may_depend_on.contains(&"jlreq")); + assert!(runner.may_depend_on.contains(&"jlreq-core")); assert!(runner.may_depend_on.contains(&"serde_json")); assert!(runner.may_depend_on.contains(&"harfrust")); assert!(runner.may_depend_on.contains(&"icu_segmenter")); @@ -1161,7 +1185,11 @@ mod tests { #[test] fn a_dependency_the_row_omits_is_a_violation_even_when_it_is_core() { let mut violations = Vec::new(); - check_row("jlreq", "[dependencies]\nserde = \"1\"\n", &mut violations); + check_row( + "jlreq-core", + "[dependencies]\nserde = \"1\"\n", + &mut violations, + ); assert_eq!(violations.len(), 1, "found {violations:?}"); assert!( violations[0].contains("serde"), diff --git a/xtask/src/repository.rs b/xtask/src/repository.rs index 13f088f..bb6527e 100644 --- a/xtask/src/repository.rs +++ b/xtask/src/repository.rs @@ -34,6 +34,7 @@ fn run(arguments: &[String]) -> io::Result> { let mut documents = 0_usize; let mut links = 0_usize; let mut violations = release_ready_state_violations(&root)?; + violations.extend(mutation_shard_violations(&root)?); violations.extend(error_code_violations(&root)?); for file in &files { let bytes = fs::read(file)?; @@ -65,6 +66,57 @@ fn run(arguments: &[String]) -> io::Result> { Ok(violations) } +/// Keep cargo-mutants' zero-based shard contract explicit while displaying shards to humans +/// as one through four. This catches both an omitted shard and the historical 1/4..4/4 bug. +fn mutation_shard_violations(root: &Path) -> io::Result> { + let source = fs::read_to_string(root.join(".github/workflows/mutants.yml"))?; + let indices = yaml_integer_values(&source, "index"); + let displays = yaml_integer_values(&source, "display"); + let mut violations = Vec::new(); + if indices != [0, 1, 2, 3] { + violations.push(format!( + ".github/workflows/mutants.yml: shard indices must be exactly 0,1,2,3; found {indices:?}" + )); + } + if displays != [1, 2, 3, 4] { + violations.push(format!( + ".github/workflows/mutants.yml: shard displays must be exactly 1,2,3,4; found {displays:?}" + )); + } + for package in ["jlreq", "jlreq-core", "jlreq-conformance"] { + let crate_matrix = "crate: [jlreq, jlreq-core, jlreq-conformance]"; + if !source.contains(crate_matrix) { + violations.push(format!( + ".github/workflows/mutants.yml: mutation matrix omits `{package}`" + )); + } + } + if !source.contains("${{ matrix.shard.index }}/4") { + violations.push( + ".github/workflows/mutants.yml: cargo-mutants must receive the zero-based shard index" + .to_owned(), + ); + } + if !source.contains("${{ matrix.shard.display }}-of-4") { + violations.push( + ".github/workflows/mutants.yml: artifacts must use the one-based shard display" + .to_owned(), + ); + } + Ok(violations) +} + +fn yaml_integer_values(source: &str, key: &str) -> Vec { + source + .lines() + .filter_map(|line| { + let trimmed = line.trim().strip_prefix("- ").unwrap_or(line.trim()); + let value = trimmed.strip_prefix(key)?.strip_prefix(':')?.trim(); + value.parse().ok() + }) + .collect() +} + /// Require the user-facing error-code reference and product literals to name the same set. fn error_code_violations(root: &Path) -> io::Result> { let mut code = BTreeSet::new(); @@ -76,6 +128,18 @@ fn error_code_violations(root: &Path) -> io::Result> { "paragraph.rs", "pipeline.rs", "style.rs", + ] { + let source = fs::read_to_string(root.join("crates/jlreq-core/src").join(name))?; + code.extend(quoted_error_codes(&source, '"')); + } + for name in [ + "document.rs", + "engine.rs", + "error.rs", + "font.rs", + "options.rs", + "result.rs", + "units.rs", ] { let source = fs::read_to_string(root.join("crates/jlreq/src").join(name))?; code.extend(quoted_error_codes(&source, '"')); @@ -102,9 +166,17 @@ fn quoted_error_codes(source: &str, delimiter: char) -> BTreeSet { .enumerate() .filter(|(index, value)| { index % 2 == 1 - && ["input.", "style.", "compose.", "layout."] - .iter() - .any(|prefix| value.starts_with(prefix)) + && [ + "input.", + "style.", + "compose.", + "layout.", + "font.", + "document.", + "limit.", + ] + .iter() + .any(|prefix| value.starts_with(prefix)) && value .chars() .all(|character| character.is_ascii_lowercase() || ".-".contains(character)) @@ -123,6 +195,7 @@ fn release_ready_state_violations(root: &Path) -> io::Result> { for manifest in [ "crates/jlreq/Cargo.toml", + "crates/jlreq-core/Cargo.toml", "crates/jlreq-conformance/Cargo.toml", ] { let source = fs::read_to_string(root.join(manifest))?; @@ -136,12 +209,16 @@ fn release_ready_state_violations(root: &Path) -> io::Result> { } } - let conformance = fs::read_to_string(root.join("crates/jlreq-conformance/Cargo.toml"))?; - if !conformance.contains("jlreq = { version = \"0.1.0\", path = \"../jlreq\" }") { - violations.push( - "crates/jlreq-conformance/Cargo.toml: jlreq needs version 0.1.0 plus its local path" - .to_owned(), - ); + for manifest in [ + "crates/jlreq/Cargo.toml", + "crates/jlreq-conformance/Cargo.toml", + ] { + let source = fs::read_to_string(root.join(manifest))?; + if !source.contains("jlreq-core = { version = \"0.1.0\", path = \"../jlreq-core\" }") { + violations.push(format!( + "{manifest}: jlreq-core needs version 0.1.0 plus its local path" + )); + } } let release = fs::read_to_string(root.join("release-plz.toml"))?; @@ -156,21 +233,59 @@ fn release_ready_state_violations(root: &Path) -> io::Result> { } let changelog = fs::read_to_string(root.join("CHANGELOG.md"))?; - for line in changelog.lines() { - if line.starts_with("## [") && line != "## [Unreleased]" { - violations - .push("CHANGELOG.md: development history remains under [Unreleased]".to_owned()); - break; + let headings = changelog + .lines() + .filter(|line| line.starts_with("## [")) + .collect::>(); + let valid = headings.as_slice() == ["## [Unreleased]"] + || (headings.len() == 2 + && headings[0] == "## [Unreleased]" + && is_initial_release_heading(headings[1])); + if !valid { + violations.push( + "CHANGELOG.md: use [Unreleased], optionally followed by one finalized 0.1.0 heading" + .to_owned(), + ); + } + for required in [ + "LICENSE-MIT", + "LICENSE-APACHE", + ".github/REPOSITORY-SETTINGS.md", + ] { + if !root.join(required).is_file() { + violations.push(format!( + "{required}: required release handoff file is missing" + )); } } Ok(violations) } +fn is_initial_release_heading(line: &str) -> bool { + let Some(date) = line.strip_prefix("## [0.1.0] - ") else { + return false; + }; + let bytes = date.as_bytes(); + bytes.len() == 10 + && bytes[4] == b'-' + && bytes[7] == b'-' + && bytes + .iter() + .enumerate() + .all(|(index, byte)| matches!(index, 4 | 7) || byte.is_ascii_digit()) +} + fn tracked_files(root: &Path) -> io::Result> { let output = Command::new("git") .arg("-C") .arg(root) - .args(["ls-files", "-z", "--"]) + .args([ + "ls-files", + "-z", + "--cached", + "--others", + "--exclude-standard", + ]) .output()?; if !output.status.success() { return Err(io::Error::other(format!( @@ -282,7 +397,10 @@ fn unresolved_link(root: &Path, document: &Path, target: &str) -> Option #[cfg(test)] mod tests { - use super::{Link, local_links, release_ready_state_violations, unresolved_link}; + use super::{ + Link, is_initial_release_heading, local_links, mutation_shard_violations, + release_ready_state_violations, unresolved_link, yaml_integer_values, + }; use std::path::Path; #[test] @@ -315,6 +433,14 @@ mod tests { assert!(unresolved_link(root, &document, "../../README.md#usage").is_none()); } + #[test] + fn a_finalized_initial_release_heading_has_one_exact_shape() { + assert!(is_initial_release_heading("## [0.1.0] - 2026-12-31")); + assert!(!is_initial_release_heading("## [0.1.1] - 2026-12-31")); + assert!(!is_initial_release_heading("## [0.1.0] - 2026-8-28")); + assert!(!is_initial_release_heading("## [0.1.0] 2026-08-28")); + } + #[test] fn the_workspace_is_publishable_but_external_release_actions_are_inert() -> std::io::Result<()> { @@ -322,6 +448,14 @@ mod tests { .parent() .expect("workspace root"); assert_eq!(release_ready_state_violations(root)?, Vec::::new()); + assert_eq!(mutation_shard_violations(root)?, Vec::::new()); Ok(()) } + + #[test] + fn mutation_shards_are_read_as_a_complete_zero_based_set() { + let source = "shard:\n - index: 0\n display: 1\n - index: 1\n display: 2\n - index: 2\n display: 3\n - index: 3\n display: 4\n"; + assert_eq!(yaml_integer_values(source, "index"), [0, 1, 2, 3]); + assert_eq!(yaml_integer_values(source, "display"), [1, 2, 3, 4]); + } } diff --git a/xtask/src/shared.rs b/xtask/src/shared.rs index 150c216..363065f 100644 --- a/xtask/src/shared.rs +++ b/xtask/src/shared.rs @@ -28,6 +28,8 @@ use std::process::ExitCode; /// spells it, and an entry naming something that is no longer a member is an error rather /// than a no-op, so this list cannot rot unnoticed. const NON_CORE_MEMBERS: &[&str] = &[ + // The std facade owns fonts, shaping, segmentation, and renderer-ready coordinates. + "crates/jlreq", // The language-independent protocol runner launches external processes and parses JSON. "crates/jlreq-conformance", // The repository's own tooling, which is this program. @@ -634,8 +636,8 @@ mod tests { let core = core_crates().expect("the workspace manifest is readable"); let names: Vec<&str> = core.iter().map(|each| each.name.as_str()).collect(); assert!( - names.contains(&"jlreq"), - "the unified no_std library is core: {names:?}" + names.contains(&"jlreq-core"), + "the dependency-free no_std library is core: {names:?}" ); assert!( !names.contains(&"jlreq-conformance"), diff --git a/xtask/src/spacing.rs b/xtask/src/spacing.rs index 03ecab6..932dacb 100644 --- a/xtask/src/spacing.rs +++ b/xtask/src/spacing.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 //! Stage 2 generation for the unified engine: the six captured matrices of Appendices B -//! through E, turned into private `static` arrays under `crates/jlreq/src/generated/`. +//! through E, turned into private `static` arrays under `crates/jlreq-core/src/generated/`. //! //! `spec/captured/table.en.tsv` is the input for each unit. The Japanese rendering, //! `table.ja.tsv`, is not: `generate`'s own `Unit` reads one file, and the control that @@ -46,11 +46,11 @@ pub(crate) const TABLE1: Unit = Unit { emit: emit_table1, }; -/// Table 1 behind the sole public library's dependency-free private boundary. +/// Table 1 behind the public core library's dependency-free private boundary. pub(crate) const UNIFIED_TABLE1: Unit = Unit { input: "spec/captured/table1.en.tsv", generator: &["xtask/src/spacing.rs"], - output: "crates/jlreq/src/generated/table1.rs", + output: "crates/jlreq-core/src/generated/table1.rs", summary: "Table 1, \"Spacing between Characters\" (Appendix B).", emit: emit_unified_table1, }; @@ -64,11 +64,11 @@ pub(crate) const TABLE2: Unit = Unit { emit: emit_table2, }; -/// Table 2 behind the sole public library's dependency-free private boundary. +/// Table 2 behind the public core library's dependency-free private boundary. pub(crate) const UNIFIED_TABLE2: Unit = Unit { input: "spec/captured/table2.en.tsv", generator: &["xtask/src/spacing.rs"], - output: "crates/jlreq/src/generated/table2.rs", + output: "crates/jlreq-core/src/generated/table2.rs", summary: "Table 2, \"Possibilities for Line-breaking between Characters\" (Appendix C).", emit: emit_unified_table2, }; @@ -82,11 +82,11 @@ pub(crate) const TABLE3: Unit = Unit { emit: |table| emit_ranged(table, RangedTable::Reduction, "LEGEND_OF_TABLES_3_4_AND_5"), }; -/// Table 3 behind the sole public library's dependency-free private boundary. +/// Table 3 behind the public core library's dependency-free private boundary. pub(crate) const UNIFIED_TABLE3: Unit = Unit { input: "spec/captured/table3.en.tsv", generator: &["xtask/src/spacing.rs"], - output: "crates/jlreq/src/generated/table3.rs", + output: "crates/jlreq-core/src/generated/table3.rs", summary: "Table 3, JLReq's own reduction-priority reading (Appendix D).", emit: |table| emit_unified_ranged(table, RangedTable::Reduction, "D.1"), }; @@ -100,11 +100,11 @@ pub(crate) const TABLE4: Unit = Unit { emit: |table| emit_ranged(table, RangedTable::Reduction, "LEGEND_OF_TABLES_3_4_AND_5"), }; -/// Table 4 behind the sole public library's dependency-free private boundary. +/// Table 4 behind the public core library's dependency-free private boundary. pub(crate) const UNIFIED_TABLE4: Unit = Unit { input: "spec/captured/table4.en.tsv", generator: &["xtask/src/spacing.rs"], - output: "crates/jlreq/src/generated/table4.rs", + output: "crates/jlreq-core/src/generated/table4.rs", summary: "Table 4, the JIS X 4051 reduction-priority reading (Appendix D).", emit: |table| emit_unified_ranged(table, RangedTable::Reduction, "D.1"), }; @@ -118,11 +118,11 @@ pub(crate) const TABLE5: Unit = Unit { emit: |table| emit_ranged(table, RangedTable::Reduction, "LEGEND_OF_TABLES_3_4_AND_5"), }; -/// Table 5 behind the sole public library's dependency-free private boundary. +/// Table 5 behind the public core library's dependency-free private boundary. pub(crate) const UNIFIED_TABLE5: Unit = Unit { input: "spec/captured/table5.en.tsv", generator: &["xtask/src/spacing.rs"], - output: "crates/jlreq/src/generated/table5.rs", + output: "crates/jlreq-core/src/generated/table5.rs", summary: "Table 5, the book-practice reduction-priority reading (Appendix D).", emit: |table| emit_unified_ranged(table, RangedTable::Reduction, "D.1"), }; @@ -142,11 +142,11 @@ pub(crate) const TABLE6: Unit = Unit { }, }; -/// Table 6 behind the sole public library's dependency-free private boundary. +/// Table 6 behind the public core library's dependency-free private boundary. pub(crate) const UNIFIED_TABLE6: Unit = Unit { input: "spec/captured/table6.en.tsv", generator: &["xtask/src/spacing.rs"], - output: "crates/jlreq/src/generated/table6.rs", + output: "crates/jlreq-core/src/generated/table6.rs", summary: "Table 6, \"Opportunities for Inter-character Space Expansion\" (Appendix E).", emit: |table| emit_unified_ranged(table, RangedTable::Expansion, "E"), }; From 2868b511068b47a156ae80f3bb5e9c34983c831d Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Fri, 28 Aug 2026 16:47:25 +0900 Subject: [PATCH 02/12] fix: load Fontconfig dynamically on Linux --- crates/jlreq/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/jlreq/Cargo.toml b/crates/jlreq/Cargo.toml index 28e6eea..d5c1642 100644 --- a/crates/jlreq/Cargo.toml +++ b/crates/jlreq/Cargo.toml @@ -39,7 +39,7 @@ unicode-bidi = { version = "=0.3.18", default-features = true } [dependencies.fontique] version = "=0.11.1" default-features = false -features = ["system"] +features = ["system", "fontconfig-dlopen"] optional = true [dev-dependencies] From fe9191666c902b2cdc4e17e24af4059b014b2bab Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Fri, 28 Aug 2026 16:50:09 +0900 Subject: [PATCH 03/12] ci: prefetch locked dependencies before offline packaging --- Justfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Justfile b/Justfile index 936dcfa..a7b11db 100644 --- a/Justfile +++ b/Justfile @@ -121,6 +121,7 @@ doc: # Build and verify all three public crate archives. Temporary crates.io patches validate the # exact-version core dependency before the initial release reaches the registry. package: + cargo fetch --locked cargo package -p jlreq-core --locked {{package_dirty}} cargo package -p jlreq --locked {{package_dirty}} --offline --config .cargo/initial-release-patch.toml cargo package -p jlreq-conformance --locked {{package_dirty}} --offline --config .cargo/initial-release-patch.toml From 906e3c2ab17ce7c15953580da6ae9ff4ea0c9ca8 Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Fri, 28 Aug 2026 16:54:20 +0900 Subject: [PATCH 04/12] ci: install actionlint explicitly for release checks --- .github/workflows/release-check.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index b12e266..67983a4 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -36,7 +36,23 @@ jobs: cargo-shear@1.13.3,cargo-msrv@0.19.3,cargo-fuzz@0.13.2, cargo-llvm-cov@0.9.0,cargo-mutants@27.1.0,taplo-cli@0.10.0, cargo-semver-checks@0.50.0,typos-cli@1.48.0, - actionlint@1.7.12,shellcheck@0.11.0,zizmor@1.28.0 + shellcheck@0.11.0,zizmor@1.28.0 + - name: install checksum-verified actionlint + env: + ACTIONLINT_VERSION: 1.7.12 + # Official digest published on the v1.7.12 GitHub Release asset. + ACTIONLINT_LINUX_AMD64_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 + run: |- + asset="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" + archive="$RUNNER_TEMP/$asset" + curl --fail --show-error --location --retry 5 --retry-connrefused \ + --output "$archive" \ + "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/$asset" + printf '%s %s\n' "$ACTIONLINT_LINUX_AMD64_SHA256" "$archive" | + sha256sum --check --strict - + tar -xzf "$archive" -C "$RUNNER_TEMP" actionlint + "$RUNNER_TEMP/actionlint" -version + echo "$RUNNER_TEMP" >> "$GITHUB_PATH" - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 with: version: 0.11.32 From 2c8b94361308cc0d82a6a5ec63c861fb190c3448 Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:03:35 +0900 Subject: [PATCH 05/12] ci: pin Linux fuzzing to the GNU host target --- Justfile | 2 +- xtask/src/repository.rs | 29 +++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Justfile b/Justfile index a7b11db..37e9987 100644 --- a/Justfile +++ b/Justfile @@ -175,7 +175,7 @@ fuzz-check-linux-ci: _fuzz-target target seconds: mkdir -p target/fuzz-corpus/{{target}} cp fuzz/seeds/{{target}}/* target/fuzz-corpus/{{target}}/ - cargo +nightly fuzz run {{target}} target/fuzz-corpus/{{target}} --fuzz-dir fuzz -- -max_total_time={{seconds}} -timeout=10 + cargo +nightly fuzz run {{target}} target/fuzz-corpus/{{target}} --fuzz-dir fuzz {{ if os() == "linux" { "--target x86_64-unknown-linux-gnu" } else { "" } }} -- -max_total_time={{seconds}} -timeout=10 [private] _fuzz-target-linux target seconds: diff --git a/xtask/src/repository.rs b/xtask/src/repository.rs index bb6527e..40db9c4 100644 --- a/xtask/src/repository.rs +++ b/xtask/src/repository.rs @@ -35,6 +35,7 @@ fn run(arguments: &[String]) -> io::Result> { let mut links = 0_usize; let mut violations = release_ready_state_violations(&root)?; violations.extend(mutation_shard_violations(&root)?); + violations.extend(fuzz_target_violations(&root)?); violations.extend(error_code_violations(&root)?); for file in &files { let bytes = fs::read(file)?; @@ -117,6 +118,28 @@ fn yaml_integer_values(source: &str, key: &str) -> Vec { .collect() } +/// ASan cannot fuzz a statically linked musl target. `cargo-fuzz` distributed by +/// cargo-binstall is itself a musl binary and otherwise mistakes that build triple for the +/// target, so every Linux path through the aggregate `just ci` gate must pin the GNU host. +fn fuzz_target_violations(root: &Path) -> io::Result> { + let source = fs::read_to_string(root.join("Justfile"))?; + let Some((_, rest)) = source.split_once("_fuzz-target target seconds:\n") else { + return Ok(vec![ + "Justfile: missing the generic _fuzz-target recipe".to_owned(), + ]); + }; + let recipe = rest.split("\n[private]\n").next().unwrap_or(rest); + if recipe.contains(r#"if os() == "linux" { "--target x86_64-unknown-linux-gnu" } else { "" }"#) + { + Ok(Vec::new()) + } else { + Ok(vec![ + "Justfile: generic Linux fuzzing must explicitly target x86_64-unknown-linux-gnu" + .to_owned(), + ]) + } +} + /// Require the user-facing error-code reference and product literals to name the same set. fn error_code_violations(root: &Path) -> io::Result> { let mut code = BTreeSet::new(); @@ -398,8 +421,9 @@ fn unresolved_link(root: &Path, document: &Path, target: &str) -> Option #[cfg(test)] mod tests { use super::{ - Link, is_initial_release_heading, local_links, mutation_shard_violations, - release_ready_state_violations, unresolved_link, yaml_integer_values, + Link, fuzz_target_violations, is_initial_release_heading, local_links, + mutation_shard_violations, release_ready_state_violations, unresolved_link, + yaml_integer_values, }; use std::path::Path; @@ -449,6 +473,7 @@ mod tests { .expect("workspace root"); assert_eq!(release_ready_state_violations(root)?, Vec::::new()); assert_eq!(mutation_shard_violations(root)?, Vec::::new()); + assert_eq!(fuzz_target_violations(root)?, Vec::::new()); Ok(()) } From 9640efb50819df97f8f1ab9ab485e3231a34d102 Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:06:41 +0900 Subject: [PATCH 06/12] style: satisfy strict clippy in release gate docs --- xtask/src/repository.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtask/src/repository.rs b/xtask/src/repository.rs index 40db9c4..fa4e8f5 100644 --- a/xtask/src/repository.rs +++ b/xtask/src/repository.rs @@ -118,7 +118,7 @@ fn yaml_integer_values(source: &str, key: &str) -> Vec { .collect() } -/// ASan cannot fuzz a statically linked musl target. `cargo-fuzz` distributed by +/// `ASan` cannot fuzz a statically linked musl target. `cargo-fuzz` distributed by /// cargo-binstall is itself a musl binary and otherwise mistakes that build triple for the /// target, so every Linux path through the aggregate `just ci` gate must pin the GNU host. fn fuzz_target_violations(root: &Path) -> io::Result> { From c7d6c3e50c29d65d3bcaabcc0dd2c8b12c520147 Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Fri, 28 Aug 2026 19:28:34 +0900 Subject: [PATCH 07/12] fix: normalize Windows archive listings --- scripts/package-binaries.sh | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/package-binaries.sh b/scripts/package-binaries.sh index 646543b..c6f4450 100755 --- a/scripts/package-binaries.sh +++ b/scripts/package-binaries.sh @@ -61,12 +61,15 @@ esac case $candidate in *.zip) - listing=$(7z l -ba "$candidate") - printf '%s\n' "$listing" | grep -F "$name/jlreq-conformance$suffix" >/dev/null - printf '%s\n' "$listing" | grep -F "$name/jlreq-sample-engine$suffix" >/dev/null - printf '%s\n' "$listing" | grep -F "$name/README.md" >/dev/null - printf '%s\n' "$listing" | grep -F "$name/LICENSE-MIT" >/dev/null - printf '%s\n' "$listing" | grep -F "$name/LICENSE-APACHE" >/dev/null + # Native 7-Zip prints backslash-separated entries on Windows. Normalize its + # listing so the archive contract is identical on every runner shell. + listing=$(7z l -ba "$candidate" | tr '\134' '/') + for required in "jlreq-conformance$suffix" "jlreq-sample-engine$suffix" README.md LICENSE-MIT LICENSE-APACHE; do + printf '%s\n' "$listing" | grep -F "$name/$required" >/dev/null || { + echo "$candidate is missing $name/$required" >&2 + exit 1 + } + done ;; *) for required in jlreq-conformance jlreq-sample-engine README.md LICENSE-MIT LICENSE-APACHE; do From c7a373d61d507c5225a393fb6b864765c84ccfc8 Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Fri, 28 Aug 2026 19:35:37 +0900 Subject: [PATCH 08/12] build: integrate Dependabot action updates --- .github/workflows/ci.yml | 42 ++++++++++++++-------------- .github/workflows/fuzz-scheduled.yml | 2 +- .github/workflows/mutants.yml | 2 +- .github/workflows/release-check.yml | 16 +++++------ .github/workflows/release.yml | 2 +- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 369f8ba..4266642 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: components: rustfmt - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - run: just fmt-check @@ -71,7 +71,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,taplo-cli@0.10.0 - run: just toml-check @@ -86,7 +86,7 @@ jobs: - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: components: clippy - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -105,7 +105,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,nextest@0.9.140 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -123,7 +123,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,cargo-hack@0.6.45 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -139,7 +139,7 @@ jobs: - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: targets: thumbv7em-none-eabi - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -158,7 +158,7 @@ jobs: - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: targets: wasm32-unknown-unknown - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -175,7 +175,7 @@ jobs: - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # nightly with: toolchain: nightly - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,cargo-fuzz@0.13.2 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -192,7 +192,7 @@ jobs: - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: components: llvm-tools-preview - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,cargo-llvm-cov@0.9.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -208,7 +208,7 @@ jobs: with: fetch-depth: 0 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,cargo-mutants@27.1.0,nextest@0.9.140 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -229,7 +229,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -246,7 +246,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -264,7 +264,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,cargo-semver-checks@0.50.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -288,7 +288,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -320,7 +320,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -343,7 +343,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -357,7 +357,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -377,7 +377,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: cargo-msrv@0.19.3,just@1.50.0 # `cargo-msrv verify` reads each crate's declared `rust-version` and proves it builds @@ -390,7 +390,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 # Run REUSE through `uvx` exactly as `just reuse` does locally rather than the @@ -407,7 +407,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: cargo-deny@0.20.2,cargo-shear@1.13.3,just@1.50.0 - run: just deny @@ -418,7 +418,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,shellcheck@0.11.0,zizmor@1.28.0 - name: shellcheck diff --git a/.github/workflows/fuzz-scheduled.yml b/.github/workflows/fuzz-scheduled.yml index 3ac0844..3f4df71 100644 --- a/.github/workflows/fuzz-scheduled.yml +++ b/.github/workflows/fuzz-scheduled.yml @@ -24,7 +24,7 @@ jobs: - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # nightly with: toolchain: nightly - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,cargo-fuzz@0.13.2 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 diff --git a/.github/workflows/mutants.yml b/.github/workflows/mutants.yml index 7ab0150..626d23e 100644 --- a/.github/workflows/mutants.yml +++ b/.github/workflows/mutants.yml @@ -41,7 +41,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,cargo-mutants@27.1.0,nextest@0.9.140 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index 67983a4..b515102 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -29,7 +29,7 @@ jobs: - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # nightly with: toolchain: nightly - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: >- just@1.50.0,nextest@0.9.140,cargo-hack@0.6.45,cargo-deny@0.20.2, @@ -84,7 +84,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0,cargo-cyclonedx@0.5.9 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 @@ -106,17 +106,17 @@ jobs: cp "$sbom" "target/dist/$package-0.1.0.cdx.json" done - name: attest jlreq-core archive and SBOM - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-path: target/dist/jlreq-core-0.1.0.crate sbom-path: target/dist/jlreq-core-0.1.0.cdx.json - name: attest jlreq archive and SBOM - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-path: target/dist/jlreq-0.1.0.crate sbom-path: target/dist/jlreq-0.1.0.cdx.json - name: attest jlreq-conformance archive and SBOM - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-path: target/dist/jlreq-conformance-0.1.0.crate sbom-path: target/dist/jlreq-conformance-0.1.0.cdx.json @@ -163,7 +163,7 @@ jobs: - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable with: targets: ${{ matrix.target }} - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: cargo-cyclonedx@0.5.9 - name: install Linux cross linker @@ -194,13 +194,13 @@ jobs: ' "$sbom" cp "$sbom" "target/dist/jlreq-0.1.0-${{ matrix.target }}.cdx.json" - name: attest build provenance - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-path: |- target/dist/*.tar.gz target/dist/*.zip - name: attest CycloneDX SBOM - uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-path: |- target/dist/*.tar.gz diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 48a0b84..351aacb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,7 +67,7 @@ jobs: ref: ${{ inputs.commit }} fetch-depth: 0 - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable - - uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5 + - uses: taiki-e/install-action@b6ff580856c41316412a0b9b60540fbc6f8c82cc # v2.86.7 with: tool: just@1.50.0 - name: verify candidate identity and finalized notes From d821b6306e9ccd9a48a47572c576c8b5dfcb8771 Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:55:47 +0900 Subject: [PATCH 09/12] perf: index layout hot paths Reuse shaping state and fallback decisions, index construct and query paths, extend prefix measurement to static constructs, generate constant-time spacing lookups, and split the large pipelines into focused modules. Add deterministic regression coverage and release median benchmarks while preserving the public API and conformance protocol. --- CONTRIBUTING.md | 22 + crates/jlreq-conformance/src/main.rs | 977 +--- crates/jlreq-conformance/src/runner/cli.rs | 215 + .../jlreq-conformance/src/runner/compare.rs | 105 + .../jlreq-conformance/src/runner/process.rs | 380 ++ crates/jlreq-conformance/src/runner/suite.rs | 223 + crates/jlreq-conformance/src/sample_engine.rs | 69 +- crates/jlreq-conformance/src/transport.rs | 48 + crates/jlreq-core/src/generated.rs | 113 +- crates/jlreq-core/src/generated/table1.rs | 1707 +------ crates/jlreq-core/src/generated/table2.rs | 1593 +----- crates/jlreq-core/src/generated/table3.rs | 1707 +------ crates/jlreq-core/src/generated/table4.rs | 1707 +------ crates/jlreq-core/src/generated/table5.rs | 1707 +------ crates/jlreq-core/src/generated/table6.rs | 1593 +----- crates/jlreq-core/src/paragraph.rs | 190 + crates/jlreq-core/src/pipeline.rs | 4515 +---------------- crates/jlreq-core/src/pipeline/adjustment.rs | 1093 ++++ crates/jlreq-core/src/pipeline/composition.rs | 831 +++ crates/jlreq-core/src/pipeline/placement.rs | 909 ++++ crates/jlreq-core/src/pipeline/ruby.rs | 1099 ++++ crates/jlreq-core/src/pipeline/search.rs | 274 + crates/jlreq-core/src/pipeline/special.rs | 485 ++ crates/jlreq-core/src/spec.rs | 16 +- crates/jlreq/examples/benchmark.rs | 271 + crates/jlreq/src/document.rs | 17 +- crates/jlreq/src/engine.rs | 1621 +----- crates/jlreq/src/engine/layout.rs | 681 +++ crates/jlreq/src/engine/lowering.rs | 187 + crates/jlreq/src/engine/preparation.rs | 408 ++ crates/jlreq/src/engine/result.rs | 327 ++ crates/jlreq/src/engine/shaping.rs | 149 + crates/jlreq/src/font.rs | 32 +- crates/jlreq/src/result.rs | 270 +- crates/jlreq/tests/high_level.rs | 12 + data/manifest.toml | 14 +- docs/mutation-ledger.toml | 22 +- xtask/src/repository.rs | 11 +- xtask/src/spacing.rs | 152 +- 39 files changed, 8778 insertions(+), 16974 deletions(-) create mode 100644 crates/jlreq-conformance/src/runner/cli.rs create mode 100644 crates/jlreq-conformance/src/runner/compare.rs create mode 100644 crates/jlreq-conformance/src/runner/process.rs create mode 100644 crates/jlreq-conformance/src/runner/suite.rs create mode 100644 crates/jlreq-conformance/src/transport.rs create mode 100644 crates/jlreq-core/src/pipeline/adjustment.rs create mode 100644 crates/jlreq-core/src/pipeline/composition.rs create mode 100644 crates/jlreq-core/src/pipeline/placement.rs create mode 100644 crates/jlreq-core/src/pipeline/ruby.rs create mode 100644 crates/jlreq-core/src/pipeline/search.rs create mode 100644 crates/jlreq-core/src/pipeline/special.rs create mode 100644 crates/jlreq/examples/benchmark.rs create mode 100644 crates/jlreq/src/engine/layout.rs create mode 100644 crates/jlreq/src/engine/lowering.rs create mode 100644 crates/jlreq/src/engine/preparation.rs create mode 100644 crates/jlreq/src/engine/result.rs create mode 100644 crates/jlreq/src/engine/shaping.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3738927..8c87c3e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,28 @@ it fails, fix the cause rather than narrowing the gate. composition/arithmetic, and protocol parsing. Curated inputs live under `fuzz/seeds/`; runtime corpora live under `target/fuzz-corpus/` and never dirty the source tree. +## Performance measurements + +Run the checked-in median benchmark in release mode and compare runs on the same machine: + +```sh +JLREQ_BENCH_SAMPLES=9 cargo run -p jlreq --release --example benchmark +``` + +The sample count may be replaced with any odd positive integer. The runner warms every case, +then reports medians for one-shot and reused high-level layout, fallback and bidi text, many +spans, vertical constructs and tabs, result queries, core-only composition, and indexed static +constructs. CI does not compare wall-clock values: unit tests instead pin exact search-transition +charging and the number of shaping calls needed by repeated fallback graphemes. + +Conformance process throughput is measured separately after a release build so process startup +is visible rather than mixed into layout timing: + +```sh +cargo build --release -p jlreq-conformance +target/release/jlreq-conformance run target/release/jlreq-sample-engine +``` + Full mutation runs cover both handwritten products. Only generated table files and exact mutants proven equivalent in [docs/mutation-ledger.toml](docs/mutation-ledger.toml) may be excluded. `just mutation-ledger` binds every such entry to its source SHA-256 and rejects an diff --git a/crates/jlreq-conformance/src/main.rs b/crates/jlreq-conformance/src/main.rs index 11cd223..4b51406 100644 --- a/crates/jlreq-conformance/src/main.rs +++ b/crates/jlreq-conformance/src/main.rs @@ -4,6 +4,7 @@ //! Versioned NDJSON black-box conformance runner. +mod transport; mod validation; use std::{ @@ -21,6 +22,7 @@ use std::{ }; use serde_json::{Map, Value}; +use transport::read_limited_line; use validation::{validate_request, validate_response}; const PROTOCOL: &str = "jlreq.conformance/1"; @@ -36,953 +38,12 @@ const DEFAULT_MAX_CASES: usize = 200_000; const STDERR_RETAIN_BYTES: usize = 1024 * 1024; const EVENT_CHANNEL_CAPACITY: usize = 64; const WATCHDOG_TICK: Duration = Duration::from_millis(100); - -fn main() -> ExitCode { - match parse_invocation(env::args().skip(1)) { - Ok(Invocation::Help) => { - print_help(); - ExitCode::SUCCESS - }, - Ok(Invocation::Version) => { - println!("jlreq-conformance {}", env!("CARGO_PKG_VERSION")); - ExitCode::SUCCESS - }, - Ok(Invocation::Command(options, command)) => run_command(&options, command), - Err(error) => { - eprintln!("jlreq-conformance: {error}"); - eprintln!("try 'jlreq-conformance --help' for usage"); - ExitCode::from(2) - }, - } -} - -#[derive(Debug, Clone)] -struct Options { - verbose: bool, - timeout: Duration, - max_message_bytes: usize, - max_suite_bytes: usize, - max_cases: usize, -} - -impl Default for Options { - fn default() -> Self { - Self { - verbose: false, - timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECONDS), - max_message_bytes: DEFAULT_MAX_MESSAGE_BYTES, - max_suite_bytes: DEFAULT_MAX_SUITE_BYTES, - max_cases: DEFAULT_MAX_CASES, - } - } -} - -#[derive(Debug, Clone)] -enum CliCommand { - Validate { - suite: Option, - }, - List { - suite: Option, - }, - Run { - engine: String, - suite: Option, - }, -} - -#[derive(Debug, Clone)] -enum Invocation { - Help, - Version, - Command(Options, CliCommand), -} - -fn parse_invocation(arguments: impl IntoIterator) -> Result { - let mut options = Options::default(); - let mut positional = Vec::new(); - let mut arguments = arguments.into_iter().peekable(); - let mut parse_options = true; - while let Some(argument) = arguments.next() { - if parse_options && argument == "--" { - parse_options = false; - } else if parse_options && matches!(argument.as_str(), "-h" | "--help") { - return Ok(Invocation::Help); - } else if parse_options && matches!(argument.as_str(), "-V" | "--version") { - return Ok(Invocation::Version); - } else if parse_options && argument == "--verbose" { - options.verbose = true; - } else if parse_options && argument.starts_with("--") { - let (name, inline) = argument - .split_once('=') - .map_or((argument.as_str(), None), |(name, value)| { - (name, Some(value)) - }); - let value = match name { - "--timeout-seconds" - | "--max-message-bytes" - | "--max-suite-bytes" - | "--max-cases" => inline.map(str::to_owned).or_else(|| arguments.next()), - _ => return Err(format!("unknown option {argument:?}")), - } - .ok_or_else(|| format!("{name} needs a positive integer"))?; - match name { - "--timeout-seconds" => { - let seconds = positive_u64(&value, name)?; - options.timeout = Duration::from_secs(seconds); - }, - "--max-message-bytes" => { - options.max_message_bytes = positive_usize(&value, name)?; - }, - "--max-suite-bytes" => { - options.max_suite_bytes = positive_usize(&value, name)?; - }, - "--max-cases" => options.max_cases = positive_usize(&value, name)?, - _ => return Err(format!("unknown option {argument:?}")), - } - } else { - positional.push(argument); - } - } - - let Some(command) = positional.first().map(String::as_str) else { - return Err("a command is required".to_owned()); - }; - let command = match command { - "validate" if positional.len() <= 2 => CliCommand::Validate { - suite: positional.get(1).cloned(), - }, - "list" if positional.len() <= 2 => CliCommand::List { - suite: positional.get(1).cloned(), - }, - "run" if (2..=3).contains(&positional.len()) => CliCommand::Run { - engine: positional[1].clone(), - suite: positional.get(2).cloned(), - }, - "validate" => return Err("usage: jlreq-conformance validate [SUITE.ndjson|-]".to_owned()), - "list" => return Err("usage: jlreq-conformance list [SUITE.ndjson]".to_owned()), - "run" => { - return Err("usage: jlreq-conformance run ENGINE [SUITE.ndjson]".to_owned()); - }, - other => return Err(format!("unknown command {other:?}")), - }; - Ok(Invocation::Command(options, command)) -} - -fn positive_u64(value: &str, option: &str) -> Result { - value - .parse::() - .ok() - .filter(|value| *value > 0) - .ok_or_else(|| format!("{option} needs a positive integer")) -} - -fn positive_usize(value: &str, option: &str) -> Result { - value - .parse::() - .ok() - .filter(|value| *value > 0) - .ok_or_else(|| format!("{option} needs a positive integer")) -} - -fn print_help() { - println!( - "jlreq-conformance {}\n\ - \n\ - Usage:\n\ - jlreq-conformance [OPTIONS] validate [SUITE.ndjson|-]\n\ - jlreq-conformance [OPTIONS] list [SUITE.ndjson]\n\ - jlreq-conformance [OPTIONS] run ENGINE [SUITE.ndjson]\n\ - \n\ - Options:\n\ - -h, --help Show this help\n\ - -V, --version Show the package version\n\ - --verbose Show bounded JSON differences\n\ - --timeout-seconds N No-I/O timeout (default: 30)\n\ - --max-message-bytes N Per-line limit (default: 1048576)\n\ - --max-suite-bytes N Stream limit (default: 268435456)\n\ - --max-cases N Message/case limit (default: 200000)", - env!("CARGO_PKG_VERSION") - ); -} - -fn run_command(options: &Options, command: CliCommand) -> ExitCode { - match command { - CliCommand::Validate { suite } => { - match read_messages(suite.as_deref(), false, false, options) { - Ok(messages) => { - eprintln!("validated {} message(s)", messages.len()); - ExitCode::SUCCESS - }, - Err(error) => protocol_exit(&error), - } - }, - CliCommand::List { suite } => match read_cases(suite.as_deref(), true, options) { - Ok(cases) => { - for case in cases { - println!("{}", case.id); - } - ExitCode::SUCCESS - }, - Err(error) => protocol_exit(&error), - }, - CliCommand::Run { engine, suite } => { - let cases = match read_cases(suite.as_deref(), true, options) { - Ok(cases) => cases, - Err(error) => return protocol_exit(&error), - }; - match run_engine(&engine, cases, options) { - Ok(0) => ExitCode::SUCCESS, - Ok(differences) => { - eprintln!("{differences} conformance case(s) differed"); - ExitCode::from(1) - }, - Err(error) => protocol_exit(&error), - } - }, - } -} - -fn protocol_exit(error: &str) -> ExitCode { - eprintln!("jlreq-conformance: {error}"); - ExitCode::from(2) -} - -#[derive(Debug, Clone)] -struct Case { - id: String, - request: Value, - expected: Value, -} - -fn suite_reader(path: Option<&str>, builtin_when_absent: bool) -> Result, String> { - match path { - None if builtin_when_absent => Ok(Box::new(Cursor::new(BUILTIN_SUITE.as_bytes()))), - Some("-") | None => Ok(Box::new(BufReader::new(io::stdin()))), - Some(path) => fs::File::open(Path::new(path)) - .map(|file| Box::new(BufReader::new(file)) as Box) - .map_err(|error| format!("could not open suite {path:?}: {error}")), - } -} - -fn read_messages( - path: Option<&str>, - builtin_when_absent: bool, - cases: bool, - options: &Options, -) -> Result, String> { - let mut reader = suite_reader(path, builtin_when_absent)?; - parse_reader(&mut *reader, cases, options, "suite") -} - -fn parse_reader( - reader: &mut dyn BufRead, - cases: bool, - options: &Options, - stream_name: &str, -) -> Result, String> { - let mut messages = Vec::new(); - let mut total = 0_usize; - let mut line_number = 0_usize; - loop { - let previous_total = total; - let Some(line) = read_limited_line( - reader, - options.max_message_bytes, - options.max_suite_bytes, - &mut total, - stream_name, - )? - else { - break; - }; - if total == previous_total { - return Err(format!("{stream_name} reader made no progress")); - } - line_number = line_number.saturating_add(1); - let line = std::str::from_utf8(&line) - .map_err(|error| format!("line {line_number}: input is not UTF-8: {error}"))?; - if line.trim().is_empty() { - continue; - } - if messages.len() >= options.max_cases { - return Err(format!( - "{stream_name} exceeds the {} message limit", - options.max_cases - )); - } - let value: Value = serde_json::from_str(line) - .map_err(|error| format!("line {line_number}: invalid JSON: {error}"))?; - validate_envelope(&value, cases).map_err(|error| format!("line {line_number}: {error}"))?; - messages.push(value); - } - if messages.is_empty() { - return Err("the NDJSON stream contains no messages".to_owned()); - } - Ok(messages) -} - -fn read_limited_line( - reader: &mut dyn BufRead, - max_message_bytes: usize, - max_total_bytes: usize, - total: &mut usize, - stream_name: &str, -) -> Result>, String> { - let mut line = Vec::new(); - loop { - let available = reader - .fill_buf() - .map_err(|error| format!("could not read {stream_name}: {error}"))?; - if available.is_empty() { - return if line.is_empty() { - Ok(None) - } else { - Ok(Some(line)) - }; - } - let newline = available.iter().position(|byte| *byte == b'\n'); - let content = newline.unwrap_or(available.len()); - let consumed = content.saturating_add(usize::from(newline.is_some())); - *total = total.saturating_add(consumed); - if *total > max_total_bytes { - return Err(format!( - "{stream_name} exceeds the {max_total_bytes} byte total limit" - )); - } - if line.len().saturating_add(content) > max_message_bytes { - return Err(format!( - "{stream_name} message exceeds the {max_message_bytes} byte line limit" - )); - } - line.extend_from_slice(&available[..content]); - reader.consume(consumed); - if newline.is_some() { - return Ok(Some(line)); - } - } -} - -fn validate_envelope(value: &Value, case: bool) -> Result<(), String> { - let object = value - .as_object() - .ok_or_else(|| "each message must be a JSON object".to_owned())?; - if let Some(field) = object.keys().find(|field| { - ![ - "protocol", "spec", "id", "rules", "request", "response", "expected", - ] - .contains(&field.as_str()) - }) { - return Err(format!("unknown envelope field {field:?}")); - } - required_string(object, "protocol", PROTOCOL)?; - required_string(object, "spec", SPEC)?; - let id = object - .get("id") - .and_then(Value::as_str) - .ok_or_else(|| "id must be a string".to_owned())?; - if id.is_empty() { - return Err("id must not be empty".to_owned()); - } - let has_request = object.get("request").is_some_and(Value::is_object); - let has_response = object.get("response").is_some_and(Value::is_object); - let has_expected = object.get("expected").is_some(); - if has_response && has_expected { - return Err("an engine response cannot contain expected".to_owned()); - } - if has_expected && !has_request { - return Err("expected is valid only beside a suite request".to_owned()); - } - match (has_expected, object.get("rules")) { - (true, Some(rules)) => validate_rules(rules)?, - (true, None) => return Err("a suite case needs non-empty rules metadata".to_owned()), - (false, Some(_)) => return Err("rules metadata is valid only in a suite case".to_owned()), - (false, None) => {}, - } - if case { - if !has_request || !object.get("expected").is_some_and(Value::is_object) { - return Err("a suite case needs object-valued request and expected fields".to_owned()); - } - validate_request( - object - .get("request") - .ok_or_else(|| "validated suite case lost its request".to_owned())?, - )?; - validate_response( - object - .get("expected") - .ok_or_else(|| "validated suite case lost its expected response".to_owned())?, - )?; - } else if has_request == has_response { - return Err("a protocol message needs exactly one of request or response".to_owned()); - } else if let Some(request) = object.get("request") { - validate_request(request)?; - if let Some(expected) = object.get("expected") { - validate_response(expected)?; - } - } else if let Some(response) = object.get("response") { - validate_response(response)?; - } - Ok(()) -} - -fn validate_rules(value: &Value) -> Result<(), String> { - let rules = value - .as_array() - .ok_or_else(|| "suite rules must be an array".to_owned())?; - if rules.is_empty() { - return Err("suite rules must not be empty".to_owned()); - } - let mut seen = BTreeSet::new(); - for rule in rules { - let rule = rule - .as_str() - .filter(|rule| !rule.is_empty()) - .ok_or_else(|| "each suite rule must be a non-empty string".to_owned())?; - if !seen.insert(rule) { - return Err(format!("suite rule {rule:?} is repeated")); - } - } - Ok(()) -} - -fn required_string(object: &Map, name: &str, expected: &str) -> Result<(), String> { - match object.get(name).and_then(Value::as_str) { - Some(actual) if actual == expected => Ok(()), - Some(actual) => Err(format!("{name} is {actual:?}, expected {expected:?}")), - None => Err(format!("{name} is required and must be a string")), - } -} - -fn read_cases( - path: Option<&str>, - builtin_when_absent: bool, - options: &Options, -) -> Result, String> { - let values = read_messages(path, builtin_when_absent, true, options)?; - cases_from_values(values) -} - -fn cases_from_values(values: Vec) -> Result, String> { - let mut seen = BTreeSet::new(); - values - .into_iter() - .map(|value| { - let object = value - .as_object() - .ok_or_else(|| "validated case stopped being an object".to_owned())?; - let id = object - .get("id") - .and_then(Value::as_str) - .ok_or_else(|| "validated case lost its id".to_owned())? - .to_owned(); - if !seen.insert(id.clone()) { - return Err(format!("suite case id {id:?} is repeated")); - } - let request = object - .get("request") - .cloned() - .ok_or_else(|| "validated case lost its request".to_owned())?; - let expected = object - .get("expected") - .cloned() - .ok_or_else(|| "validated case lost its expected response".to_owned())?; - Ok(Case { - id, - request, - expected, - }) - }) - .collect() -} - -#[cfg(test)] -fn parse_messages(input: &str, cases: bool) -> Result, String> { - let mut reader = Cursor::new(input.as_bytes()); - parse_reader(&mut reader, cases, &Options::default(), "NDJSON stream") -} - -#[cfg(test)] -fn parse_cases(input: &str) -> Result, String> { - cases_from_values(parse_messages(input, true)?) -} - -#[derive(Debug)] -enum EngineEvent { - Response(Value), - WriterDone(Result<(), String>), - ReaderDone(Result<(), String>), -} - -#[derive(Debug)] -struct StderrCapture { - retained: Vec, - discarded: usize, -} - -fn run_engine(engine: &str, cases: Vec, options: &Options) -> Result { - let mut child = Command::new(engine) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .map_err(|error| format!("could not start engine {engine:?}: {error}"))?; - let stdin = child - .stdin - .take() - .ok_or_else(|| stop_unstarted_child(&mut child, "engine stdin was not piped"))?; - let stdout = child - .stdout - .take() - .ok_or_else(|| stop_unstarted_child(&mut child, "engine stdout was not piped"))?; - let stderr = child - .stderr - .take() - .ok_or_else(|| stop_unstarted_child(&mut child, "engine stderr was not piped"))?; - - let cases = Arc::new(cases); - let activity = Arc::new(Mutex::new(Instant::now())); - let (sender, receiver) = mpsc::sync_channel(EVENT_CHANNEL_CAPACITY); - - let writer = spawn_writer( - stdin, - Arc::clone(&cases), - sender.clone(), - Arc::clone(&activity), - ) - .map_err(|error| { - stop_child(&mut child); - format!("could not start engine stdin writer: {error}") - })?; - let reader = match spawn_reader(stdout, options.clone(), sender, Arc::clone(&activity)) { - Ok(reader) => reader, - Err(error) => { - stop_child(&mut child); - let _ = writer.join(); - return Err(format!("could not start engine stdout reader: {error}")); - }, - }; - let stderr_reader = match spawn_stderr(stderr) { - Ok(stderr_reader) => stderr_reader, - Err(error) => { - stop_child(&mut child); - drop(receiver); - let _ = writer.join(); - let _ = reader.join(); - return Err(format!("could not start engine stderr drainer: {error}")); - }, - }; - - let expected: BTreeMap<_, _> = cases - .iter() - .enumerate() - .map(|(index, case)| (case.id.clone(), index)) - .collect(); - let mut seen = BTreeSet::new(); - let mut differences = 0_usize; - let mut writer_done = false; - let mut reader_done = false; - let mut status = None; - let mut failure = None; - - while engine_work_pending( - failure.is_some(), - (writer_done, reader_done, status.is_some()), - ) { - if status.is_none() { - match child.try_wait() { - Ok(found) => status = found, - Err(error) => failure = Some(format!("could not inspect engine status: {error}")), - } - } - let idle = activity_elapsed(&activity).unwrap_or(options.timeout); - if idle >= options.timeout { - failure = Some(format!( - "engine made no stdin/stdout progress for {} second(s)", - options.timeout.as_secs() - )); - break; - } - let remaining = options.timeout.saturating_sub(idle); - let wait = WATCHDOG_TICK.min(remaining); - match receiver.recv_timeout(wait) { - Ok(EngineEvent::Response(response)) => { - if let Err(error) = compare_response( - &response, - &expected, - &cases, - &mut seen, - &mut differences, - options.verbose, - ) { - failure = Some(error); - } - }, - Ok(EngineEvent::WriterDone(result)) => { - writer_done = true; - if let Err(error) = result { - failure = Some(error); - } - }, - Ok(EngineEvent::ReaderDone(result)) => { - reader_done = true; - if let Err(error) = result { - failure = Some(error); - } - }, - Err(RecvTimeoutError::Timeout) => {}, - Err(RecvTimeoutError::Disconnected) => { - if workers_disconnected_early(writer_done, reader_done) { - failure = Some("engine I/O workers stopped unexpectedly".to_owned()); - } - }, - } - } - - if failure.is_some() { - let _ = child.kill(); - } - drop(receiver); - let waited = child.wait(); - let writer_join = join_worker(writer, "stdin writer"); - let reader_join = join_worker(reader, "stdout reader"); - let captured = join_stderr(stderr_reader); - - if let Some(error) = failure { - return Err(with_stderr(error, captured.as_ref().ok())); - } - writer_join?; - reader_join?; - let status = status - .or_else(|| waited.ok()) - .ok_or_else(|| "could not wait for engine".to_owned())?; - if !status.success() { - return Err(with_stderr( - format!("engine exited with {status}"), - captured.as_ref().ok(), - )); - } - let captured = captured?; - if seen.len() != cases.len() { - let missing = cases - .iter() - .filter(|case| !seen.contains(case.id.as_str())) - .take(3) - .map(|case| case.id.as_str()) - .collect::>() - .join(", "); - return Err(with_stderr( - format!( - "engine omitted {} response(s){}", - cases.len().saturating_sub(seen.len()), - if missing.is_empty() { - String::new() - } else { - format!(": {missing}") - } - ), - Some(&captured), - )); - } - Ok(differences) -} - -fn engine_work_pending(failed: bool, progress: (bool, bool, bool)) -> bool { - let (writer_done, reader_done, status_seen) = progress; - !(failed || (writer_done && reader_done && status_seen)) -} - -fn workers_disconnected_early(writer_done: bool, reader_done: bool) -> bool { - !(writer_done && reader_done) -} - -fn stop_unstarted_child(child: &mut Child, message: &str) -> String { - stop_child(child); - message.to_owned() -} - -fn stop_child(child: &mut Child) { - let _ = child.kill(); - let _ = child.wait(); -} - -fn spawn_writer( - stdin: ChildStdin, - cases: Arc>, - sender: SyncSender, - activity: Arc>, -) -> io::Result> { - thread::Builder::new() - .name("jlreq-engine-stdin".to_owned()) - .spawn(move || { - let result = write_requests(stdin, &cases, &activity); - let _ = sender.send(EngineEvent::WriterDone(result)); - }) -} - -fn write_requests( - mut stdin: ChildStdin, - cases: &[Case], - activity: &Mutex, -) -> Result<(), String> { - for case in cases { - let message = serde_json::json!({ - "protocol": PROTOCOL, - "spec": SPEC, - "id": case.id, - "request": case.request, - }); - serde_json::to_writer(&mut stdin, &message).map_err(|error| { - format!( - "engine stdin could not encode request {:?}: {error}", - case.id - ) - })?; - stdin - .write_all(b"\n") - .and_then(|()| stdin.flush()) - .map_err(|error| { - format!( - "engine stdin could not write request {:?}: {error}", - case.id - ) - })?; - mark_activity(activity); - } - Ok(()) -} - -fn spawn_reader( - stdout: ChildStdout, - options: Options, - sender: SyncSender, - activity: Arc>, -) -> io::Result> { - thread::Builder::new() - .name("jlreq-engine-stdout".to_owned()) - .spawn(move || { - let result = read_responses(stdout, &options, &sender, &activity); - let _ = sender.send(EngineEvent::ReaderDone(result)); - }) -} - -fn read_responses( - stdout: ChildStdout, - options: &Options, - sender: &SyncSender, - activity: &Mutex, -) -> Result<(), String> { - let mut reader = BufReader::new(stdout); - let mut total = 0_usize; - let mut line_number = 0_usize; - let mut response_count = 0_usize; - loop { - let previous_total = total; - let Some(line) = read_limited_line( - &mut reader, - options.max_message_bytes, - options.max_suite_bytes, - &mut total, - "engine stdout", - )? - else { - break; - }; - if total == previous_total { - return Err("engine stdout reader made no progress".to_owned()); - } - line_number = line_number.saturating_add(1); - let line = std::str::from_utf8(&line) - .map_err(|error| format!("engine line {line_number} is not UTF-8: {error}"))?; - if line.trim().is_empty() { - continue; - } - response_count = response_count.saturating_add(1); - if response_count > options.max_cases { - return Err(format!( - "engine stdout exceeds the {} response limit", - options.max_cases - )); - } - let value: Value = serde_json::from_str(line) - .map_err(|error| format!("engine line {line_number}: invalid JSON: {error}"))?; - validate_envelope(&value, false) - .map_err(|error| format!("engine line {line_number}: {error}"))?; - mark_activity(activity); - sender - .send(EngineEvent::Response(value)) - .map_err(|_| "engine response consumer stopped".to_owned())?; - } - Ok(()) -} - -fn spawn_stderr(stderr: ChildStderr) -> io::Result> { - thread::Builder::new() - .name("jlreq-engine-stderr".to_owned()) - .spawn(move || drain_stderr(stderr)) -} - -fn drain_stderr(mut stderr: ChildStderr) -> StderrCapture { - let mut retained = Vec::new(); - let mut discarded = 0_usize; - let mut buffer = [0_u8; 8192]; - loop { - match stderr.read(&mut buffer) { - Ok(0) | Err(_) => break, - Ok(read) => { - let room = STDERR_RETAIN_BYTES.saturating_sub(retained.len()); - let keep = read.min(room); - retained.extend_from_slice(&buffer[..keep]); - discarded = discarded.saturating_add(read.saturating_sub(keep)); - }, - } - } - StderrCapture { - retained, - discarded, - } -} - -fn mark_activity(activity: &Mutex) { - if let Ok(mut last) = activity.lock() { - *last = Instant::now(); - } -} - -fn activity_elapsed(activity: &Mutex) -> Option { - activity.lock().ok().map(|last| last.elapsed()) -} - -fn join_worker(worker: JoinHandle<()>, name: &str) -> Result<(), String> { - worker.join().map_err(|_| format!("engine {name} panicked")) -} - -fn join_stderr(worker: JoinHandle) -> Result { - worker - .join() - .map_err(|_| "engine stderr drainer panicked".to_owned()) -} - -fn with_stderr(message: String, captured: Option<&StderrCapture>) -> String { - let Some(captured) = captured else { - return message; - }; - if captured.retained.is_empty() && captured.discarded == 0 { - return message; - } - let stderr = String::from_utf8_lossy(&captured.retained); - if captured.discarded == 0 { - format!("{message}; engine stderr: {}", stderr.trim_end()) - } else { - format!( - "{message}; engine stderr: {} [discarded {} byte(s)]", - stderr.trim_end(), - captured.discarded - ) - } -} - -fn compare_response( - response: &Value, - expected_ids: &BTreeMap, - cases: &[Case], - seen: &mut BTreeSet, - differences: &mut usize, - verbose: bool, -) -> Result<(), String> { - let object = response - .as_object() - .ok_or_else(|| "validated response stopped being an object".to_owned())?; - let id = object - .get("id") - .and_then(Value::as_str) - .ok_or_else(|| "validated response lost its id".to_owned())?; - let Some(index) = expected_ids.get(id).copied() else { - return Err(format!("engine returned unknown response id {id:?}")); - }; - if !seen.insert(id.to_owned()) { - return Err(format!("engine returned duplicate response id {id:?}")); - } - let actual = object - .get("response") - .ok_or_else(|| "validated response lost its body".to_owned())?; - let expected = &cases[index].expected; - if actual != expected { - *differences = differences.saturating_add(1); - if verbose { - let difference = first_difference(expected, actual); - eprintln!( - "DIFF {id} path={} expected={} actual={}", - difference.path, difference.expected, difference.actual - ); - } else { - eprintln!("DIFF {id}"); - } - } - Ok(()) -} - -#[derive(Debug, PartialEq, Eq)] -struct JsonDifference { - path: String, - expected: String, - actual: String, -} - -fn first_difference(expected: &Value, actual: &Value) -> JsonDifference { - fn walk(expected: Option<&Value>, actual: Option<&Value>, path: &str) -> JsonDifference { - match (expected, actual) { - (Some(Value::Array(expected)), Some(Value::Array(actual))) => { - let length = expected.len().max(actual.len()); - for index in 0..length { - if expected.get(index) != actual.get(index) { - let next_path = bounded(&format!("{path}[{index}]"), 256); - return walk(expected.get(index), actual.get(index), &next_path); - } - } - }, - (Some(Value::Object(expected)), Some(Value::Object(actual))) => { - let keys: BTreeSet<_> = expected.keys().chain(actual.keys()).collect(); - for key in keys { - if expected.get(key) != actual.get(key) { - let rendered_key = - serde_json::to_string(key).unwrap_or_else(|_| "?".to_owned()); - let next_path = bounded(&format!("{path}[{rendered_key}]"), 256); - return walk(expected.get(key), actual.get(key), &next_path); - } - } - }, - _ => {}, - } - JsonDifference { - path: bounded(path, 256), - expected: render_json(expected), - actual: render_json(actual), - } - } - walk(Some(expected), Some(actual), "$") -} - -fn render_json(value: Option<&Value>) -> String { - value.map_or_else( - || "".to_owned(), - |value| { - bounded( - &serde_json::to_string(value).unwrap_or_else(|_| "".to_owned()), - 512, - ) - }, - ) -} - -fn bounded(value: &str, maximum: usize) -> String { - let mut output: String = value.chars().take(maximum).collect(); - if value.chars().count() > maximum { - output.push('…'); - } - output -} - +// The runner stages share one private module contract while keeping parsing, process control, +// and comparison independently maintainable. +include!("runner/cli.rs"); +include!("runner/suite.rs"); +include!("runner/process.rs"); +include!("runner/compare.rs"); #[cfg(test)] mod tests { use super::{ @@ -1238,20 +299,32 @@ mod tests { #[test] fn line_and_total_limits_are_inclusive() { let mut total = 0; - let mut reader = Cursor::new(b"abc\n".as_slice()); + let mut line = Vec::new(); + let mut reader = Cursor::new(b"abc\nx\n".as_slice()); assert_eq!( - read_limited_line(&mut reader, 3, 4, &mut total, "test"), - Ok(Some(b"abc".to_vec())) + read_limited_line(&mut reader, &mut line, 3, 4, &mut total, "test"), + Ok(true) ); + assert_eq!(line, b"abc"); assert_eq!(total, 4); + let retained_capacity = line.capacity(); + assert_eq!( + read_limited_line(&mut reader, &mut line, 3, 6, &mut total, "test"), + Ok(true) + ); + assert_eq!(line, b"x"); + assert_eq!(line.capacity(), retained_capacity); + assert_eq!(total, 6); let mut total = 0; + let mut line = Vec::new(); let mut reader = Cursor::new(b"abc\n".as_slice()); - assert!(read_limited_line(&mut reader, 2, 4, &mut total, "test").is_err()); + assert!(read_limited_line(&mut reader, &mut line, 2, 4, &mut total, "test").is_err()); let mut total = 0; + let mut line = Vec::new(); let mut reader = Cursor::new(b"abc\n".as_slice()); - assert!(read_limited_line(&mut reader, 3, 3, &mut total, "test").is_err()); + assert!(read_limited_line(&mut reader, &mut line, 3, 3, &mut total, "test").is_err()); } #[test] diff --git a/crates/jlreq-conformance/src/runner/cli.rs b/crates/jlreq-conformance/src/runner/cli.rs new file mode 100644 index 0000000..cc4006f --- /dev/null +++ b/crates/jlreq-conformance/src/runner/cli.rs @@ -0,0 +1,215 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +fn main() -> ExitCode { + match parse_invocation(env::args().skip(1)) { + Ok(Invocation::Help) => { + print_help(); + ExitCode::SUCCESS + }, + Ok(Invocation::Version) => { + println!("jlreq-conformance {}", env!("CARGO_PKG_VERSION")); + ExitCode::SUCCESS + }, + Ok(Invocation::Command(options, command)) => run_command(&options, command), + Err(error) => { + eprintln!("jlreq-conformance: {error}"); + eprintln!("try 'jlreq-conformance --help' for usage"); + ExitCode::from(2) + }, + } +} + +#[derive(Debug, Clone)] +struct Options { + verbose: bool, + timeout: Duration, + max_message_bytes: usize, + max_suite_bytes: usize, + max_cases: usize, +} + +impl Default for Options { + fn default() -> Self { + Self { + verbose: false, + timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECONDS), + max_message_bytes: DEFAULT_MAX_MESSAGE_BYTES, + max_suite_bytes: DEFAULT_MAX_SUITE_BYTES, + max_cases: DEFAULT_MAX_CASES, + } + } +} + +#[derive(Debug, Clone)] +enum CliCommand { + Validate { + suite: Option, + }, + List { + suite: Option, + }, + Run { + engine: String, + suite: Option, + }, +} + +#[derive(Debug, Clone)] +enum Invocation { + Help, + Version, + Command(Options, CliCommand), +} + +fn parse_invocation(arguments: impl IntoIterator) -> Result { + let mut options = Options::default(); + let mut positional = Vec::new(); + let mut arguments = arguments.into_iter().peekable(); + let mut parse_options = true; + while let Some(argument) = arguments.next() { + if parse_options && argument == "--" { + parse_options = false; + } else if parse_options && matches!(argument.as_str(), "-h" | "--help") { + return Ok(Invocation::Help); + } else if parse_options && matches!(argument.as_str(), "-V" | "--version") { + return Ok(Invocation::Version); + } else if parse_options && argument == "--verbose" { + options.verbose = true; + } else if parse_options && argument.starts_with("--") { + let (name, inline) = argument + .split_once('=') + .map_or((argument.as_str(), None), |(name, value)| { + (name, Some(value)) + }); + let value = match name { + "--timeout-seconds" + | "--max-message-bytes" + | "--max-suite-bytes" + | "--max-cases" => inline.map(str::to_owned).or_else(|| arguments.next()), + _ => return Err(format!("unknown option {argument:?}")), + } + .ok_or_else(|| format!("{name} needs a positive integer"))?; + match name { + "--timeout-seconds" => { + let seconds = positive_u64(&value, name)?; + options.timeout = Duration::from_secs(seconds); + }, + "--max-message-bytes" => { + options.max_message_bytes = positive_usize(&value, name)?; + }, + "--max-suite-bytes" => { + options.max_suite_bytes = positive_usize(&value, name)?; + }, + "--max-cases" => options.max_cases = positive_usize(&value, name)?, + _ => return Err(format!("unknown option {argument:?}")), + } + } else { + positional.push(argument); + } + } + + let Some(command) = positional.first().map(String::as_str) else { + return Err("a command is required".to_owned()); + }; + let command = match command { + "validate" if positional.len() <= 2 => CliCommand::Validate { + suite: positional.get(1).cloned(), + }, + "list" if positional.len() <= 2 => CliCommand::List { + suite: positional.get(1).cloned(), + }, + "run" if (2..=3).contains(&positional.len()) => CliCommand::Run { + engine: positional[1].clone(), + suite: positional.get(2).cloned(), + }, + "validate" => return Err("usage: jlreq-conformance validate [SUITE.ndjson|-]".to_owned()), + "list" => return Err("usage: jlreq-conformance list [SUITE.ndjson]".to_owned()), + "run" => { + return Err("usage: jlreq-conformance run ENGINE [SUITE.ndjson]".to_owned()); + }, + other => return Err(format!("unknown command {other:?}")), + }; + Ok(Invocation::Command(options, command)) +} + +fn positive_u64(value: &str, option: &str) -> Result { + value + .parse::() + .ok() + .filter(|value| *value > 0) + .ok_or_else(|| format!("{option} needs a positive integer")) +} + +fn positive_usize(value: &str, option: &str) -> Result { + value + .parse::() + .ok() + .filter(|value| *value > 0) + .ok_or_else(|| format!("{option} needs a positive integer")) +} + +fn print_help() { + println!( + "jlreq-conformance {}\n\ + \n\ + Usage:\n\ + jlreq-conformance [OPTIONS] validate [SUITE.ndjson|-]\n\ + jlreq-conformance [OPTIONS] list [SUITE.ndjson]\n\ + jlreq-conformance [OPTIONS] run ENGINE [SUITE.ndjson]\n\ + \n\ + Options:\n\ + -h, --help Show this help\n\ + -V, --version Show the package version\n\ + --verbose Show bounded JSON differences\n\ + --timeout-seconds N No-I/O timeout (default: 30)\n\ + --max-message-bytes N Per-line limit (default: 1048576)\n\ + --max-suite-bytes N Stream limit (default: 268435456)\n\ + --max-cases N Message/case limit (default: 200000)", + env!("CARGO_PKG_VERSION") + ); +} + +fn run_command(options: &Options, command: CliCommand) -> ExitCode { + match command { + CliCommand::Validate { suite } => { + match read_messages(suite.as_deref(), false, false, options) { + Ok(messages) => { + eprintln!("validated {} message(s)", messages.len()); + ExitCode::SUCCESS + }, + Err(error) => protocol_exit(&error), + } + }, + CliCommand::List { suite } => match read_cases(suite.as_deref(), true, options) { + Ok(cases) => { + for case in cases { + println!("{}", case.id); + } + ExitCode::SUCCESS + }, + Err(error) => protocol_exit(&error), + }, + CliCommand::Run { engine, suite } => { + let cases = match read_cases(suite.as_deref(), true, options) { + Ok(cases) => cases, + Err(error) => return protocol_exit(&error), + }; + match run_engine(&engine, cases, options) { + Ok(0) => ExitCode::SUCCESS, + Ok(differences) => { + eprintln!("{differences} conformance case(s) differed"); + ExitCode::from(1) + }, + Err(error) => protocol_exit(&error), + } + }, + } +} + +fn protocol_exit(error: &str) -> ExitCode { + eprintln!("jlreq-conformance: {error}"); + ExitCode::from(2) +} + diff --git a/crates/jlreq-conformance/src/runner/compare.rs b/crates/jlreq-conformance/src/runner/compare.rs new file mode 100644 index 0000000..7345e52 --- /dev/null +++ b/crates/jlreq-conformance/src/runner/compare.rs @@ -0,0 +1,105 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +fn compare_response( + response: &Value, + expected_ids: &BTreeMap, + cases: &[Case], + seen: &mut BTreeSet, + differences: &mut usize, + verbose: bool, +) -> Result<(), String> { + let object = response + .as_object() + .ok_or_else(|| "validated response stopped being an object".to_owned())?; + let id = object + .get("id") + .and_then(Value::as_str) + .ok_or_else(|| "validated response lost its id".to_owned())?; + let Some(index) = expected_ids.get(id).copied() else { + return Err(format!("engine returned unknown response id {id:?}")); + }; + if !seen.insert(id.to_owned()) { + return Err(format!("engine returned duplicate response id {id:?}")); + } + let actual = object + .get("response") + .ok_or_else(|| "validated response lost its body".to_owned())?; + let expected = &cases[index].expected; + if actual != expected { + *differences = differences.saturating_add(1); + if verbose { + let difference = first_difference(expected, actual); + eprintln!( + "DIFF {id} path={} expected={} actual={}", + difference.path, difference.expected, difference.actual + ); + } else { + eprintln!("DIFF {id}"); + } + } + Ok(()) +} + +#[derive(Debug, PartialEq, Eq)] +struct JsonDifference { + path: String, + expected: String, + actual: String, +} + +fn first_difference(expected: &Value, actual: &Value) -> JsonDifference { + fn walk(expected: Option<&Value>, actual: Option<&Value>, path: &str) -> JsonDifference { + match (expected, actual) { + (Some(Value::Array(expected)), Some(Value::Array(actual))) => { + let length = expected.len().max(actual.len()); + for index in 0..length { + if expected.get(index) != actual.get(index) { + let next_path = bounded(&format!("{path}[{index}]"), 256); + return walk(expected.get(index), actual.get(index), &next_path); + } + } + }, + (Some(Value::Object(expected)), Some(Value::Object(actual))) => { + let keys: BTreeSet<_> = expected.keys().chain(actual.keys()).collect(); + for key in keys { + if expected.get(key) != actual.get(key) { + let rendered_key = + serde_json::to_string(key).unwrap_or_else(|_| "?".to_owned()); + let next_path = bounded(&format!("{path}[{rendered_key}]"), 256); + return walk(expected.get(key), actual.get(key), &next_path); + } + } + }, + _ => {}, + } + JsonDifference { + path: bounded(path, 256), + expected: render_json(expected), + actual: render_json(actual), + } + } + walk(Some(expected), Some(actual), "$") +} + +fn render_json(value: Option<&Value>) -> String { + value.map_or_else( + || "".to_owned(), + |value| { + bounded( + &serde_json::to_string(value).unwrap_or_else(|_| "".to_owned()), + 512, + ) + }, + ) +} + +fn bounded(value: &str, maximum: usize) -> String { + let mut output: String = value.chars().take(maximum).collect(); + if value.chars().count() > maximum { + output.push('…'); + } + output +} + diff --git a/crates/jlreq-conformance/src/runner/process.rs b/crates/jlreq-conformance/src/runner/process.rs new file mode 100644 index 0000000..273ea9e --- /dev/null +++ b/crates/jlreq-conformance/src/runner/process.rs @@ -0,0 +1,380 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[derive(Debug)] +enum EngineEvent { + Response(Value), + WriterDone(Result<(), String>), + ReaderDone(Result<(), String>), +} + +#[derive(Debug)] +struct StderrCapture { + retained: Vec, + discarded: usize, +} + +fn run_engine(engine: &str, cases: Vec, options: &Options) -> Result { + let mut child = Command::new(engine) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .map_err(|error| format!("could not start engine {engine:?}: {error}"))?; + let stdin = child + .stdin + .take() + .ok_or_else(|| stop_unstarted_child(&mut child, "engine stdin was not piped"))?; + let stdout = child + .stdout + .take() + .ok_or_else(|| stop_unstarted_child(&mut child, "engine stdout was not piped"))?; + let stderr = child + .stderr + .take() + .ok_or_else(|| stop_unstarted_child(&mut child, "engine stderr was not piped"))?; + + let cases = Arc::new(cases); + let activity = Arc::new(Mutex::new(Instant::now())); + let (sender, receiver) = mpsc::sync_channel(EVENT_CHANNEL_CAPACITY); + + let writer = spawn_writer( + stdin, + Arc::clone(&cases), + sender.clone(), + Arc::clone(&activity), + ) + .map_err(|error| { + stop_child(&mut child); + format!("could not start engine stdin writer: {error}") + })?; + let reader = match spawn_reader(stdout, options.clone(), sender, Arc::clone(&activity)) { + Ok(reader) => reader, + Err(error) => { + stop_child(&mut child); + let _ = writer.join(); + return Err(format!("could not start engine stdout reader: {error}")); + }, + }; + let stderr_reader = match spawn_stderr(stderr) { + Ok(stderr_reader) => stderr_reader, + Err(error) => { + stop_child(&mut child); + drop(receiver); + let _ = writer.join(); + let _ = reader.join(); + return Err(format!("could not start engine stderr drainer: {error}")); + }, + }; + + let expected: BTreeMap<_, _> = cases + .iter() + .enumerate() + .map(|(index, case)| (case.id.clone(), index)) + .collect(); + let mut seen = BTreeSet::new(); + let mut differences = 0_usize; + let mut writer_done = false; + let mut reader_done = false; + let mut status = None; + let mut failure = None; + + while engine_work_pending( + failure.is_some(), + (writer_done, reader_done, status.is_some()), + ) { + if status.is_none() { + match child.try_wait() { + Ok(found) => status = found, + Err(error) => failure = Some(format!("could not inspect engine status: {error}")), + } + } + let idle = activity_elapsed(&activity).unwrap_or(options.timeout); + if idle >= options.timeout { + failure = Some(format!( + "engine made no stdin/stdout progress for {} second(s)", + options.timeout.as_secs() + )); + break; + } + let remaining = options.timeout.saturating_sub(idle); + let wait = WATCHDOG_TICK.min(remaining); + match receiver.recv_timeout(wait) { + Ok(EngineEvent::Response(response)) => { + if let Err(error) = compare_response( + &response, + &expected, + &cases, + &mut seen, + &mut differences, + options.verbose, + ) { + failure = Some(error); + } + }, + Ok(EngineEvent::WriterDone(result)) => { + writer_done = true; + if let Err(error) = result { + failure = Some(error); + } + }, + Ok(EngineEvent::ReaderDone(result)) => { + reader_done = true; + if let Err(error) = result { + failure = Some(error); + } + }, + Err(RecvTimeoutError::Timeout) => {}, + Err(RecvTimeoutError::Disconnected) => { + if workers_disconnected_early(writer_done, reader_done) { + failure = Some("engine I/O workers stopped unexpectedly".to_owned()); + } + }, + } + } + + if failure.is_some() { + let _ = child.kill(); + } + drop(receiver); + let waited = child.wait(); + let writer_join = join_worker(writer, "stdin writer"); + let reader_join = join_worker(reader, "stdout reader"); + let captured = join_stderr(stderr_reader); + + if let Some(error) = failure { + return Err(with_stderr(error, captured.as_ref().ok())); + } + writer_join?; + reader_join?; + let status = status + .or_else(|| waited.ok()) + .ok_or_else(|| "could not wait for engine".to_owned())?; + if !status.success() { + return Err(with_stderr( + format!("engine exited with {status}"), + captured.as_ref().ok(), + )); + } + let captured = captured?; + if seen.len() != cases.len() { + let missing = cases + .iter() + .filter(|case| !seen.contains(case.id.as_str())) + .take(3) + .map(|case| case.id.as_str()) + .collect::>() + .join(", "); + return Err(with_stderr( + format!( + "engine omitted {} response(s){}", + cases.len().saturating_sub(seen.len()), + if missing.is_empty() { + String::new() + } else { + format!(": {missing}") + } + ), + Some(&captured), + )); + } + Ok(differences) +} + +fn engine_work_pending(failed: bool, progress: (bool, bool, bool)) -> bool { + let (writer_done, reader_done, status_seen) = progress; + !(failed || (writer_done && reader_done && status_seen)) +} + +fn workers_disconnected_early(writer_done: bool, reader_done: bool) -> bool { + !(writer_done && reader_done) +} + +fn stop_unstarted_child(child: &mut Child, message: &str) -> String { + stop_child(child); + message.to_owned() +} + +fn stop_child(child: &mut Child) { + let _ = child.kill(); + let _ = child.wait(); +} + +fn spawn_writer( + stdin: ChildStdin, + cases: Arc>, + sender: SyncSender, + activity: Arc>, +) -> io::Result> { + thread::Builder::new() + .name("jlreq-engine-stdin".to_owned()) + .spawn(move || { + let result = write_requests(stdin, &cases, &activity); + let _ = sender.send(EngineEvent::WriterDone(result)); + }) +} + +fn write_requests( + mut stdin: ChildStdin, + cases: &[Case], + activity: &Mutex, +) -> Result<(), String> { + for case in cases { + let message = serde_json::json!({ + "protocol": PROTOCOL, + "spec": SPEC, + "id": case.id, + "request": case.request, + }); + serde_json::to_writer(&mut stdin, &message).map_err(|error| { + format!( + "engine stdin could not encode request {:?}: {error}", + case.id + ) + })?; + stdin + .write_all(b"\n") + .and_then(|()| stdin.flush()) + .map_err(|error| { + format!( + "engine stdin could not write request {:?}: {error}", + case.id + ) + })?; + mark_activity(activity); + } + Ok(()) +} + +fn spawn_reader( + stdout: ChildStdout, + options: Options, + sender: SyncSender, + activity: Arc>, +) -> io::Result> { + thread::Builder::new() + .name("jlreq-engine-stdout".to_owned()) + .spawn(move || { + let result = read_responses(stdout, &options, &sender, &activity); + let _ = sender.send(EngineEvent::ReaderDone(result)); + }) +} + +fn read_responses( + stdout: ChildStdout, + options: &Options, + sender: &SyncSender, + activity: &Mutex, +) -> Result<(), String> { + let mut reader = BufReader::new(stdout); + let mut line = Vec::new(); + let mut total = 0_usize; + let mut line_number = 0_usize; + let mut response_count = 0_usize; + loop { + let previous_total = total; + if !read_limited_line( + &mut reader, + &mut line, + options.max_message_bytes, + options.max_suite_bytes, + &mut total, + "engine stdout", + )? { + break; + } + if total == previous_total { + return Err("engine stdout reader made no progress".to_owned()); + } + line_number = line_number.saturating_add(1); + let line = std::str::from_utf8(&line) + .map_err(|error| format!("engine line {line_number} is not UTF-8: {error}"))?; + if line.trim().is_empty() { + continue; + } + response_count = response_count.saturating_add(1); + if response_count > options.max_cases { + return Err(format!( + "engine stdout exceeds the {} response limit", + options.max_cases + )); + } + let value: Value = serde_json::from_str(line) + .map_err(|error| format!("engine line {line_number}: invalid JSON: {error}"))?; + validate_envelope(&value, false) + .map_err(|error| format!("engine line {line_number}: {error}"))?; + mark_activity(activity); + sender + .send(EngineEvent::Response(value)) + .map_err(|_| "engine response consumer stopped".to_owned())?; + } + Ok(()) +} + +fn spawn_stderr(stderr: ChildStderr) -> io::Result> { + thread::Builder::new() + .name("jlreq-engine-stderr".to_owned()) + .spawn(move || drain_stderr(stderr)) +} + +fn drain_stderr(mut stderr: ChildStderr) -> StderrCapture { + let mut retained = Vec::new(); + let mut discarded = 0_usize; + let mut buffer = [0_u8; 8192]; + loop { + match stderr.read(&mut buffer) { + Ok(0) | Err(_) => break, + Ok(read) => { + let room = STDERR_RETAIN_BYTES.saturating_sub(retained.len()); + let keep = read.min(room); + retained.extend_from_slice(&buffer[..keep]); + discarded = discarded.saturating_add(read.saturating_sub(keep)); + }, + } + } + StderrCapture { + retained, + discarded, + } +} + +fn mark_activity(activity: &Mutex) { + if let Ok(mut last) = activity.lock() { + *last = Instant::now(); + } +} + +fn activity_elapsed(activity: &Mutex) -> Option { + activity.lock().ok().map(|last| last.elapsed()) +} + +fn join_worker(worker: JoinHandle<()>, name: &str) -> Result<(), String> { + worker.join().map_err(|_| format!("engine {name} panicked")) +} + +fn join_stderr(worker: JoinHandle) -> Result { + worker + .join() + .map_err(|_| "engine stderr drainer panicked".to_owned()) +} + +fn with_stderr(message: String, captured: Option<&StderrCapture>) -> String { + let Some(captured) = captured else { + return message; + }; + if captured.retained.is_empty() && captured.discarded == 0 { + return message; + } + let stderr = String::from_utf8_lossy(&captured.retained); + if captured.discarded == 0 { + format!("{message}; engine stderr: {}", stderr.trim_end()) + } else { + format!( + "{message}; engine stderr: {} [discarded {} byte(s)]", + stderr.trim_end(), + captured.discarded + ) + } +} + diff --git a/crates/jlreq-conformance/src/runner/suite.rs b/crates/jlreq-conformance/src/runner/suite.rs new file mode 100644 index 0000000..aa2956e --- /dev/null +++ b/crates/jlreq-conformance/src/runner/suite.rs @@ -0,0 +1,223 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[derive(Debug, Clone)] +struct Case { + id: String, + request: Value, + expected: Value, +} + +fn suite_reader(path: Option<&str>, builtin_when_absent: bool) -> Result, String> { + match path { + None if builtin_when_absent => Ok(Box::new(Cursor::new(BUILTIN_SUITE.as_bytes()))), + Some("-") | None => Ok(Box::new(BufReader::new(io::stdin()))), + Some(path) => fs::File::open(Path::new(path)) + .map(|file| Box::new(BufReader::new(file)) as Box) + .map_err(|error| format!("could not open suite {path:?}: {error}")), + } +} + +fn read_messages( + path: Option<&str>, + builtin_when_absent: bool, + cases: bool, + options: &Options, +) -> Result, String> { + let mut reader = suite_reader(path, builtin_when_absent)?; + parse_reader(&mut *reader, cases, options, "suite") +} + +fn parse_reader( + reader: &mut dyn BufRead, + cases: bool, + options: &Options, + stream_name: &str, +) -> Result, String> { + let mut messages = Vec::new(); + let mut line = Vec::new(); + let mut total = 0_usize; + let mut line_number = 0_usize; + loop { + let previous_total = total; + if !read_limited_line( + reader, + &mut line, + options.max_message_bytes, + options.max_suite_bytes, + &mut total, + stream_name, + )? { + break; + } + if total == previous_total { + return Err(format!("{stream_name} reader made no progress")); + } + line_number = line_number.saturating_add(1); + let line = std::str::from_utf8(&line) + .map_err(|error| format!("line {line_number}: input is not UTF-8: {error}"))?; + if line.trim().is_empty() { + continue; + } + if messages.len() >= options.max_cases { + return Err(format!( + "{stream_name} exceeds the {} message limit", + options.max_cases + )); + } + let value: Value = serde_json::from_str(line) + .map_err(|error| format!("line {line_number}: invalid JSON: {error}"))?; + validate_envelope(&value, cases).map_err(|error| format!("line {line_number}: {error}"))?; + messages.push(value); + } + if messages.is_empty() { + return Err("the NDJSON stream contains no messages".to_owned()); + } + Ok(messages) +} + +fn validate_envelope(value: &Value, case: bool) -> Result<(), String> { + let object = value + .as_object() + .ok_or_else(|| "each message must be a JSON object".to_owned())?; + if let Some(field) = object.keys().find(|field| { + ![ + "protocol", "spec", "id", "rules", "request", "response", "expected", + ] + .contains(&field.as_str()) + }) { + return Err(format!("unknown envelope field {field:?}")); + } + required_string(object, "protocol", PROTOCOL)?; + required_string(object, "spec", SPEC)?; + let id = object + .get("id") + .and_then(Value::as_str) + .ok_or_else(|| "id must be a string".to_owned())?; + if id.is_empty() { + return Err("id must not be empty".to_owned()); + } + let has_request = object.get("request").is_some_and(Value::is_object); + let has_response = object.get("response").is_some_and(Value::is_object); + let has_expected = object.get("expected").is_some(); + if has_response && has_expected { + return Err("an engine response cannot contain expected".to_owned()); + } + if has_expected && !has_request { + return Err("expected is valid only beside a suite request".to_owned()); + } + match (has_expected, object.get("rules")) { + (true, Some(rules)) => validate_rules(rules)?, + (true, None) => return Err("a suite case needs non-empty rules metadata".to_owned()), + (false, Some(_)) => return Err("rules metadata is valid only in a suite case".to_owned()), + (false, None) => {}, + } + if case { + if !has_request || !object.get("expected").is_some_and(Value::is_object) { + return Err("a suite case needs object-valued request and expected fields".to_owned()); + } + validate_request( + object + .get("request") + .ok_or_else(|| "validated suite case lost its request".to_owned())?, + )?; + validate_response( + object + .get("expected") + .ok_or_else(|| "validated suite case lost its expected response".to_owned())?, + )?; + } else if has_request == has_response { + return Err("a protocol message needs exactly one of request or response".to_owned()); + } else if let Some(request) = object.get("request") { + validate_request(request)?; + if let Some(expected) = object.get("expected") { + validate_response(expected)?; + } + } else if let Some(response) = object.get("response") { + validate_response(response)?; + } + Ok(()) +} + +fn validate_rules(value: &Value) -> Result<(), String> { + let rules = value + .as_array() + .ok_or_else(|| "suite rules must be an array".to_owned())?; + if rules.is_empty() { + return Err("suite rules must not be empty".to_owned()); + } + let mut seen = BTreeSet::new(); + for rule in rules { + let rule = rule + .as_str() + .filter(|rule| !rule.is_empty()) + .ok_or_else(|| "each suite rule must be a non-empty string".to_owned())?; + if !seen.insert(rule) { + return Err(format!("suite rule {rule:?} is repeated")); + } + } + Ok(()) +} + +fn required_string(object: &Map, name: &str, expected: &str) -> Result<(), String> { + match object.get(name).and_then(Value::as_str) { + Some(actual) if actual == expected => Ok(()), + Some(actual) => Err(format!("{name} is {actual:?}, expected {expected:?}")), + None => Err(format!("{name} is required and must be a string")), + } +} + +fn read_cases( + path: Option<&str>, + builtin_when_absent: bool, + options: &Options, +) -> Result, String> { + let values = read_messages(path, builtin_when_absent, true, options)?; + cases_from_values(values) +} + +fn cases_from_values(values: Vec) -> Result, String> { + let mut seen = BTreeSet::new(); + values + .into_iter() + .map(|value| { + let object = value + .as_object() + .ok_or_else(|| "validated case stopped being an object".to_owned())?; + let id = object + .get("id") + .and_then(Value::as_str) + .ok_or_else(|| "validated case lost its id".to_owned())? + .to_owned(); + if !seen.insert(id.clone()) { + return Err(format!("suite case id {id:?} is repeated")); + } + let request = object + .get("request") + .cloned() + .ok_or_else(|| "validated case lost its request".to_owned())?; + let expected = object + .get("expected") + .cloned() + .ok_or_else(|| "validated case lost its expected response".to_owned())?; + Ok(Case { + id, + request, + expected, + }) + }) + .collect() +} + +#[cfg(test)] +fn parse_messages(input: &str, cases: bool) -> Result, String> { + let mut reader = Cursor::new(input.as_bytes()); + parse_reader(&mut reader, cases, &Options::default(), "NDJSON stream") +} + +#[cfg(test)] +fn parse_cases(input: &str) -> Result, String> { + cases_from_values(parse_messages(input, true)?) +} + diff --git a/crates/jlreq-conformance/src/sample_engine.rs b/crates/jlreq-conformance/src/sample_engine.rs index 6ae5e4e..edd1392 100644 --- a/crates/jlreq-conformance/src/sample_engine.rs +++ b/crates/jlreq-conformance/src/sample_engine.rs @@ -4,6 +4,8 @@ //! Reference implementation of the language-independent conformance protocol. +mod transport; + use std::{ io::{self, BufRead, Write}, process::ExitCode, @@ -22,6 +24,7 @@ use jlreq_core::{ }, }; use serde_json::{Map, Value, json}; +use transport::read_limited_line; const PROTOCOL: &str = "jlreq.conformance/1"; const SPEC: &str = jlreq_core::SPECIFICATION; @@ -53,15 +56,21 @@ fn run_stream( max_messages: usize, ) -> Result<(), String> { let mut total_bytes = 0_usize; + let mut line = Vec::new(); let mut line_number = 0_usize; let mut message_count = 0_usize; loop { let previous_total = total_bytes; - let Some(line) = - read_limited_line(input, &mut total_bytes, MAX_MESSAGE_BYTES, MAX_STREAM_BYTES)? - else { + if !read_limited_line( + input, + &mut line, + MAX_MESSAGE_BYTES, + MAX_STREAM_BYTES, + &mut total_bytes, + "input", + )? { break; - }; + } if total_bytes == previous_total { return Err("input reader made no progress".to_owned()); } @@ -105,46 +114,6 @@ fn run_stream( Ok(()) } -fn read_limited_line( - reader: &mut dyn BufRead, - total_bytes: &mut usize, - max_message_bytes: usize, - max_stream_bytes: usize, -) -> Result>, String> { - let mut line = Vec::new(); - loop { - let available = reader - .fill_buf() - .map_err(|error| format!("could not read input: {error}"))?; - if available.is_empty() { - return if line.is_empty() { - Ok(None) - } else { - Ok(Some(line)) - }; - } - let newline = available.iter().position(|byte| *byte == b'\n'); - let content = newline.unwrap_or(available.len()); - let consumed = content.saturating_add(usize::from(newline.is_some())); - *total_bytes = total_bytes.saturating_add(consumed); - if *total_bytes > max_stream_bytes { - return Err(format!( - "input exceeds the {max_stream_bytes} byte total limit" - )); - } - if line.len().saturating_add(content) > max_message_bytes { - return Err(format!( - "input message exceeds the {max_message_bytes} byte line limit" - )); - } - line.extend_from_slice(&available[..content]); - reader.consume(consumed); - if newline.is_some() { - return Ok(Some(line)); - } - } -} - fn parse_request(value: &Value) -> Result<(Paragraph, Style), String> { let request = object(value, "request")?; let text = parse_shaped_text(value)?; @@ -818,20 +787,24 @@ mod tests { assert_eq!(MAX_STREAM_BYTES, 268_435_456); let mut total = 0; + let mut line = Vec::new(); let mut input = Cursor::new(b"abc\n".as_slice()); assert_eq!( - read_limited_line(&mut input, &mut total, 3, 4), - Ok(Some(b"abc".to_vec())) + read_limited_line(&mut input, &mut line, 3, 4, &mut total, "input"), + Ok(true) ); + assert_eq!(line, b"abc"); assert_eq!(total, 4); let mut total = 0; + let mut line = Vec::new(); let mut input = Cursor::new(b"abc\n".as_slice()); - assert!(read_limited_line(&mut input, &mut total, 2, 4).is_err()); + assert!(read_limited_line(&mut input, &mut line, 2, 4, &mut total, "input").is_err()); let mut total = 0; + let mut line = Vec::new(); let mut input = Cursor::new(b"abc\n".as_slice()); - assert!(read_limited_line(&mut input, &mut total, 3, 3).is_err()); + assert!(read_limited_line(&mut input, &mut line, 3, 3, &mut total, "input").is_err()); } #[test] diff --git a/crates/jlreq-conformance/src/transport.rs b/crates/jlreq-conformance/src/transport.rs new file mode 100644 index 0000000..62b123e --- /dev/null +++ b/crates/jlreq-conformance/src/transport.rs @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use std::io::BufRead; + +/// Read one bounded NDJSON line into reusable storage. +/// +/// The returned boolean is false only when the reader reaches EOF before +/// producing another line. The byte counters include a consumed newline, as +/// required by the conformance protocol's stream limits. +pub(crate) fn read_limited_line( + reader: &mut dyn BufRead, + line: &mut Vec, + max_message_bytes: usize, + max_total_bytes: usize, + total: &mut usize, + stream_name: &str, +) -> Result { + line.clear(); + loop { + let available = reader + .fill_buf() + .map_err(|error| format!("could not read {stream_name}: {error}"))?; + if available.is_empty() { + return Ok(!line.is_empty()); + } + let newline = available.iter().position(|byte| *byte == b'\n'); + let content = newline.unwrap_or(available.len()); + let consumed = content.saturating_add(usize::from(newline.is_some())); + *total = total.saturating_add(consumed); + if *total > max_total_bytes { + return Err(format!( + "{stream_name} exceeds the {max_total_bytes} byte total limit" + )); + } + if line.len().saturating_add(content) > max_message_bytes { + return Err(format!( + "{stream_name} message exceeds the {max_message_bytes} byte line limit" + )); + } + line.extend_from_slice(&available[..content]); + reader.consume(consumed); + if newline.is_some() { + return Ok(true); + } + } +} diff --git a/crates/jlreq-core/src/generated.rs b/crates/jlreq-core/src/generated.rs index 18735f8..a2f4162 100644 --- a/crates/jlreq-core/src/generated.rs +++ b/crates/jlreq-core/src/generated.rs @@ -131,12 +131,6 @@ const fn ranged_cells_valid(tables: &[&[crate::spec::RawRangedCell]]) -> bool { let mut index = 0_usize; while index < cells.len() { let cell = &cells[index]; - if cell.before > CLASS_COUNT || cell.after > CLASS_COUNT { - return false; - } - if cell.before == 17 || cell.before == 18 || cell.after == 17 || cell.after == 18 { - return false; - } if let Some(limit) = cell.limit { if limit < 0 || limit > 720 { return false; @@ -159,15 +153,6 @@ const fn break_cells_valid(cells: &[crate::spec::RawBreakCell]) -> bool { let mut index = 0_usize; while index < cells.len() { let cell = &cells[index]; - if cell.before < 1 || cell.before > CLASS_COUNT { - return false; - } - if cell.after < 1 || cell.after > CLASS_COUNT { - return false; - } - if cell.before == 17 || cell.before == 18 || cell.after == 17 || cell.after == 18 { - return false; - } if cell.levels > 0b1111 { return false; } @@ -182,12 +167,6 @@ const fn spacing_cells_valid(cells: &[crate::spec::RawSpacingCell]) -> bool { let mut index = 0_usize; while index < cells.len() { let cell = &cells[index]; - if cell.before > CLASS_COUNT || cell.after > CLASS_COUNT { - return false; - } - if cell.before == 17 || cell.before == 18 || cell.after == 17 || cell.after == 18 { - return false; - } if cell.terms.len() > 2 { return false; } @@ -385,8 +364,6 @@ mod tests { fn valid_ranged_cell() -> RawRangedCell { RawRangedCell { - before: CLASS_COUNT, - after: CLASS_COUNT, limit: Some(720), two_valued: true, residual: true, @@ -401,8 +378,6 @@ mod tests { fn valid_break_cell() -> RawBreakCell { RawBreakCell { - before: 1, - after: CLASS_COUNT, prohibited: false, levels: 0b1111, rule: "test", @@ -415,8 +390,6 @@ mod tests { fn valid_spacing_cell(terms: &'static [RawTerm]) -> RawSpacingCell { RawSpacingCell { - before: 0, - after: CLASS_COUNT, prohibited: false, hang: RawHang::None, rule: "test", @@ -500,24 +473,6 @@ mod tests { let valid = valid_ranged_cell(); assert!(ranged_cells_valid(&[&[], &[valid]])); for invalid in [ - RawRangedCell { - before: CLASS_COUNT.saturating_add(1), - ..valid - }, - RawRangedCell { - after: CLASS_COUNT.saturating_add(1), - ..valid - }, - RawRangedCell { - before: 17, - ..valid - }, - RawRangedCell { - before: 18, - ..valid - }, - RawRangedCell { after: 17, ..valid }, - RawRangedCell { after: 18, ..valid }, RawRangedCell { limit: Some(-1), ..valid @@ -536,34 +491,10 @@ mod tests { fn break_cell_validation_checks_all_boundaries() { let valid = valid_break_cell(); assert!(break_cells_valid(&[valid])); - for invalid in [ - RawBreakCell { before: 0, ..valid }, - RawBreakCell { - before: CLASS_COUNT.saturating_add(1), - ..valid - }, - RawBreakCell { - before: 17, - ..valid - }, - RawBreakCell { - before: 18, - ..valid - }, - RawBreakCell { after: 0, ..valid }, - RawBreakCell { - after: CLASS_COUNT.saturating_add(1), - ..valid - }, - RawBreakCell { after: 17, ..valid }, - RawBreakCell { after: 18, ..valid }, - RawBreakCell { - levels: 0b1_0000, - ..valid - }, - ] { - assert_break_cell_rejected(invalid); - } + assert_break_cell_rejected(RawBreakCell { + levels: 0b1_0000, + ..valid + }); } #[test] @@ -571,24 +502,6 @@ mod tests { let valid = valid_spacing_cell(&VALID_TERMS); assert!(spacing_cells_valid(&[valid])); for invalid in [ - RawSpacingCell { - before: CLASS_COUNT.saturating_add(1), - ..valid - }, - RawSpacingCell { - after: CLASS_COUNT.saturating_add(1), - ..valid - }, - RawSpacingCell { - before: 17, - ..valid - }, - RawSpacingCell { - before: 18, - ..valid - }, - RawSpacingCell { after: 17, ..valid }, - RawSpacingCell { after: 18, ..valid }, valid_spacing_cell(&TOO_MANY_TERMS), valid_spacing_cell(&NEGATIVE_TERM), valid_spacing_cell(&OVERSIZED_TERM), @@ -597,6 +510,24 @@ mod tests { } } + #[test] + fn generated_matrix_accessors_cover_exactly_the_transcribed_axes() { + for before in 0_u8..=31 { + for after in 0_u8..=31 { + let class = + |value: u8| (1..=CLASS_COUNT).contains(&value) && value != 17 && value != 18; + let with_edge = (before == 0 || class(before)) && (after == 0 || class(after)); + let without_edge = class(before) && class(after); + assert_eq!(table1::cell(before, after).is_some(), with_edge); + assert_eq!(table2::cell(before, after).is_some(), without_edge); + assert_eq!(table3::cell(before, after).is_some(), with_edge); + assert_eq!(table4::cell(before, after).is_some(), with_edge); + assert_eq!(table5::cell(before, after).is_some(), with_edge); + assert_eq!(table6::cell(before, after).is_some(), without_edge); + } + } + } + #[test] fn remarks_validation_checks_masks_qualifiers_and_empty_cells() { assert!(remarks_valid(&[empty_remark(), described_remark()])); diff --git a/crates/jlreq-core/src/generated/table1.rs b/crates/jlreq-core/src/generated/table1.rs index b8baa29..d1bc568 100644 --- a/crates/jlreq-core/src/generated/table1.rs +++ b/crates/jlreq-core/src/generated/table1.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `02736559abf8f4be082781304b9291dbf8765ab95c8d07c630128153cce2332f` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` +//! - Generator SHA-256: `02c2e2c3acf5e6532ae3311484d2a3913a8224c7f50075782607830573080ddc` //! - Entries: 841 use crate::spec::{RawHang, RawSpacingCell, RawTerm, em}; @@ -23,40 +23,30 @@ use crate::spec::{RawHang, RawSpacingCell, RawTerm, em}; /// JLReq: §B.1 pub(crate) static CELLS: &[RawSpacingCell] = &[ RawSpacingCell { - before: 1, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -66,200 +56,150 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 1, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 22, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#1", terms: &[], }, RawSpacingCell { - before: 1, - after: 23, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#1", terms: &[], }, RawSpacingCell { - before: 1, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 29, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 1, - after: 0, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 2, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -269,16 +209,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 2, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -288,8 +224,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -299,8 +233,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -310,24 +242,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 2, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 2, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -337,8 +263,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -348,8 +272,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -359,8 +281,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -370,8 +290,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -381,8 +299,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -392,16 +308,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 2, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -411,8 +323,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -422,8 +332,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -433,16 +341,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 2, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -452,8 +356,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 22, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -463,8 +365,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 23, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -474,8 +374,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -485,8 +383,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -496,8 +392,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -507,8 +401,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -518,8 +410,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -529,16 +419,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 2, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -548,8 +434,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 2, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.2#2", @@ -559,8 +443,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 3, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -570,32 +452,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 3, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -605,200 +479,150 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 3, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 3, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -808,32 +632,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 4, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -843,112 +659,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 4, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -958,24 +746,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 4, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -985,8 +767,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 4, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -996,16 +776,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 4, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1015,40 +791,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 4, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 4, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 5, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1058,8 +824,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1069,8 +833,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1080,8 +842,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1091,8 +851,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.2#3", @@ -1108,8 +866,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ ], }, RawSpacingCell { - before: 5, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1119,8 +875,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1130,8 +884,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1141,8 +893,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1152,8 +902,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1163,8 +911,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1174,8 +920,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1185,8 +929,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1196,8 +938,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1207,8 +947,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1218,8 +956,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1229,8 +965,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1240,8 +974,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1251,8 +983,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1262,8 +992,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 22, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -1273,8 +1001,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 23, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -1284,8 +1010,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1295,8 +1019,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1306,8 +1028,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1317,8 +1037,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1328,8 +1046,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1339,8 +1055,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1350,8 +1064,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1361,8 +1073,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 5, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.2#4", @@ -1372,8 +1082,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1383,16 +1091,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 6, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1402,8 +1106,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1413,8 +1115,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.2#5", @@ -1430,24 +1130,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ ], }, RawSpacingCell { - before: 6, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 6, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 6, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1457,8 +1151,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1468,8 +1160,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1479,8 +1169,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1490,8 +1178,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1501,8 +1187,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1512,8 +1196,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1523,8 +1205,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1534,8 +1214,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1545,8 +1223,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1556,16 +1232,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 6, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1575,8 +1247,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 22, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -1586,8 +1256,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 23, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -1597,8 +1265,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1608,8 +1274,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1619,8 +1283,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1630,8 +1292,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1641,8 +1301,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1652,8 +1310,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1663,8 +1319,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1674,8 +1328,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 6, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.2#6", @@ -1685,8 +1337,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1696,16 +1346,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 7, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1715,8 +1361,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1726,8 +1370,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.2#5", @@ -1743,24 +1385,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ ], }, RawSpacingCell { - before: 7, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 7, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 7, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1770,8 +1406,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1781,8 +1415,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1792,8 +1424,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1803,8 +1433,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1814,8 +1442,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1825,8 +1451,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1836,8 +1460,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1847,8 +1469,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1858,8 +1478,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1869,16 +1487,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 7, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1888,8 +1502,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 22, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -1899,8 +1511,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 23, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -1910,8 +1520,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1921,8 +1529,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1932,8 +1538,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1943,8 +1547,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1954,8 +1556,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1965,8 +1565,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1976,8 +1574,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -1987,8 +1583,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 7, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.2#6", @@ -1998,8 +1592,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 8, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2009,32 +1601,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 8, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2044,200 +1628,150 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 8, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 22, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 23, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 8, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2247,32 +1781,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 9, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2282,112 +1808,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 9, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2397,24 +1895,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 9, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2424,8 +1916,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 9, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2435,16 +1925,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 9, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2454,40 +1940,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 9, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 9, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2497,32 +1973,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 10, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2532,112 +2000,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 10, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2647,24 +2087,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 10, - after: 22, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 10, - after: 23, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 10, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2674,8 +2108,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 10, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2685,16 +2117,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 10, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2704,40 +2132,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 10, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 10, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2747,32 +2165,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 11, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2782,112 +2192,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 11, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2897,24 +2279,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 11, - after: 22, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 11, - after: 23, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 11, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2924,8 +2300,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 11, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2935,16 +2309,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 11, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2954,40 +2324,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 11, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 11, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -2997,32 +2357,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 12, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3032,200 +2384,150 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 12, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 29, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 12, - after: 0, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3235,32 +2537,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 13, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3270,232 +2564,174 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 13, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 13, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3505,200 +2741,150 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 14, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 22, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#8", terms: &[], }, RawSpacingCell { - before: 14, - after: 23, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#8", terms: &[], }, RawSpacingCell { - before: 14, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 14, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3708,32 +2894,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 15, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3743,112 +2921,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 15, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3858,24 +3008,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 15, - after: 22, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 23, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3885,8 +3029,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 15, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3896,16 +3038,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 15, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3915,40 +3053,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 15, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 15, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3958,32 +3086,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 16, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -3993,112 +3113,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 16, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4108,24 +3200,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 16, - after: 22, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 16, - after: 23, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 16, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4135,8 +3221,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 16, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4146,16 +3230,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 16, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4165,40 +3245,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 16, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 16, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4208,32 +3278,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 19, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4243,112 +3305,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 19, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4358,24 +3392,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 19, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4385,8 +3413,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 19, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4396,16 +3422,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 19, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4415,40 +3437,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 19, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 19, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4458,32 +3470,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 20, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4493,200 +3497,150 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 20, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 20, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4696,32 +3650,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4731,32 +3677,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4766,8 +3704,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4777,8 +3713,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4788,32 +3722,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4823,8 +3749,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4834,8 +3758,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4845,24 +3767,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.2#9", terms: &[], }, RawSpacingCell { - before: 21, - after: 22, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -4872,8 +3788,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 23, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -4883,40 +3797,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4926,16 +3830,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 21, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -4945,16 +3845,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 21, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 1, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -4964,32 +3860,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 22, - after: 2, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 5, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -4999,112 +3887,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 22, - after: 6, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 7, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 8, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 10, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 22, - after: 11, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 22, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 14, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#8", terms: &[], }, RawSpacingCell { - before: 22, - after: 15, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 16, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 22, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 21, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5114,24 +3974,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 22, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.2#10", terms: &[], }, RawSpacingCell { - before: 22, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 24, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5141,8 +3995,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 22, - after: 25, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5152,16 +4004,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 22, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 27, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5171,40 +4019,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 22, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 29, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 22, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 1, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5214,32 +4052,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 23, - after: 2, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 5, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5249,112 +4079,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 23, - after: 6, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 7, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 8, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 10, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 23, - after: 11, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 23, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 14, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#8", terms: &[], }, RawSpacingCell { - before: 23, - after: 15, prohibited: false, hang: RawHang::OverCharacter, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 16, prohibited: false, hang: RawHang::OverCharacter, rule: "B.2#7", terms: &[], }, RawSpacingCell { - before: 23, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 21, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5364,24 +4166,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 23, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.2#11", terms: &[], }, RawSpacingCell { - before: 23, - after: 24, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5391,8 +4187,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 23, - after: 25, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5402,16 +4196,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 23, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 27, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5421,40 +4211,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 23, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 29, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 23, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5464,32 +4244,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5499,32 +4271,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5534,8 +4298,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5545,8 +4307,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5556,32 +4316,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5591,8 +4343,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5602,8 +4352,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5613,24 +4361,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 22, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5640,8 +4382,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 23, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5651,16 +4391,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5670,24 +4406,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5697,16 +4427,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 24, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5716,16 +4442,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 24, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5735,32 +4457,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.2#12", @@ -5770,32 +4484,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5805,8 +4511,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5816,8 +4520,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5827,32 +4529,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5862,8 +4556,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5873,8 +4565,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5884,24 +4574,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 22, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5911,8 +4595,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 23, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -5922,8 +4604,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5933,32 +4613,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5968,16 +4640,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 25, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -5987,16 +4655,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 25, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6006,32 +4670,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 26, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6041,200 +4697,150 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 26, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.2#13", terms: &[], }, RawSpacingCell { - before: 26, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 26, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.2#13", terms: &[], }, RawSpacingCell { - before: 27, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6244,32 +4850,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6279,32 +4877,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6314,8 +4904,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6325,8 +4913,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6336,32 +4922,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6371,8 +4949,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6382,8 +4958,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6393,24 +4967,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 22, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -6420,8 +4988,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 23, prohibited: false, hang: RawHang::OverSpace, rule: "B.1", @@ -6431,16 +4997,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6450,24 +5012,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6477,16 +5033,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 27, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6496,248 +5048,186 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 27, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 2, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 3, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 4, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 5, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 6, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 7, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 9, prohibited: true, hang: RawHang::None, rule: "B.2#14", terms: &[], }, RawSpacingCell { - before: 28, - after: 10, prohibited: true, hang: RawHang::None, rule: "B.2#15", terms: &[], }, RawSpacingCell { - before: 28, - after: 11, prohibited: true, hang: RawHang::None, rule: "B.2#16", terms: &[], }, RawSpacingCell { - before: 28, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 20, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 22, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 23, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.2#13", terms: &[], }, RawSpacingCell { - before: 28, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 28, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 29, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 28, - after: 0, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6747,32 +5237,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 29, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6782,112 +5264,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 29, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6897,24 +5351,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 29, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6924,8 +5372,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 29, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6935,16 +5381,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 29, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6954,40 +5396,30 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 29, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 29, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 29, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -6997,32 +5429,24 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 30, - after: 2, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 3, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 4, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 5, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -7032,112 +5456,84 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 30, - after: 6, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 7, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 9, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 10, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 11, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 20, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -7147,24 +5543,18 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 30, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -7174,8 +5564,6 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 30, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -7185,16 +5573,12 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 30, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", @@ -7204,267 +5588,224 @@ pub(crate) static CELLS: &[RawSpacingCell] = &[ }], }, RawSpacingCell { - before: 30, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 29, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 30, - after: 0, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 1, prohibited: false, hang: RawHang::None, rule: "B.2#17", terms: &[], }, RawSpacingCell { - before: 0, - after: 2, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 3, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 4, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 5, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 6, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 7, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 8, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 9, prohibited: true, hang: RawHang::None, rule: "B.2#14", terms: &[], }, RawSpacingCell { - before: 0, - after: 10, prohibited: true, hang: RawHang::None, rule: "B.2#15", terms: &[], }, RawSpacingCell { - before: 0, - after: 11, prohibited: true, hang: RawHang::None, rule: "B.2#16", terms: &[], }, RawSpacingCell { - before: 0, - after: 12, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 13, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 14, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 15, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 16, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 19, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 20, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 21, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 22, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 23, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 24, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 25, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 26, prohibited: false, hang: RawHang::None, rule: "B.2#13", terms: &[], }, RawSpacingCell { - before: 0, - after: 27, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 28, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 29, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 30, prohibited: false, hang: RawHang::None, rule: "B.1", terms: &[], }, RawSpacingCell { - before: 0, - after: 0, prohibited: true, hang: RawHang::None, rule: "B.1", terms: &[], }, ]; + +const ROW_INDEX: [u8; 31] = [ + 28, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, +]; +const COLUMN_INDEX: [u8; 31] = [ + 28, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, +]; +const COLUMN_COUNT: usize = 29; + +/// Look up one cell directly from its class or line-edge coordinates. +pub(crate) fn cell(before: u8, after: u8) -> Option<&'static RawSpacingCell> { + let row = *ROW_INDEX.get(usize::from(before))?; + let column = *COLUMN_INDEX.get(usize::from(after))?; + if row == u8::MAX || column == u8::MAX { + return None; + } + let index = usize::from(row) + .checked_mul(COLUMN_COUNT)? + .checked_add(usize::from(column))?; + CELLS.get(index) +} diff --git a/crates/jlreq-core/src/generated/table2.rs b/crates/jlreq-core/src/generated/table2.rs index c664a5c..933439e 100644 --- a/crates/jlreq-core/src/generated/table2.rs +++ b/crates/jlreq-core/src/generated/table2.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `3e93d1104a5c730bc9eca01880ef989520b7c3ebb1ef98833be0424a44edbd66` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` +//! - Generator SHA-256: `02c2e2c3acf5e6532ae3311484d2a3913a8224c7f50075782607830573080ddc` //! - Entries: 784 use crate::spec::RawBreakCell; @@ -23,5491 +23,3946 @@ use crate::spec::RawBreakCell; /// JLReq: §C.1 pub(crate) static CELLS: &[RawBreakCell] = &[ RawBreakCell { - before: 1, - after: 1, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 8, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 12, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 13, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 14, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 15, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 16, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 19, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 21, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 22, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 23, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 24, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 25, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 26, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 27, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 28, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 29, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 1, - after: 30, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 2, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 2, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 3, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 3, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 14, prohibited: false, levels: 0b0000, rule: "C.2#4", }, RawBreakCell { - before: 4, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 4, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 4, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 5, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 5, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 6, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 6, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 7, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 7, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 8, prohibited: false, levels: 0b0000, rule: "C.2#5", }, RawBreakCell { - before: 8, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 8, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 8, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 9, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 9, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 10, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 10, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 11, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 11, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 12, - after: 1, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 8, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 12, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 13, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 14, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 15, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 16, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 19, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 21, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 22, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 23, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 24, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 25, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 26, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 27, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 28, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 29, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 12, - after: 30, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 13, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 13, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 14, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 14, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 15, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 15, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 16, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 16, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 19, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 19, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 20, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 20, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 21, prohibited: false, levels: 0b0000, rule: "C.2#6", }, RawBreakCell { - before: 21, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 21, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 21, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 22, prohibited: false, levels: 0b0000, rule: "C.2#7", }, RawBreakCell { - before: 22, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 22, - after: 29, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 22, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 23, prohibited: false, levels: 0b0000, rule: "C.2#8", }, RawBreakCell { - before: 23, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 23, - after: 29, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 23, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 13, prohibited: false, levels: 0b1111, rule: "C.2#9", }, RawBreakCell { - before: 24, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 24, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 27, prohibited: false, levels: 0b0000, rule: "C.2#10", }, RawBreakCell { - before: 24, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 24, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 24, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 25, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 27, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 25, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 25, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 26, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 26, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 13, prohibited: false, levels: 0b0000, rule: "C.2#11", }, RawBreakCell { - before: 27, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 24, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 25, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 27, prohibited: false, levels: 0b1111, rule: "C.2#12", }, RawBreakCell { - before: 27, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 27, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 27, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 28, - after: 1, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 2, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 3, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 4, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 5, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 6, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 7, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 8, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 9, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 10, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 11, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 12, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 13, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 14, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 15, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 16, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 19, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 20, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 21, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 22, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 23, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 24, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 25, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 26, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 27, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 28, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 29, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 28, - after: 30, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 29, - after: 29, prohibited: true, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 29, - after: 30, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 1, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 2, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 3, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 4, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 5, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 6, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 7, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 8, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 9, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 10, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 11, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 12, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 13, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 14, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 15, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 16, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 19, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 20, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 21, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 22, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 23, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 24, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 25, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 26, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 27, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 28, prohibited: false, levels: 0b0000, rule: "C", }, RawBreakCell { - before: 30, - after: 29, prohibited: false, levels: 0b1111, rule: "C", }, RawBreakCell { - before: 30, - after: 30, prohibited: false, levels: 0b0000, rule: "C.2#13", }, ]; + +const ROW_INDEX: [u8; 31] = [ + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, +]; +const COLUMN_INDEX: [u8; 31] = [ + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, +]; +const COLUMN_COUNT: usize = 28; + +/// Look up one cell directly from its class or line-edge coordinates. +pub(crate) fn cell(before: u8, after: u8) -> Option<&'static RawBreakCell> { + let row = *ROW_INDEX.get(usize::from(before))?; + let column = *COLUMN_INDEX.get(usize::from(after))?; + if row == u8::MAX || column == u8::MAX { + return None; + } + let index = usize::from(row) + .checked_mul(COLUMN_COUNT)? + .checked_add(usize::from(column))?; + CELLS.get(index) +} diff --git a/crates/jlreq-core/src/generated/table3.rs b/crates/jlreq-core/src/generated/table3.rs index 8524064..d88dd2c 100644 --- a/crates/jlreq-core/src/generated/table3.rs +++ b/crates/jlreq-core/src/generated/table3.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `ca550d5323625d71d583ecd27006e5ec0d7c569814e4dfdd392593bc9aa43222` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` +//! - Generator SHA-256: `02c2e2c3acf5e6532ae3311484d2a3913a8224c7f50075782607830573080ddc` //! - Entries: 841 use crate::spec::{RawRangedCell, em}; @@ -23,8 +23,6 @@ use crate::spec::{RawRangedCell, em}; /// JLReq: §D.1 pub(crate) static CELLS: &[RawRangedCell] = &[ RawRangedCell { - before: 1, - after: 1, limit: None, two_valued: false, residual: false, @@ -32,8 +30,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 2, limit: None, two_valued: false, residual: false, @@ -41,8 +37,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 3, limit: None, two_valued: false, residual: false, @@ -50,8 +44,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 4, limit: None, two_valued: false, residual: false, @@ -59,8 +51,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -68,8 +58,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 6, limit: None, two_valued: false, residual: false, @@ -77,8 +65,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 7, limit: None, two_valued: false, residual: false, @@ -86,8 +72,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 8, limit: None, two_valued: false, residual: false, @@ -95,8 +79,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 9, limit: None, two_valued: false, residual: false, @@ -104,8 +86,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 10, limit: None, two_valued: false, residual: false, @@ -113,8 +93,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 11, limit: None, two_valued: false, residual: false, @@ -122,8 +100,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 12, limit: None, two_valued: false, residual: false, @@ -131,8 +107,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 13, limit: None, two_valued: false, residual: false, @@ -140,8 +114,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 14, limit: None, two_valued: false, residual: false, @@ -149,8 +121,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 15, limit: None, two_valued: false, residual: false, @@ -158,8 +128,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 16, limit: None, two_valued: false, residual: false, @@ -167,8 +135,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 19, limit: None, two_valued: false, residual: false, @@ -176,8 +142,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 20, limit: None, two_valued: false, residual: false, @@ -185,8 +149,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 21, limit: None, two_valued: false, residual: false, @@ -194,8 +156,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 22, limit: None, two_valued: false, residual: false, @@ -203,8 +163,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 23, limit: None, two_valued: false, residual: false, @@ -212,8 +170,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 24, limit: None, two_valued: false, residual: false, @@ -221,8 +177,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 25, limit: None, two_valued: false, residual: false, @@ -230,8 +184,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 26, limit: None, two_valued: false, residual: false, @@ -239,8 +191,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 27, limit: None, two_valued: false, residual: false, @@ -248,8 +198,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 28, limit: None, two_valued: false, residual: false, @@ -257,8 +205,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 29, limit: None, two_valued: false, residual: false, @@ -266,8 +212,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 30, limit: None, two_valued: false, residual: false, @@ -275,8 +219,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 0, limit: None, two_valued: false, residual: false, @@ -284,8 +226,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -293,8 +233,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 2, limit: None, two_valued: false, residual: false, @@ -302,8 +240,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 3, limit: Some(em(0)), two_valued: false, residual: false, @@ -311,8 +247,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 4, limit: Some(em(0)), two_valued: false, residual: false, @@ -320,8 +254,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -329,8 +261,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 6, limit: None, two_valued: false, residual: false, @@ -338,8 +268,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 7, limit: None, two_valued: false, residual: false, @@ -347,8 +275,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 8, limit: Some(em(0)), two_valued: false, residual: false, @@ -356,8 +282,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 9, limit: Some(em(0)), two_valued: false, residual: false, @@ -365,8 +289,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 10, limit: Some(em(0)), two_valued: false, residual: false, @@ -374,8 +296,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 11, limit: Some(em(0)), two_valued: false, residual: false, @@ -383,8 +303,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 12, limit: Some(em(0)), two_valued: false, residual: false, @@ -392,8 +310,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 13, limit: Some(em(0)), two_valued: false, residual: false, @@ -401,8 +317,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 14, limit: None, two_valued: false, residual: false, @@ -410,8 +324,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 15, limit: Some(em(0)), two_valued: false, residual: false, @@ -419,8 +331,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 16, limit: Some(em(0)), two_valued: false, residual: false, @@ -428,8 +338,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 19, limit: Some(em(0)), two_valued: false, residual: false, @@ -437,8 +345,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 20, limit: None, two_valued: false, residual: false, @@ -446,8 +352,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 21, limit: Some(em(0)), two_valued: false, residual: false, @@ -455,8 +359,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 22, limit: Some(em(0)), two_valued: false, residual: false, @@ -464,8 +366,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 23, limit: Some(em(0)), two_valued: false, residual: false, @@ -473,8 +373,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 24, limit: Some(em(0)), two_valued: false, residual: false, @@ -482,8 +380,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 25, limit: Some(em(0)), two_valued: false, residual: false, @@ -491,8 +387,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 26, limit: Some(em(0)), two_valued: false, residual: false, @@ -500,8 +394,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 27, limit: Some(em(0)), two_valued: false, residual: false, @@ -509,8 +401,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 28, limit: Some(em(0)), two_valued: false, residual: false, @@ -518,8 +408,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 29, limit: None, two_valued: false, residual: false, @@ -527,8 +415,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 30, limit: Some(em(0)), two_valued: false, residual: false, @@ -536,8 +422,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 0, limit: Some(em(0)), two_valued: true, residual: false, @@ -545,8 +429,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -554,8 +436,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 2, limit: None, two_valued: false, residual: false, @@ -563,8 +443,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 3, limit: None, two_valued: false, residual: false, @@ -572,8 +450,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 4, limit: None, two_valued: false, residual: false, @@ -581,8 +457,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -590,8 +464,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 6, limit: None, two_valued: false, residual: false, @@ -599,8 +471,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 7, limit: None, two_valued: false, residual: false, @@ -608,8 +478,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 8, limit: None, two_valued: false, residual: false, @@ -617,8 +485,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 9, limit: None, two_valued: false, residual: false, @@ -626,8 +492,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 10, limit: None, two_valued: false, residual: false, @@ -635,8 +499,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 11, limit: None, two_valued: false, residual: false, @@ -644,8 +506,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 12, limit: None, two_valued: false, residual: false, @@ -653,8 +513,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 13, limit: None, two_valued: false, residual: false, @@ -662,8 +520,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 14, limit: None, two_valued: false, residual: false, @@ -671,8 +527,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 15, limit: None, two_valued: false, residual: false, @@ -680,8 +534,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 16, limit: None, two_valued: false, residual: false, @@ -689,8 +541,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 19, limit: None, two_valued: false, residual: false, @@ -698,8 +548,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 20, limit: None, two_valued: false, residual: false, @@ -707,8 +555,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 21, limit: None, two_valued: false, residual: false, @@ -716,8 +562,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 22, limit: None, two_valued: false, residual: false, @@ -725,8 +569,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 23, limit: None, two_valued: false, residual: false, @@ -734,8 +576,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 24, limit: None, two_valued: false, residual: false, @@ -743,8 +583,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 25, limit: None, two_valued: false, residual: false, @@ -752,8 +590,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 26, limit: None, two_valued: false, residual: false, @@ -761,8 +597,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 27, limit: None, two_valued: false, residual: false, @@ -770,8 +604,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 28, limit: None, two_valued: false, residual: false, @@ -779,8 +611,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 29, limit: None, two_valued: false, residual: false, @@ -788,8 +618,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 30, limit: None, two_valued: false, residual: false, @@ -797,8 +625,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 0, limit: None, two_valued: false, residual: false, @@ -806,8 +632,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -815,8 +639,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 2, limit: None, two_valued: false, residual: false, @@ -824,8 +646,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 3, limit: None, two_valued: false, residual: false, @@ -833,8 +653,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 4, limit: None, two_valued: false, residual: false, @@ -842,8 +660,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -851,8 +667,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 6, limit: None, two_valued: false, residual: false, @@ -860,8 +674,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 7, limit: None, two_valued: false, residual: false, @@ -869,8 +681,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 8, limit: None, two_valued: false, residual: false, @@ -878,8 +688,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 9, limit: None, two_valued: false, residual: false, @@ -887,8 +695,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 10, limit: None, two_valued: false, residual: false, @@ -896,8 +702,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 11, limit: None, two_valued: false, residual: false, @@ -905,8 +709,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 12, limit: None, two_valued: false, residual: false, @@ -914,8 +716,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 13, limit: None, two_valued: false, residual: false, @@ -923,8 +723,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 14, limit: None, two_valued: false, residual: false, @@ -932,8 +730,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 15, limit: None, two_valued: false, residual: false, @@ -941,8 +737,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 16, limit: None, two_valued: false, residual: false, @@ -950,8 +744,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 19, limit: None, two_valued: false, residual: false, @@ -959,8 +751,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 20, limit: None, two_valued: false, residual: false, @@ -968,8 +758,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -977,8 +765,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 22, limit: None, two_valued: false, residual: false, @@ -986,8 +772,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 23, limit: None, two_valued: false, residual: false, @@ -995,8 +779,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -1004,8 +786,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -1013,8 +793,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 26, limit: None, two_valued: false, residual: false, @@ -1022,8 +800,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -1031,8 +807,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 28, limit: None, two_valued: false, residual: false, @@ -1040,8 +814,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 29, limit: None, two_valued: false, residual: false, @@ -1049,8 +821,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 30, limit: None, two_valued: false, residual: false, @@ -1058,8 +828,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 0, limit: None, two_valued: false, residual: false, @@ -1067,8 +835,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -1076,8 +842,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 2, limit: Some(em(0)), two_valued: false, residual: false, @@ -1085,8 +849,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 3, limit: Some(em(0)), two_valued: false, residual: false, @@ -1094,8 +856,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 4, limit: Some(em(0)), two_valued: false, residual: false, @@ -1103,8 +863,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -1112,8 +870,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#1", }, RawRangedCell { - before: 5, - after: 6, limit: Some(em(0)), two_valued: false, residual: false, @@ -1121,8 +877,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 7, limit: Some(em(0)), two_valued: false, residual: false, @@ -1130,8 +884,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 8, limit: Some(em(0)), two_valued: false, residual: false, @@ -1139,8 +891,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 9, limit: Some(em(0)), two_valued: false, residual: false, @@ -1148,8 +898,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 10, limit: Some(em(0)), two_valued: false, residual: false, @@ -1157,8 +905,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 11, limit: Some(em(0)), two_valued: false, residual: false, @@ -1166,8 +912,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 12, limit: Some(em(0)), two_valued: false, residual: false, @@ -1175,8 +919,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 13, limit: Some(em(0)), two_valued: false, residual: false, @@ -1184,8 +926,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 14, limit: Some(em(0)), two_valued: false, residual: false, @@ -1193,8 +933,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 15, limit: Some(em(0)), two_valued: false, residual: false, @@ -1202,8 +940,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 16, limit: Some(em(0)), two_valued: false, residual: false, @@ -1211,8 +947,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 19, limit: Some(em(0)), two_valued: false, residual: false, @@ -1220,8 +954,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 20, limit: Some(em(0)), two_valued: false, residual: false, @@ -1229,8 +961,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 21, limit: Some(em(0)), two_valued: false, residual: false, @@ -1238,8 +968,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 22, limit: Some(em(0)), two_valued: false, residual: false, @@ -1247,8 +975,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 23, limit: Some(em(0)), two_valued: false, residual: false, @@ -1256,8 +982,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 24, limit: Some(em(0)), two_valued: false, residual: false, @@ -1265,8 +989,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 25, limit: Some(em(0)), two_valued: false, residual: false, @@ -1274,8 +996,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 26, limit: Some(em(0)), two_valued: false, residual: false, @@ -1283,8 +1003,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 27, limit: Some(em(0)), two_valued: false, residual: false, @@ -1292,8 +1010,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 28, limit: Some(em(0)), two_valued: false, residual: false, @@ -1301,8 +1017,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 29, limit: Some(em(0)), two_valued: false, residual: false, @@ -1310,8 +1024,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 30, limit: Some(em(0)), two_valued: false, residual: false, @@ -1319,8 +1031,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 0, limit: Some(em(0)), two_valued: false, residual: false, @@ -1328,8 +1038,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#5", }, RawRangedCell { - before: 6, - after: 1, limit: None, two_valued: false, residual: false, @@ -1337,8 +1045,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 2, limit: None, two_valued: false, residual: false, @@ -1346,8 +1052,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 3, limit: None, two_valued: false, residual: false, @@ -1355,8 +1059,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 4, limit: None, two_valued: false, residual: false, @@ -1364,8 +1066,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 5, limit: Some(em(360)), two_valued: false, residual: false, @@ -1373,8 +1073,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#2", }, RawRangedCell { - before: 6, - after: 6, limit: None, two_valued: false, residual: false, @@ -1382,8 +1080,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 7, limit: None, two_valued: false, residual: false, @@ -1391,8 +1087,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 8, limit: None, two_valued: false, residual: false, @@ -1400,8 +1094,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 9, limit: None, two_valued: false, residual: false, @@ -1409,8 +1101,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 10, limit: None, two_valued: false, residual: false, @@ -1418,8 +1108,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 11, limit: None, two_valued: false, residual: false, @@ -1427,8 +1115,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 12, limit: None, two_valued: false, residual: false, @@ -1436,8 +1122,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 13, limit: None, two_valued: false, residual: false, @@ -1445,8 +1129,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 14, limit: None, two_valued: false, residual: false, @@ -1454,8 +1136,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 15, limit: None, two_valued: false, residual: false, @@ -1463,8 +1143,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 16, limit: None, two_valued: false, residual: false, @@ -1472,8 +1150,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 19, limit: None, two_valued: false, residual: false, @@ -1481,8 +1157,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 20, limit: None, two_valued: false, residual: false, @@ -1490,8 +1164,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 21, limit: None, two_valued: false, residual: false, @@ -1499,8 +1171,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 22, limit: None, two_valued: false, residual: false, @@ -1508,8 +1178,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 23, limit: None, two_valued: false, residual: false, @@ -1517,8 +1185,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 24, limit: None, two_valued: false, residual: false, @@ -1526,8 +1192,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 25, limit: None, two_valued: false, residual: false, @@ -1535,8 +1199,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 26, limit: None, two_valued: false, residual: false, @@ -1544,8 +1206,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 27, limit: None, two_valued: false, residual: false, @@ -1553,8 +1213,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 28, limit: None, two_valued: false, residual: false, @@ -1562,8 +1220,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 29, limit: None, two_valued: false, residual: false, @@ -1571,8 +1227,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 30, limit: None, two_valued: false, residual: false, @@ -1580,8 +1234,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 0, limit: Some(em(0)), two_valued: true, residual: false, @@ -1589,8 +1241,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -1598,8 +1248,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 2, limit: None, two_valued: false, residual: false, @@ -1607,8 +1255,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 3, limit: Some(em(0)), two_valued: false, residual: false, @@ -1616,8 +1262,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 4, limit: Some(em(0)), two_valued: false, residual: false, @@ -1625,8 +1269,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -1634,8 +1276,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#3", }, RawRangedCell { - before: 7, - after: 6, limit: None, two_valued: false, residual: false, @@ -1643,8 +1283,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 7, limit: None, two_valued: false, residual: false, @@ -1652,8 +1290,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 8, limit: Some(em(0)), two_valued: false, residual: false, @@ -1661,8 +1297,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 9, limit: Some(em(0)), two_valued: false, residual: false, @@ -1670,8 +1304,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 10, limit: Some(em(0)), two_valued: false, residual: false, @@ -1679,8 +1311,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 11, limit: Some(em(0)), two_valued: false, residual: false, @@ -1688,8 +1318,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 12, limit: Some(em(0)), two_valued: false, residual: false, @@ -1697,8 +1325,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 13, limit: Some(em(0)), two_valued: false, residual: false, @@ -1706,8 +1332,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 14, limit: Some(em(0)), two_valued: false, residual: false, @@ -1715,8 +1339,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 15, limit: Some(em(0)), two_valued: false, residual: false, @@ -1724,8 +1346,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 16, limit: Some(em(0)), two_valued: false, residual: false, @@ -1733,8 +1353,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 19, limit: Some(em(0)), two_valued: false, residual: false, @@ -1742,8 +1360,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 20, limit: None, two_valued: false, residual: false, @@ -1751,8 +1367,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 21, limit: Some(em(0)), two_valued: false, residual: false, @@ -1760,8 +1374,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 22, limit: Some(em(0)), two_valued: false, residual: false, @@ -1769,8 +1381,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 23, limit: Some(em(0)), two_valued: false, residual: false, @@ -1778,8 +1388,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 24, limit: Some(em(0)), two_valued: false, residual: false, @@ -1787,8 +1395,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 25, limit: Some(em(0)), two_valued: false, residual: false, @@ -1796,8 +1402,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 26, limit: Some(em(0)), two_valued: false, residual: false, @@ -1805,8 +1409,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 27, limit: Some(em(0)), two_valued: false, residual: false, @@ -1814,8 +1416,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 28, limit: Some(em(0)), two_valued: false, residual: false, @@ -1823,8 +1423,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 29, limit: Some(em(0)), two_valued: false, residual: false, @@ -1832,8 +1430,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 30, limit: Some(em(0)), two_valued: false, residual: false, @@ -1841,8 +1437,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 0, limit: Some(em(0)), two_valued: true, residual: false, @@ -1850,8 +1444,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -1859,8 +1451,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 2, limit: None, two_valued: false, residual: false, @@ -1868,8 +1458,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 3, limit: None, two_valued: false, residual: false, @@ -1877,8 +1465,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 4, limit: None, two_valued: false, residual: false, @@ -1886,8 +1472,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -1895,8 +1479,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 6, limit: None, two_valued: false, residual: false, @@ -1904,8 +1486,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 7, limit: None, two_valued: false, residual: false, @@ -1913,8 +1493,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 8, limit: None, two_valued: false, residual: false, @@ -1922,8 +1500,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 9, limit: None, two_valued: false, residual: false, @@ -1931,8 +1507,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 10, limit: None, two_valued: false, residual: false, @@ -1940,8 +1514,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 11, limit: None, two_valued: false, residual: false, @@ -1949,8 +1521,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 12, limit: None, two_valued: false, residual: false, @@ -1958,8 +1528,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 13, limit: None, two_valued: false, residual: false, @@ -1967,8 +1535,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 14, limit: None, two_valued: false, residual: false, @@ -1976,8 +1542,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 15, limit: None, two_valued: false, residual: false, @@ -1985,8 +1549,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 16, limit: None, two_valued: false, residual: false, @@ -1994,8 +1556,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 19, limit: None, two_valued: false, residual: false, @@ -2003,8 +1563,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 20, limit: None, two_valued: false, residual: false, @@ -2012,8 +1570,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 21, limit: None, two_valued: false, residual: false, @@ -2021,8 +1577,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 22, limit: None, two_valued: false, residual: false, @@ -2030,8 +1584,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 23, limit: None, two_valued: false, residual: false, @@ -2039,8 +1591,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 24, limit: None, two_valued: false, residual: false, @@ -2048,8 +1598,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 25, limit: None, two_valued: false, residual: false, @@ -2057,8 +1605,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 26, limit: None, two_valued: false, residual: false, @@ -2066,8 +1612,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 27, limit: None, two_valued: false, residual: false, @@ -2075,8 +1619,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 28, limit: None, two_valued: false, residual: false, @@ -2084,8 +1626,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 29, limit: None, two_valued: false, residual: false, @@ -2093,8 +1633,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 30, limit: None, two_valued: false, residual: false, @@ -2102,8 +1640,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 0, limit: None, two_valued: false, residual: false, @@ -2111,8 +1647,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -2120,8 +1654,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 2, limit: None, two_valued: false, residual: false, @@ -2129,8 +1661,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 3, limit: None, two_valued: false, residual: false, @@ -2138,8 +1668,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 4, limit: None, two_valued: false, residual: false, @@ -2147,8 +1675,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -2156,8 +1682,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 6, limit: None, two_valued: false, residual: false, @@ -2165,8 +1689,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 7, limit: None, two_valued: false, residual: false, @@ -2174,8 +1696,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 8, limit: None, two_valued: false, residual: false, @@ -2183,8 +1703,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 9, limit: None, two_valued: false, residual: false, @@ -2192,8 +1710,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 10, limit: None, two_valued: false, residual: false, @@ -2201,8 +1717,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 11, limit: None, two_valued: false, residual: false, @@ -2210,8 +1724,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 12, limit: None, two_valued: false, residual: false, @@ -2219,8 +1731,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 13, limit: None, two_valued: false, residual: false, @@ -2228,8 +1738,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 14, limit: None, two_valued: false, residual: false, @@ -2237,8 +1745,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 15, limit: None, two_valued: false, residual: false, @@ -2246,8 +1752,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 16, limit: None, two_valued: false, residual: false, @@ -2255,8 +1759,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 19, limit: None, two_valued: false, residual: false, @@ -2264,8 +1766,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 20, limit: None, two_valued: false, residual: false, @@ -2273,8 +1773,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -2282,8 +1780,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 22, limit: None, two_valued: false, residual: false, @@ -2291,8 +1787,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 23, limit: None, two_valued: false, residual: false, @@ -2300,8 +1794,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -2309,8 +1801,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -2318,8 +1808,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 26, limit: None, two_valued: false, residual: false, @@ -2327,8 +1815,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -2336,8 +1822,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 28, limit: None, two_valued: false, residual: false, @@ -2345,8 +1829,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 29, limit: None, two_valued: false, residual: false, @@ -2354,8 +1836,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 30, limit: None, two_valued: false, residual: false, @@ -2363,8 +1843,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 0, limit: None, two_valued: false, residual: false, @@ -2372,8 +1850,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -2381,8 +1857,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 2, limit: None, two_valued: false, residual: false, @@ -2390,8 +1864,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 3, limit: None, two_valued: false, residual: false, @@ -2399,8 +1871,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 4, limit: None, two_valued: false, residual: false, @@ -2408,8 +1878,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -2417,8 +1885,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 6, limit: None, two_valued: false, residual: false, @@ -2426,8 +1892,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 7, limit: None, two_valued: false, residual: false, @@ -2435,8 +1899,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 8, limit: None, two_valued: false, residual: false, @@ -2444,8 +1906,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 9, limit: None, two_valued: false, residual: false, @@ -2453,8 +1913,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 10, limit: None, two_valued: false, residual: false, @@ -2462,8 +1920,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 11, limit: None, two_valued: false, residual: false, @@ -2471,8 +1927,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 12, limit: None, two_valued: false, residual: false, @@ -2480,8 +1934,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 13, limit: None, two_valued: false, residual: false, @@ -2489,8 +1941,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 14, limit: None, two_valued: false, residual: false, @@ -2498,8 +1948,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 15, limit: None, two_valued: false, residual: false, @@ -2507,8 +1955,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 16, limit: None, two_valued: false, residual: false, @@ -2516,8 +1962,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 19, limit: None, two_valued: false, residual: false, @@ -2525,8 +1969,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 20, limit: None, two_valued: false, residual: false, @@ -2534,8 +1976,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -2543,8 +1983,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 22, limit: None, two_valued: false, residual: false, @@ -2552,8 +1990,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 23, limit: None, two_valued: false, residual: false, @@ -2561,8 +1997,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -2570,8 +2004,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -2579,8 +2011,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 26, limit: None, two_valued: false, residual: false, @@ -2588,8 +2018,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -2597,8 +2025,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 28, limit: None, two_valued: false, residual: false, @@ -2606,8 +2032,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 29, limit: None, two_valued: false, residual: false, @@ -2615,8 +2039,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 30, limit: None, two_valued: false, residual: false, @@ -2624,8 +2046,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 0, limit: None, two_valued: false, residual: false, @@ -2633,8 +2053,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -2642,8 +2060,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 2, limit: None, two_valued: false, residual: false, @@ -2651,8 +2067,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 3, limit: None, two_valued: false, residual: false, @@ -2660,8 +2074,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 4, limit: None, two_valued: false, residual: false, @@ -2669,8 +2081,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -2678,8 +2088,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 6, limit: None, two_valued: false, residual: false, @@ -2687,8 +2095,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 7, limit: None, two_valued: false, residual: false, @@ -2696,8 +2102,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 8, limit: None, two_valued: false, residual: false, @@ -2705,8 +2109,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 9, limit: None, two_valued: false, residual: false, @@ -2714,8 +2116,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 10, limit: None, two_valued: false, residual: false, @@ -2723,8 +2123,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 11, limit: None, two_valued: false, residual: false, @@ -2732,8 +2130,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 12, limit: None, two_valued: false, residual: false, @@ -2741,8 +2137,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 13, limit: None, two_valued: false, residual: false, @@ -2750,8 +2144,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 14, limit: None, two_valued: false, residual: false, @@ -2759,8 +2151,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 15, limit: None, two_valued: false, residual: false, @@ -2768,8 +2158,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 16, limit: None, two_valued: false, residual: false, @@ -2777,8 +2165,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 19, limit: None, two_valued: false, residual: false, @@ -2786,8 +2172,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 20, limit: None, two_valued: false, residual: false, @@ -2795,8 +2179,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -2804,8 +2186,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 22, limit: None, two_valued: false, residual: false, @@ -2813,8 +2193,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 23, limit: None, two_valued: false, residual: false, @@ -2822,8 +2200,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -2831,8 +2207,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -2840,8 +2214,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 26, limit: None, two_valued: false, residual: false, @@ -2849,8 +2221,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -2858,8 +2228,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 28, limit: None, two_valued: false, residual: false, @@ -2867,8 +2235,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 29, limit: None, two_valued: false, residual: false, @@ -2876,8 +2242,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 30, limit: None, two_valued: false, residual: false, @@ -2885,8 +2249,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 0, limit: None, two_valued: false, residual: false, @@ -2894,8 +2256,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -2903,8 +2263,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 2, limit: None, two_valued: false, residual: false, @@ -2912,8 +2270,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 3, limit: None, two_valued: false, residual: false, @@ -2921,8 +2277,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 4, limit: None, two_valued: false, residual: false, @@ -2930,8 +2284,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -2939,8 +2291,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 6, limit: None, two_valued: false, residual: false, @@ -2948,8 +2298,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 7, limit: None, two_valued: false, residual: false, @@ -2957,8 +2305,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 8, limit: None, two_valued: false, residual: false, @@ -2966,8 +2312,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 9, limit: None, two_valued: false, residual: false, @@ -2975,8 +2319,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 10, limit: None, two_valued: false, residual: false, @@ -2984,8 +2326,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 11, limit: None, two_valued: false, residual: false, @@ -2993,8 +2333,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 12, limit: None, two_valued: false, residual: false, @@ -3002,8 +2340,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 13, limit: None, two_valued: false, residual: false, @@ -3011,8 +2347,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 14, limit: None, two_valued: false, residual: false, @@ -3020,8 +2354,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 15, limit: None, two_valued: false, residual: false, @@ -3029,8 +2361,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 16, limit: None, two_valued: false, residual: false, @@ -3038,8 +2368,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 19, limit: None, two_valued: false, residual: false, @@ -3047,8 +2375,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 20, limit: None, two_valued: false, residual: false, @@ -3056,8 +2382,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 21, limit: None, two_valued: false, residual: false, @@ -3065,8 +2389,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 22, limit: None, two_valued: false, residual: false, @@ -3074,8 +2396,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 23, limit: None, two_valued: false, residual: false, @@ -3083,8 +2403,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 24, limit: None, two_valued: false, residual: false, @@ -3092,8 +2410,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 25, limit: None, two_valued: false, residual: false, @@ -3101,8 +2417,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 26, limit: None, two_valued: false, residual: false, @@ -3110,8 +2424,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 27, limit: None, two_valued: false, residual: false, @@ -3119,8 +2431,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 28, limit: None, two_valued: false, residual: false, @@ -3128,8 +2438,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 29, limit: None, two_valued: false, residual: false, @@ -3137,8 +2445,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 30, limit: None, two_valued: false, residual: false, @@ -3146,8 +2452,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 0, limit: None, two_valued: false, residual: false, @@ -3155,8 +2459,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -3164,8 +2466,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 2, limit: None, two_valued: false, residual: false, @@ -3173,8 +2473,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 3, limit: None, two_valued: false, residual: false, @@ -3182,8 +2480,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 4, limit: None, two_valued: false, residual: false, @@ -3191,8 +2487,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -3200,8 +2494,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 6, limit: None, two_valued: false, residual: false, @@ -3209,8 +2501,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 7, limit: None, two_valued: false, residual: false, @@ -3218,8 +2508,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 8, limit: None, two_valued: false, residual: false, @@ -3227,8 +2515,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 9, limit: None, two_valued: false, residual: false, @@ -3236,8 +2522,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 10, limit: None, two_valued: false, residual: false, @@ -3245,8 +2529,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 11, limit: None, two_valued: false, residual: false, @@ -3254,8 +2536,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 12, limit: None, two_valued: false, residual: false, @@ -3263,8 +2543,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 13, limit: None, two_valued: false, residual: false, @@ -3272,8 +2550,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 14, limit: None, two_valued: false, residual: false, @@ -3281,8 +2557,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 15, limit: None, two_valued: false, residual: false, @@ -3290,8 +2564,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 16, limit: None, two_valued: false, residual: false, @@ -3299,8 +2571,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 19, limit: None, two_valued: false, residual: false, @@ -3308,8 +2578,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 20, limit: None, two_valued: false, residual: false, @@ -3317,8 +2585,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 21, limit: None, two_valued: false, residual: false, @@ -3326,8 +2592,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 22, limit: None, two_valued: false, residual: false, @@ -3335,8 +2599,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 23, limit: None, two_valued: false, residual: false, @@ -3344,8 +2606,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 24, limit: None, two_valued: false, residual: false, @@ -3353,8 +2613,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 25, limit: None, two_valued: false, residual: false, @@ -3362,8 +2620,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 26, limit: None, two_valued: false, residual: false, @@ -3371,8 +2627,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 27, limit: None, two_valued: false, residual: false, @@ -3380,8 +2634,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 28, limit: None, two_valued: false, residual: false, @@ -3389,8 +2641,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 29, limit: None, two_valued: false, residual: false, @@ -3398,8 +2648,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 30, limit: None, two_valued: false, residual: false, @@ -3407,8 +2655,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 0, limit: None, two_valued: false, residual: false, @@ -3416,8 +2662,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 1, limit: None, two_valued: false, residual: false, @@ -3425,8 +2669,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 2, limit: None, two_valued: false, residual: false, @@ -3434,8 +2676,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 3, limit: None, two_valued: false, residual: false, @@ -3443,8 +2683,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 4, limit: None, two_valued: false, residual: false, @@ -3452,8 +2690,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -3461,8 +2697,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 6, limit: None, two_valued: false, residual: false, @@ -3470,8 +2704,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 7, limit: None, two_valued: false, residual: false, @@ -3479,8 +2711,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 8, limit: None, two_valued: false, residual: false, @@ -3488,8 +2718,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 9, limit: None, two_valued: false, residual: false, @@ -3497,8 +2725,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 10, limit: None, two_valued: false, residual: false, @@ -3506,8 +2732,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 11, limit: None, two_valued: false, residual: false, @@ -3515,8 +2739,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 12, limit: None, two_valued: false, residual: false, @@ -3524,8 +2746,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 13, limit: None, two_valued: false, residual: false, @@ -3533,8 +2753,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 14, limit: None, two_valued: false, residual: false, @@ -3542,8 +2760,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 15, limit: None, two_valued: false, residual: false, @@ -3551,8 +2767,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 16, limit: None, two_valued: false, residual: false, @@ -3560,8 +2774,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 19, limit: None, two_valued: false, residual: false, @@ -3569,8 +2781,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 20, limit: None, two_valued: false, residual: false, @@ -3578,8 +2788,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 21, limit: None, two_valued: false, residual: false, @@ -3587,8 +2795,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 22, limit: None, two_valued: false, residual: false, @@ -3596,8 +2802,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 23, limit: None, two_valued: false, residual: false, @@ -3605,8 +2809,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 24, limit: None, two_valued: false, residual: false, @@ -3614,8 +2816,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 25, limit: None, two_valued: false, residual: false, @@ -3623,8 +2823,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 26, limit: None, two_valued: false, residual: false, @@ -3632,8 +2830,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 27, limit: None, two_valued: false, residual: false, @@ -3641,8 +2837,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 28, limit: None, two_valued: false, residual: false, @@ -3650,8 +2844,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 29, limit: None, two_valued: false, residual: false, @@ -3659,8 +2851,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 30, limit: None, two_valued: false, residual: false, @@ -3668,8 +2858,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 0, limit: None, two_valued: false, residual: false, @@ -3677,8 +2865,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -3686,8 +2872,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 2, limit: None, two_valued: false, residual: false, @@ -3695,8 +2879,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 3, limit: None, two_valued: false, residual: false, @@ -3704,8 +2886,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 4, limit: None, two_valued: false, residual: false, @@ -3713,8 +2893,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -3722,8 +2900,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 6, limit: None, two_valued: false, residual: false, @@ -3731,8 +2907,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 7, limit: None, two_valued: false, residual: false, @@ -3740,8 +2914,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 8, limit: None, two_valued: false, residual: false, @@ -3749,8 +2921,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 9, limit: None, two_valued: false, residual: false, @@ -3758,8 +2928,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 10, limit: None, two_valued: false, residual: false, @@ -3767,8 +2935,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 11, limit: None, two_valued: false, residual: false, @@ -3776,8 +2942,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 12, limit: None, two_valued: false, residual: false, @@ -3785,8 +2949,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 13, limit: None, two_valued: false, residual: false, @@ -3794,8 +2956,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 14, limit: None, two_valued: false, residual: false, @@ -3803,8 +2963,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 15, limit: None, two_valued: false, residual: false, @@ -3812,8 +2970,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 16, limit: None, two_valued: false, residual: false, @@ -3821,8 +2977,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 19, limit: None, two_valued: false, residual: false, @@ -3830,8 +2984,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 20, limit: None, two_valued: false, residual: false, @@ -3839,8 +2991,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -3848,8 +2998,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 22, limit: None, two_valued: false, residual: false, @@ -3857,8 +3005,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 23, limit: None, two_valued: false, residual: false, @@ -3866,8 +3012,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -3875,8 +3019,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -3884,8 +3026,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 26, limit: None, two_valued: false, residual: false, @@ -3893,8 +3033,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -3902,8 +3040,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 28, limit: None, two_valued: false, residual: false, @@ -3911,8 +3047,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 29, limit: None, two_valued: false, residual: false, @@ -3920,8 +3054,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 30, limit: None, two_valued: false, residual: false, @@ -3929,8 +3061,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 0, limit: None, two_valued: false, residual: false, @@ -3938,8 +3068,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -3947,8 +3075,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 2, limit: None, two_valued: false, residual: false, @@ -3956,8 +3082,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 3, limit: None, two_valued: false, residual: false, @@ -3965,8 +3089,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 4, limit: None, two_valued: false, residual: false, @@ -3974,8 +3096,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -3983,8 +3103,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 6, limit: None, two_valued: false, residual: false, @@ -3992,8 +3110,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 7, limit: None, two_valued: false, residual: false, @@ -4001,8 +3117,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 8, limit: None, two_valued: false, residual: false, @@ -4010,8 +3124,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 9, limit: None, two_valued: false, residual: false, @@ -4019,8 +3131,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 10, limit: None, two_valued: false, residual: false, @@ -4028,8 +3138,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 11, limit: None, two_valued: false, residual: false, @@ -4037,8 +3145,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 12, limit: None, two_valued: false, residual: false, @@ -4046,8 +3152,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 13, limit: None, two_valued: false, residual: false, @@ -4055,8 +3159,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 14, limit: None, two_valued: false, residual: false, @@ -4064,8 +3166,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 15, limit: None, two_valued: false, residual: false, @@ -4073,8 +3173,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 16, limit: None, two_valued: false, residual: false, @@ -4082,8 +3180,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 19, limit: None, two_valued: false, residual: false, @@ -4091,8 +3187,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 20, limit: None, two_valued: false, residual: false, @@ -4100,8 +3194,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -4109,8 +3201,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 22, limit: None, two_valued: false, residual: false, @@ -4118,8 +3208,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 23, limit: None, two_valued: false, residual: false, @@ -4127,8 +3215,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -4136,8 +3222,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -4145,8 +3229,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 26, limit: None, two_valued: false, residual: false, @@ -4154,8 +3236,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -4163,8 +3243,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 28, limit: None, two_valued: false, residual: false, @@ -4172,8 +3250,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 29, limit: None, two_valued: false, residual: false, @@ -4181,8 +3257,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 30, limit: None, two_valued: false, residual: false, @@ -4190,8 +3264,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 0, limit: None, two_valued: false, residual: false, @@ -4199,8 +3271,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -4208,8 +3278,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 2, limit: None, two_valued: false, residual: false, @@ -4217,8 +3285,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 3, limit: None, two_valued: false, residual: false, @@ -4226,8 +3292,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 4, limit: None, two_valued: false, residual: false, @@ -4235,8 +3299,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -4244,8 +3306,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 6, limit: None, two_valued: false, residual: false, @@ -4253,8 +3313,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 7, limit: None, two_valued: false, residual: false, @@ -4262,8 +3320,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 8, limit: None, two_valued: false, residual: false, @@ -4271,8 +3327,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 9, limit: None, two_valued: false, residual: false, @@ -4280,8 +3334,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 10, limit: None, two_valued: false, residual: false, @@ -4289,8 +3341,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 11, limit: None, two_valued: false, residual: false, @@ -4298,8 +3348,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 12, limit: None, two_valued: false, residual: false, @@ -4307,8 +3355,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 13, limit: None, two_valued: false, residual: false, @@ -4316,8 +3362,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 14, limit: None, two_valued: false, residual: false, @@ -4325,8 +3369,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 15, limit: None, two_valued: false, residual: false, @@ -4334,8 +3376,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 16, limit: None, two_valued: false, residual: false, @@ -4343,8 +3383,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 19, limit: None, two_valued: false, residual: false, @@ -4352,8 +3390,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 20, limit: None, two_valued: false, residual: false, @@ -4361,8 +3397,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -4370,8 +3404,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 22, limit: None, two_valued: false, residual: false, @@ -4379,8 +3411,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 23, limit: None, two_valued: false, residual: false, @@ -4388,8 +3418,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -4397,8 +3425,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -4406,8 +3432,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 26, limit: None, two_valued: false, residual: false, @@ -4415,8 +3439,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -4424,8 +3446,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 28, limit: None, two_valued: false, residual: false, @@ -4433,8 +3453,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 29, limit: None, two_valued: false, residual: false, @@ -4442,8 +3460,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 30, limit: None, two_valued: false, residual: false, @@ -4451,8 +3467,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 0, limit: None, two_valued: false, residual: false, @@ -4460,8 +3474,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -4469,8 +3481,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 2, limit: None, two_valued: false, residual: false, @@ -4478,8 +3488,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 3, limit: None, two_valued: false, residual: false, @@ -4487,8 +3495,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 4, limit: None, two_valued: false, residual: false, @@ -4496,8 +3502,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -4505,8 +3509,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 6, limit: None, two_valued: false, residual: false, @@ -4514,8 +3516,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 7, limit: None, two_valued: false, residual: false, @@ -4523,8 +3523,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 8, limit: None, two_valued: false, residual: false, @@ -4532,8 +3530,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 9, limit: None, two_valued: false, residual: false, @@ -4541,8 +3537,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 10, limit: None, two_valued: false, residual: false, @@ -4550,8 +3544,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 11, limit: None, two_valued: false, residual: false, @@ -4559,8 +3551,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 12, limit: None, two_valued: false, residual: false, @@ -4568,8 +3558,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 13, limit: None, two_valued: false, residual: false, @@ -4577,8 +3565,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 14, limit: None, two_valued: false, residual: false, @@ -4586,8 +3572,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 15, limit: None, two_valued: false, residual: false, @@ -4595,8 +3579,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 16, limit: None, two_valued: false, residual: false, @@ -4604,8 +3586,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 19, limit: None, two_valued: false, residual: false, @@ -4613,8 +3593,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 20, limit: None, two_valued: false, residual: false, @@ -4622,8 +3600,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 21, limit: None, two_valued: false, residual: false, @@ -4631,8 +3607,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 22, limit: None, two_valued: false, residual: false, @@ -4640,8 +3614,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 23, limit: None, two_valued: false, residual: false, @@ -4649,8 +3621,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 24, limit: None, two_valued: false, residual: false, @@ -4658,8 +3628,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 25, limit: None, two_valued: false, residual: false, @@ -4667,8 +3635,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 26, limit: None, two_valued: false, residual: false, @@ -4676,8 +3642,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 27, limit: None, two_valued: false, residual: false, @@ -4685,8 +3649,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 28, limit: None, two_valued: false, residual: false, @@ -4694,8 +3656,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 29, limit: None, two_valued: false, residual: false, @@ -4703,8 +3663,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 30, limit: None, two_valued: false, residual: false, @@ -4712,8 +3670,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 0, limit: None, two_valued: false, residual: false, @@ -4721,8 +3677,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -4730,8 +3684,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 2, limit: None, two_valued: false, residual: false, @@ -4739,8 +3691,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 3, limit: None, two_valued: false, residual: false, @@ -4748,8 +3698,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 4, limit: None, two_valued: false, residual: false, @@ -4757,8 +3705,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -4766,8 +3712,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 6, limit: None, two_valued: false, residual: false, @@ -4775,8 +3719,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 7, limit: None, two_valued: false, residual: false, @@ -4784,8 +3726,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 8, limit: None, two_valued: false, residual: false, @@ -4793,8 +3733,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 9, limit: Some(em(90)), two_valued: false, residual: false, @@ -4802,8 +3740,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 10, limit: Some(em(90)), two_valued: false, residual: false, @@ -4811,8 +3747,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 11, limit: Some(em(90)), two_valued: false, residual: false, @@ -4820,8 +3754,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 12, limit: None, two_valued: false, residual: false, @@ -4829,8 +3761,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 13, limit: None, two_valued: false, residual: false, @@ -4838,8 +3768,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 14, limit: None, two_valued: false, residual: false, @@ -4847,8 +3775,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 15, limit: Some(em(90)), two_valued: false, residual: false, @@ -4856,8 +3782,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 16, limit: Some(em(90)), two_valued: false, residual: false, @@ -4865,8 +3789,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 19, limit: Some(em(90)), two_valued: false, residual: false, @@ -4874,8 +3796,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 20, limit: None, two_valued: false, residual: false, @@ -4883,8 +3803,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 21, limit: None, two_valued: false, residual: false, @@ -4892,8 +3810,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 22, limit: Some(em(90)), two_valued: false, residual: false, @@ -4901,8 +3817,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 23, limit: Some(em(90)), two_valued: false, residual: false, @@ -4910,8 +3824,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 24, limit: None, two_valued: false, residual: false, @@ -4919,8 +3831,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 25, limit: None, two_valued: false, residual: false, @@ -4928,8 +3838,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 26, limit: None, two_valued: false, residual: false, @@ -4937,8 +3845,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 27, limit: None, two_valued: false, residual: false, @@ -4946,8 +3852,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 28, limit: Some(em(90)), two_valued: false, residual: false, @@ -4955,8 +3859,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 29, limit: None, two_valued: false, residual: false, @@ -4964,8 +3866,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 30, limit: Some(em(90)), two_valued: false, residual: false, @@ -4973,8 +3873,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 0, limit: None, two_valued: false, residual: false, @@ -4982,8 +3880,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -4991,8 +3887,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 2, limit: None, two_valued: false, residual: false, @@ -5000,8 +3894,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 3, limit: None, two_valued: false, residual: false, @@ -5009,8 +3901,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 4, limit: None, two_valued: false, residual: false, @@ -5018,8 +3908,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -5027,8 +3915,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 6, limit: None, two_valued: false, residual: false, @@ -5036,8 +3922,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 7, limit: None, two_valued: false, residual: false, @@ -5045,8 +3929,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 8, limit: None, two_valued: false, residual: false, @@ -5054,8 +3936,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 9, limit: None, two_valued: false, residual: false, @@ -5063,8 +3943,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 10, limit: None, two_valued: false, residual: false, @@ -5072,8 +3950,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 11, limit: None, two_valued: false, residual: false, @@ -5081,8 +3957,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 12, limit: None, two_valued: false, residual: false, @@ -5090,8 +3964,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 13, limit: None, two_valued: false, residual: false, @@ -5099,8 +3971,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 14, limit: None, two_valued: false, residual: false, @@ -5108,8 +3978,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 15, limit: None, two_valued: false, residual: false, @@ -5117,8 +3985,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 16, limit: None, two_valued: false, residual: false, @@ -5126,8 +3992,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 19, limit: None, two_valued: false, residual: false, @@ -5135,8 +3999,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 20, limit: None, two_valued: false, residual: false, @@ -5144,8 +4006,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -5153,8 +4013,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 22, limit: None, two_valued: false, residual: false, @@ -5162,8 +4020,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 23, limit: None, two_valued: false, residual: false, @@ -5171,8 +4027,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -5180,8 +4034,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -5189,8 +4041,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 26, limit: None, two_valued: false, residual: false, @@ -5198,8 +4048,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -5207,8 +4055,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 28, limit: None, two_valued: false, residual: false, @@ -5216,8 +4062,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 29, limit: None, two_valued: false, residual: false, @@ -5225,8 +4069,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 30, limit: None, two_valued: false, residual: false, @@ -5234,8 +4076,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 0, limit: None, two_valued: false, residual: false, @@ -5243,8 +4083,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -5252,8 +4090,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 2, limit: None, two_valued: false, residual: false, @@ -5261,8 +4097,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 3, limit: None, two_valued: false, residual: false, @@ -5270,8 +4104,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 4, limit: None, two_valued: false, residual: false, @@ -5279,8 +4111,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -5288,8 +4118,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 6, limit: None, two_valued: false, residual: false, @@ -5297,8 +4125,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 7, limit: None, two_valued: false, residual: false, @@ -5306,8 +4132,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 8, limit: None, two_valued: false, residual: false, @@ -5315,8 +4139,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 9, limit: None, two_valued: false, residual: false, @@ -5324,8 +4146,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 10, limit: None, two_valued: false, residual: false, @@ -5333,8 +4153,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 11, limit: None, two_valued: false, residual: false, @@ -5342,8 +4160,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 12, limit: None, two_valued: false, residual: false, @@ -5351,8 +4167,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 13, limit: None, two_valued: false, residual: false, @@ -5360,8 +4174,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 14, limit: None, two_valued: false, residual: false, @@ -5369,8 +4181,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 15, limit: None, two_valued: false, residual: false, @@ -5378,8 +4188,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 16, limit: None, two_valued: false, residual: false, @@ -5387,8 +4195,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 19, limit: None, two_valued: false, residual: false, @@ -5396,8 +4202,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 20, limit: None, two_valued: false, residual: false, @@ -5405,8 +4209,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -5414,8 +4216,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 22, limit: None, two_valued: false, residual: false, @@ -5423,8 +4223,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 23, limit: None, two_valued: false, residual: false, @@ -5432,8 +4230,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -5441,8 +4237,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -5450,8 +4244,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 26, limit: None, two_valued: false, residual: false, @@ -5459,8 +4251,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -5468,8 +4258,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 28, limit: None, two_valued: false, residual: false, @@ -5477,8 +4265,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 29, limit: None, two_valued: false, residual: false, @@ -5486,8 +4272,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 30, limit: None, two_valued: false, residual: false, @@ -5495,8 +4279,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 0, limit: None, two_valued: false, residual: false, @@ -5504,8 +4286,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -5513,8 +4293,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 2, limit: None, two_valued: false, residual: false, @@ -5522,8 +4300,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 3, limit: None, two_valued: false, residual: false, @@ -5531,8 +4307,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 4, limit: None, two_valued: false, residual: false, @@ -5540,8 +4314,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -5549,8 +4321,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 6, limit: None, two_valued: false, residual: false, @@ -5558,8 +4328,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 7, limit: None, two_valued: false, residual: false, @@ -5567,8 +4335,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 8, limit: None, two_valued: false, residual: false, @@ -5576,8 +4342,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 9, limit: Some(em(90)), two_valued: false, residual: false, @@ -5585,8 +4349,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 10, limit: Some(em(90)), two_valued: false, residual: false, @@ -5594,8 +4356,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 11, limit: Some(em(90)), two_valued: false, residual: false, @@ -5603,8 +4363,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 12, limit: None, two_valued: false, residual: false, @@ -5612,8 +4370,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 13, limit: None, two_valued: false, residual: false, @@ -5621,8 +4377,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 14, limit: None, two_valued: false, residual: false, @@ -5630,8 +4384,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 15, limit: Some(em(90)), two_valued: false, residual: false, @@ -5639,8 +4391,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 16, limit: Some(em(90)), two_valued: false, residual: false, @@ -5648,8 +4398,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 19, limit: Some(em(90)), two_valued: false, residual: false, @@ -5657,8 +4405,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 20, limit: None, two_valued: false, residual: false, @@ -5666,8 +4412,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 21, limit: None, two_valued: false, residual: false, @@ -5675,8 +4419,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 22, limit: Some(em(90)), two_valued: false, residual: false, @@ -5684,8 +4426,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 23, limit: Some(em(90)), two_valued: false, residual: false, @@ -5693,8 +4433,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 24, limit: None, two_valued: false, residual: false, @@ -5702,8 +4440,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 25, limit: None, two_valued: false, residual: false, @@ -5711,8 +4447,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 26, limit: None, two_valued: false, residual: false, @@ -5720,8 +4454,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 27, limit: None, two_valued: false, residual: false, @@ -5729,8 +4461,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 28, limit: Some(em(90)), two_valued: false, residual: false, @@ -5738,8 +4468,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 29, limit: None, two_valued: false, residual: false, @@ -5747,8 +4475,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 30, limit: Some(em(90)), two_valued: false, residual: false, @@ -5756,8 +4482,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 0, limit: None, two_valued: false, residual: false, @@ -5765,8 +4489,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -5774,8 +4496,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 2, limit: None, two_valued: false, residual: false, @@ -5783,8 +4503,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 3, limit: None, two_valued: false, residual: false, @@ -5792,8 +4510,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 4, limit: None, two_valued: false, residual: false, @@ -5801,8 +4517,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -5810,8 +4524,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 6, limit: None, two_valued: false, residual: false, @@ -5819,8 +4531,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 7, limit: None, two_valued: false, residual: false, @@ -5828,8 +4538,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 8, limit: None, two_valued: false, residual: false, @@ -5837,8 +4545,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 9, limit: Some(em(90)), two_valued: false, residual: false, @@ -5846,8 +4552,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 10, limit: Some(em(90)), two_valued: false, residual: false, @@ -5855,8 +4559,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 11, limit: Some(em(90)), two_valued: false, residual: false, @@ -5864,8 +4566,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 12, limit: None, two_valued: false, residual: false, @@ -5873,8 +4573,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 13, limit: None, two_valued: false, residual: false, @@ -5882,8 +4580,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 14, limit: None, two_valued: false, residual: false, @@ -5891,8 +4587,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 15, limit: Some(em(90)), two_valued: false, residual: false, @@ -5900,8 +4594,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 16, limit: Some(em(90)), two_valued: false, residual: false, @@ -5909,8 +4601,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 19, limit: Some(em(90)), two_valued: false, residual: false, @@ -5918,8 +4608,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 20, limit: None, two_valued: false, residual: false, @@ -5927,8 +4615,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 21, limit: None, two_valued: false, residual: false, @@ -5936,8 +4622,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 22, limit: Some(em(90)), two_valued: false, residual: false, @@ -5945,8 +4629,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 23, limit: Some(em(90)), two_valued: false, residual: false, @@ -5954,8 +4636,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -5963,8 +4643,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 25, limit: None, two_valued: false, residual: false, @@ -5972,8 +4650,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 26, limit: None, two_valued: false, residual: false, @@ -5981,8 +4657,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 27, limit: None, two_valued: false, residual: false, @@ -5990,8 +4664,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 28, limit: Some(em(90)), two_valued: false, residual: false, @@ -5999,8 +4671,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 29, limit: None, two_valued: false, residual: false, @@ -6008,8 +4678,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 30, limit: Some(em(90)), two_valued: false, residual: false, @@ -6017,8 +4685,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 0, limit: None, two_valued: false, residual: false, @@ -6026,8 +4692,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -6035,8 +4699,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 2, limit: None, two_valued: false, residual: false, @@ -6044,8 +4706,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 3, limit: None, two_valued: false, residual: false, @@ -6053,8 +4713,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 4, limit: None, two_valued: false, residual: false, @@ -6062,8 +4720,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -6071,8 +4727,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 6, limit: None, two_valued: false, residual: false, @@ -6080,8 +4734,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 7, limit: None, two_valued: false, residual: false, @@ -6089,8 +4741,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 8, limit: None, two_valued: false, residual: false, @@ -6098,8 +4748,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 9, limit: None, two_valued: false, residual: false, @@ -6107,8 +4755,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 10, limit: None, two_valued: false, residual: false, @@ -6116,8 +4762,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 11, limit: None, two_valued: false, residual: false, @@ -6125,8 +4769,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 12, limit: None, two_valued: false, residual: false, @@ -6134,8 +4776,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 13, limit: None, two_valued: false, residual: false, @@ -6143,8 +4783,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 14, limit: None, two_valued: false, residual: false, @@ -6152,8 +4790,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 15, limit: None, two_valued: false, residual: false, @@ -6161,8 +4797,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 16, limit: None, two_valued: false, residual: false, @@ -6170,8 +4804,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 19, limit: None, two_valued: false, residual: false, @@ -6179,8 +4811,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 20, limit: None, two_valued: false, residual: false, @@ -6188,8 +4818,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 21, limit: None, two_valued: false, residual: false, @@ -6197,8 +4825,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 22, limit: None, two_valued: false, residual: false, @@ -6206,8 +4832,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 23, limit: None, two_valued: false, residual: false, @@ -6215,8 +4839,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 24, limit: None, two_valued: false, residual: false, @@ -6224,8 +4846,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 25, limit: None, two_valued: false, residual: false, @@ -6233,8 +4853,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 26, limit: None, two_valued: false, residual: false, @@ -6242,8 +4860,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 27, limit: None, two_valued: false, residual: false, @@ -6251,8 +4867,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 28, limit: None, two_valued: false, residual: false, @@ -6260,8 +4874,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 29, limit: None, two_valued: false, residual: false, @@ -6269,8 +4881,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 26, - after: 30, limit: None, two_valued: false, residual: false, @@ -6278,8 +4888,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 0, limit: None, two_valued: false, residual: false, @@ -6287,8 +4895,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 27, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -6296,8 +4902,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 2, limit: None, two_valued: false, residual: false, @@ -6305,8 +4909,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 3, limit: None, two_valued: false, residual: false, @@ -6314,8 +4916,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 4, limit: None, two_valued: false, residual: false, @@ -6323,8 +4923,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -6332,8 +4930,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 6, limit: None, two_valued: false, residual: false, @@ -6341,8 +4937,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 7, limit: None, two_valued: false, residual: false, @@ -6350,8 +4944,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 8, limit: None, two_valued: false, residual: false, @@ -6359,8 +4951,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 9, limit: Some(em(90)), two_valued: false, residual: false, @@ -6368,8 +4958,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 10, limit: Some(em(90)), two_valued: false, residual: false, @@ -6377,8 +4965,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 11, limit: Some(em(90)), two_valued: false, residual: false, @@ -6386,8 +4972,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 12, limit: None, two_valued: false, residual: false, @@ -6395,8 +4979,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 13, limit: None, two_valued: false, residual: false, @@ -6404,8 +4986,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 14, limit: None, two_valued: false, residual: false, @@ -6413,8 +4993,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 15, limit: Some(em(90)), two_valued: false, residual: false, @@ -6422,8 +5000,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 16, limit: Some(em(90)), two_valued: false, residual: false, @@ -6431,8 +5007,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 19, limit: Some(em(90)), two_valued: false, residual: false, @@ -6440,8 +5014,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 20, limit: None, two_valued: false, residual: false, @@ -6449,8 +5021,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 21, limit: None, two_valued: false, residual: false, @@ -6458,8 +5028,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 22, limit: Some(em(90)), two_valued: false, residual: false, @@ -6467,8 +5035,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 23, limit: Some(em(90)), two_valued: false, residual: false, @@ -6476,8 +5042,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 24, limit: None, two_valued: false, residual: false, @@ -6485,8 +5049,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -6494,8 +5056,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 26, limit: None, two_valued: false, residual: false, @@ -6503,8 +5063,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 27, limit: None, two_valued: false, residual: false, @@ -6512,8 +5070,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 28, limit: Some(em(90)), two_valued: false, residual: false, @@ -6521,8 +5077,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 29, limit: None, two_valued: false, residual: false, @@ -6530,8 +5084,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 30, limit: Some(em(90)), two_valued: false, residual: false, @@ -6539,8 +5091,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 0, limit: None, two_valued: false, residual: false, @@ -6548,8 +5098,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 1, limit: None, two_valued: false, residual: false, @@ -6557,8 +5105,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 2, limit: None, two_valued: false, residual: false, @@ -6566,8 +5112,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 3, limit: None, two_valued: false, residual: false, @@ -6575,8 +5119,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 4, limit: None, two_valued: false, residual: false, @@ -6584,8 +5126,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 5, limit: None, two_valued: false, residual: false, @@ -6593,8 +5133,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 6, limit: None, two_valued: false, residual: false, @@ -6602,8 +5140,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 7, limit: None, two_valued: false, residual: false, @@ -6611,8 +5147,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 8, limit: None, two_valued: false, residual: false, @@ -6620,8 +5154,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 9, limit: None, two_valued: false, residual: false, @@ -6629,8 +5161,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 10, limit: None, two_valued: false, residual: false, @@ -6638,8 +5168,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 11, limit: None, two_valued: false, residual: false, @@ -6647,8 +5175,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 12, limit: None, two_valued: false, residual: false, @@ -6656,8 +5182,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 13, limit: None, two_valued: false, residual: false, @@ -6665,8 +5189,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 14, limit: None, two_valued: false, residual: false, @@ -6674,8 +5196,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 15, limit: None, two_valued: false, residual: false, @@ -6683,8 +5203,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 16, limit: None, two_valued: false, residual: false, @@ -6692,8 +5210,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 19, limit: None, two_valued: false, residual: false, @@ -6701,8 +5217,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 20, limit: None, two_valued: false, residual: false, @@ -6710,8 +5224,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 21, limit: None, two_valued: false, residual: false, @@ -6719,8 +5231,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 22, limit: None, two_valued: false, residual: false, @@ -6728,8 +5238,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 23, limit: None, two_valued: false, residual: false, @@ -6737,8 +5245,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 24, limit: None, two_valued: false, residual: false, @@ -6746,8 +5252,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 25, limit: None, two_valued: false, residual: false, @@ -6755,8 +5259,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 26, limit: None, two_valued: false, residual: false, @@ -6764,8 +5266,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 28, - after: 27, limit: None, two_valued: false, residual: false, @@ -6773,8 +5273,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 28, limit: None, two_valued: false, residual: false, @@ -6782,8 +5280,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 29, limit: None, two_valued: false, residual: false, @@ -6791,8 +5287,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 30, limit: None, two_valued: false, residual: false, @@ -6800,8 +5294,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 0, limit: None, two_valued: false, residual: false, @@ -6809,8 +5301,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -6818,8 +5308,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 2, limit: None, two_valued: false, residual: false, @@ -6827,8 +5315,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 3, limit: None, two_valued: false, residual: false, @@ -6836,8 +5322,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 4, limit: None, two_valued: false, residual: false, @@ -6845,8 +5329,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -6854,8 +5336,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 6, limit: None, two_valued: false, residual: false, @@ -6863,8 +5343,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 7, limit: None, two_valued: false, residual: false, @@ -6872,8 +5350,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 8, limit: None, two_valued: false, residual: false, @@ -6881,8 +5357,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 9, limit: None, two_valued: false, residual: false, @@ -6890,8 +5364,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 10, limit: None, two_valued: false, residual: false, @@ -6899,8 +5371,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 11, limit: None, two_valued: false, residual: false, @@ -6908,8 +5378,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 12, limit: None, two_valued: false, residual: false, @@ -6917,8 +5385,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 13, limit: None, two_valued: false, residual: false, @@ -6926,8 +5392,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 14, limit: None, two_valued: false, residual: false, @@ -6935,8 +5399,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 15, limit: None, two_valued: false, residual: false, @@ -6944,8 +5406,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 16, limit: None, two_valued: false, residual: false, @@ -6953,8 +5413,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 19, limit: None, two_valued: false, residual: false, @@ -6962,8 +5420,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 20, limit: None, two_valued: false, residual: false, @@ -6971,8 +5427,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -6980,8 +5434,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 22, limit: None, two_valued: false, residual: false, @@ -6989,8 +5441,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 23, limit: None, two_valued: false, residual: false, @@ -6998,8 +5448,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -7007,8 +5455,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -7016,8 +5462,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 26, limit: None, two_valued: false, residual: false, @@ -7025,8 +5469,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -7034,8 +5476,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 28, limit: None, two_valued: false, residual: false, @@ -7043,8 +5483,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 29, limit: None, two_valued: false, residual: false, @@ -7052,8 +5490,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 30, limit: None, two_valued: false, residual: false, @@ -7061,8 +5497,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 0, limit: None, two_valued: false, residual: false, @@ -7070,8 +5504,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -7079,8 +5511,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 2, limit: None, two_valued: false, residual: false, @@ -7088,8 +5518,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 3, limit: None, two_valued: false, residual: false, @@ -7097,8 +5525,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 4, limit: None, two_valued: false, residual: false, @@ -7106,8 +5532,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -7115,8 +5539,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 6, limit: None, two_valued: false, residual: false, @@ -7124,8 +5546,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 7, limit: None, two_valued: false, residual: false, @@ -7133,8 +5553,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 8, limit: None, two_valued: false, residual: false, @@ -7142,8 +5560,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 9, limit: None, two_valued: false, residual: false, @@ -7151,8 +5567,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 10, limit: None, two_valued: false, residual: false, @@ -7160,8 +5574,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 11, limit: None, two_valued: false, residual: false, @@ -7169,8 +5581,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 12, limit: None, two_valued: false, residual: false, @@ -7178,8 +5588,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 13, limit: None, two_valued: false, residual: false, @@ -7187,8 +5595,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 14, limit: None, two_valued: false, residual: false, @@ -7196,8 +5602,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 15, limit: None, two_valued: false, residual: false, @@ -7205,8 +5609,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 16, limit: None, two_valued: false, residual: false, @@ -7214,8 +5616,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 19, limit: None, two_valued: false, residual: false, @@ -7223,8 +5623,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 20, limit: None, two_valued: false, residual: false, @@ -7232,8 +5630,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -7241,8 +5637,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 22, limit: None, two_valued: false, residual: false, @@ -7250,8 +5644,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 23, limit: None, two_valued: false, residual: false, @@ -7259,8 +5651,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -7268,8 +5658,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -7277,8 +5665,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 26, limit: None, two_valued: false, residual: false, @@ -7286,8 +5672,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -7295,8 +5679,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 28, limit: None, two_valued: false, residual: false, @@ -7304,8 +5686,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 29, limit: None, two_valued: false, residual: false, @@ -7313,8 +5693,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 30, limit: None, two_valued: false, residual: false, @@ -7322,8 +5700,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 0, limit: None, two_valued: false, residual: false, @@ -7331,8 +5707,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 1, limit: None, two_valued: false, residual: false, @@ -7340,8 +5714,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 2, limit: None, two_valued: false, residual: false, @@ -7349,8 +5721,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 3, limit: None, two_valued: false, residual: false, @@ -7358,8 +5728,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 4, limit: None, two_valued: false, residual: false, @@ -7367,8 +5735,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 5, limit: None, two_valued: false, residual: false, @@ -7376,8 +5742,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 6, limit: None, two_valued: false, residual: false, @@ -7385,8 +5749,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 7, limit: None, two_valued: false, residual: false, @@ -7394,8 +5756,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 8, limit: None, two_valued: false, residual: false, @@ -7403,8 +5763,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 9, limit: None, two_valued: false, residual: false, @@ -7412,8 +5770,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 10, limit: None, two_valued: false, residual: false, @@ -7421,8 +5777,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 11, limit: None, two_valued: false, residual: false, @@ -7430,8 +5784,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 12, limit: None, two_valued: false, residual: false, @@ -7439,8 +5791,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 13, limit: None, two_valued: false, residual: false, @@ -7448,8 +5798,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 14, limit: None, two_valued: false, residual: false, @@ -7457,8 +5805,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 15, limit: None, two_valued: false, residual: false, @@ -7466,8 +5812,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 16, limit: None, two_valued: false, residual: false, @@ -7475,8 +5819,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 19, limit: None, two_valued: false, residual: false, @@ -7484,8 +5826,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 20, limit: None, two_valued: false, residual: false, @@ -7493,8 +5833,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 21, limit: None, two_valued: false, residual: false, @@ -7502,8 +5840,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 22, limit: None, two_valued: false, residual: false, @@ -7511,8 +5847,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 23, limit: None, two_valued: false, residual: false, @@ -7520,8 +5854,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 24, limit: None, two_valued: false, residual: false, @@ -7529,8 +5861,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 25, limit: None, two_valued: false, residual: false, @@ -7538,8 +5868,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 26, limit: None, two_valued: false, residual: false, @@ -7547,8 +5875,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 0, - after: 27, limit: None, two_valued: false, residual: false, @@ -7556,8 +5882,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 28, limit: None, two_valued: false, residual: false, @@ -7565,8 +5889,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 29, limit: None, two_valued: false, residual: false, @@ -7574,8 +5896,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 30, limit: None, two_valued: false, residual: false, @@ -7583,8 +5903,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 0, limit: None, two_valued: false, residual: false, @@ -7592,3 +5910,26 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, ]; + +const ROW_INDEX: [u8; 31] = [ + 28, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, +]; +const COLUMN_INDEX: [u8; 31] = [ + 28, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, +]; +const COLUMN_COUNT: usize = 29; + +/// Look up one cell directly from its class or line-edge coordinates. +pub(crate) fn cell(before: u8, after: u8) -> Option<&'static RawRangedCell> { + let row = *ROW_INDEX.get(usize::from(before))?; + let column = *COLUMN_INDEX.get(usize::from(after))?; + if row == u8::MAX || column == u8::MAX { + return None; + } + let index = usize::from(row) + .checked_mul(COLUMN_COUNT)? + .checked_add(usize::from(column))?; + CELLS.get(index) +} diff --git a/crates/jlreq-core/src/generated/table4.rs b/crates/jlreq-core/src/generated/table4.rs index 6a9747f..ad374e7 100644 --- a/crates/jlreq-core/src/generated/table4.rs +++ b/crates/jlreq-core/src/generated/table4.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `f8274c63edd9a561f2d951ac2831f731da7db89f22d0a58c5ae533fe5d6d2a65` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` +//! - Generator SHA-256: `02c2e2c3acf5e6532ae3311484d2a3913a8224c7f50075782607830573080ddc` //! - Entries: 841 use crate::spec::{RawRangedCell, em}; @@ -23,8 +23,6 @@ use crate::spec::{RawRangedCell, em}; /// JLReq: §D.1 pub(crate) static CELLS: &[RawRangedCell] = &[ RawRangedCell { - before: 1, - after: 1, limit: None, two_valued: false, residual: false, @@ -32,8 +30,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 2, limit: None, two_valued: false, residual: false, @@ -41,8 +37,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 3, limit: None, two_valued: false, residual: false, @@ -50,8 +44,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 4, limit: None, two_valued: false, residual: false, @@ -59,8 +51,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -68,8 +58,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 6, limit: None, two_valued: false, residual: false, @@ -77,8 +65,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 7, limit: None, two_valued: false, residual: false, @@ -86,8 +72,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 8, limit: None, two_valued: false, residual: false, @@ -95,8 +79,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 9, limit: None, two_valued: false, residual: false, @@ -104,8 +86,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 10, limit: None, two_valued: false, residual: false, @@ -113,8 +93,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 11, limit: None, two_valued: false, residual: false, @@ -122,8 +100,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 12, limit: None, two_valued: false, residual: false, @@ -131,8 +107,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 13, limit: None, two_valued: false, residual: false, @@ -140,8 +114,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 14, limit: None, two_valued: false, residual: false, @@ -149,8 +121,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 15, limit: None, two_valued: false, residual: false, @@ -158,8 +128,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 16, limit: None, two_valued: false, residual: false, @@ -167,8 +135,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 19, limit: None, two_valued: false, residual: false, @@ -176,8 +142,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 20, limit: None, two_valued: false, residual: false, @@ -185,8 +149,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 21, limit: None, two_valued: false, residual: false, @@ -194,8 +156,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 22, limit: None, two_valued: false, residual: false, @@ -203,8 +163,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 23, limit: None, two_valued: false, residual: false, @@ -212,8 +170,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 24, limit: None, two_valued: false, residual: false, @@ -221,8 +177,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 25, limit: None, two_valued: false, residual: false, @@ -230,8 +184,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 26, limit: None, two_valued: false, residual: false, @@ -239,8 +191,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 27, limit: None, two_valued: false, residual: false, @@ -248,8 +198,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 28, limit: None, two_valued: false, residual: false, @@ -257,8 +205,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 29, limit: None, two_valued: false, residual: false, @@ -266,8 +212,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 30, limit: None, two_valued: false, residual: false, @@ -275,8 +219,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 0, limit: None, two_valued: false, residual: false, @@ -284,8 +226,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -293,8 +233,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 2, limit: None, two_valued: false, residual: false, @@ -302,8 +240,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 3, limit: Some(em(0)), two_valued: false, residual: false, @@ -311,8 +247,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 4, limit: Some(em(0)), two_valued: false, residual: false, @@ -320,8 +254,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -329,8 +261,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 6, limit: None, two_valued: false, residual: false, @@ -338,8 +268,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 7, limit: None, two_valued: false, residual: false, @@ -347,8 +275,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 8, limit: Some(em(0)), two_valued: false, residual: false, @@ -356,8 +282,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 9, limit: Some(em(0)), two_valued: false, residual: false, @@ -365,8 +289,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 10, limit: Some(em(0)), two_valued: false, residual: false, @@ -374,8 +296,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 11, limit: Some(em(0)), two_valued: false, residual: false, @@ -383,8 +303,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 12, limit: Some(em(0)), two_valued: false, residual: false, @@ -392,8 +310,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 13, limit: Some(em(0)), two_valued: false, residual: false, @@ -401,8 +317,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 14, limit: None, two_valued: false, residual: false, @@ -410,8 +324,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 15, limit: Some(em(0)), two_valued: false, residual: false, @@ -419,8 +331,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 16, limit: Some(em(0)), two_valued: false, residual: false, @@ -428,8 +338,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 19, limit: Some(em(0)), two_valued: false, residual: false, @@ -437,8 +345,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 20, limit: None, two_valued: false, residual: false, @@ -446,8 +352,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 21, limit: Some(em(0)), two_valued: false, residual: false, @@ -455,8 +359,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 22, limit: Some(em(0)), two_valued: false, residual: false, @@ -464,8 +366,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 23, limit: Some(em(0)), two_valued: false, residual: false, @@ -473,8 +373,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 24, limit: Some(em(0)), two_valued: false, residual: false, @@ -482,8 +380,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 25, limit: Some(em(0)), two_valued: false, residual: false, @@ -491,8 +387,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 26, limit: None, two_valued: false, residual: false, @@ -500,8 +394,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 27, limit: Some(em(0)), two_valued: false, residual: false, @@ -509,8 +401,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 28, limit: Some(em(0)), two_valued: false, residual: false, @@ -518,8 +408,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 29, limit: None, two_valued: false, residual: false, @@ -527,8 +415,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 30, limit: Some(em(0)), two_valued: false, residual: false, @@ -536,8 +422,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 0, limit: None, two_valued: false, residual: false, @@ -545,8 +429,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -554,8 +436,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 2, limit: None, two_valued: false, residual: false, @@ -563,8 +443,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 3, limit: None, two_valued: false, residual: false, @@ -572,8 +450,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 4, limit: None, two_valued: false, residual: false, @@ -581,8 +457,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -590,8 +464,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 6, limit: None, two_valued: false, residual: false, @@ -599,8 +471,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 7, limit: None, two_valued: false, residual: false, @@ -608,8 +478,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 8, limit: None, two_valued: false, residual: false, @@ -617,8 +485,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 9, limit: None, two_valued: false, residual: false, @@ -626,8 +492,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 10, limit: None, two_valued: false, residual: false, @@ -635,8 +499,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 11, limit: None, two_valued: false, residual: false, @@ -644,8 +506,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 12, limit: None, two_valued: false, residual: false, @@ -653,8 +513,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 13, limit: None, two_valued: false, residual: false, @@ -662,8 +520,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 14, limit: None, two_valued: false, residual: false, @@ -671,8 +527,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 15, limit: None, two_valued: false, residual: false, @@ -680,8 +534,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 16, limit: None, two_valued: false, residual: false, @@ -689,8 +541,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 19, limit: None, two_valued: false, residual: false, @@ -698,8 +548,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 20, limit: None, two_valued: false, residual: false, @@ -707,8 +555,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 21, limit: None, two_valued: false, residual: false, @@ -716,8 +562,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 22, limit: None, two_valued: false, residual: false, @@ -725,8 +569,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 23, limit: None, two_valued: false, residual: false, @@ -734,8 +576,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 24, limit: None, two_valued: false, residual: false, @@ -743,8 +583,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 25, limit: None, two_valued: false, residual: false, @@ -752,8 +590,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 26, limit: None, two_valued: false, residual: false, @@ -761,8 +597,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 27, limit: None, two_valued: false, residual: false, @@ -770,8 +604,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 28, limit: None, two_valued: false, residual: false, @@ -779,8 +611,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 29, limit: None, two_valued: false, residual: false, @@ -788,8 +618,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 30, limit: None, two_valued: false, residual: false, @@ -797,8 +625,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 0, limit: None, two_valued: false, residual: false, @@ -806,8 +632,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -815,8 +639,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 2, limit: None, two_valued: false, residual: false, @@ -824,8 +646,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 3, limit: None, two_valued: false, residual: false, @@ -833,8 +653,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 4, limit: None, two_valued: false, residual: false, @@ -842,8 +660,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -851,8 +667,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 6, limit: None, two_valued: false, residual: false, @@ -860,8 +674,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 7, limit: None, two_valued: false, residual: false, @@ -869,8 +681,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 8, limit: None, two_valued: false, residual: false, @@ -878,8 +688,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 9, limit: None, two_valued: false, residual: false, @@ -887,8 +695,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 10, limit: None, two_valued: false, residual: false, @@ -896,8 +702,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 11, limit: None, two_valued: false, residual: false, @@ -905,8 +709,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 12, limit: None, two_valued: false, residual: false, @@ -914,8 +716,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 13, limit: None, two_valued: false, residual: false, @@ -923,8 +723,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 14, limit: None, two_valued: false, residual: false, @@ -932,8 +730,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 15, limit: None, two_valued: false, residual: false, @@ -941,8 +737,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 16, limit: None, two_valued: false, residual: false, @@ -950,8 +744,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 19, limit: None, two_valued: false, residual: false, @@ -959,8 +751,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 20, limit: None, two_valued: false, residual: false, @@ -968,8 +758,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -977,8 +765,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 22, limit: None, two_valued: false, residual: false, @@ -986,8 +772,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 23, limit: None, two_valued: false, residual: false, @@ -995,8 +779,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -1004,8 +786,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -1013,8 +793,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 26, limit: None, two_valued: false, residual: false, @@ -1022,8 +800,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -1031,8 +807,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 28, limit: None, two_valued: false, residual: false, @@ -1040,8 +814,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 29, limit: None, two_valued: false, residual: false, @@ -1049,8 +821,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 30, limit: None, two_valued: false, residual: false, @@ -1058,8 +828,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 0, limit: None, two_valued: false, residual: false, @@ -1067,8 +835,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -1076,8 +842,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 2, limit: Some(em(0)), two_valued: false, residual: false, @@ -1085,8 +849,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 3, limit: Some(em(0)), two_valued: false, residual: false, @@ -1094,8 +856,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 4, limit: Some(em(0)), two_valued: false, residual: false, @@ -1103,8 +863,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -1112,8 +870,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#1", }, RawRangedCell { - before: 5, - after: 6, limit: Some(em(0)), two_valued: false, residual: false, @@ -1121,8 +877,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 7, limit: Some(em(0)), two_valued: false, residual: false, @@ -1130,8 +884,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 8, limit: Some(em(0)), two_valued: false, residual: false, @@ -1139,8 +891,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 9, limit: Some(em(0)), two_valued: false, residual: false, @@ -1148,8 +898,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 10, limit: Some(em(0)), two_valued: false, residual: false, @@ -1157,8 +905,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 11, limit: Some(em(0)), two_valued: false, residual: false, @@ -1166,8 +912,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 12, limit: Some(em(0)), two_valued: false, residual: false, @@ -1175,8 +919,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 13, limit: Some(em(0)), two_valued: false, residual: false, @@ -1184,8 +926,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 14, limit: Some(em(0)), two_valued: false, residual: false, @@ -1193,8 +933,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 15, limit: Some(em(0)), two_valued: false, residual: false, @@ -1202,8 +940,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 16, limit: Some(em(0)), two_valued: false, residual: false, @@ -1211,8 +947,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 19, limit: Some(em(0)), two_valued: false, residual: false, @@ -1220,8 +954,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 20, limit: Some(em(0)), two_valued: false, residual: false, @@ -1229,8 +961,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 21, limit: Some(em(0)), two_valued: false, residual: false, @@ -1238,8 +968,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 22, limit: Some(em(0)), two_valued: false, residual: false, @@ -1247,8 +975,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 23, limit: Some(em(0)), two_valued: false, residual: false, @@ -1256,8 +982,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 24, limit: Some(em(0)), two_valued: false, residual: false, @@ -1265,8 +989,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 25, limit: Some(em(0)), two_valued: false, residual: false, @@ -1274,8 +996,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 26, limit: None, two_valued: false, residual: false, @@ -1283,8 +1003,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 27, limit: Some(em(0)), two_valued: false, residual: false, @@ -1292,8 +1010,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 28, limit: Some(em(0)), two_valued: false, residual: false, @@ -1301,8 +1017,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 29, limit: Some(em(0)), two_valued: false, residual: false, @@ -1310,8 +1024,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 30, limit: Some(em(0)), two_valued: false, residual: false, @@ -1319,8 +1031,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 0, limit: None, two_valued: false, residual: false, @@ -1328,8 +1038,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 1, limit: None, two_valued: false, residual: false, @@ -1337,8 +1045,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 2, limit: None, two_valued: false, residual: false, @@ -1346,8 +1052,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 3, limit: None, two_valued: false, residual: false, @@ -1355,8 +1059,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 4, limit: None, two_valued: false, residual: false, @@ -1364,8 +1066,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 5, limit: Some(em(360)), two_valued: false, residual: false, @@ -1373,8 +1073,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#2", }, RawRangedCell { - before: 6, - after: 6, limit: None, two_valued: false, residual: false, @@ -1382,8 +1080,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 7, limit: None, two_valued: false, residual: false, @@ -1391,8 +1087,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 8, limit: None, two_valued: false, residual: false, @@ -1400,8 +1094,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 9, limit: None, two_valued: false, residual: false, @@ -1409,8 +1101,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 10, limit: None, two_valued: false, residual: false, @@ -1418,8 +1108,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 11, limit: None, two_valued: false, residual: false, @@ -1427,8 +1115,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 12, limit: None, two_valued: false, residual: false, @@ -1436,8 +1122,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 13, limit: None, two_valued: false, residual: false, @@ -1445,8 +1129,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 14, limit: None, two_valued: false, residual: false, @@ -1454,8 +1136,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 15, limit: None, two_valued: false, residual: false, @@ -1463,8 +1143,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 16, limit: None, two_valued: false, residual: false, @@ -1472,8 +1150,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 19, limit: None, two_valued: false, residual: false, @@ -1481,8 +1157,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 20, limit: None, two_valued: false, residual: false, @@ -1490,8 +1164,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 21, limit: None, two_valued: false, residual: false, @@ -1499,8 +1171,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 22, limit: None, two_valued: false, residual: false, @@ -1508,8 +1178,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 23, limit: None, two_valued: false, residual: false, @@ -1517,8 +1185,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 24, limit: None, two_valued: false, residual: false, @@ -1526,8 +1192,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 25, limit: None, two_valued: false, residual: false, @@ -1535,8 +1199,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 26, limit: None, two_valued: false, residual: false, @@ -1544,8 +1206,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 27, limit: None, two_valued: false, residual: false, @@ -1553,8 +1213,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 28, limit: None, two_valued: false, residual: false, @@ -1562,8 +1220,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 29, limit: None, two_valued: false, residual: false, @@ -1571,8 +1227,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 30, limit: None, two_valued: false, residual: false, @@ -1580,8 +1234,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 0, limit: None, two_valued: false, residual: false, @@ -1589,8 +1241,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -1598,8 +1248,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 2, limit: None, two_valued: false, residual: false, @@ -1607,8 +1255,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 3, limit: Some(em(0)), two_valued: false, residual: false, @@ -1616,8 +1262,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 4, limit: Some(em(0)), two_valued: false, residual: false, @@ -1625,8 +1269,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 5, limit: Some(em(360)), two_valued: false, residual: false, @@ -1634,8 +1276,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#3", }, RawRangedCell { - before: 7, - after: 6, limit: None, two_valued: false, residual: false, @@ -1643,8 +1283,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 7, limit: None, two_valued: false, residual: false, @@ -1652,8 +1290,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 8, limit: Some(em(0)), two_valued: false, residual: false, @@ -1661,8 +1297,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 9, limit: Some(em(0)), two_valued: false, residual: false, @@ -1670,8 +1304,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 10, limit: Some(em(0)), two_valued: false, residual: false, @@ -1679,8 +1311,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 11, limit: Some(em(0)), two_valued: false, residual: false, @@ -1688,8 +1318,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 12, limit: Some(em(0)), two_valued: false, residual: false, @@ -1697,8 +1325,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 13, limit: Some(em(0)), two_valued: false, residual: false, @@ -1706,8 +1332,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 14, limit: Some(em(0)), two_valued: false, residual: false, @@ -1715,8 +1339,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 15, limit: Some(em(0)), two_valued: false, residual: false, @@ -1724,8 +1346,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 16, limit: Some(em(0)), two_valued: false, residual: false, @@ -1733,8 +1353,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 19, limit: Some(em(0)), two_valued: false, residual: false, @@ -1742,8 +1360,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 20, limit: None, two_valued: false, residual: false, @@ -1751,8 +1367,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 21, limit: Some(em(0)), two_valued: false, residual: false, @@ -1760,8 +1374,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 22, limit: Some(em(0)), two_valued: false, residual: false, @@ -1769,8 +1381,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 23, limit: Some(em(0)), two_valued: false, residual: false, @@ -1778,8 +1388,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 24, limit: Some(em(0)), two_valued: false, residual: false, @@ -1787,8 +1395,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 25, limit: Some(em(0)), two_valued: false, residual: false, @@ -1796,8 +1402,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 26, limit: None, two_valued: false, residual: false, @@ -1805,8 +1409,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 27, limit: Some(em(0)), two_valued: false, residual: false, @@ -1814,8 +1416,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 28, limit: Some(em(0)), two_valued: false, residual: false, @@ -1823,8 +1423,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 29, limit: Some(em(0)), two_valued: false, residual: false, @@ -1832,8 +1430,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 30, limit: Some(em(0)), two_valued: false, residual: false, @@ -1841,8 +1437,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 0, limit: None, two_valued: false, residual: false, @@ -1850,8 +1444,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -1859,8 +1451,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 2, limit: None, two_valued: false, residual: false, @@ -1868,8 +1458,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 3, limit: None, two_valued: false, residual: false, @@ -1877,8 +1465,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 4, limit: None, two_valued: false, residual: false, @@ -1886,8 +1472,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -1895,8 +1479,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 6, limit: None, two_valued: false, residual: false, @@ -1904,8 +1486,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 7, limit: None, two_valued: false, residual: false, @@ -1913,8 +1493,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 8, limit: None, two_valued: false, residual: false, @@ -1922,8 +1500,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 9, limit: None, two_valued: false, residual: false, @@ -1931,8 +1507,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 10, limit: None, two_valued: false, residual: false, @@ -1940,8 +1514,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 11, limit: None, two_valued: false, residual: false, @@ -1949,8 +1521,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 12, limit: None, two_valued: false, residual: false, @@ -1958,8 +1528,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 13, limit: None, two_valued: false, residual: false, @@ -1967,8 +1535,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 14, limit: None, two_valued: false, residual: false, @@ -1976,8 +1542,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 15, limit: None, two_valued: false, residual: false, @@ -1985,8 +1549,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 16, limit: None, two_valued: false, residual: false, @@ -1994,8 +1556,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 19, limit: None, two_valued: false, residual: false, @@ -2003,8 +1563,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 20, limit: None, two_valued: false, residual: false, @@ -2012,8 +1570,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 21, limit: None, two_valued: false, residual: false, @@ -2021,8 +1577,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 22, limit: None, two_valued: false, residual: false, @@ -2030,8 +1584,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 23, limit: None, two_valued: false, residual: false, @@ -2039,8 +1591,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 24, limit: None, two_valued: false, residual: false, @@ -2048,8 +1598,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 25, limit: None, two_valued: false, residual: false, @@ -2057,8 +1605,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 26, limit: None, two_valued: false, residual: false, @@ -2066,8 +1612,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 27, limit: None, two_valued: false, residual: false, @@ -2075,8 +1619,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 28, limit: None, two_valued: false, residual: false, @@ -2084,8 +1626,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 29, limit: None, two_valued: false, residual: false, @@ -2093,8 +1633,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 30, limit: None, two_valued: false, residual: false, @@ -2102,8 +1640,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 0, limit: None, two_valued: false, residual: false, @@ -2111,8 +1647,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -2120,8 +1654,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 2, limit: None, two_valued: false, residual: false, @@ -2129,8 +1661,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 3, limit: None, two_valued: false, residual: false, @@ -2138,8 +1668,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 4, limit: None, two_valued: false, residual: false, @@ -2147,8 +1675,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -2156,8 +1682,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 6, limit: None, two_valued: false, residual: false, @@ -2165,8 +1689,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 7, limit: None, two_valued: false, residual: false, @@ -2174,8 +1696,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 8, limit: None, two_valued: false, residual: false, @@ -2183,8 +1703,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 9, limit: None, two_valued: false, residual: false, @@ -2192,8 +1710,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 10, limit: None, two_valued: false, residual: false, @@ -2201,8 +1717,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 11, limit: None, two_valued: false, residual: false, @@ -2210,8 +1724,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 12, limit: None, two_valued: false, residual: false, @@ -2219,8 +1731,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 13, limit: None, two_valued: false, residual: false, @@ -2228,8 +1738,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 14, limit: None, two_valued: false, residual: false, @@ -2237,8 +1745,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 15, limit: None, two_valued: false, residual: false, @@ -2246,8 +1752,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 16, limit: None, two_valued: false, residual: false, @@ -2255,8 +1759,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 19, limit: None, two_valued: false, residual: false, @@ -2264,8 +1766,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 20, limit: None, two_valued: false, residual: false, @@ -2273,8 +1773,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -2282,8 +1780,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 22, limit: None, two_valued: false, residual: false, @@ -2291,8 +1787,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 23, limit: None, two_valued: false, residual: false, @@ -2300,8 +1794,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -2309,8 +1801,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -2318,8 +1808,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 26, limit: None, two_valued: false, residual: false, @@ -2327,8 +1815,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -2336,8 +1822,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 28, limit: None, two_valued: false, residual: false, @@ -2345,8 +1829,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 29, limit: None, two_valued: false, residual: false, @@ -2354,8 +1836,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 30, limit: None, two_valued: false, residual: false, @@ -2363,8 +1843,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 0, limit: None, two_valued: false, residual: false, @@ -2372,8 +1850,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -2381,8 +1857,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 2, limit: None, two_valued: false, residual: false, @@ -2390,8 +1864,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 3, limit: None, two_valued: false, residual: false, @@ -2399,8 +1871,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 4, limit: None, two_valued: false, residual: false, @@ -2408,8 +1878,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -2417,8 +1885,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 6, limit: None, two_valued: false, residual: false, @@ -2426,8 +1892,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 7, limit: None, two_valued: false, residual: false, @@ -2435,8 +1899,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 8, limit: None, two_valued: false, residual: false, @@ -2444,8 +1906,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 9, limit: None, two_valued: false, residual: false, @@ -2453,8 +1913,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 10, limit: None, two_valued: false, residual: false, @@ -2462,8 +1920,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 11, limit: None, two_valued: false, residual: false, @@ -2471,8 +1927,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 12, limit: None, two_valued: false, residual: false, @@ -2480,8 +1934,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 13, limit: None, two_valued: false, residual: false, @@ -2489,8 +1941,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 14, limit: None, two_valued: false, residual: false, @@ -2498,8 +1948,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 15, limit: None, two_valued: false, residual: false, @@ -2507,8 +1955,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 16, limit: None, two_valued: false, residual: false, @@ -2516,8 +1962,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 19, limit: None, two_valued: false, residual: false, @@ -2525,8 +1969,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 20, limit: None, two_valued: false, residual: false, @@ -2534,8 +1976,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -2543,8 +1983,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 22, limit: None, two_valued: false, residual: false, @@ -2552,8 +1990,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 23, limit: None, two_valued: false, residual: false, @@ -2561,8 +1997,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -2570,8 +2004,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -2579,8 +2011,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 26, limit: None, two_valued: false, residual: false, @@ -2588,8 +2018,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -2597,8 +2025,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 28, limit: None, two_valued: false, residual: false, @@ -2606,8 +2032,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 29, limit: None, two_valued: false, residual: false, @@ -2615,8 +2039,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 30, limit: None, two_valued: false, residual: false, @@ -2624,8 +2046,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 0, limit: None, two_valued: false, residual: false, @@ -2633,8 +2053,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -2642,8 +2060,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 2, limit: None, two_valued: false, residual: false, @@ -2651,8 +2067,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 3, limit: None, two_valued: false, residual: false, @@ -2660,8 +2074,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 4, limit: None, two_valued: false, residual: false, @@ -2669,8 +2081,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -2678,8 +2088,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 6, limit: None, two_valued: false, residual: false, @@ -2687,8 +2095,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 7, limit: None, two_valued: false, residual: false, @@ -2696,8 +2102,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 8, limit: None, two_valued: false, residual: false, @@ -2705,8 +2109,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 9, limit: None, two_valued: false, residual: false, @@ -2714,8 +2116,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 10, limit: None, two_valued: false, residual: false, @@ -2723,8 +2123,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 11, limit: None, two_valued: false, residual: false, @@ -2732,8 +2130,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 12, limit: None, two_valued: false, residual: false, @@ -2741,8 +2137,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 13, limit: None, two_valued: false, residual: false, @@ -2750,8 +2144,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 14, limit: None, two_valued: false, residual: false, @@ -2759,8 +2151,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 15, limit: None, two_valued: false, residual: false, @@ -2768,8 +2158,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 16, limit: None, two_valued: false, residual: false, @@ -2777,8 +2165,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 19, limit: None, two_valued: false, residual: false, @@ -2786,8 +2172,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 20, limit: None, two_valued: false, residual: false, @@ -2795,8 +2179,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -2804,8 +2186,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 22, limit: None, two_valued: false, residual: false, @@ -2813,8 +2193,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 23, limit: None, two_valued: false, residual: false, @@ -2822,8 +2200,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -2831,8 +2207,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -2840,8 +2214,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 26, limit: None, two_valued: false, residual: false, @@ -2849,8 +2221,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -2858,8 +2228,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 28, limit: None, two_valued: false, residual: false, @@ -2867,8 +2235,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 29, limit: None, two_valued: false, residual: false, @@ -2876,8 +2242,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 30, limit: None, two_valued: false, residual: false, @@ -2885,8 +2249,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 0, limit: None, two_valued: false, residual: false, @@ -2894,8 +2256,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -2903,8 +2263,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 2, limit: None, two_valued: false, residual: false, @@ -2912,8 +2270,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 3, limit: None, two_valued: false, residual: false, @@ -2921,8 +2277,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 4, limit: None, two_valued: false, residual: false, @@ -2930,8 +2284,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -2939,8 +2291,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 6, limit: None, two_valued: false, residual: false, @@ -2948,8 +2298,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 7, limit: None, two_valued: false, residual: false, @@ -2957,8 +2305,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 8, limit: None, two_valued: false, residual: false, @@ -2966,8 +2312,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 9, limit: None, two_valued: false, residual: false, @@ -2975,8 +2319,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 10, limit: None, two_valued: false, residual: false, @@ -2984,8 +2326,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 11, limit: None, two_valued: false, residual: false, @@ -2993,8 +2333,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 12, limit: None, two_valued: false, residual: false, @@ -3002,8 +2340,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 13, limit: None, two_valued: false, residual: false, @@ -3011,8 +2347,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 14, limit: None, two_valued: false, residual: false, @@ -3020,8 +2354,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 15, limit: None, two_valued: false, residual: false, @@ -3029,8 +2361,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 16, limit: None, two_valued: false, residual: false, @@ -3038,8 +2368,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 19, limit: None, two_valued: false, residual: false, @@ -3047,8 +2375,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 20, limit: None, two_valued: false, residual: false, @@ -3056,8 +2382,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 21, limit: None, two_valued: false, residual: false, @@ -3065,8 +2389,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 22, limit: None, two_valued: false, residual: false, @@ -3074,8 +2396,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 23, limit: None, two_valued: false, residual: false, @@ -3083,8 +2403,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 24, limit: None, two_valued: false, residual: false, @@ -3092,8 +2410,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 25, limit: None, two_valued: false, residual: false, @@ -3101,8 +2417,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 26, limit: None, two_valued: false, residual: false, @@ -3110,8 +2424,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 27, limit: None, two_valued: false, residual: false, @@ -3119,8 +2431,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 28, limit: None, two_valued: false, residual: false, @@ -3128,8 +2438,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 29, limit: None, two_valued: false, residual: false, @@ -3137,8 +2445,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 30, limit: None, two_valued: false, residual: false, @@ -3146,8 +2452,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 0, limit: None, two_valued: false, residual: false, @@ -3155,8 +2459,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -3164,8 +2466,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 2, limit: None, two_valued: false, residual: false, @@ -3173,8 +2473,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 3, limit: None, two_valued: false, residual: false, @@ -3182,8 +2480,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 4, limit: None, two_valued: false, residual: false, @@ -3191,8 +2487,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -3200,8 +2494,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 6, limit: None, two_valued: false, residual: false, @@ -3209,8 +2501,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 7, limit: None, two_valued: false, residual: false, @@ -3218,8 +2508,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 8, limit: None, two_valued: false, residual: false, @@ -3227,8 +2515,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 9, limit: None, two_valued: false, residual: false, @@ -3236,8 +2522,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 10, limit: None, two_valued: false, residual: false, @@ -3245,8 +2529,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 11, limit: None, two_valued: false, residual: false, @@ -3254,8 +2536,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 12, limit: None, two_valued: false, residual: false, @@ -3263,8 +2543,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 13, limit: None, two_valued: false, residual: false, @@ -3272,8 +2550,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 14, limit: None, two_valued: false, residual: false, @@ -3281,8 +2557,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 15, limit: None, two_valued: false, residual: false, @@ -3290,8 +2564,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 16, limit: None, two_valued: false, residual: false, @@ -3299,8 +2571,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 19, limit: None, two_valued: false, residual: false, @@ -3308,8 +2578,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 20, limit: None, two_valued: false, residual: false, @@ -3317,8 +2585,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 21, limit: None, two_valued: false, residual: false, @@ -3326,8 +2592,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 22, limit: None, two_valued: false, residual: false, @@ -3335,8 +2599,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 23, limit: None, two_valued: false, residual: false, @@ -3344,8 +2606,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 24, limit: None, two_valued: false, residual: false, @@ -3353,8 +2613,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 25, limit: None, two_valued: false, residual: false, @@ -3362,8 +2620,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 26, limit: None, two_valued: false, residual: false, @@ -3371,8 +2627,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 27, limit: None, two_valued: false, residual: false, @@ -3380,8 +2634,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 28, limit: None, two_valued: false, residual: false, @@ -3389,8 +2641,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 29, limit: None, two_valued: false, residual: false, @@ -3398,8 +2648,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 30, limit: None, two_valued: false, residual: false, @@ -3407,8 +2655,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 0, limit: None, two_valued: false, residual: false, @@ -3416,8 +2662,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 1, limit: None, two_valued: false, residual: false, @@ -3425,8 +2669,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 2, limit: None, two_valued: false, residual: false, @@ -3434,8 +2676,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 3, limit: None, two_valued: false, residual: false, @@ -3443,8 +2683,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 4, limit: None, two_valued: false, residual: false, @@ -3452,8 +2690,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -3461,8 +2697,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 6, limit: None, two_valued: false, residual: false, @@ -3470,8 +2704,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 7, limit: None, two_valued: false, residual: false, @@ -3479,8 +2711,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 8, limit: None, two_valued: false, residual: false, @@ -3488,8 +2718,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 9, limit: None, two_valued: false, residual: false, @@ -3497,8 +2725,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 10, limit: None, two_valued: false, residual: false, @@ -3506,8 +2732,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 11, limit: None, two_valued: false, residual: false, @@ -3515,8 +2739,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 12, limit: None, two_valued: false, residual: false, @@ -3524,8 +2746,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 13, limit: None, two_valued: false, residual: false, @@ -3533,8 +2753,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 14, limit: None, two_valued: false, residual: false, @@ -3542,8 +2760,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 15, limit: None, two_valued: false, residual: false, @@ -3551,8 +2767,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 16, limit: None, two_valued: false, residual: false, @@ -3560,8 +2774,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 19, limit: None, two_valued: false, residual: false, @@ -3569,8 +2781,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 20, limit: None, two_valued: false, residual: false, @@ -3578,8 +2788,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 21, limit: None, two_valued: false, residual: false, @@ -3587,8 +2795,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 22, limit: None, two_valued: false, residual: false, @@ -3596,8 +2802,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 23, limit: None, two_valued: false, residual: false, @@ -3605,8 +2809,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 24, limit: None, two_valued: false, residual: false, @@ -3614,8 +2816,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 25, limit: None, two_valued: false, residual: false, @@ -3623,8 +2823,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 26, limit: None, two_valued: false, residual: false, @@ -3632,8 +2830,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 27, limit: None, two_valued: false, residual: false, @@ -3641,8 +2837,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 28, limit: None, two_valued: false, residual: false, @@ -3650,8 +2844,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 29, limit: None, two_valued: false, residual: false, @@ -3659,8 +2851,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 30, limit: None, two_valued: false, residual: false, @@ -3668,8 +2858,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 0, limit: None, two_valued: false, residual: false, @@ -3677,8 +2865,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -3686,8 +2872,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 2, limit: None, two_valued: false, residual: false, @@ -3695,8 +2879,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 3, limit: None, two_valued: false, residual: false, @@ -3704,8 +2886,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 4, limit: None, two_valued: false, residual: false, @@ -3713,8 +2893,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -3722,8 +2900,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 6, limit: None, two_valued: false, residual: false, @@ -3731,8 +2907,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 7, limit: None, two_valued: false, residual: false, @@ -3740,8 +2914,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 8, limit: None, two_valued: false, residual: false, @@ -3749,8 +2921,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 9, limit: None, two_valued: false, residual: false, @@ -3758,8 +2928,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 10, limit: None, two_valued: false, residual: false, @@ -3767,8 +2935,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 11, limit: None, two_valued: false, residual: false, @@ -3776,8 +2942,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 12, limit: None, two_valued: false, residual: false, @@ -3785,8 +2949,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 13, limit: None, two_valued: false, residual: false, @@ -3794,8 +2956,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 14, limit: None, two_valued: false, residual: false, @@ -3803,8 +2963,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 15, limit: None, two_valued: false, residual: false, @@ -3812,8 +2970,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 16, limit: None, two_valued: false, residual: false, @@ -3821,8 +2977,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 19, limit: None, two_valued: false, residual: false, @@ -3830,8 +2984,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 20, limit: None, two_valued: false, residual: false, @@ -3839,8 +2991,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -3848,8 +2998,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 22, limit: None, two_valued: false, residual: false, @@ -3857,8 +3005,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 23, limit: None, two_valued: false, residual: false, @@ -3866,8 +3012,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -3875,8 +3019,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -3884,8 +3026,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 26, limit: None, two_valued: false, residual: false, @@ -3893,8 +3033,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -3902,8 +3040,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 28, limit: None, two_valued: false, residual: false, @@ -3911,8 +3047,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 29, limit: None, two_valued: false, residual: false, @@ -3920,8 +3054,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 30, limit: None, two_valued: false, residual: false, @@ -3929,8 +3061,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 0, limit: None, two_valued: false, residual: false, @@ -3938,8 +3068,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -3947,8 +3075,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 2, limit: None, two_valued: false, residual: false, @@ -3956,8 +3082,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 3, limit: None, two_valued: false, residual: false, @@ -3965,8 +3089,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 4, limit: None, two_valued: false, residual: false, @@ -3974,8 +3096,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -3983,8 +3103,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 6, limit: None, two_valued: false, residual: false, @@ -3992,8 +3110,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 7, limit: None, two_valued: false, residual: false, @@ -4001,8 +3117,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 8, limit: None, two_valued: false, residual: false, @@ -4010,8 +3124,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 9, limit: None, two_valued: false, residual: false, @@ -4019,8 +3131,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 10, limit: None, two_valued: false, residual: false, @@ -4028,8 +3138,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 11, limit: None, two_valued: false, residual: false, @@ -4037,8 +3145,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 12, limit: None, two_valued: false, residual: false, @@ -4046,8 +3152,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 13, limit: None, two_valued: false, residual: false, @@ -4055,8 +3159,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 14, limit: None, two_valued: false, residual: false, @@ -4064,8 +3166,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 15, limit: None, two_valued: false, residual: false, @@ -4073,8 +3173,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 16, limit: None, two_valued: false, residual: false, @@ -4082,8 +3180,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 19, limit: None, two_valued: false, residual: false, @@ -4091,8 +3187,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 20, limit: None, two_valued: false, residual: false, @@ -4100,8 +3194,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -4109,8 +3201,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 22, limit: None, two_valued: false, residual: false, @@ -4118,8 +3208,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 23, limit: None, two_valued: false, residual: false, @@ -4127,8 +3215,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -4136,8 +3222,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -4145,8 +3229,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 26, limit: None, two_valued: false, residual: false, @@ -4154,8 +3236,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -4163,8 +3243,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 28, limit: None, two_valued: false, residual: false, @@ -4172,8 +3250,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 29, limit: None, two_valued: false, residual: false, @@ -4181,8 +3257,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 30, limit: None, two_valued: false, residual: false, @@ -4190,8 +3264,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 0, limit: None, two_valued: false, residual: false, @@ -4199,8 +3271,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -4208,8 +3278,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 2, limit: None, two_valued: false, residual: false, @@ -4217,8 +3285,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 3, limit: None, two_valued: false, residual: false, @@ -4226,8 +3292,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 4, limit: None, two_valued: false, residual: false, @@ -4235,8 +3299,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -4244,8 +3306,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 6, limit: None, two_valued: false, residual: false, @@ -4253,8 +3313,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 7, limit: None, two_valued: false, residual: false, @@ -4262,8 +3320,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 8, limit: None, two_valued: false, residual: false, @@ -4271,8 +3327,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 9, limit: None, two_valued: false, residual: false, @@ -4280,8 +3334,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 10, limit: None, two_valued: false, residual: false, @@ -4289,8 +3341,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 11, limit: None, two_valued: false, residual: false, @@ -4298,8 +3348,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 12, limit: None, two_valued: false, residual: false, @@ -4307,8 +3355,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 13, limit: None, two_valued: false, residual: false, @@ -4316,8 +3362,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 14, limit: None, two_valued: false, residual: false, @@ -4325,8 +3369,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 15, limit: None, two_valued: false, residual: false, @@ -4334,8 +3376,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 16, limit: None, two_valued: false, residual: false, @@ -4343,8 +3383,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 19, limit: None, two_valued: false, residual: false, @@ -4352,8 +3390,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 20, limit: None, two_valued: false, residual: false, @@ -4361,8 +3397,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -4370,8 +3404,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 22, limit: None, two_valued: false, residual: false, @@ -4379,8 +3411,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 23, limit: None, two_valued: false, residual: false, @@ -4388,8 +3418,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -4397,8 +3425,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -4406,8 +3432,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 26, limit: None, two_valued: false, residual: false, @@ -4415,8 +3439,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -4424,8 +3446,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 28, limit: None, two_valued: false, residual: false, @@ -4433,8 +3453,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 29, limit: None, two_valued: false, residual: false, @@ -4442,8 +3460,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 30, limit: None, two_valued: false, residual: false, @@ -4451,8 +3467,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 0, limit: None, two_valued: false, residual: false, @@ -4460,8 +3474,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -4469,8 +3481,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 2, limit: None, two_valued: false, residual: false, @@ -4478,8 +3488,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 3, limit: None, two_valued: false, residual: false, @@ -4487,8 +3495,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 4, limit: None, two_valued: false, residual: false, @@ -4496,8 +3502,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -4505,8 +3509,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 6, limit: None, two_valued: false, residual: false, @@ -4514,8 +3516,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 7, limit: None, two_valued: false, residual: false, @@ -4523,8 +3523,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 8, limit: None, two_valued: false, residual: false, @@ -4532,8 +3530,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 9, limit: None, two_valued: false, residual: false, @@ -4541,8 +3537,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 10, limit: None, two_valued: false, residual: false, @@ -4550,8 +3544,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 11, limit: None, two_valued: false, residual: false, @@ -4559,8 +3551,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 12, limit: None, two_valued: false, residual: false, @@ -4568,8 +3558,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 13, limit: None, two_valued: false, residual: false, @@ -4577,8 +3565,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 14, limit: None, two_valued: false, residual: false, @@ -4586,8 +3572,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 15, limit: None, two_valued: false, residual: false, @@ -4595,8 +3579,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 16, limit: None, two_valued: false, residual: false, @@ -4604,8 +3586,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 19, limit: None, two_valued: false, residual: false, @@ -4613,8 +3593,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 20, limit: None, two_valued: false, residual: false, @@ -4622,8 +3600,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 21, limit: None, two_valued: false, residual: false, @@ -4631,8 +3607,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 22, limit: None, two_valued: false, residual: false, @@ -4640,8 +3614,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 23, limit: None, two_valued: false, residual: false, @@ -4649,8 +3621,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 24, limit: None, two_valued: false, residual: false, @@ -4658,8 +3628,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 25, limit: None, two_valued: false, residual: false, @@ -4667,8 +3635,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 26, limit: None, two_valued: false, residual: false, @@ -4676,8 +3642,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 27, limit: None, two_valued: false, residual: false, @@ -4685,8 +3649,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 28, limit: None, two_valued: false, residual: false, @@ -4694,8 +3656,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 29, limit: None, two_valued: false, residual: false, @@ -4703,8 +3663,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 30, limit: None, two_valued: false, residual: false, @@ -4712,8 +3670,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 0, limit: None, two_valued: false, residual: false, @@ -4721,8 +3677,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -4730,8 +3684,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 2, limit: None, two_valued: false, residual: false, @@ -4739,8 +3691,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 3, limit: None, two_valued: false, residual: false, @@ -4748,8 +3698,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 4, limit: None, two_valued: false, residual: false, @@ -4757,8 +3705,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -4766,8 +3712,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 6, limit: None, two_valued: false, residual: false, @@ -4775,8 +3719,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 7, limit: None, two_valued: false, residual: false, @@ -4784,8 +3726,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 8, limit: None, two_valued: false, residual: false, @@ -4793,8 +3733,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 9, limit: Some(em(90)), two_valued: false, residual: false, @@ -4802,8 +3740,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 10, limit: Some(em(90)), two_valued: false, residual: false, @@ -4811,8 +3747,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 11, limit: Some(em(90)), two_valued: false, residual: false, @@ -4820,8 +3754,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 12, limit: None, two_valued: false, residual: false, @@ -4829,8 +3761,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 13, limit: None, two_valued: false, residual: false, @@ -4838,8 +3768,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 14, limit: None, two_valued: false, residual: false, @@ -4847,8 +3775,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 15, limit: Some(em(90)), two_valued: false, residual: false, @@ -4856,8 +3782,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 16, limit: Some(em(90)), two_valued: false, residual: false, @@ -4865,8 +3789,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 19, limit: Some(em(90)), two_valued: false, residual: false, @@ -4874,8 +3796,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 20, limit: None, two_valued: false, residual: false, @@ -4883,8 +3803,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 21, limit: None, two_valued: false, residual: false, @@ -4892,8 +3810,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 22, limit: Some(em(90)), two_valued: false, residual: false, @@ -4901,8 +3817,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 23, limit: Some(em(90)), two_valued: false, residual: false, @@ -4910,8 +3824,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 24, limit: None, two_valued: false, residual: false, @@ -4919,8 +3831,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 25, limit: None, two_valued: false, residual: false, @@ -4928,8 +3838,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 26, limit: None, two_valued: false, residual: false, @@ -4937,8 +3845,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 27, limit: None, two_valued: false, residual: false, @@ -4946,8 +3852,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 28, limit: Some(em(90)), two_valued: false, residual: false, @@ -4955,8 +3859,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 29, limit: None, two_valued: false, residual: false, @@ -4964,8 +3866,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 30, limit: Some(em(90)), two_valued: false, residual: false, @@ -4973,8 +3873,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 0, limit: None, two_valued: false, residual: false, @@ -4982,8 +3880,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -4991,8 +3887,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 2, limit: None, two_valued: false, residual: false, @@ -5000,8 +3894,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 3, limit: None, two_valued: false, residual: false, @@ -5009,8 +3901,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 4, limit: None, two_valued: false, residual: false, @@ -5018,8 +3908,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -5027,8 +3915,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 6, limit: None, two_valued: false, residual: false, @@ -5036,8 +3922,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 7, limit: None, two_valued: false, residual: false, @@ -5045,8 +3929,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 8, limit: None, two_valued: false, residual: false, @@ -5054,8 +3936,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 9, limit: None, two_valued: false, residual: false, @@ -5063,8 +3943,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 10, limit: None, two_valued: false, residual: false, @@ -5072,8 +3950,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 11, limit: None, two_valued: false, residual: false, @@ -5081,8 +3957,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 12, limit: None, two_valued: false, residual: false, @@ -5090,8 +3964,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 13, limit: None, two_valued: false, residual: false, @@ -5099,8 +3971,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 14, limit: None, two_valued: false, residual: false, @@ -5108,8 +3978,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 15, limit: None, two_valued: false, residual: false, @@ -5117,8 +3985,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 16, limit: None, two_valued: false, residual: false, @@ -5126,8 +3992,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 19, limit: None, two_valued: false, residual: false, @@ -5135,8 +3999,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 20, limit: None, two_valued: false, residual: false, @@ -5144,8 +4006,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -5153,8 +4013,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 22, limit: None, two_valued: false, residual: false, @@ -5162,8 +4020,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 23, limit: None, two_valued: false, residual: false, @@ -5171,8 +4027,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -5180,8 +4034,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -5189,8 +4041,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 26, limit: None, two_valued: false, residual: false, @@ -5198,8 +4048,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -5207,8 +4055,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 28, limit: None, two_valued: false, residual: false, @@ -5216,8 +4062,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 29, limit: None, two_valued: false, residual: false, @@ -5225,8 +4069,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 30, limit: None, two_valued: false, residual: false, @@ -5234,8 +4076,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 0, limit: None, two_valued: false, residual: false, @@ -5243,8 +4083,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -5252,8 +4090,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 2, limit: None, two_valued: false, residual: false, @@ -5261,8 +4097,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 3, limit: None, two_valued: false, residual: false, @@ -5270,8 +4104,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 4, limit: None, two_valued: false, residual: false, @@ -5279,8 +4111,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -5288,8 +4118,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 6, limit: None, two_valued: false, residual: false, @@ -5297,8 +4125,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 7, limit: None, two_valued: false, residual: false, @@ -5306,8 +4132,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 8, limit: None, two_valued: false, residual: false, @@ -5315,8 +4139,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 9, limit: None, two_valued: false, residual: false, @@ -5324,8 +4146,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 10, limit: None, two_valued: false, residual: false, @@ -5333,8 +4153,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 11, limit: None, two_valued: false, residual: false, @@ -5342,8 +4160,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 12, limit: None, two_valued: false, residual: false, @@ -5351,8 +4167,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 13, limit: None, two_valued: false, residual: false, @@ -5360,8 +4174,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 14, limit: None, two_valued: false, residual: false, @@ -5369,8 +4181,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 15, limit: None, two_valued: false, residual: false, @@ -5378,8 +4188,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 16, limit: None, two_valued: false, residual: false, @@ -5387,8 +4195,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 19, limit: None, two_valued: false, residual: false, @@ -5396,8 +4202,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 20, limit: None, two_valued: false, residual: false, @@ -5405,8 +4209,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -5414,8 +4216,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 22, limit: None, two_valued: false, residual: false, @@ -5423,8 +4223,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 23, limit: None, two_valued: false, residual: false, @@ -5432,8 +4230,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -5441,8 +4237,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -5450,8 +4244,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 26, limit: None, two_valued: false, residual: false, @@ -5459,8 +4251,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -5468,8 +4258,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 28, limit: None, two_valued: false, residual: false, @@ -5477,8 +4265,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 29, limit: None, two_valued: false, residual: false, @@ -5486,8 +4272,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 30, limit: None, two_valued: false, residual: false, @@ -5495,8 +4279,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 0, limit: None, two_valued: false, residual: false, @@ -5504,8 +4286,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -5513,8 +4293,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 2, limit: None, two_valued: false, residual: false, @@ -5522,8 +4300,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 3, limit: None, two_valued: false, residual: false, @@ -5531,8 +4307,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 4, limit: None, two_valued: false, residual: false, @@ -5540,8 +4314,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -5549,8 +4321,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 6, limit: None, two_valued: false, residual: false, @@ -5558,8 +4328,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 7, limit: None, two_valued: false, residual: false, @@ -5567,8 +4335,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 8, limit: None, two_valued: false, residual: false, @@ -5576,8 +4342,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 9, limit: Some(em(90)), two_valued: false, residual: false, @@ -5585,8 +4349,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 10, limit: Some(em(90)), two_valued: false, residual: false, @@ -5594,8 +4356,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 11, limit: Some(em(90)), two_valued: false, residual: false, @@ -5603,8 +4363,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 12, limit: None, two_valued: false, residual: false, @@ -5612,8 +4370,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 13, limit: None, two_valued: false, residual: false, @@ -5621,8 +4377,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 14, limit: None, two_valued: false, residual: false, @@ -5630,8 +4384,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 15, limit: Some(em(90)), two_valued: false, residual: false, @@ -5639,8 +4391,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 16, limit: Some(em(90)), two_valued: false, residual: false, @@ -5648,8 +4398,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 19, limit: Some(em(90)), two_valued: false, residual: false, @@ -5657,8 +4405,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 20, limit: None, two_valued: false, residual: false, @@ -5666,8 +4412,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 21, limit: None, two_valued: false, residual: false, @@ -5675,8 +4419,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 22, limit: Some(em(90)), two_valued: false, residual: false, @@ -5684,8 +4426,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 23, limit: Some(em(90)), two_valued: false, residual: false, @@ -5693,8 +4433,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 24, limit: None, two_valued: false, residual: false, @@ -5702,8 +4440,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 25, limit: None, two_valued: false, residual: false, @@ -5711,8 +4447,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 26, limit: None, two_valued: false, residual: false, @@ -5720,8 +4454,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 27, limit: None, two_valued: false, residual: false, @@ -5729,8 +4461,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 28, limit: Some(em(90)), two_valued: false, residual: false, @@ -5738,8 +4468,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 29, limit: None, two_valued: false, residual: false, @@ -5747,8 +4475,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 30, limit: Some(em(90)), two_valued: false, residual: false, @@ -5756,8 +4482,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 0, limit: None, two_valued: false, residual: false, @@ -5765,8 +4489,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -5774,8 +4496,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 2, limit: None, two_valued: false, residual: false, @@ -5783,8 +4503,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 3, limit: None, two_valued: false, residual: false, @@ -5792,8 +4510,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 4, limit: None, two_valued: false, residual: false, @@ -5801,8 +4517,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -5810,8 +4524,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 6, limit: None, two_valued: false, residual: false, @@ -5819,8 +4531,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 7, limit: None, two_valued: false, residual: false, @@ -5828,8 +4538,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 8, limit: None, two_valued: false, residual: false, @@ -5837,8 +4545,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 9, limit: Some(em(90)), two_valued: false, residual: false, @@ -5846,8 +4552,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 10, limit: Some(em(90)), two_valued: false, residual: false, @@ -5855,8 +4559,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 11, limit: Some(em(90)), two_valued: false, residual: false, @@ -5864,8 +4566,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 12, limit: None, two_valued: false, residual: false, @@ -5873,8 +4573,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 13, limit: None, two_valued: false, residual: false, @@ -5882,8 +4580,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 14, limit: None, two_valued: false, residual: false, @@ -5891,8 +4587,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 15, limit: Some(em(90)), two_valued: false, residual: false, @@ -5900,8 +4594,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 16, limit: Some(em(90)), two_valued: false, residual: false, @@ -5909,8 +4601,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 19, limit: Some(em(90)), two_valued: false, residual: false, @@ -5918,8 +4608,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 20, limit: None, two_valued: false, residual: false, @@ -5927,8 +4615,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 21, limit: None, two_valued: false, residual: false, @@ -5936,8 +4622,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 22, limit: Some(em(90)), two_valued: false, residual: false, @@ -5945,8 +4629,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 23, limit: Some(em(90)), two_valued: false, residual: false, @@ -5954,8 +4636,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -5963,8 +4643,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 25, limit: None, two_valued: false, residual: false, @@ -5972,8 +4650,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 26, limit: None, two_valued: false, residual: false, @@ -5981,8 +4657,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 27, limit: None, two_valued: false, residual: false, @@ -5990,8 +4664,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 28, limit: Some(em(90)), two_valued: false, residual: false, @@ -5999,8 +4671,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 29, limit: None, two_valued: false, residual: false, @@ -6008,8 +4678,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 30, limit: Some(em(90)), two_valued: false, residual: false, @@ -6017,8 +4685,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 0, limit: None, two_valued: false, residual: false, @@ -6026,8 +4692,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 1, limit: None, two_valued: false, residual: false, @@ -6035,8 +4699,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 2, limit: None, two_valued: false, residual: false, @@ -6044,8 +4706,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 3, limit: None, two_valued: false, residual: false, @@ -6053,8 +4713,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 4, limit: None, two_valued: false, residual: false, @@ -6062,8 +4720,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 5, limit: None, two_valued: false, residual: false, @@ -6071,8 +4727,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 6, limit: None, two_valued: false, residual: false, @@ -6080,8 +4734,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 7, limit: None, two_valued: false, residual: false, @@ -6089,8 +4741,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 8, limit: None, two_valued: false, residual: false, @@ -6098,8 +4748,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 9, limit: None, two_valued: false, residual: false, @@ -6107,8 +4755,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 10, limit: None, two_valued: false, residual: false, @@ -6116,8 +4762,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 11, limit: None, two_valued: false, residual: false, @@ -6125,8 +4769,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 12, limit: None, two_valued: false, residual: false, @@ -6134,8 +4776,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 13, limit: None, two_valued: false, residual: false, @@ -6143,8 +4783,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 14, limit: None, two_valued: false, residual: false, @@ -6152,8 +4790,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 15, limit: None, two_valued: false, residual: false, @@ -6161,8 +4797,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 16, limit: None, two_valued: false, residual: false, @@ -6170,8 +4804,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 19, limit: None, two_valued: false, residual: false, @@ -6179,8 +4811,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 20, limit: None, two_valued: false, residual: false, @@ -6188,8 +4818,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 21, limit: None, two_valued: false, residual: false, @@ -6197,8 +4825,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 22, limit: None, two_valued: false, residual: false, @@ -6206,8 +4832,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 23, limit: None, two_valued: false, residual: false, @@ -6215,8 +4839,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 24, limit: None, two_valued: false, residual: false, @@ -6224,8 +4846,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 25, limit: None, two_valued: false, residual: false, @@ -6233,8 +4853,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 26, limit: None, two_valued: false, residual: false, @@ -6242,8 +4860,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 27, limit: None, two_valued: false, residual: false, @@ -6251,8 +4867,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 28, limit: None, two_valued: false, residual: false, @@ -6260,8 +4874,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 29, limit: None, two_valued: false, residual: false, @@ -6269,8 +4881,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 26, - after: 30, limit: None, two_valued: false, residual: false, @@ -6278,8 +4888,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 0, limit: None, two_valued: false, residual: false, @@ -6287,8 +4895,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 27, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -6296,8 +4902,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 2, limit: None, two_valued: false, residual: false, @@ -6305,8 +4909,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 3, limit: None, two_valued: false, residual: false, @@ -6314,8 +4916,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 4, limit: None, two_valued: false, residual: false, @@ -6323,8 +4923,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -6332,8 +4930,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 6, limit: None, two_valued: false, residual: false, @@ -6341,8 +4937,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 7, limit: None, two_valued: false, residual: false, @@ -6350,8 +4944,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 8, limit: None, two_valued: false, residual: false, @@ -6359,8 +4951,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 9, limit: Some(em(90)), two_valued: false, residual: false, @@ -6368,8 +4958,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 10, limit: Some(em(90)), two_valued: false, residual: false, @@ -6377,8 +4965,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 11, limit: Some(em(90)), two_valued: false, residual: false, @@ -6386,8 +4972,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 12, limit: None, two_valued: false, residual: false, @@ -6395,8 +4979,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 13, limit: None, two_valued: false, residual: false, @@ -6404,8 +4986,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 14, limit: None, two_valued: false, residual: false, @@ -6413,8 +4993,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 15, limit: Some(em(90)), two_valued: false, residual: false, @@ -6422,8 +5000,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 16, limit: Some(em(90)), two_valued: false, residual: false, @@ -6431,8 +5007,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 19, limit: Some(em(90)), two_valued: false, residual: false, @@ -6440,8 +5014,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 20, limit: None, two_valued: false, residual: false, @@ -6449,8 +5021,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 21, limit: None, two_valued: false, residual: false, @@ -6458,8 +5028,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 22, limit: Some(em(90)), two_valued: false, residual: false, @@ -6467,8 +5035,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 23, limit: Some(em(90)), two_valued: false, residual: false, @@ -6476,8 +5042,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 24, limit: None, two_valued: false, residual: false, @@ -6485,8 +5049,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -6494,8 +5056,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 26, limit: None, two_valued: false, residual: false, @@ -6503,8 +5063,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 27, limit: None, two_valued: false, residual: false, @@ -6512,8 +5070,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 28, limit: Some(em(90)), two_valued: false, residual: false, @@ -6521,8 +5077,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 29, limit: None, two_valued: false, residual: false, @@ -6530,8 +5084,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 30, limit: Some(em(90)), two_valued: false, residual: false, @@ -6539,8 +5091,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 0, limit: None, two_valued: false, residual: false, @@ -6548,8 +5098,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 1, limit: None, two_valued: false, residual: false, @@ -6557,8 +5105,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 2, limit: None, two_valued: false, residual: false, @@ -6566,8 +5112,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 3, limit: None, two_valued: false, residual: false, @@ -6575,8 +5119,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 4, limit: None, two_valued: false, residual: false, @@ -6584,8 +5126,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 5, limit: None, two_valued: false, residual: false, @@ -6593,8 +5133,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 6, limit: None, two_valued: false, residual: false, @@ -6602,8 +5140,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 7, limit: None, two_valued: false, residual: false, @@ -6611,8 +5147,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 8, limit: None, two_valued: false, residual: false, @@ -6620,8 +5154,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 9, limit: None, two_valued: false, residual: false, @@ -6629,8 +5161,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 10, limit: None, two_valued: false, residual: false, @@ -6638,8 +5168,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 11, limit: None, two_valued: false, residual: false, @@ -6647,8 +5175,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 12, limit: None, two_valued: false, residual: false, @@ -6656,8 +5182,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 13, limit: None, two_valued: false, residual: false, @@ -6665,8 +5189,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 14, limit: None, two_valued: false, residual: false, @@ -6674,8 +5196,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 15, limit: None, two_valued: false, residual: false, @@ -6683,8 +5203,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 16, limit: None, two_valued: false, residual: false, @@ -6692,8 +5210,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 19, limit: None, two_valued: false, residual: false, @@ -6701,8 +5217,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 20, limit: None, two_valued: false, residual: false, @@ -6710,8 +5224,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 21, limit: None, two_valued: false, residual: false, @@ -6719,8 +5231,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 22, limit: None, two_valued: false, residual: false, @@ -6728,8 +5238,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 23, limit: None, two_valued: false, residual: false, @@ -6737,8 +5245,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 24, limit: None, two_valued: false, residual: false, @@ -6746,8 +5252,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 25, limit: None, two_valued: false, residual: false, @@ -6755,8 +5259,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 26, limit: None, two_valued: false, residual: false, @@ -6764,8 +5266,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 28, - after: 27, limit: None, two_valued: false, residual: false, @@ -6773,8 +5273,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 28, limit: None, two_valued: false, residual: false, @@ -6782,8 +5280,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 29, limit: None, two_valued: false, residual: false, @@ -6791,8 +5287,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 30, limit: None, two_valued: false, residual: false, @@ -6800,8 +5294,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 0, limit: None, two_valued: false, residual: false, @@ -6809,8 +5301,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -6818,8 +5308,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 2, limit: None, two_valued: false, residual: false, @@ -6827,8 +5315,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 3, limit: None, two_valued: false, residual: false, @@ -6836,8 +5322,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 4, limit: None, two_valued: false, residual: false, @@ -6845,8 +5329,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -6854,8 +5336,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 6, limit: None, two_valued: false, residual: false, @@ -6863,8 +5343,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 7, limit: None, two_valued: false, residual: false, @@ -6872,8 +5350,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 8, limit: None, two_valued: false, residual: false, @@ -6881,8 +5357,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 9, limit: None, two_valued: false, residual: false, @@ -6890,8 +5364,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 10, limit: None, two_valued: false, residual: false, @@ -6899,8 +5371,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 11, limit: None, two_valued: false, residual: false, @@ -6908,8 +5378,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 12, limit: None, two_valued: false, residual: false, @@ -6917,8 +5385,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 13, limit: None, two_valued: false, residual: false, @@ -6926,8 +5392,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 14, limit: None, two_valued: false, residual: false, @@ -6935,8 +5399,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 15, limit: None, two_valued: false, residual: false, @@ -6944,8 +5406,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 16, limit: None, two_valued: false, residual: false, @@ -6953,8 +5413,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 19, limit: None, two_valued: false, residual: false, @@ -6962,8 +5420,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 20, limit: None, two_valued: false, residual: false, @@ -6971,8 +5427,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -6980,8 +5434,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 22, limit: None, two_valued: false, residual: false, @@ -6989,8 +5441,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 23, limit: None, two_valued: false, residual: false, @@ -6998,8 +5448,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -7007,8 +5455,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -7016,8 +5462,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 26, limit: None, two_valued: false, residual: false, @@ -7025,8 +5469,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -7034,8 +5476,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 28, limit: None, two_valued: false, residual: false, @@ -7043,8 +5483,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 29, limit: None, two_valued: false, residual: false, @@ -7052,8 +5490,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 30, limit: None, two_valued: false, residual: false, @@ -7061,8 +5497,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 0, limit: None, two_valued: false, residual: false, @@ -7070,8 +5504,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 1, limit: Some(em(0)), two_valued: false, residual: false, @@ -7079,8 +5511,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 2, limit: None, two_valued: false, residual: false, @@ -7088,8 +5518,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 3, limit: None, two_valued: false, residual: false, @@ -7097,8 +5525,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 4, limit: None, two_valued: false, residual: false, @@ -7106,8 +5532,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 5, limit: Some(em(0)), two_valued: false, residual: false, @@ -7115,8 +5539,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 6, limit: None, two_valued: false, residual: false, @@ -7124,8 +5546,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 7, limit: None, two_valued: false, residual: false, @@ -7133,8 +5553,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 8, limit: None, two_valued: false, residual: false, @@ -7142,8 +5560,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 9, limit: None, two_valued: false, residual: false, @@ -7151,8 +5567,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 10, limit: None, two_valued: false, residual: false, @@ -7160,8 +5574,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 11, limit: None, two_valued: false, residual: false, @@ -7169,8 +5581,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 12, limit: None, two_valued: false, residual: false, @@ -7178,8 +5588,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 13, limit: None, two_valued: false, residual: false, @@ -7187,8 +5595,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 14, limit: None, two_valued: false, residual: false, @@ -7196,8 +5602,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 15, limit: None, two_valued: false, residual: false, @@ -7205,8 +5609,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 16, limit: None, two_valued: false, residual: false, @@ -7214,8 +5616,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 19, limit: None, two_valued: false, residual: false, @@ -7223,8 +5623,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 20, limit: None, two_valued: false, residual: false, @@ -7232,8 +5630,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 21, limit: Some(em(90)), two_valued: false, residual: false, @@ -7241,8 +5637,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 22, limit: None, two_valued: false, residual: false, @@ -7250,8 +5644,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 23, limit: None, two_valued: false, residual: false, @@ -7259,8 +5651,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 24, limit: Some(em(90)), two_valued: false, residual: false, @@ -7268,8 +5658,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 25, limit: Some(em(90)), two_valued: false, residual: false, @@ -7277,8 +5665,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 26, limit: None, two_valued: false, residual: false, @@ -7286,8 +5672,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 27, limit: Some(em(90)), two_valued: false, residual: false, @@ -7295,8 +5679,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 28, limit: None, two_valued: false, residual: false, @@ -7304,8 +5686,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 29, limit: None, two_valued: false, residual: false, @@ -7313,8 +5693,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 30, limit: None, two_valued: false, residual: false, @@ -7322,8 +5700,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 0, limit: None, two_valued: false, residual: false, @@ -7331,8 +5707,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 1, limit: None, two_valued: false, residual: false, @@ -7340,8 +5714,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 2, limit: None, two_valued: false, residual: false, @@ -7349,8 +5721,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 3, limit: None, two_valued: false, residual: false, @@ -7358,8 +5728,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 4, limit: None, two_valued: false, residual: false, @@ -7367,8 +5735,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 5, limit: None, two_valued: false, residual: false, @@ -7376,8 +5742,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 6, limit: None, two_valued: false, residual: false, @@ -7385,8 +5749,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 7, limit: None, two_valued: false, residual: false, @@ -7394,8 +5756,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 8, limit: None, two_valued: false, residual: false, @@ -7403,8 +5763,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 9, limit: None, two_valued: false, residual: false, @@ -7412,8 +5770,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 10, limit: None, two_valued: false, residual: false, @@ -7421,8 +5777,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 11, limit: None, two_valued: false, residual: false, @@ -7430,8 +5784,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 12, limit: None, two_valued: false, residual: false, @@ -7439,8 +5791,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 13, limit: None, two_valued: false, residual: false, @@ -7448,8 +5798,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 14, limit: None, two_valued: false, residual: false, @@ -7457,8 +5805,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 15, limit: None, two_valued: false, residual: false, @@ -7466,8 +5812,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 16, limit: None, two_valued: false, residual: false, @@ -7475,8 +5819,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 19, limit: None, two_valued: false, residual: false, @@ -7484,8 +5826,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 20, limit: None, two_valued: false, residual: false, @@ -7493,8 +5833,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 21, limit: None, two_valued: false, residual: false, @@ -7502,8 +5840,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 22, limit: None, two_valued: false, residual: false, @@ -7511,8 +5847,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 23, limit: None, two_valued: false, residual: false, @@ -7520,8 +5854,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 24, limit: None, two_valued: false, residual: false, @@ -7529,8 +5861,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 25, limit: None, two_valued: false, residual: false, @@ -7538,8 +5868,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 26, limit: None, two_valued: false, residual: false, @@ -7547,8 +5875,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 0, - after: 27, limit: None, two_valued: false, residual: false, @@ -7556,8 +5882,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 28, limit: None, two_valued: false, residual: false, @@ -7565,8 +5889,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 29, limit: None, two_valued: false, residual: false, @@ -7574,8 +5896,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 30, limit: None, two_valued: false, residual: false, @@ -7583,8 +5903,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 0, limit: None, two_valued: false, residual: false, @@ -7592,3 +5910,26 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, ]; + +const ROW_INDEX: [u8; 31] = [ + 28, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, +]; +const COLUMN_INDEX: [u8; 31] = [ + 28, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, +]; +const COLUMN_COUNT: usize = 29; + +/// Look up one cell directly from its class or line-edge coordinates. +pub(crate) fn cell(before: u8, after: u8) -> Option<&'static RawRangedCell> { + let row = *ROW_INDEX.get(usize::from(before))?; + let column = *COLUMN_INDEX.get(usize::from(after))?; + if row == u8::MAX || column == u8::MAX { + return None; + } + let index = usize::from(row) + .checked_mul(COLUMN_COUNT)? + .checked_add(usize::from(column))?; + CELLS.get(index) +} diff --git a/crates/jlreq-core/src/generated/table5.rs b/crates/jlreq-core/src/generated/table5.rs index db4ab2a..b169ac9 100644 --- a/crates/jlreq-core/src/generated/table5.rs +++ b/crates/jlreq-core/src/generated/table5.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `785b6275731e54b8b235efabfb9418b9e766145c5f59cdf99cda8a9e018c3519` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` +//! - Generator SHA-256: `02c2e2c3acf5e6532ae3311484d2a3913a8224c7f50075782607830573080ddc` //! - Entries: 841 use crate::spec::{RawRangedCell, em}; @@ -23,8 +23,6 @@ use crate::spec::{RawRangedCell, em}; /// JLReq: §D.1 pub(crate) static CELLS: &[RawRangedCell] = &[ RawRangedCell { - before: 1, - after: 1, limit: None, two_valued: false, residual: false, @@ -32,8 +30,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 2, limit: None, two_valued: false, residual: false, @@ -41,8 +37,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 3, limit: None, two_valued: false, residual: false, @@ -50,8 +44,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 4, limit: None, two_valued: false, residual: false, @@ -59,8 +51,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 5, limit: None, two_valued: false, residual: false, @@ -68,8 +58,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 6, limit: None, two_valued: false, residual: false, @@ -77,8 +65,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 7, limit: None, two_valued: false, residual: false, @@ -86,8 +72,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 8, limit: None, two_valued: false, residual: false, @@ -95,8 +79,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 9, limit: None, two_valued: false, residual: false, @@ -104,8 +86,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 10, limit: None, two_valued: false, residual: false, @@ -113,8 +93,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 11, limit: None, two_valued: false, residual: false, @@ -122,8 +100,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 12, limit: None, two_valued: false, residual: false, @@ -131,8 +107,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 13, limit: None, two_valued: false, residual: false, @@ -140,8 +114,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 14, limit: None, two_valued: false, residual: false, @@ -149,8 +121,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 15, limit: None, two_valued: false, residual: false, @@ -158,8 +128,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 16, limit: None, two_valued: false, residual: false, @@ -167,8 +135,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 19, limit: None, two_valued: false, residual: false, @@ -176,8 +142,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 20, limit: None, two_valued: false, residual: false, @@ -185,8 +149,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 21, limit: None, two_valued: false, residual: false, @@ -194,8 +156,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 22, limit: None, two_valued: false, residual: false, @@ -203,8 +163,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 23, limit: None, two_valued: false, residual: false, @@ -212,8 +170,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 24, limit: None, two_valued: false, residual: false, @@ -221,8 +177,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 25, limit: None, two_valued: false, residual: false, @@ -230,8 +184,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 26, limit: None, two_valued: false, residual: false, @@ -239,8 +191,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 27, limit: None, two_valued: false, residual: false, @@ -248,8 +198,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 28, limit: None, two_valued: false, residual: false, @@ -257,8 +205,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 29, limit: None, two_valued: false, residual: false, @@ -266,8 +212,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 30, limit: None, two_valued: false, residual: false, @@ -275,8 +219,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 1, - after: 0, limit: None, two_valued: false, residual: false, @@ -284,8 +226,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -293,8 +233,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 2, limit: None, two_valued: false, residual: false, @@ -302,8 +240,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 3, limit: Some(em(180)), two_valued: false, residual: false, @@ -311,8 +247,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 4, limit: Some(em(180)), two_valued: false, residual: false, @@ -320,8 +254,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 5, limit: None, two_valued: false, residual: false, @@ -329,8 +261,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 6, limit: None, two_valued: false, residual: false, @@ -338,8 +268,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 7, limit: None, two_valued: false, residual: false, @@ -347,8 +275,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -356,8 +282,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -365,8 +289,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -374,8 +296,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -383,8 +303,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -392,8 +310,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -401,8 +317,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 14, limit: None, two_valued: false, residual: false, @@ -410,8 +324,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -419,8 +331,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -428,8 +338,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -437,8 +345,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 20, limit: None, two_valued: false, residual: false, @@ -446,8 +352,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 21, limit: Some(em(180)), two_valued: false, residual: false, @@ -455,8 +359,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -464,8 +366,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -473,8 +373,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 24, limit: Some(em(180)), two_valued: false, residual: false, @@ -482,8 +380,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 25, limit: Some(em(180)), two_valued: false, residual: false, @@ -491,8 +387,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 26, limit: None, two_valued: false, residual: false, @@ -500,8 +394,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 27, limit: Some(em(180)), two_valued: false, residual: false, @@ -509,8 +401,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -518,8 +408,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 29, limit: None, two_valued: false, residual: false, @@ -527,8 +415,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -536,8 +422,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 2, - after: 0, limit: Some(em(0)), two_valued: true, residual: false, @@ -545,8 +429,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -554,8 +436,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 2, limit: None, two_valued: false, residual: false, @@ -563,8 +443,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 3, limit: None, two_valued: false, residual: false, @@ -572,8 +450,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 4, limit: None, two_valued: false, residual: false, @@ -581,8 +457,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 5, limit: None, two_valued: false, residual: false, @@ -590,8 +464,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 6, limit: None, two_valued: false, residual: false, @@ -599,8 +471,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 7, limit: None, two_valued: false, residual: false, @@ -608,8 +478,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 8, limit: None, two_valued: false, residual: false, @@ -617,8 +485,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 9, limit: None, two_valued: false, residual: false, @@ -626,8 +492,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 10, limit: None, two_valued: false, residual: false, @@ -635,8 +499,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 11, limit: None, two_valued: false, residual: false, @@ -644,8 +506,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 12, limit: None, two_valued: false, residual: false, @@ -653,8 +513,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 13, limit: None, two_valued: false, residual: false, @@ -662,8 +520,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 14, limit: None, two_valued: false, residual: false, @@ -671,8 +527,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 15, limit: None, two_valued: false, residual: false, @@ -680,8 +534,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 16, limit: None, two_valued: false, residual: false, @@ -689,8 +541,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 19, limit: None, two_valued: false, residual: false, @@ -698,8 +548,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 20, limit: None, two_valued: false, residual: false, @@ -707,8 +555,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 21, limit: None, two_valued: false, residual: false, @@ -716,8 +562,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 22, limit: None, two_valued: false, residual: false, @@ -725,8 +569,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 23, limit: None, two_valued: false, residual: false, @@ -734,8 +576,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 24, limit: None, two_valued: false, residual: false, @@ -743,8 +583,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 25, limit: None, two_valued: false, residual: false, @@ -752,8 +590,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 26, limit: None, two_valued: false, residual: false, @@ -761,8 +597,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 27, limit: None, two_valued: false, residual: false, @@ -770,8 +604,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 28, limit: None, two_valued: false, residual: false, @@ -779,8 +611,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 29, limit: None, two_valued: false, residual: false, @@ -788,8 +618,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 30, limit: None, two_valued: false, residual: false, @@ -797,8 +625,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 3, - after: 0, limit: None, two_valued: false, residual: false, @@ -806,8 +632,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -815,8 +639,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 2, limit: None, two_valued: false, residual: false, @@ -824,8 +646,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 3, limit: None, two_valued: false, residual: false, @@ -833,8 +653,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 4, limit: None, two_valued: false, residual: false, @@ -842,8 +660,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 5, limit: None, two_valued: false, residual: false, @@ -851,8 +667,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 6, limit: None, two_valued: false, residual: false, @@ -860,8 +674,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 7, limit: None, two_valued: false, residual: false, @@ -869,8 +681,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 8, limit: None, two_valued: false, residual: false, @@ -878,8 +688,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 9, limit: None, two_valued: false, residual: false, @@ -887,8 +695,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 10, limit: None, two_valued: false, residual: false, @@ -896,8 +702,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 11, limit: None, two_valued: false, residual: false, @@ -905,8 +709,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 12, limit: None, two_valued: false, residual: false, @@ -914,8 +716,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 13, limit: None, two_valued: false, residual: false, @@ -923,8 +723,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 14, limit: None, two_valued: false, residual: false, @@ -932,8 +730,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 15, limit: None, two_valued: false, residual: false, @@ -941,8 +737,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 16, limit: None, two_valued: false, residual: false, @@ -950,8 +744,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 19, limit: None, two_valued: false, residual: false, @@ -959,8 +751,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 20, limit: None, two_valued: false, residual: false, @@ -968,8 +758,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 21, limit: None, two_valued: false, residual: false, @@ -977,8 +765,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 22, limit: None, two_valued: false, residual: false, @@ -986,8 +772,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 23, limit: None, two_valued: false, residual: false, @@ -995,8 +779,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 24, limit: None, two_valued: false, residual: false, @@ -1004,8 +786,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 25, limit: None, two_valued: false, residual: false, @@ -1013,8 +793,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 26, limit: None, two_valued: false, residual: false, @@ -1022,8 +800,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 27, limit: None, two_valued: false, residual: false, @@ -1031,8 +807,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 28, limit: None, two_valued: false, residual: false, @@ -1040,8 +814,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 29, limit: None, two_valued: false, residual: false, @@ -1049,8 +821,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 30, limit: None, two_valued: false, residual: false, @@ -1058,8 +828,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 4, - after: 0, limit: None, two_valued: false, residual: false, @@ -1067,8 +835,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 1, limit: None, two_valued: false, residual: false, @@ -1076,8 +842,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 2, limit: None, two_valued: false, residual: false, @@ -1085,8 +849,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 3, limit: None, two_valued: false, residual: false, @@ -1094,8 +856,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 4, limit: None, two_valued: false, residual: false, @@ -1103,8 +863,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 5, limit: None, two_valued: false, residual: false, @@ -1112,8 +870,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#1", }, RawRangedCell { - before: 5, - after: 6, limit: None, two_valued: false, residual: false, @@ -1121,8 +877,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 7, limit: None, two_valued: false, residual: false, @@ -1130,8 +884,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 8, limit: None, two_valued: false, residual: false, @@ -1139,8 +891,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 9, limit: None, two_valued: false, residual: false, @@ -1148,8 +898,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 10, limit: None, two_valued: false, residual: false, @@ -1157,8 +905,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 11, limit: None, two_valued: false, residual: false, @@ -1166,8 +912,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 12, limit: None, two_valued: false, residual: false, @@ -1175,8 +919,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 13, limit: None, two_valued: false, residual: false, @@ -1184,8 +926,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 14, limit: None, two_valued: false, residual: false, @@ -1193,8 +933,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 15, limit: None, two_valued: false, residual: false, @@ -1202,8 +940,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 16, limit: None, two_valued: false, residual: false, @@ -1211,8 +947,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 19, limit: None, two_valued: false, residual: false, @@ -1220,8 +954,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 20, limit: None, two_valued: false, residual: false, @@ -1229,8 +961,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 21, limit: None, two_valued: false, residual: false, @@ -1238,8 +968,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 22, limit: None, two_valued: false, residual: false, @@ -1247,8 +975,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 23, limit: None, two_valued: false, residual: false, @@ -1256,8 +982,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 24, limit: None, two_valued: false, residual: false, @@ -1265,8 +989,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 25, limit: None, two_valued: false, residual: false, @@ -1274,8 +996,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 26, limit: None, two_valued: false, residual: false, @@ -1283,8 +1003,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 27, limit: None, two_valued: false, residual: false, @@ -1292,8 +1010,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 28, limit: None, two_valued: false, residual: false, @@ -1301,8 +1017,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 29, limit: None, two_valued: false, residual: false, @@ -1310,8 +1024,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 30, limit: None, two_valued: false, residual: false, @@ -1319,8 +1031,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 5, - after: 0, limit: None, two_valued: false, residual: false, @@ -1328,8 +1038,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 1, limit: None, two_valued: false, residual: false, @@ -1337,8 +1045,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 2, limit: None, two_valued: false, residual: false, @@ -1346,8 +1052,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 3, limit: None, two_valued: false, residual: false, @@ -1355,8 +1059,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 4, limit: None, two_valued: false, residual: false, @@ -1364,8 +1066,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 5, limit: None, two_valued: false, residual: false, @@ -1373,8 +1073,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#2", }, RawRangedCell { - before: 6, - after: 6, limit: None, two_valued: false, residual: false, @@ -1382,8 +1080,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 7, limit: None, two_valued: false, residual: false, @@ -1391,8 +1087,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 8, limit: None, two_valued: false, residual: false, @@ -1400,8 +1094,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 9, limit: None, two_valued: false, residual: false, @@ -1409,8 +1101,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 10, limit: None, two_valued: false, residual: false, @@ -1418,8 +1108,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 11, limit: None, two_valued: false, residual: false, @@ -1427,8 +1115,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 12, limit: None, two_valued: false, residual: false, @@ -1436,8 +1122,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 13, limit: None, two_valued: false, residual: false, @@ -1445,8 +1129,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 14, limit: None, two_valued: false, residual: false, @@ -1454,8 +1136,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 15, limit: None, two_valued: false, residual: false, @@ -1463,8 +1143,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 16, limit: None, two_valued: false, residual: false, @@ -1472,8 +1150,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 19, limit: None, two_valued: false, residual: false, @@ -1481,8 +1157,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 20, limit: None, two_valued: false, residual: false, @@ -1490,8 +1164,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 21, limit: None, two_valued: false, residual: false, @@ -1499,8 +1171,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 22, limit: None, two_valued: false, residual: false, @@ -1508,8 +1178,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 23, limit: None, two_valued: false, residual: false, @@ -1517,8 +1185,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 24, limit: None, two_valued: false, residual: false, @@ -1526,8 +1192,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 25, limit: None, two_valued: false, residual: false, @@ -1535,8 +1199,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 26, limit: None, two_valued: false, residual: false, @@ -1544,8 +1206,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 27, limit: None, two_valued: false, residual: false, @@ -1553,8 +1213,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 28, limit: None, two_valued: false, residual: false, @@ -1562,8 +1220,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 29, limit: None, two_valued: false, residual: false, @@ -1571,8 +1227,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 30, limit: None, two_valued: false, residual: false, @@ -1580,8 +1234,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 6, - after: 0, limit: Some(em(0)), two_valued: true, residual: false, @@ -1589,8 +1241,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -1598,8 +1248,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 2, limit: None, two_valued: false, residual: false, @@ -1607,8 +1255,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 3, limit: Some(em(180)), two_valued: false, residual: false, @@ -1616,8 +1262,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 4, limit: Some(em(180)), two_valued: false, residual: false, @@ -1625,8 +1269,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 5, limit: Some(em(180)), two_valued: false, residual: false, @@ -1634,8 +1276,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#3", }, RawRangedCell { - before: 7, - after: 6, limit: None, two_valued: false, residual: false, @@ -1643,8 +1283,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 7, limit: None, two_valued: false, residual: false, @@ -1652,8 +1290,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -1661,8 +1297,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -1670,8 +1304,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -1679,8 +1311,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -1688,8 +1318,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -1697,8 +1325,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -1706,8 +1332,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 14, limit: Some(em(180)), two_valued: false, residual: false, @@ -1715,8 +1339,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -1724,8 +1346,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -1733,8 +1353,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -1742,8 +1360,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 20, limit: None, two_valued: false, residual: false, @@ -1751,8 +1367,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 21, limit: Some(em(180)), two_valued: false, residual: false, @@ -1760,8 +1374,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -1769,8 +1381,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -1778,8 +1388,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 24, limit: Some(em(180)), two_valued: false, residual: false, @@ -1787,8 +1395,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 25, limit: Some(em(180)), two_valued: false, residual: false, @@ -1796,8 +1402,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 26, limit: None, two_valued: false, residual: false, @@ -1805,8 +1409,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 27, limit: Some(em(180)), two_valued: false, residual: false, @@ -1814,8 +1416,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -1823,8 +1423,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 29, limit: Some(em(180)), two_valued: false, residual: false, @@ -1832,8 +1430,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -1841,8 +1437,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 7, - after: 0, limit: Some(em(0)), two_valued: true, residual: false, @@ -1850,8 +1444,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -1859,8 +1451,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 2, limit: None, two_valued: false, residual: false, @@ -1868,8 +1458,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 3, limit: None, two_valued: false, residual: false, @@ -1877,8 +1465,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 4, limit: None, two_valued: false, residual: false, @@ -1886,8 +1472,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 5, limit: None, two_valued: false, residual: false, @@ -1895,8 +1479,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 6, limit: None, two_valued: false, residual: false, @@ -1904,8 +1486,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 7, limit: None, two_valued: false, residual: false, @@ -1913,8 +1493,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 8, limit: None, two_valued: false, residual: false, @@ -1922,8 +1500,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 9, limit: None, two_valued: false, residual: false, @@ -1931,8 +1507,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 10, limit: None, two_valued: false, residual: false, @@ -1940,8 +1514,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 11, limit: None, two_valued: false, residual: false, @@ -1949,8 +1521,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 12, limit: None, two_valued: false, residual: false, @@ -1958,8 +1528,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 13, limit: None, two_valued: false, residual: false, @@ -1967,8 +1535,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 14, limit: None, two_valued: false, residual: false, @@ -1976,8 +1542,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 15, limit: None, two_valued: false, residual: false, @@ -1985,8 +1549,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 16, limit: None, two_valued: false, residual: false, @@ -1994,8 +1556,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 19, limit: None, two_valued: false, residual: false, @@ -2003,8 +1563,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 20, limit: None, two_valued: false, residual: false, @@ -2012,8 +1570,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 21, limit: None, two_valued: false, residual: false, @@ -2021,8 +1577,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 22, limit: None, two_valued: false, residual: false, @@ -2030,8 +1584,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 23, limit: None, two_valued: false, residual: false, @@ -2039,8 +1591,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 24, limit: None, two_valued: false, residual: false, @@ -2048,8 +1598,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 25, limit: None, two_valued: false, residual: false, @@ -2057,8 +1605,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 26, limit: None, two_valued: false, residual: false, @@ -2066,8 +1612,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 27, limit: None, two_valued: false, residual: false, @@ -2075,8 +1619,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 28, limit: None, two_valued: false, residual: false, @@ -2084,8 +1626,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 29, limit: None, two_valued: false, residual: false, @@ -2093,8 +1633,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 30, limit: None, two_valued: false, residual: false, @@ -2102,8 +1640,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 8, - after: 0, limit: None, two_valued: false, residual: false, @@ -2111,8 +1647,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -2120,8 +1654,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 2, limit: None, two_valued: false, residual: false, @@ -2129,8 +1661,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 3, limit: None, two_valued: false, residual: false, @@ -2138,8 +1668,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 4, limit: None, two_valued: false, residual: false, @@ -2147,8 +1675,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 5, limit: None, two_valued: false, residual: false, @@ -2156,8 +1682,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 6, limit: None, two_valued: false, residual: false, @@ -2165,8 +1689,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 7, limit: None, two_valued: false, residual: false, @@ -2174,8 +1696,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 8, limit: None, two_valued: false, residual: false, @@ -2183,8 +1703,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 9, limit: None, two_valued: false, residual: false, @@ -2192,8 +1710,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 10, limit: None, two_valued: false, residual: false, @@ -2201,8 +1717,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 11, limit: None, two_valued: false, residual: false, @@ -2210,8 +1724,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 12, limit: None, two_valued: false, residual: false, @@ -2219,8 +1731,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 13, limit: None, two_valued: false, residual: false, @@ -2228,8 +1738,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 14, limit: None, two_valued: false, residual: false, @@ -2237,8 +1745,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 15, limit: None, two_valued: false, residual: false, @@ -2246,8 +1752,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 16, limit: None, two_valued: false, residual: false, @@ -2255,8 +1759,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 19, limit: None, two_valued: false, residual: false, @@ -2264,8 +1766,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 20, limit: None, two_valued: false, residual: false, @@ -2273,8 +1773,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 21, limit: None, two_valued: false, residual: false, @@ -2282,8 +1780,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 22, limit: None, two_valued: false, residual: false, @@ -2291,8 +1787,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 23, limit: None, two_valued: false, residual: false, @@ -2300,8 +1794,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 24, limit: None, two_valued: false, residual: false, @@ -2309,8 +1801,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 25, limit: None, two_valued: false, residual: false, @@ -2318,8 +1808,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 26, limit: None, two_valued: false, residual: false, @@ -2327,8 +1815,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 27, limit: None, two_valued: false, residual: false, @@ -2336,8 +1822,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 28, limit: None, two_valued: false, residual: false, @@ -2345,8 +1829,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 29, limit: None, two_valued: false, residual: false, @@ -2354,8 +1836,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 30, limit: None, two_valued: false, residual: false, @@ -2363,8 +1843,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 9, - after: 0, limit: None, two_valued: false, residual: false, @@ -2372,8 +1850,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -2381,8 +1857,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 2, limit: None, two_valued: false, residual: false, @@ -2390,8 +1864,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 3, limit: None, two_valued: false, residual: false, @@ -2399,8 +1871,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 4, limit: None, two_valued: false, residual: false, @@ -2408,8 +1878,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 5, limit: None, two_valued: false, residual: false, @@ -2417,8 +1885,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 6, limit: None, two_valued: false, residual: false, @@ -2426,8 +1892,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 7, limit: None, two_valued: false, residual: false, @@ -2435,8 +1899,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 8, limit: None, two_valued: false, residual: false, @@ -2444,8 +1906,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 9, limit: None, two_valued: false, residual: false, @@ -2453,8 +1913,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 10, limit: None, two_valued: false, residual: false, @@ -2462,8 +1920,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 11, limit: None, two_valued: false, residual: false, @@ -2471,8 +1927,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 12, limit: None, two_valued: false, residual: false, @@ -2480,8 +1934,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 13, limit: None, two_valued: false, residual: false, @@ -2489,8 +1941,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 14, limit: None, two_valued: false, residual: false, @@ -2498,8 +1948,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 15, limit: None, two_valued: false, residual: false, @@ -2507,8 +1955,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 16, limit: None, two_valued: false, residual: false, @@ -2516,8 +1962,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 19, limit: None, two_valued: false, residual: false, @@ -2525,8 +1969,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 20, limit: None, two_valued: false, residual: false, @@ -2534,8 +1976,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 21, limit: None, two_valued: false, residual: false, @@ -2543,8 +1983,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 22, limit: None, two_valued: false, residual: false, @@ -2552,8 +1990,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 23, limit: None, two_valued: false, residual: false, @@ -2561,8 +1997,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 24, limit: None, two_valued: false, residual: false, @@ -2570,8 +2004,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 25, limit: None, two_valued: false, residual: false, @@ -2579,8 +2011,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 26, limit: None, two_valued: false, residual: false, @@ -2588,8 +2018,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 27, limit: None, two_valued: false, residual: false, @@ -2597,8 +2025,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 28, limit: None, two_valued: false, residual: false, @@ -2606,8 +2032,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 29, limit: None, two_valued: false, residual: false, @@ -2615,8 +2039,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 30, limit: None, two_valued: false, residual: false, @@ -2624,8 +2046,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 10, - after: 0, limit: None, two_valued: false, residual: false, @@ -2633,8 +2053,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -2642,8 +2060,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 2, limit: None, two_valued: false, residual: false, @@ -2651,8 +2067,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 3, limit: None, two_valued: false, residual: false, @@ -2660,8 +2074,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 4, limit: None, two_valued: false, residual: false, @@ -2669,8 +2081,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 5, limit: None, two_valued: false, residual: false, @@ -2678,8 +2088,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 6, limit: None, two_valued: false, residual: false, @@ -2687,8 +2095,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 7, limit: None, two_valued: false, residual: false, @@ -2696,8 +2102,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 8, limit: None, two_valued: false, residual: false, @@ -2705,8 +2109,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 9, limit: None, two_valued: false, residual: false, @@ -2714,8 +2116,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 10, limit: None, two_valued: false, residual: false, @@ -2723,8 +2123,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 11, limit: None, two_valued: false, residual: false, @@ -2732,8 +2130,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 12, limit: None, two_valued: false, residual: false, @@ -2741,8 +2137,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 13, limit: None, two_valued: false, residual: false, @@ -2750,8 +2144,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 14, limit: None, two_valued: false, residual: false, @@ -2759,8 +2151,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 15, limit: None, two_valued: false, residual: false, @@ -2768,8 +2158,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 16, limit: None, two_valued: false, residual: false, @@ -2777,8 +2165,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 19, limit: None, two_valued: false, residual: false, @@ -2786,8 +2172,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 20, limit: None, two_valued: false, residual: false, @@ -2795,8 +2179,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 21, limit: None, two_valued: false, residual: false, @@ -2804,8 +2186,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 22, limit: None, two_valued: false, residual: false, @@ -2813,8 +2193,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 23, limit: None, two_valued: false, residual: false, @@ -2822,8 +2200,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 24, limit: None, two_valued: false, residual: false, @@ -2831,8 +2207,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 25, limit: None, two_valued: false, residual: false, @@ -2840,8 +2214,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 26, limit: None, two_valued: false, residual: false, @@ -2849,8 +2221,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 27, limit: None, two_valued: false, residual: false, @@ -2858,8 +2228,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 28, limit: None, two_valued: false, residual: false, @@ -2867,8 +2235,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 29, limit: None, two_valued: false, residual: false, @@ -2876,8 +2242,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 30, limit: None, two_valued: false, residual: false, @@ -2885,8 +2249,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 11, - after: 0, limit: None, two_valued: false, residual: false, @@ -2894,8 +2256,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -2903,8 +2263,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 2, limit: None, two_valued: false, residual: false, @@ -2912,8 +2270,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 3, limit: None, two_valued: false, residual: false, @@ -2921,8 +2277,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 4, limit: None, two_valued: false, residual: false, @@ -2930,8 +2284,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 5, limit: None, two_valued: false, residual: false, @@ -2939,8 +2291,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 6, limit: None, two_valued: false, residual: false, @@ -2948,8 +2298,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 7, limit: None, two_valued: false, residual: false, @@ -2957,8 +2305,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 8, limit: None, two_valued: false, residual: false, @@ -2966,8 +2312,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 9, limit: None, two_valued: false, residual: false, @@ -2975,8 +2319,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 10, limit: None, two_valued: false, residual: false, @@ -2984,8 +2326,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 11, limit: None, two_valued: false, residual: false, @@ -2993,8 +2333,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 12, limit: None, two_valued: false, residual: false, @@ -3002,8 +2340,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 13, limit: None, two_valued: false, residual: false, @@ -3011,8 +2347,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 14, limit: None, two_valued: false, residual: false, @@ -3020,8 +2354,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 15, limit: None, two_valued: false, residual: false, @@ -3029,8 +2361,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 16, limit: None, two_valued: false, residual: false, @@ -3038,8 +2368,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 19, limit: None, two_valued: false, residual: false, @@ -3047,8 +2375,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 20, limit: None, two_valued: false, residual: false, @@ -3056,8 +2382,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 21, limit: None, two_valued: false, residual: false, @@ -3065,8 +2389,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 22, limit: None, two_valued: false, residual: false, @@ -3074,8 +2396,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 23, limit: None, two_valued: false, residual: false, @@ -3083,8 +2403,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 24, limit: None, two_valued: false, residual: false, @@ -3092,8 +2410,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 25, limit: None, two_valued: false, residual: false, @@ -3101,8 +2417,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 26, limit: None, two_valued: false, residual: false, @@ -3110,8 +2424,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 27, limit: None, two_valued: false, residual: false, @@ -3119,8 +2431,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 28, limit: None, two_valued: false, residual: false, @@ -3128,8 +2438,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 29, limit: None, two_valued: false, residual: false, @@ -3137,8 +2445,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 30, limit: None, two_valued: false, residual: false, @@ -3146,8 +2452,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 12, - after: 0, limit: None, two_valued: false, residual: false, @@ -3155,8 +2459,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -3164,8 +2466,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 2, limit: None, two_valued: false, residual: false, @@ -3173,8 +2473,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 3, limit: None, two_valued: false, residual: false, @@ -3182,8 +2480,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 4, limit: None, two_valued: false, residual: false, @@ -3191,8 +2487,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 5, limit: None, two_valued: false, residual: false, @@ -3200,8 +2494,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 6, limit: None, two_valued: false, residual: false, @@ -3209,8 +2501,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 7, limit: None, two_valued: false, residual: false, @@ -3218,8 +2508,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 8, limit: None, two_valued: false, residual: false, @@ -3227,8 +2515,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 9, limit: None, two_valued: false, residual: false, @@ -3236,8 +2522,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 10, limit: None, two_valued: false, residual: false, @@ -3245,8 +2529,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 11, limit: None, two_valued: false, residual: false, @@ -3254,8 +2536,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 12, limit: None, two_valued: false, residual: false, @@ -3263,8 +2543,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 13, limit: None, two_valued: false, residual: false, @@ -3272,8 +2550,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 14, limit: None, two_valued: false, residual: false, @@ -3281,8 +2557,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 15, limit: None, two_valued: false, residual: false, @@ -3290,8 +2564,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 16, limit: None, two_valued: false, residual: false, @@ -3299,8 +2571,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 19, limit: None, two_valued: false, residual: false, @@ -3308,8 +2578,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 20, limit: None, two_valued: false, residual: false, @@ -3317,8 +2585,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 21, limit: None, two_valued: false, residual: false, @@ -3326,8 +2592,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 22, limit: None, two_valued: false, residual: false, @@ -3335,8 +2599,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 23, limit: None, two_valued: false, residual: false, @@ -3344,8 +2606,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 24, limit: None, two_valued: false, residual: false, @@ -3353,8 +2613,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 25, limit: None, two_valued: false, residual: false, @@ -3362,8 +2620,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 26, limit: None, two_valued: false, residual: false, @@ -3371,8 +2627,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 27, limit: None, two_valued: false, residual: false, @@ -3380,8 +2634,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 28, limit: None, two_valued: false, residual: false, @@ -3389,8 +2641,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 29, limit: None, two_valued: false, residual: false, @@ -3398,8 +2648,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 30, limit: None, two_valued: false, residual: false, @@ -3407,8 +2655,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 13, - after: 0, limit: None, two_valued: false, residual: false, @@ -3416,8 +2662,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 1, limit: None, two_valued: false, residual: false, @@ -3425,8 +2669,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 2, limit: None, two_valued: false, residual: false, @@ -3434,8 +2676,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 3, limit: None, two_valued: false, residual: false, @@ -3443,8 +2683,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 4, limit: None, two_valued: false, residual: false, @@ -3452,8 +2690,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 5, limit: None, two_valued: false, residual: false, @@ -3461,8 +2697,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 6, limit: None, two_valued: false, residual: false, @@ -3470,8 +2704,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 7, limit: None, two_valued: false, residual: false, @@ -3479,8 +2711,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 8, limit: None, two_valued: false, residual: false, @@ -3488,8 +2718,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 9, limit: None, two_valued: false, residual: false, @@ -3497,8 +2725,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 10, limit: None, two_valued: false, residual: false, @@ -3506,8 +2732,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 11, limit: None, two_valued: false, residual: false, @@ -3515,8 +2739,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 12, limit: None, two_valued: false, residual: false, @@ -3524,8 +2746,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 13, limit: None, two_valued: false, residual: false, @@ -3533,8 +2753,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 14, limit: None, two_valued: false, residual: false, @@ -3542,8 +2760,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 15, limit: None, two_valued: false, residual: false, @@ -3551,8 +2767,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 16, limit: None, two_valued: false, residual: false, @@ -3560,8 +2774,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 19, limit: None, two_valued: false, residual: false, @@ -3569,8 +2781,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 20, limit: None, two_valued: false, residual: false, @@ -3578,8 +2788,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 21, limit: None, two_valued: false, residual: false, @@ -3587,8 +2795,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 22, limit: None, two_valued: false, residual: false, @@ -3596,8 +2802,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 23, limit: None, two_valued: false, residual: false, @@ -3605,8 +2809,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 24, limit: None, two_valued: false, residual: false, @@ -3614,8 +2816,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 25, limit: None, two_valued: false, residual: false, @@ -3623,8 +2823,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 26, limit: None, two_valued: false, residual: false, @@ -3632,8 +2830,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 27, limit: None, two_valued: false, residual: false, @@ -3641,8 +2837,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 28, limit: None, two_valued: false, residual: false, @@ -3650,8 +2844,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 29, limit: None, two_valued: false, residual: false, @@ -3659,8 +2851,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 30, limit: None, two_valued: false, residual: false, @@ -3668,8 +2858,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 14, - after: 0, limit: None, two_valued: false, residual: false, @@ -3677,8 +2865,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -3686,8 +2872,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 2, limit: None, two_valued: false, residual: false, @@ -3695,8 +2879,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 3, limit: None, two_valued: false, residual: false, @@ -3704,8 +2886,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 4, limit: None, two_valued: false, residual: false, @@ -3713,8 +2893,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 5, limit: None, two_valued: false, residual: false, @@ -3722,8 +2900,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 6, limit: None, two_valued: false, residual: false, @@ -3731,8 +2907,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 7, limit: None, two_valued: false, residual: false, @@ -3740,8 +2914,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 8, limit: None, two_valued: false, residual: false, @@ -3749,8 +2921,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 9, limit: None, two_valued: false, residual: false, @@ -3758,8 +2928,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 10, limit: None, two_valued: false, residual: false, @@ -3767,8 +2935,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 11, limit: None, two_valued: false, residual: false, @@ -3776,8 +2942,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 12, limit: None, two_valued: false, residual: false, @@ -3785,8 +2949,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 13, limit: None, two_valued: false, residual: false, @@ -3794,8 +2956,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 14, limit: None, two_valued: false, residual: false, @@ -3803,8 +2963,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 15, limit: None, two_valued: false, residual: false, @@ -3812,8 +2970,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 16, limit: None, two_valued: false, residual: false, @@ -3821,8 +2977,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 19, limit: None, two_valued: false, residual: false, @@ -3830,8 +2984,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 20, limit: None, two_valued: false, residual: false, @@ -3839,8 +2991,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 21, limit: None, two_valued: false, residual: false, @@ -3848,8 +2998,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 22, limit: None, two_valued: false, residual: false, @@ -3857,8 +3005,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 23, limit: None, two_valued: false, residual: false, @@ -3866,8 +3012,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 24, limit: None, two_valued: false, residual: false, @@ -3875,8 +3019,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 25, limit: None, two_valued: false, residual: false, @@ -3884,8 +3026,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 26, limit: None, two_valued: false, residual: false, @@ -3893,8 +3033,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 27, limit: None, two_valued: false, residual: false, @@ -3902,8 +3040,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 28, limit: None, two_valued: false, residual: false, @@ -3911,8 +3047,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 29, limit: None, two_valued: false, residual: false, @@ -3920,8 +3054,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 30, limit: None, two_valued: false, residual: false, @@ -3929,8 +3061,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 15, - after: 0, limit: None, two_valued: false, residual: false, @@ -3938,8 +3068,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -3947,8 +3075,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 2, limit: None, two_valued: false, residual: false, @@ -3956,8 +3082,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 3, limit: None, two_valued: false, residual: false, @@ -3965,8 +3089,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 4, limit: None, two_valued: false, residual: false, @@ -3974,8 +3096,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 5, limit: None, two_valued: false, residual: false, @@ -3983,8 +3103,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 6, limit: None, two_valued: false, residual: false, @@ -3992,8 +3110,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 7, limit: None, two_valued: false, residual: false, @@ -4001,8 +3117,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 8, limit: None, two_valued: false, residual: false, @@ -4010,8 +3124,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 9, limit: None, two_valued: false, residual: false, @@ -4019,8 +3131,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 10, limit: None, two_valued: false, residual: false, @@ -4028,8 +3138,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 11, limit: None, two_valued: false, residual: false, @@ -4037,8 +3145,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 12, limit: None, two_valued: false, residual: false, @@ -4046,8 +3152,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 13, limit: None, two_valued: false, residual: false, @@ -4055,8 +3159,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 14, limit: None, two_valued: false, residual: false, @@ -4064,8 +3166,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 15, limit: None, two_valued: false, residual: false, @@ -4073,8 +3173,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 16, limit: None, two_valued: false, residual: false, @@ -4082,8 +3180,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 19, limit: None, two_valued: false, residual: false, @@ -4091,8 +3187,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 20, limit: None, two_valued: false, residual: false, @@ -4100,8 +3194,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 21, limit: None, two_valued: false, residual: false, @@ -4109,8 +3201,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 22, limit: None, two_valued: false, residual: false, @@ -4118,8 +3208,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 23, limit: None, two_valued: false, residual: false, @@ -4127,8 +3215,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 24, limit: None, two_valued: false, residual: false, @@ -4136,8 +3222,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 25, limit: None, two_valued: false, residual: false, @@ -4145,8 +3229,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 26, limit: None, two_valued: false, residual: false, @@ -4154,8 +3236,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 27, limit: None, two_valued: false, residual: false, @@ -4163,8 +3243,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 28, limit: None, two_valued: false, residual: false, @@ -4172,8 +3250,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 29, limit: None, two_valued: false, residual: false, @@ -4181,8 +3257,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 30, limit: None, two_valued: false, residual: false, @@ -4190,8 +3264,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 16, - after: 0, limit: None, two_valued: false, residual: false, @@ -4199,8 +3271,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -4208,8 +3278,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 2, limit: None, two_valued: false, residual: false, @@ -4217,8 +3285,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 3, limit: None, two_valued: false, residual: false, @@ -4226,8 +3292,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 4, limit: None, two_valued: false, residual: false, @@ -4235,8 +3299,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 5, limit: None, two_valued: false, residual: false, @@ -4244,8 +3306,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 6, limit: None, two_valued: false, residual: false, @@ -4253,8 +3313,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 7, limit: None, two_valued: false, residual: false, @@ -4262,8 +3320,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 8, limit: None, two_valued: false, residual: false, @@ -4271,8 +3327,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 9, limit: None, two_valued: false, residual: false, @@ -4280,8 +3334,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 10, limit: None, two_valued: false, residual: false, @@ -4289,8 +3341,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 11, limit: None, two_valued: false, residual: false, @@ -4298,8 +3348,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 12, limit: None, two_valued: false, residual: false, @@ -4307,8 +3355,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 13, limit: None, two_valued: false, residual: false, @@ -4316,8 +3362,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 14, limit: None, two_valued: false, residual: false, @@ -4325,8 +3369,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 15, limit: None, two_valued: false, residual: false, @@ -4334,8 +3376,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 16, limit: None, two_valued: false, residual: false, @@ -4343,8 +3383,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 19, limit: None, two_valued: false, residual: false, @@ -4352,8 +3390,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 20, limit: None, two_valued: false, residual: false, @@ -4361,8 +3397,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 21, limit: None, two_valued: false, residual: false, @@ -4370,8 +3404,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 22, limit: None, two_valued: false, residual: false, @@ -4379,8 +3411,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 23, limit: None, two_valued: false, residual: false, @@ -4388,8 +3418,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 24, limit: None, two_valued: false, residual: false, @@ -4397,8 +3425,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 25, limit: None, two_valued: false, residual: false, @@ -4406,8 +3432,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 26, limit: None, two_valued: false, residual: false, @@ -4415,8 +3439,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 27, limit: None, two_valued: false, residual: false, @@ -4424,8 +3446,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 28, limit: None, two_valued: false, residual: false, @@ -4433,8 +3453,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 29, limit: None, two_valued: false, residual: false, @@ -4442,8 +3460,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 30, limit: None, two_valued: false, residual: false, @@ -4451,8 +3467,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 19, - after: 0, limit: None, two_valued: false, residual: false, @@ -4460,8 +3474,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -4469,8 +3481,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 2, limit: None, two_valued: false, residual: false, @@ -4478,8 +3488,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 3, limit: None, two_valued: false, residual: false, @@ -4487,8 +3495,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 4, limit: None, two_valued: false, residual: false, @@ -4496,8 +3502,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 5, limit: None, two_valued: false, residual: false, @@ -4505,8 +3509,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 6, limit: None, two_valued: false, residual: false, @@ -4514,8 +3516,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 7, limit: None, two_valued: false, residual: false, @@ -4523,8 +3523,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 8, limit: None, two_valued: false, residual: false, @@ -4532,8 +3530,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 9, limit: None, two_valued: false, residual: false, @@ -4541,8 +3537,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 10, limit: None, two_valued: false, residual: false, @@ -4550,8 +3544,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 11, limit: None, two_valued: false, residual: false, @@ -4559,8 +3551,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 12, limit: None, two_valued: false, residual: false, @@ -4568,8 +3558,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 13, limit: None, two_valued: false, residual: false, @@ -4577,8 +3565,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 14, limit: None, two_valued: false, residual: false, @@ -4586,8 +3572,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 15, limit: None, two_valued: false, residual: false, @@ -4595,8 +3579,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 16, limit: None, two_valued: false, residual: false, @@ -4604,8 +3586,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 19, limit: None, two_valued: false, residual: false, @@ -4613,8 +3593,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 20, limit: None, two_valued: false, residual: false, @@ -4622,8 +3600,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 21, limit: None, two_valued: false, residual: false, @@ -4631,8 +3607,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 22, limit: None, two_valued: false, residual: false, @@ -4640,8 +3614,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 23, limit: None, two_valued: false, residual: false, @@ -4649,8 +3621,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 24, limit: None, two_valued: false, residual: false, @@ -4658,8 +3628,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 25, limit: None, two_valued: false, residual: false, @@ -4667,8 +3635,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 26, limit: None, two_valued: false, residual: false, @@ -4676,8 +3642,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 27, limit: None, two_valued: false, residual: false, @@ -4685,8 +3649,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 28, limit: None, two_valued: false, residual: false, @@ -4694,8 +3656,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 29, limit: None, two_valued: false, residual: false, @@ -4703,8 +3663,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 30, limit: None, two_valued: false, residual: false, @@ -4712,8 +3670,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 20, - after: 0, limit: None, two_valued: false, residual: false, @@ -4721,8 +3677,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -4730,8 +3684,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 2, limit: None, two_valued: false, residual: false, @@ -4739,8 +3691,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 3, limit: None, two_valued: false, residual: false, @@ -4748,8 +3698,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 4, limit: None, two_valued: false, residual: false, @@ -4757,8 +3705,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 5, limit: None, two_valued: false, residual: false, @@ -4766,8 +3712,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 6, limit: None, two_valued: false, residual: false, @@ -4775,8 +3719,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 7, limit: None, two_valued: false, residual: false, @@ -4784,8 +3726,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 8, limit: None, two_valued: false, residual: false, @@ -4793,8 +3733,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 9, limit: None, two_valued: false, residual: false, @@ -4802,8 +3740,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 10, limit: None, two_valued: false, residual: false, @@ -4811,8 +3747,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 11, limit: None, two_valued: false, residual: false, @@ -4820,8 +3754,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 12, limit: None, two_valued: false, residual: false, @@ -4829,8 +3761,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 13, limit: None, two_valued: false, residual: false, @@ -4838,8 +3768,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 14, limit: None, two_valued: false, residual: false, @@ -4847,8 +3775,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 15, limit: None, two_valued: false, residual: false, @@ -4856,8 +3782,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 16, limit: None, two_valued: false, residual: false, @@ -4865,8 +3789,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 19, limit: None, two_valued: false, residual: false, @@ -4874,8 +3796,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 20, limit: None, two_valued: false, residual: false, @@ -4883,8 +3803,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 21, limit: None, two_valued: false, residual: false, @@ -4892,8 +3810,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 22, limit: None, two_valued: false, residual: false, @@ -4901,8 +3817,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 23, limit: None, two_valued: false, residual: false, @@ -4910,8 +3824,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 24, limit: None, two_valued: false, residual: false, @@ -4919,8 +3831,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 25, limit: None, two_valued: false, residual: false, @@ -4928,8 +3838,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 26, limit: None, two_valued: false, residual: false, @@ -4937,8 +3845,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 27, limit: None, two_valued: false, residual: false, @@ -4946,8 +3852,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 28, limit: None, two_valued: false, residual: false, @@ -4955,8 +3859,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 29, limit: None, two_valued: false, residual: false, @@ -4964,8 +3866,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 30, limit: None, two_valued: false, residual: false, @@ -4973,8 +3873,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 21, - after: 0, limit: None, two_valued: false, residual: false, @@ -4982,8 +3880,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -4991,8 +3887,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 2, limit: None, two_valued: false, residual: false, @@ -5000,8 +3894,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 3, limit: None, two_valued: false, residual: false, @@ -5009,8 +3901,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 4, limit: None, two_valued: false, residual: false, @@ -5018,8 +3908,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 5, limit: None, two_valued: false, residual: false, @@ -5027,8 +3915,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 6, limit: None, two_valued: false, residual: false, @@ -5036,8 +3922,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 7, limit: None, two_valued: false, residual: false, @@ -5045,8 +3929,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 8, limit: None, two_valued: false, residual: false, @@ -5054,8 +3936,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 9, limit: None, two_valued: false, residual: false, @@ -5063,8 +3943,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 10, limit: None, two_valued: false, residual: false, @@ -5072,8 +3950,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 11, limit: None, two_valued: false, residual: false, @@ -5081,8 +3957,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 12, limit: None, two_valued: false, residual: false, @@ -5090,8 +3964,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 13, limit: None, two_valued: false, residual: false, @@ -5099,8 +3971,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 14, limit: None, two_valued: false, residual: false, @@ -5108,8 +3978,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 15, limit: None, two_valued: false, residual: false, @@ -5117,8 +3985,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 16, limit: None, two_valued: false, residual: false, @@ -5126,8 +3992,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 19, limit: None, two_valued: false, residual: false, @@ -5135,8 +3999,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 20, limit: None, two_valued: false, residual: false, @@ -5144,8 +4006,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 21, limit: None, two_valued: false, residual: false, @@ -5153,8 +4013,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 22, limit: None, two_valued: false, residual: false, @@ -5162,8 +4020,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 23, limit: None, two_valued: false, residual: false, @@ -5171,8 +4027,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 24, limit: None, two_valued: false, residual: false, @@ -5180,8 +4034,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 25, limit: None, two_valued: false, residual: false, @@ -5189,8 +4041,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 26, limit: None, two_valued: false, residual: false, @@ -5198,8 +4048,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 27, limit: None, two_valued: false, residual: false, @@ -5207,8 +4055,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 28, limit: None, two_valued: false, residual: false, @@ -5216,8 +4062,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 29, limit: None, two_valued: false, residual: false, @@ -5225,8 +4069,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 30, limit: None, two_valued: false, residual: false, @@ -5234,8 +4076,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 22, - after: 0, limit: None, two_valued: false, residual: false, @@ -5243,8 +4083,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -5252,8 +4090,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 2, limit: None, two_valued: false, residual: false, @@ -5261,8 +4097,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 3, limit: None, two_valued: false, residual: false, @@ -5270,8 +4104,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 4, limit: None, two_valued: false, residual: false, @@ -5279,8 +4111,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 5, limit: None, two_valued: false, residual: false, @@ -5288,8 +4118,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 6, limit: None, two_valued: false, residual: false, @@ -5297,8 +4125,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 7, limit: None, two_valued: false, residual: false, @@ -5306,8 +4132,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 8, limit: None, two_valued: false, residual: false, @@ -5315,8 +4139,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 9, limit: None, two_valued: false, residual: false, @@ -5324,8 +4146,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 10, limit: None, two_valued: false, residual: false, @@ -5333,8 +4153,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 11, limit: None, two_valued: false, residual: false, @@ -5342,8 +4160,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 12, limit: None, two_valued: false, residual: false, @@ -5351,8 +4167,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 13, limit: None, two_valued: false, residual: false, @@ -5360,8 +4174,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 14, limit: None, two_valued: false, residual: false, @@ -5369,8 +4181,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 15, limit: None, two_valued: false, residual: false, @@ -5378,8 +4188,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 16, limit: None, two_valued: false, residual: false, @@ -5387,8 +4195,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 19, limit: None, two_valued: false, residual: false, @@ -5396,8 +4202,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 20, limit: None, two_valued: false, residual: false, @@ -5405,8 +4209,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 21, limit: None, two_valued: false, residual: false, @@ -5414,8 +4216,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 22, limit: None, two_valued: false, residual: false, @@ -5423,8 +4223,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 23, limit: None, two_valued: false, residual: false, @@ -5432,8 +4230,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 24, limit: None, two_valued: false, residual: false, @@ -5441,8 +4237,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 25, limit: None, two_valued: false, residual: false, @@ -5450,8 +4244,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 26, limit: None, two_valued: false, residual: false, @@ -5459,8 +4251,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 27, limit: None, two_valued: false, residual: false, @@ -5468,8 +4258,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 28, limit: None, two_valued: false, residual: false, @@ -5477,8 +4265,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 29, limit: None, two_valued: false, residual: false, @@ -5486,8 +4272,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 30, limit: None, two_valued: false, residual: false, @@ -5495,8 +4279,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 23, - after: 0, limit: None, two_valued: false, residual: false, @@ -5504,8 +4286,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -5513,8 +4293,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 2, limit: None, two_valued: false, residual: false, @@ -5522,8 +4300,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 3, limit: None, two_valued: false, residual: false, @@ -5531,8 +4307,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 4, limit: None, two_valued: false, residual: false, @@ -5540,8 +4314,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 5, limit: None, two_valued: false, residual: false, @@ -5549,8 +4321,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 6, limit: None, two_valued: false, residual: false, @@ -5558,8 +4328,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 7, limit: None, two_valued: false, residual: false, @@ -5567,8 +4335,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 8, limit: None, two_valued: false, residual: false, @@ -5576,8 +4342,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 9, limit: None, two_valued: false, residual: false, @@ -5585,8 +4349,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 10, limit: None, two_valued: false, residual: false, @@ -5594,8 +4356,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 11, limit: None, two_valued: false, residual: false, @@ -5603,8 +4363,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 12, limit: None, two_valued: false, residual: false, @@ -5612,8 +4370,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 13, limit: None, two_valued: false, residual: false, @@ -5621,8 +4377,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 14, limit: None, two_valued: false, residual: false, @@ -5630,8 +4384,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 15, limit: None, two_valued: false, residual: false, @@ -5639,8 +4391,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 16, limit: None, two_valued: false, residual: false, @@ -5648,8 +4398,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 19, limit: None, two_valued: false, residual: false, @@ -5657,8 +4405,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 20, limit: None, two_valued: false, residual: false, @@ -5666,8 +4412,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 21, limit: None, two_valued: false, residual: false, @@ -5675,8 +4419,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 22, limit: None, two_valued: false, residual: false, @@ -5684,8 +4426,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 23, limit: None, two_valued: false, residual: false, @@ -5693,8 +4433,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 24, limit: None, two_valued: false, residual: false, @@ -5702,8 +4440,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 25, limit: None, two_valued: false, residual: false, @@ -5711,8 +4447,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 26, limit: None, two_valued: false, residual: false, @@ -5720,8 +4454,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 27, limit: None, two_valued: false, residual: false, @@ -5729,8 +4461,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 28, limit: None, two_valued: false, residual: false, @@ -5738,8 +4468,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 29, limit: None, two_valued: false, residual: false, @@ -5747,8 +4475,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 30, limit: None, two_valued: false, residual: false, @@ -5756,8 +4482,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 24, - after: 0, limit: None, two_valued: false, residual: false, @@ -5765,8 +4489,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -5774,8 +4496,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 2, limit: None, two_valued: false, residual: false, @@ -5783,8 +4503,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 3, limit: None, two_valued: false, residual: false, @@ -5792,8 +4510,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 4, limit: None, two_valued: false, residual: false, @@ -5801,8 +4517,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 5, limit: None, two_valued: false, residual: false, @@ -5810,8 +4524,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 6, limit: None, two_valued: false, residual: false, @@ -5819,8 +4531,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 7, limit: None, two_valued: false, residual: false, @@ -5828,8 +4538,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 8, limit: None, two_valued: false, residual: false, @@ -5837,8 +4545,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 9, limit: None, two_valued: false, residual: false, @@ -5846,8 +4552,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 10, limit: None, two_valued: false, residual: false, @@ -5855,8 +4559,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 11, limit: None, two_valued: false, residual: false, @@ -5864,8 +4566,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 12, limit: None, two_valued: false, residual: false, @@ -5873,8 +4573,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 13, limit: None, two_valued: false, residual: false, @@ -5882,8 +4580,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 14, limit: None, two_valued: false, residual: false, @@ -5891,8 +4587,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 15, limit: None, two_valued: false, residual: false, @@ -5900,8 +4594,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 16, limit: None, two_valued: false, residual: false, @@ -5909,8 +4601,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 19, limit: None, two_valued: false, residual: false, @@ -5918,8 +4608,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 20, limit: None, two_valued: false, residual: false, @@ -5927,8 +4615,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 21, limit: None, two_valued: false, residual: false, @@ -5936,8 +4622,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 22, limit: None, two_valued: false, residual: false, @@ -5945,8 +4629,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 23, limit: None, two_valued: false, residual: false, @@ -5954,8 +4636,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 24, limit: None, two_valued: false, residual: false, @@ -5963,8 +4643,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 25, limit: None, two_valued: false, residual: false, @@ -5972,8 +4650,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 26, limit: None, two_valued: false, residual: false, @@ -5981,8 +4657,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 27, limit: None, two_valued: false, residual: false, @@ -5990,8 +4664,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 28, limit: None, two_valued: false, residual: false, @@ -5999,8 +4671,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 29, limit: None, two_valued: false, residual: false, @@ -6008,8 +4678,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 30, limit: None, two_valued: false, residual: false, @@ -6017,8 +4685,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 25, - after: 0, limit: None, two_valued: false, residual: false, @@ -6026,8 +4692,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 1, limit: None, two_valued: false, residual: false, @@ -6035,8 +4699,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 2, limit: None, two_valued: false, residual: false, @@ -6044,8 +4706,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 3, limit: None, two_valued: false, residual: false, @@ -6053,8 +4713,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 4, limit: None, two_valued: false, residual: false, @@ -6062,8 +4720,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 5, limit: None, two_valued: false, residual: false, @@ -6071,8 +4727,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 6, limit: None, two_valued: false, residual: false, @@ -6080,8 +4734,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 7, limit: None, two_valued: false, residual: false, @@ -6089,8 +4741,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 8, limit: None, two_valued: false, residual: false, @@ -6098,8 +4748,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 9, limit: None, two_valued: false, residual: false, @@ -6107,8 +4755,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 10, limit: None, two_valued: false, residual: false, @@ -6116,8 +4762,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 11, limit: None, two_valued: false, residual: false, @@ -6125,8 +4769,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 12, limit: None, two_valued: false, residual: false, @@ -6134,8 +4776,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 13, limit: None, two_valued: false, residual: false, @@ -6143,8 +4783,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 14, limit: None, two_valued: false, residual: false, @@ -6152,8 +4790,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 15, limit: None, two_valued: false, residual: false, @@ -6161,8 +4797,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 16, limit: None, two_valued: false, residual: false, @@ -6170,8 +4804,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 19, limit: None, two_valued: false, residual: false, @@ -6179,8 +4811,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 20, limit: None, two_valued: false, residual: false, @@ -6188,8 +4818,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 21, limit: None, two_valued: false, residual: false, @@ -6197,8 +4825,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 22, limit: None, two_valued: false, residual: false, @@ -6206,8 +4832,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 23, limit: None, two_valued: false, residual: false, @@ -6215,8 +4839,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 24, limit: None, two_valued: false, residual: false, @@ -6224,8 +4846,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 25, limit: None, two_valued: false, residual: false, @@ -6233,8 +4853,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 26, limit: None, two_valued: false, residual: false, @@ -6242,8 +4860,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 27, limit: None, two_valued: false, residual: false, @@ -6251,8 +4867,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 28, limit: None, two_valued: false, residual: false, @@ -6260,8 +4874,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 29, limit: None, two_valued: false, residual: false, @@ -6269,8 +4881,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 26, - after: 30, limit: None, two_valued: false, residual: false, @@ -6278,8 +4888,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 26, - after: 0, limit: None, two_valued: false, residual: false, @@ -6287,8 +4895,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 27, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -6296,8 +4902,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 2, limit: None, two_valued: false, residual: false, @@ -6305,8 +4909,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 3, limit: None, two_valued: false, residual: false, @@ -6314,8 +4916,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 4, limit: None, two_valued: false, residual: false, @@ -6323,8 +4923,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 5, limit: None, two_valued: false, residual: false, @@ -6332,8 +4930,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 6, limit: None, two_valued: false, residual: false, @@ -6341,8 +4937,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 7, limit: None, two_valued: false, residual: false, @@ -6350,8 +4944,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 8, limit: None, two_valued: false, residual: false, @@ -6359,8 +4951,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 9, limit: None, two_valued: false, residual: false, @@ -6368,8 +4958,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 10, limit: None, two_valued: false, residual: false, @@ -6377,8 +4965,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 11, limit: None, two_valued: false, residual: false, @@ -6386,8 +4972,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 12, limit: None, two_valued: false, residual: false, @@ -6395,8 +4979,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 13, limit: None, two_valued: false, residual: false, @@ -6404,8 +4986,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 14, limit: None, two_valued: false, residual: false, @@ -6413,8 +4993,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 15, limit: None, two_valued: false, residual: false, @@ -6422,8 +5000,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 16, limit: None, two_valued: false, residual: false, @@ -6431,8 +5007,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 19, limit: None, two_valued: false, residual: false, @@ -6440,8 +5014,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 20, limit: None, two_valued: false, residual: false, @@ -6449,8 +5021,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 21, limit: None, two_valued: false, residual: false, @@ -6458,8 +5028,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 22, limit: None, two_valued: false, residual: false, @@ -6467,8 +5035,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 23, limit: None, two_valued: false, residual: false, @@ -6476,8 +5042,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 24, limit: None, two_valued: false, residual: false, @@ -6485,8 +5049,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 25, limit: None, two_valued: false, residual: false, @@ -6494,8 +5056,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 26, limit: None, two_valued: false, residual: false, @@ -6503,8 +5063,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 27, limit: None, two_valued: false, residual: false, @@ -6512,8 +5070,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 28, limit: None, two_valued: false, residual: false, @@ -6521,8 +5077,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 29, limit: None, two_valued: false, residual: false, @@ -6530,8 +5084,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 30, limit: None, two_valued: false, residual: false, @@ -6539,8 +5091,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 27, - after: 0, limit: None, two_valued: false, residual: false, @@ -6548,8 +5098,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 1, limit: None, two_valued: false, residual: false, @@ -6557,8 +5105,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 2, limit: None, two_valued: false, residual: false, @@ -6566,8 +5112,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 3, limit: None, two_valued: false, residual: false, @@ -6575,8 +5119,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 4, limit: None, two_valued: false, residual: false, @@ -6584,8 +5126,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 5, limit: None, two_valued: false, residual: false, @@ -6593,8 +5133,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 6, limit: None, two_valued: false, residual: false, @@ -6602,8 +5140,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 7, limit: None, two_valued: false, residual: false, @@ -6611,8 +5147,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 8, limit: None, two_valued: false, residual: false, @@ -6620,8 +5154,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 9, limit: None, two_valued: false, residual: false, @@ -6629,8 +5161,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 10, limit: None, two_valued: false, residual: false, @@ -6638,8 +5168,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 11, limit: None, two_valued: false, residual: false, @@ -6647,8 +5175,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 12, limit: None, two_valued: false, residual: false, @@ -6656,8 +5182,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 13, limit: None, two_valued: false, residual: false, @@ -6665,8 +5189,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 14, limit: None, two_valued: false, residual: false, @@ -6674,8 +5196,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 15, limit: None, two_valued: false, residual: false, @@ -6683,8 +5203,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 16, limit: None, two_valued: false, residual: false, @@ -6692,8 +5210,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 19, limit: None, two_valued: false, residual: false, @@ -6701,8 +5217,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 20, limit: None, two_valued: false, residual: false, @@ -6710,8 +5224,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 21, limit: None, two_valued: false, residual: false, @@ -6719,8 +5231,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 22, limit: None, two_valued: false, residual: false, @@ -6728,8 +5238,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 23, limit: None, two_valued: false, residual: false, @@ -6737,8 +5245,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 24, limit: None, two_valued: false, residual: false, @@ -6746,8 +5252,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 25, limit: None, two_valued: false, residual: false, @@ -6755,8 +5259,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 26, limit: None, two_valued: false, residual: false, @@ -6764,8 +5266,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 28, - after: 27, limit: None, two_valued: false, residual: false, @@ -6773,8 +5273,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 28, limit: None, two_valued: false, residual: false, @@ -6782,8 +5280,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 29, limit: None, two_valued: false, residual: false, @@ -6791,8 +5287,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 30, limit: None, two_valued: false, residual: false, @@ -6800,8 +5294,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 28, - after: 0, limit: None, two_valued: false, residual: false, @@ -6809,8 +5301,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -6818,8 +5308,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 2, limit: None, two_valued: false, residual: false, @@ -6827,8 +5315,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 3, limit: None, two_valued: false, residual: false, @@ -6836,8 +5322,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 4, limit: None, two_valued: false, residual: false, @@ -6845,8 +5329,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 5, limit: None, two_valued: false, residual: false, @@ -6854,8 +5336,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 6, limit: None, two_valued: false, residual: false, @@ -6863,8 +5343,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 7, limit: None, two_valued: false, residual: false, @@ -6872,8 +5350,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 8, limit: None, two_valued: false, residual: false, @@ -6881,8 +5357,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 9, limit: None, two_valued: false, residual: false, @@ -6890,8 +5364,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 10, limit: None, two_valued: false, residual: false, @@ -6899,8 +5371,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 11, limit: None, two_valued: false, residual: false, @@ -6908,8 +5378,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 12, limit: None, two_valued: false, residual: false, @@ -6917,8 +5385,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 13, limit: None, two_valued: false, residual: false, @@ -6926,8 +5392,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 14, limit: None, two_valued: false, residual: false, @@ -6935,8 +5399,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 15, limit: None, two_valued: false, residual: false, @@ -6944,8 +5406,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 16, limit: None, two_valued: false, residual: false, @@ -6953,8 +5413,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 19, limit: None, two_valued: false, residual: false, @@ -6962,8 +5420,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 20, limit: None, two_valued: false, residual: false, @@ -6971,8 +5427,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 21, limit: None, two_valued: false, residual: false, @@ -6980,8 +5434,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 22, limit: None, two_valued: false, residual: false, @@ -6989,8 +5441,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 23, limit: None, two_valued: false, residual: false, @@ -6998,8 +5448,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 24, limit: None, two_valued: false, residual: false, @@ -7007,8 +5455,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 25, limit: None, two_valued: false, residual: false, @@ -7016,8 +5462,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 26, limit: None, two_valued: false, residual: false, @@ -7025,8 +5469,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 27, limit: None, two_valued: false, residual: false, @@ -7034,8 +5476,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 28, limit: None, two_valued: false, residual: false, @@ -7043,8 +5483,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 29, limit: None, two_valued: false, residual: false, @@ -7052,8 +5490,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 30, limit: None, two_valued: false, residual: false, @@ -7061,8 +5497,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 29, - after: 0, limit: None, two_valued: false, residual: false, @@ -7070,8 +5504,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 1, limit: Some(em(180)), two_valued: false, residual: false, @@ -7079,8 +5511,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 2, limit: None, two_valued: false, residual: false, @@ -7088,8 +5518,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 3, limit: None, two_valued: false, residual: false, @@ -7097,8 +5525,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 4, limit: None, two_valued: false, residual: false, @@ -7106,8 +5532,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 5, limit: None, two_valued: false, residual: false, @@ -7115,8 +5539,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 6, limit: None, two_valued: false, residual: false, @@ -7124,8 +5546,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 7, limit: None, two_valued: false, residual: false, @@ -7133,8 +5553,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 8, limit: None, two_valued: false, residual: false, @@ -7142,8 +5560,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 9, limit: None, two_valued: false, residual: false, @@ -7151,8 +5567,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 10, limit: None, two_valued: false, residual: false, @@ -7160,8 +5574,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 11, limit: None, two_valued: false, residual: false, @@ -7169,8 +5581,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 12, limit: None, two_valued: false, residual: false, @@ -7178,8 +5588,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 13, limit: None, two_valued: false, residual: false, @@ -7187,8 +5595,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 14, limit: None, two_valued: false, residual: false, @@ -7196,8 +5602,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 15, limit: None, two_valued: false, residual: false, @@ -7205,8 +5609,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 16, limit: None, two_valued: false, residual: false, @@ -7214,8 +5616,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 19, limit: None, two_valued: false, residual: false, @@ -7223,8 +5623,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 20, limit: None, two_valued: false, residual: false, @@ -7232,8 +5630,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 21, limit: None, two_valued: false, residual: false, @@ -7241,8 +5637,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 22, limit: None, two_valued: false, residual: false, @@ -7250,8 +5644,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 23, limit: None, two_valued: false, residual: false, @@ -7259,8 +5651,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 24, limit: None, two_valued: false, residual: false, @@ -7268,8 +5658,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 25, limit: None, two_valued: false, residual: false, @@ -7277,8 +5665,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 26, limit: None, two_valued: false, residual: false, @@ -7286,8 +5672,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 27, limit: None, two_valued: false, residual: false, @@ -7295,8 +5679,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 28, limit: None, two_valued: false, residual: false, @@ -7304,8 +5686,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 29, limit: None, two_valued: false, residual: false, @@ -7313,8 +5693,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 30, limit: None, two_valued: false, residual: false, @@ -7322,8 +5700,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 30, - after: 0, limit: None, two_valued: false, residual: false, @@ -7331,8 +5707,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 1, limit: None, two_valued: false, residual: false, @@ -7340,8 +5714,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 2, limit: None, two_valued: false, residual: false, @@ -7349,8 +5721,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 3, limit: None, two_valued: false, residual: false, @@ -7358,8 +5728,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 4, limit: None, two_valued: false, residual: false, @@ -7367,8 +5735,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 5, limit: None, two_valued: false, residual: false, @@ -7376,8 +5742,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 6, limit: None, two_valued: false, residual: false, @@ -7385,8 +5749,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 7, limit: None, two_valued: false, residual: false, @@ -7394,8 +5756,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 8, limit: None, two_valued: false, residual: false, @@ -7403,8 +5763,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 9, limit: None, two_valued: false, residual: false, @@ -7412,8 +5770,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 10, limit: None, two_valued: false, residual: false, @@ -7421,8 +5777,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 11, limit: None, two_valued: false, residual: false, @@ -7430,8 +5784,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 12, limit: None, two_valued: false, residual: false, @@ -7439,8 +5791,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 13, limit: None, two_valued: false, residual: false, @@ -7448,8 +5798,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 14, limit: None, two_valued: false, residual: false, @@ -7457,8 +5805,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 15, limit: None, two_valued: false, residual: false, @@ -7466,8 +5812,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 16, limit: None, two_valued: false, residual: false, @@ -7475,8 +5819,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 19, limit: None, two_valued: false, residual: false, @@ -7484,8 +5826,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 20, limit: None, two_valued: false, residual: false, @@ -7493,8 +5833,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 21, limit: None, two_valued: false, residual: false, @@ -7502,8 +5840,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 22, limit: None, two_valued: false, residual: false, @@ -7511,8 +5847,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 23, limit: None, two_valued: false, residual: false, @@ -7520,8 +5854,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 24, limit: None, two_valued: false, residual: false, @@ -7529,8 +5861,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 25, limit: None, two_valued: false, residual: false, @@ -7538,8 +5868,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 26, limit: None, two_valued: false, residual: false, @@ -7547,8 +5875,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.2#4", }, RawRangedCell { - before: 0, - after: 27, limit: None, two_valued: false, residual: false, @@ -7556,8 +5882,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 28, limit: None, two_valued: false, residual: false, @@ -7565,8 +5889,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 29, limit: None, two_valued: false, residual: false, @@ -7574,8 +5896,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 30, limit: None, two_valued: false, residual: false, @@ -7583,8 +5903,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, RawRangedCell { - before: 0, - after: 0, limit: None, two_valued: false, residual: false, @@ -7592,3 +5910,26 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "D.1", }, ]; + +const ROW_INDEX: [u8; 31] = [ + 28, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, +]; +const COLUMN_INDEX: [u8; 31] = [ + 28, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, +]; +const COLUMN_COUNT: usize = 29; + +/// Look up one cell directly from its class or line-edge coordinates. +pub(crate) fn cell(before: u8, after: u8) -> Option<&'static RawRangedCell> { + let row = *ROW_INDEX.get(usize::from(before))?; + let column = *COLUMN_INDEX.get(usize::from(after))?; + if row == u8::MAX || column == u8::MAX { + return None; + } + let index = usize::from(row) + .checked_mul(COLUMN_COUNT)? + .checked_add(usize::from(column))?; + CELLS.get(index) +} diff --git a/crates/jlreq-core/src/generated/table6.rs b/crates/jlreq-core/src/generated/table6.rs index d2f134c..2db7179 100644 --- a/crates/jlreq-core/src/generated/table6.rs +++ b/crates/jlreq-core/src/generated/table6.rs @@ -13,7 +13,7 @@ //! - Source SHA-256: `e63166631b11e80c00adff7e1bf3126be347e95133e74112310307fff1f27ac9` //! - Specification: JLReq, 2020-08-11 //! - Generator: `xtask/src/generate.rs`, `xtask/src/spacing.rs` -//! - Generator SHA-256: `aff4dd3baf294cabb5cf358f4f29005075985279af1df3f0cb5287cf85bdd6cb` +//! - Generator SHA-256: `02c2e2c3acf5e6532ae3311484d2a3913a8224c7f50075782607830573080ddc` //! - Entries: 784 use crate::spec::{RawRangedCell, em}; @@ -23,8 +23,6 @@ use crate::spec::{RawRangedCell, em}; /// JLReq: §E.1 pub(crate) static CELLS: &[RawRangedCell] = &[ RawRangedCell { - before: 1, - after: 1, limit: None, two_valued: false, residual: false, @@ -32,8 +30,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 2, limit: None, two_valued: false, residual: false, @@ -41,8 +37,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 3, limit: None, two_valued: false, residual: false, @@ -50,8 +44,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 4, limit: None, two_valued: false, residual: false, @@ -59,8 +51,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 5, limit: None, two_valued: false, residual: false, @@ -68,8 +58,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 6, limit: None, two_valued: false, residual: false, @@ -77,8 +65,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 7, limit: None, two_valued: false, residual: false, @@ -86,8 +72,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 8, limit: None, two_valued: false, residual: false, @@ -95,8 +79,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 9, limit: None, two_valued: false, residual: false, @@ -104,8 +86,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 10, limit: None, two_valued: false, residual: false, @@ -113,8 +93,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 11, limit: None, two_valued: false, residual: false, @@ -122,8 +100,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 12, limit: None, two_valued: false, residual: false, @@ -131,8 +107,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 13, limit: None, two_valued: false, residual: false, @@ -140,8 +114,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 14, limit: None, two_valued: false, residual: false, @@ -149,8 +121,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 15, limit: None, two_valued: false, residual: false, @@ -158,8 +128,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 16, limit: None, two_valued: false, residual: false, @@ -167,8 +135,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 19, limit: None, two_valued: false, residual: false, @@ -176,8 +142,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 20, limit: None, two_valued: false, residual: false, @@ -185,8 +149,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 21, limit: None, two_valued: false, residual: false, @@ -194,8 +156,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 22, limit: None, two_valued: false, residual: false, @@ -203,8 +163,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 23, limit: None, two_valued: false, residual: false, @@ -212,8 +170,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 24, limit: None, two_valued: false, residual: false, @@ -221,8 +177,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 25, limit: None, two_valued: false, residual: false, @@ -230,8 +184,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 26, limit: None, two_valued: false, residual: false, @@ -239,8 +191,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 27, limit: None, two_valued: false, residual: false, @@ -248,8 +198,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 28, limit: None, two_valued: false, residual: false, @@ -257,8 +205,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 29, limit: None, two_valued: false, residual: false, @@ -266,8 +212,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 1, - after: 30, limit: None, two_valued: false, residual: false, @@ -275,8 +219,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 1, limit: None, two_valued: false, residual: true, @@ -284,8 +226,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 2, limit: None, two_valued: false, residual: false, @@ -293,8 +233,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 3, limit: None, two_valued: false, residual: false, @@ -302,8 +240,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 4, limit: None, two_valued: false, residual: false, @@ -311,8 +247,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 5, limit: None, two_valued: false, residual: false, @@ -320,8 +254,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 6, limit: None, two_valued: false, residual: false, @@ -329,8 +261,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 7, limit: None, two_valued: false, residual: false, @@ -338,8 +268,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 8, limit: None, two_valued: false, residual: true, @@ -347,8 +275,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 9, limit: None, two_valued: false, residual: false, @@ -356,8 +282,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 10, limit: None, two_valued: false, residual: false, @@ -365,8 +289,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 11, limit: None, two_valued: false, residual: false, @@ -374,8 +296,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 12, limit: None, two_valued: false, residual: true, @@ -383,8 +303,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 13, limit: None, two_valued: false, residual: true, @@ -392,8 +310,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 14, limit: None, two_valued: false, residual: true, @@ -401,8 +317,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 15, limit: None, two_valued: false, residual: true, @@ -410,8 +324,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 16, limit: None, two_valued: false, residual: true, @@ -419,8 +331,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 19, limit: None, two_valued: false, residual: true, @@ -428,8 +338,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 20, limit: None, two_valued: false, residual: false, @@ -437,8 +345,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 21, limit: None, two_valued: false, residual: true, @@ -446,8 +352,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 22, limit: None, two_valued: false, residual: true, @@ -455,8 +359,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 23, limit: None, two_valued: false, residual: true, @@ -464,8 +366,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 24, limit: None, two_valued: false, residual: true, @@ -473,8 +373,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 25, limit: None, two_valued: false, residual: true, @@ -482,8 +380,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 26, limit: None, two_valued: false, residual: true, @@ -491,8 +387,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 27, limit: None, two_valued: false, residual: true, @@ -500,8 +394,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 28, limit: None, two_valued: false, residual: true, @@ -509,8 +401,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 29, limit: None, two_valued: false, residual: false, @@ -518,8 +408,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 2, - after: 30, limit: None, two_valued: false, residual: true, @@ -527,8 +415,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 1, limit: None, two_valued: false, residual: true, @@ -536,8 +422,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 2, limit: None, two_valued: false, residual: false, @@ -545,8 +429,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 3, limit: None, two_valued: false, residual: false, @@ -554,8 +436,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 4, limit: None, two_valued: false, residual: false, @@ -563,8 +443,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 5, limit: None, two_valued: false, residual: false, @@ -572,8 +450,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 6, limit: None, two_valued: false, residual: false, @@ -581,8 +457,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 7, limit: None, two_valued: false, residual: false, @@ -590,8 +464,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 8, limit: None, two_valued: false, residual: true, @@ -599,8 +471,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 9, limit: None, two_valued: false, residual: false, @@ -608,8 +478,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 10, limit: None, two_valued: false, residual: false, @@ -617,8 +485,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 11, limit: None, two_valued: false, residual: false, @@ -626,8 +492,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 12, limit: None, two_valued: false, residual: true, @@ -635,8 +499,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 13, limit: None, two_valued: false, residual: true, @@ -644,8 +506,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 14, limit: None, two_valued: false, residual: true, @@ -653,8 +513,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 15, limit: None, two_valued: false, residual: true, @@ -662,8 +520,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 16, limit: None, two_valued: false, residual: true, @@ -671,8 +527,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 19, limit: None, two_valued: false, residual: true, @@ -680,8 +534,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 20, limit: None, two_valued: false, residual: false, @@ -689,8 +541,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 21, limit: None, two_valued: false, residual: true, @@ -698,8 +548,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 22, limit: None, two_valued: false, residual: true, @@ -707,8 +555,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 23, limit: None, two_valued: false, residual: true, @@ -716,8 +562,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 24, limit: None, two_valued: false, residual: true, @@ -725,8 +569,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 25, limit: None, two_valued: false, residual: true, @@ -734,8 +576,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 26, limit: None, two_valued: false, residual: true, @@ -743,8 +583,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 27, limit: None, two_valued: false, residual: true, @@ -752,8 +590,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 28, limit: None, two_valued: false, residual: true, @@ -761,8 +597,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 29, limit: None, two_valued: false, residual: false, @@ -770,8 +604,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 3, - after: 30, limit: None, two_valued: false, residual: true, @@ -779,8 +611,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 1, limit: None, two_valued: false, residual: true, @@ -788,8 +618,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 2, limit: None, two_valued: false, residual: false, @@ -797,8 +625,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 3, limit: None, two_valued: false, residual: false, @@ -806,8 +632,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 4, limit: None, two_valued: false, residual: false, @@ -815,8 +639,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 5, limit: None, two_valued: false, residual: false, @@ -824,8 +646,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 6, limit: None, two_valued: false, residual: false, @@ -833,8 +653,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 7, limit: None, two_valued: false, residual: false, @@ -842,8 +660,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 8, limit: None, two_valued: false, residual: true, @@ -851,8 +667,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 9, limit: None, two_valued: false, residual: false, @@ -860,8 +674,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 10, limit: None, two_valued: false, residual: false, @@ -869,8 +681,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 11, limit: None, two_valued: false, residual: false, @@ -878,8 +688,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 12, limit: None, two_valued: false, residual: true, @@ -887,8 +695,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 13, limit: None, two_valued: false, residual: true, @@ -896,8 +702,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 14, limit: None, two_valued: false, residual: true, @@ -905,8 +709,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 15, limit: None, two_valued: false, residual: true, @@ -914,8 +716,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 16, limit: None, two_valued: false, residual: true, @@ -923,8 +723,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 19, limit: None, two_valued: false, residual: true, @@ -932,8 +730,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 20, limit: None, two_valued: false, residual: false, @@ -941,8 +737,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -950,8 +744,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 22, limit: None, two_valued: false, residual: true, @@ -959,8 +751,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 23, limit: None, two_valued: false, residual: true, @@ -968,8 +758,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -977,8 +765,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -986,8 +772,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 26, limit: None, two_valued: false, residual: true, @@ -995,8 +779,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -1004,8 +786,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 28, limit: None, two_valued: false, residual: true, @@ -1013,8 +793,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 29, limit: None, two_valued: false, residual: false, @@ -1022,8 +800,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 4, - after: 30, limit: None, two_valued: false, residual: true, @@ -1031,8 +807,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 1, limit: None, two_valued: false, residual: true, @@ -1040,8 +814,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 2, limit: None, two_valued: false, residual: false, @@ -1049,8 +821,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 3, limit: None, two_valued: false, residual: false, @@ -1058,8 +828,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 4, limit: None, two_valued: false, residual: false, @@ -1067,8 +835,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 5, limit: None, two_valued: false, residual: false, @@ -1076,8 +842,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 6, limit: None, two_valued: false, residual: false, @@ -1085,8 +849,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 7, limit: None, two_valued: false, residual: false, @@ -1094,8 +856,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 8, limit: None, two_valued: false, residual: true, @@ -1103,8 +863,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 9, limit: None, two_valued: false, residual: false, @@ -1112,8 +870,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 10, limit: None, two_valued: false, residual: false, @@ -1121,8 +877,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 11, limit: None, two_valued: false, residual: false, @@ -1130,8 +884,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 12, limit: None, two_valued: false, residual: true, @@ -1139,8 +891,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 13, limit: None, two_valued: false, residual: true, @@ -1148,8 +898,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 14, limit: None, two_valued: false, residual: true, @@ -1157,8 +905,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 15, limit: None, two_valued: false, residual: true, @@ -1166,8 +912,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 16, limit: None, two_valued: false, residual: true, @@ -1175,8 +919,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 19, limit: None, two_valued: false, residual: true, @@ -1184,8 +926,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 20, limit: None, two_valued: false, residual: false, @@ -1193,8 +933,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 21, limit: None, two_valued: false, residual: true, @@ -1202,8 +940,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 22, limit: None, two_valued: false, residual: true, @@ -1211,8 +947,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 23, limit: None, two_valued: false, residual: true, @@ -1220,8 +954,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 24, limit: None, two_valued: false, residual: true, @@ -1229,8 +961,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 25, limit: None, two_valued: false, residual: true, @@ -1238,8 +968,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 26, limit: None, two_valued: false, residual: true, @@ -1247,8 +975,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 27, limit: None, two_valued: false, residual: true, @@ -1256,8 +982,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 28, limit: None, two_valued: false, residual: true, @@ -1265,8 +989,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 29, limit: None, two_valued: false, residual: false, @@ -1274,8 +996,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 5, - after: 30, limit: None, two_valued: false, residual: true, @@ -1283,8 +1003,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 1, limit: None, two_valued: false, residual: true, @@ -1292,8 +1010,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 2, limit: None, two_valued: false, residual: false, @@ -1301,8 +1017,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 3, limit: None, two_valued: false, residual: false, @@ -1310,8 +1024,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 4, limit: None, two_valued: false, residual: false, @@ -1319,8 +1031,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 5, limit: None, two_valued: false, residual: false, @@ -1328,8 +1038,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 6, limit: None, two_valued: false, residual: false, @@ -1337,8 +1045,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 7, limit: None, two_valued: false, residual: false, @@ -1346,8 +1052,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 8, limit: None, two_valued: false, residual: true, @@ -1355,8 +1059,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 9, limit: None, two_valued: false, residual: false, @@ -1364,8 +1066,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 10, limit: None, two_valued: false, residual: false, @@ -1373,8 +1073,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 11, limit: None, two_valued: false, residual: false, @@ -1382,8 +1080,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 12, limit: None, two_valued: false, residual: true, @@ -1391,8 +1087,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 13, limit: None, two_valued: false, residual: true, @@ -1400,8 +1094,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 14, limit: None, two_valued: false, residual: true, @@ -1409,8 +1101,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 15, limit: None, two_valued: false, residual: true, @@ -1418,8 +1108,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 16, limit: None, two_valued: false, residual: true, @@ -1427,8 +1115,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 19, limit: None, two_valued: false, residual: true, @@ -1436,8 +1122,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 20, limit: None, two_valued: false, residual: false, @@ -1445,8 +1129,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 21, limit: None, two_valued: false, residual: true, @@ -1454,8 +1136,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 22, limit: None, two_valued: false, residual: true, @@ -1463,8 +1143,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 23, limit: None, two_valued: false, residual: true, @@ -1472,8 +1150,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 24, limit: None, two_valued: false, residual: true, @@ -1481,8 +1157,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 25, limit: None, two_valued: false, residual: true, @@ -1490,8 +1164,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 26, limit: None, two_valued: false, residual: true, @@ -1499,8 +1171,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 27, limit: None, two_valued: false, residual: true, @@ -1508,8 +1178,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 28, limit: None, two_valued: false, residual: true, @@ -1517,8 +1185,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 29, limit: None, two_valued: false, residual: false, @@ -1526,8 +1192,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 6, - after: 30, limit: None, two_valued: false, residual: true, @@ -1535,8 +1199,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 1, limit: None, two_valued: false, residual: true, @@ -1544,8 +1206,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 2, limit: None, two_valued: false, residual: false, @@ -1553,8 +1213,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 3, limit: None, two_valued: false, residual: false, @@ -1562,8 +1220,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 4, limit: None, two_valued: false, residual: false, @@ -1571,8 +1227,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 5, limit: None, two_valued: false, residual: false, @@ -1580,8 +1234,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 6, limit: None, two_valued: false, residual: false, @@ -1589,8 +1241,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 7, limit: None, two_valued: false, residual: false, @@ -1598,8 +1248,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 8, limit: None, two_valued: false, residual: true, @@ -1607,8 +1255,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 9, limit: None, two_valued: false, residual: false, @@ -1616,8 +1262,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 10, limit: None, two_valued: false, residual: false, @@ -1625,8 +1269,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 11, limit: None, two_valued: false, residual: false, @@ -1634,8 +1276,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 12, limit: None, two_valued: false, residual: true, @@ -1643,8 +1283,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 13, limit: None, two_valued: false, residual: true, @@ -1652,8 +1290,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 14, limit: None, two_valued: false, residual: true, @@ -1661,8 +1297,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 15, limit: None, two_valued: false, residual: true, @@ -1670,8 +1304,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 16, limit: None, two_valued: false, residual: true, @@ -1679,8 +1311,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 19, limit: None, two_valued: false, residual: true, @@ -1688,8 +1318,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 20, limit: None, two_valued: false, residual: false, @@ -1697,8 +1325,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 21, limit: None, two_valued: false, residual: true, @@ -1706,8 +1332,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 22, limit: None, two_valued: false, residual: true, @@ -1715,8 +1339,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 23, limit: None, two_valued: false, residual: true, @@ -1724,8 +1346,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 24, limit: None, two_valued: false, residual: true, @@ -1733,8 +1353,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 25, limit: None, two_valued: false, residual: true, @@ -1742,8 +1360,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 26, limit: None, two_valued: false, residual: true, @@ -1751,8 +1367,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 27, limit: None, two_valued: false, residual: true, @@ -1760,8 +1374,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 28, limit: None, two_valued: false, residual: true, @@ -1769,8 +1381,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 29, limit: None, two_valued: false, residual: false, @@ -1778,8 +1388,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 7, - after: 30, limit: None, two_valued: false, residual: true, @@ -1787,8 +1395,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 1, limit: None, two_valued: false, residual: true, @@ -1796,8 +1402,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 2, limit: None, two_valued: false, residual: false, @@ -1805,8 +1409,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 3, limit: None, two_valued: false, residual: false, @@ -1814,8 +1416,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 4, limit: None, two_valued: false, residual: false, @@ -1823,8 +1423,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 5, limit: None, two_valued: false, residual: false, @@ -1832,8 +1430,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 6, limit: None, two_valued: false, residual: false, @@ -1841,8 +1437,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 7, limit: None, two_valued: false, residual: false, @@ -1850,8 +1444,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -1859,8 +1451,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E.2#4", }, RawRangedCell { - before: 8, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -1868,8 +1458,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -1877,8 +1465,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -1886,8 +1472,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -1895,8 +1479,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -1904,8 +1486,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 14, limit: None, two_valued: false, residual: true, @@ -1913,8 +1493,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -1922,8 +1500,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -1931,8 +1507,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -1940,8 +1514,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 20, limit: None, two_valued: false, residual: false, @@ -1949,8 +1521,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 21, limit: Some(em(180)), two_valued: false, residual: false, @@ -1958,8 +1528,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -1967,8 +1535,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -1976,8 +1542,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 24, limit: None, two_valued: false, residual: true, @@ -1985,8 +1549,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 25, limit: None, two_valued: false, residual: true, @@ -1994,8 +1556,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 26, limit: None, two_valued: false, residual: true, @@ -2003,8 +1563,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 27, limit: Some(em(180)), two_valued: false, residual: false, @@ -2012,8 +1570,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -2021,8 +1577,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 29, limit: None, two_valued: false, residual: false, @@ -2030,8 +1584,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 8, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -2039,8 +1591,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 1, limit: None, two_valued: false, residual: true, @@ -2048,8 +1598,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 2, limit: None, two_valued: false, residual: false, @@ -2057,8 +1605,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 3, limit: None, two_valued: false, residual: false, @@ -2066,8 +1612,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 4, limit: None, two_valued: false, residual: false, @@ -2075,8 +1619,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 5, limit: None, two_valued: false, residual: false, @@ -2084,8 +1626,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 6, limit: None, two_valued: false, residual: false, @@ -2093,8 +1633,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 7, limit: None, two_valued: false, residual: false, @@ -2102,8 +1640,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 8, limit: None, two_valued: false, residual: true, @@ -2111,8 +1647,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -2120,8 +1654,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -2129,8 +1661,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -2138,8 +1668,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -2147,8 +1675,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -2156,8 +1682,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 14, limit: None, two_valued: false, residual: true, @@ -2165,8 +1689,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -2174,8 +1696,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -2183,8 +1703,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -2192,8 +1710,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 20, limit: None, two_valued: false, residual: false, @@ -2201,8 +1717,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -2210,8 +1724,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -2219,8 +1731,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -2228,8 +1738,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -2237,8 +1745,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -2246,8 +1752,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 26, limit: None, two_valued: false, residual: true, @@ -2255,8 +1759,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -2264,8 +1766,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -2273,8 +1773,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 29, limit: None, two_valued: false, residual: false, @@ -2282,8 +1780,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 9, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -2291,8 +1787,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 1, limit: None, two_valued: false, residual: true, @@ -2300,8 +1794,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 2, limit: None, two_valued: false, residual: false, @@ -2309,8 +1801,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 3, limit: None, two_valued: false, residual: false, @@ -2318,8 +1808,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 4, limit: None, two_valued: false, residual: false, @@ -2327,8 +1815,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 5, limit: None, two_valued: false, residual: false, @@ -2336,8 +1822,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 6, limit: None, two_valued: false, residual: false, @@ -2345,8 +1829,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 7, limit: None, two_valued: false, residual: false, @@ -2354,8 +1836,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 8, limit: None, two_valued: false, residual: true, @@ -2363,8 +1843,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -2372,8 +1850,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -2381,8 +1857,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -2390,8 +1864,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -2399,8 +1871,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -2408,8 +1878,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 14, limit: None, two_valued: false, residual: true, @@ -2417,8 +1885,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -2426,8 +1892,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -2435,8 +1899,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -2444,8 +1906,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 20, limit: None, two_valued: false, residual: false, @@ -2453,8 +1913,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -2462,8 +1920,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -2471,8 +1927,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -2480,8 +1934,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -2489,8 +1941,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -2498,8 +1948,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 26, limit: None, two_valued: false, residual: true, @@ -2507,8 +1955,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -2516,8 +1962,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -2525,8 +1969,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 29, limit: None, two_valued: false, residual: false, @@ -2534,8 +1976,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 10, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -2543,8 +1983,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 1, limit: None, two_valued: false, residual: true, @@ -2552,8 +1990,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 2, limit: None, two_valued: false, residual: false, @@ -2561,8 +1997,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 3, limit: None, two_valued: false, residual: false, @@ -2570,8 +2004,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 4, limit: None, two_valued: false, residual: false, @@ -2579,8 +2011,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 5, limit: None, two_valued: false, residual: false, @@ -2588,8 +2018,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 6, limit: None, two_valued: false, residual: false, @@ -2597,8 +2025,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 7, limit: None, two_valued: false, residual: false, @@ -2606,8 +2032,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 8, limit: None, two_valued: false, residual: true, @@ -2615,8 +2039,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -2624,8 +2046,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -2633,8 +2053,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -2642,8 +2060,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -2651,8 +2067,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -2660,8 +2074,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 14, limit: None, two_valued: false, residual: true, @@ -2669,8 +2081,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -2678,8 +2088,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -2687,8 +2095,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -2696,8 +2102,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 20, limit: None, two_valued: false, residual: false, @@ -2705,8 +2109,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -2714,8 +2116,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -2723,8 +2123,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -2732,8 +2130,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -2741,8 +2137,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -2750,8 +2144,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 26, limit: None, two_valued: false, residual: true, @@ -2759,8 +2151,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -2768,8 +2158,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -2777,8 +2165,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 29, limit: None, two_valued: false, residual: false, @@ -2786,8 +2172,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 11, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -2795,8 +2179,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 1, limit: None, two_valued: false, residual: false, @@ -2804,8 +2186,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 2, limit: None, two_valued: false, residual: false, @@ -2813,8 +2193,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 3, limit: None, two_valued: false, residual: false, @@ -2822,8 +2200,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 4, limit: None, two_valued: false, residual: false, @@ -2831,8 +2207,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 5, limit: None, two_valued: false, residual: false, @@ -2840,8 +2214,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 6, limit: None, two_valued: false, residual: false, @@ -2849,8 +2221,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 7, limit: None, two_valued: false, residual: false, @@ -2858,8 +2228,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -2867,8 +2235,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -2876,8 +2242,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -2885,8 +2249,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -2894,8 +2256,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -2903,8 +2263,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -2912,8 +2270,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 14, limit: None, two_valued: false, residual: false, @@ -2921,8 +2277,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -2930,8 +2284,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -2939,8 +2291,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -2948,8 +2298,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 20, limit: None, two_valued: false, residual: false, @@ -2957,8 +2305,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 21, limit: Some(em(180)), two_valued: false, residual: false, @@ -2966,8 +2312,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -2975,8 +2319,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -2984,8 +2326,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 24, limit: None, two_valued: false, residual: false, @@ -2993,8 +2333,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 25, limit: Some(em(180)), two_valued: false, residual: false, @@ -3002,8 +2340,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 26, limit: None, two_valued: false, residual: true, @@ -3011,8 +2347,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 27, limit: Some(em(180)), two_valued: false, residual: false, @@ -3020,8 +2354,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -3029,8 +2361,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 29, limit: None, two_valued: false, residual: false, @@ -3038,8 +2368,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 12, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -3047,8 +2375,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 1, limit: None, two_valued: false, residual: true, @@ -3056,8 +2382,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 2, limit: None, two_valued: false, residual: false, @@ -3065,8 +2389,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 3, limit: None, two_valued: false, residual: false, @@ -3074,8 +2396,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 4, limit: None, two_valued: false, residual: false, @@ -3083,8 +2403,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 5, limit: None, two_valued: false, residual: false, @@ -3092,8 +2410,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 6, limit: None, two_valued: false, residual: false, @@ -3101,8 +2417,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 7, limit: None, two_valued: false, residual: false, @@ -3110,8 +2424,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -3119,8 +2431,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -3128,8 +2438,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -3137,8 +2445,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -3146,8 +2452,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -3155,8 +2459,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -3164,8 +2466,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 14, limit: None, two_valued: false, residual: true, @@ -3173,8 +2473,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -3182,8 +2480,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -3191,8 +2487,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -3200,8 +2494,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 20, limit: None, two_valued: false, residual: false, @@ -3209,8 +2501,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 21, limit: Some(em(180)), two_valued: false, residual: false, @@ -3218,8 +2508,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -3227,8 +2515,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -3236,8 +2522,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 24, limit: Some(em(180)), two_valued: false, residual: false, @@ -3245,8 +2529,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 25, limit: Some(em(180)), two_valued: false, residual: false, @@ -3254,8 +2536,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 26, limit: None, two_valued: false, residual: true, @@ -3263,8 +2543,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 27, limit: Some(em(180)), two_valued: false, residual: false, @@ -3272,8 +2550,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -3281,8 +2557,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 29, limit: None, two_valued: false, residual: false, @@ -3290,8 +2564,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 13, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -3299,8 +2571,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 1, limit: None, two_valued: false, residual: true, @@ -3308,8 +2578,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 2, limit: None, two_valued: false, residual: false, @@ -3317,8 +2585,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 3, limit: None, two_valued: false, residual: false, @@ -3326,8 +2592,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 4, limit: None, two_valued: false, residual: false, @@ -3335,8 +2599,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 5, limit: None, two_valued: false, residual: false, @@ -3344,8 +2606,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 6, limit: None, two_valued: false, residual: false, @@ -3353,8 +2613,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 7, limit: None, two_valued: false, residual: false, @@ -3362,8 +2620,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 8, limit: None, two_valued: false, residual: true, @@ -3371,8 +2627,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 9, limit: None, two_valued: false, residual: false, @@ -3380,8 +2634,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 10, limit: None, two_valued: false, residual: false, @@ -3389,8 +2641,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 11, limit: None, two_valued: false, residual: false, @@ -3398,8 +2648,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 12, limit: None, two_valued: false, residual: true, @@ -3407,8 +2655,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 13, limit: None, two_valued: false, residual: true, @@ -3416,8 +2662,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 14, limit: None, two_valued: false, residual: true, @@ -3425,8 +2669,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 15, limit: None, two_valued: false, residual: true, @@ -3434,8 +2676,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 16, limit: None, two_valued: false, residual: true, @@ -3443,8 +2683,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 19, limit: None, two_valued: false, residual: true, @@ -3452,8 +2690,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 20, limit: None, two_valued: false, residual: false, @@ -3461,8 +2697,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 21, limit: None, two_valued: false, residual: true, @@ -3470,8 +2704,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 22, limit: None, two_valued: false, residual: true, @@ -3479,8 +2711,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 23, limit: None, two_valued: false, residual: true, @@ -3488,8 +2718,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 24, limit: None, two_valued: false, residual: true, @@ -3497,8 +2725,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 25, limit: None, two_valued: false, residual: true, @@ -3506,8 +2732,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 26, limit: None, two_valued: false, residual: true, @@ -3515,8 +2739,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 27, limit: None, two_valued: false, residual: true, @@ -3524,8 +2746,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 28, limit: None, two_valued: false, residual: true, @@ -3533,8 +2753,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 29, limit: None, two_valued: false, residual: false, @@ -3542,8 +2760,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 14, - after: 30, limit: None, two_valued: false, residual: true, @@ -3551,8 +2767,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 1, limit: None, two_valued: false, residual: true, @@ -3560,8 +2774,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 2, limit: None, two_valued: false, residual: false, @@ -3569,8 +2781,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 3, limit: None, two_valued: false, residual: false, @@ -3578,8 +2788,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 4, limit: None, two_valued: false, residual: false, @@ -3587,8 +2795,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 5, limit: None, two_valued: false, residual: false, @@ -3596,8 +2802,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 6, limit: None, two_valued: false, residual: false, @@ -3605,8 +2809,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 7, limit: None, two_valued: false, residual: false, @@ -3614,8 +2816,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -3623,8 +2823,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -3632,8 +2830,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -3641,8 +2837,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -3650,8 +2844,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -3659,8 +2851,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -3668,8 +2858,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 14, limit: None, two_valued: false, residual: true, @@ -3677,8 +2865,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -3686,8 +2872,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -3695,8 +2879,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -3704,8 +2886,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 20, limit: None, two_valued: false, residual: false, @@ -3713,8 +2893,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -3722,8 +2900,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -3731,8 +2907,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -3740,8 +2914,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -3749,8 +2921,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -3758,8 +2928,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 26, limit: None, two_valued: false, residual: true, @@ -3767,8 +2935,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -3776,8 +2942,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -3785,8 +2949,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 29, limit: None, two_valued: false, residual: false, @@ -3794,8 +2956,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 15, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -3803,8 +2963,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 1, limit: None, two_valued: false, residual: true, @@ -3812,8 +2970,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 2, limit: None, two_valued: false, residual: false, @@ -3821,8 +2977,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 3, limit: None, two_valued: false, residual: false, @@ -3830,8 +2984,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 4, limit: None, two_valued: false, residual: false, @@ -3839,8 +2991,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 5, limit: None, two_valued: false, residual: false, @@ -3848,8 +2998,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 6, limit: None, two_valued: false, residual: false, @@ -3857,8 +3005,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 7, limit: None, two_valued: false, residual: false, @@ -3866,8 +3012,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -3875,8 +3019,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -3884,8 +3026,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -3893,8 +3033,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -3902,8 +3040,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -3911,8 +3047,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -3920,8 +3054,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 14, limit: None, two_valued: false, residual: true, @@ -3929,8 +3061,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -3938,8 +3068,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -3947,8 +3075,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -3956,8 +3082,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 20, limit: None, two_valued: false, residual: false, @@ -3965,8 +3089,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -3974,8 +3096,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -3983,8 +3103,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -3992,8 +3110,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -4001,8 +3117,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -4010,8 +3124,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 26, limit: None, two_valued: false, residual: true, @@ -4019,8 +3131,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -4028,8 +3138,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -4037,8 +3145,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 29, limit: None, two_valued: false, residual: false, @@ -4046,8 +3152,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 16, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -4055,8 +3159,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 1, limit: None, two_valued: false, residual: true, @@ -4064,8 +3166,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 2, limit: None, two_valued: false, residual: false, @@ -4073,8 +3173,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 3, limit: None, two_valued: false, residual: false, @@ -4082,8 +3180,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 4, limit: None, two_valued: false, residual: false, @@ -4091,8 +3187,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 5, limit: None, two_valued: false, residual: false, @@ -4100,8 +3194,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 6, limit: None, two_valued: false, residual: false, @@ -4109,8 +3201,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 7, limit: None, two_valued: false, residual: false, @@ -4118,8 +3208,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -4127,8 +3215,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -4136,8 +3222,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -4145,8 +3229,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -4154,8 +3236,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -4163,8 +3243,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -4172,8 +3250,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 14, limit: None, two_valued: false, residual: true, @@ -4181,8 +3257,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -4190,8 +3264,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -4199,8 +3271,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -4208,8 +3278,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 20, limit: None, two_valued: false, residual: false, @@ -4217,8 +3285,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -4226,8 +3292,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -4235,8 +3299,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -4244,8 +3306,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -4253,8 +3313,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -4262,8 +3320,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 26, limit: None, two_valued: false, residual: true, @@ -4271,8 +3327,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -4280,8 +3334,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -4289,8 +3341,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 29, limit: None, two_valued: false, residual: false, @@ -4298,8 +3348,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 19, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -4307,8 +3355,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 1, limit: None, two_valued: false, residual: true, @@ -4316,8 +3362,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 2, limit: None, two_valued: false, residual: false, @@ -4325,8 +3369,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 3, limit: None, two_valued: false, residual: false, @@ -4334,8 +3376,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 4, limit: None, two_valued: false, residual: false, @@ -4343,8 +3383,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 5, limit: None, two_valued: false, residual: false, @@ -4352,8 +3390,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 6, limit: None, two_valued: false, residual: false, @@ -4361,8 +3397,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 7, limit: None, two_valued: false, residual: false, @@ -4370,8 +3404,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -4379,8 +3411,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -4388,8 +3418,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -4397,8 +3425,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -4406,8 +3432,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -4415,8 +3439,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -4424,8 +3446,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 14, limit: None, two_valued: false, residual: true, @@ -4433,8 +3453,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -4442,8 +3460,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -4451,8 +3467,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -4460,8 +3474,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 20, limit: None, two_valued: false, residual: false, @@ -4469,8 +3481,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 21, limit: Some(em(180)), two_valued: false, residual: false, @@ -4478,8 +3488,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -4487,8 +3495,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -4496,8 +3502,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 24, limit: Some(em(180)), two_valued: false, residual: false, @@ -4505,8 +3509,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 25, limit: Some(em(180)), two_valued: false, residual: false, @@ -4514,8 +3516,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 26, limit: None, two_valued: false, residual: true, @@ -4523,8 +3523,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 27, limit: Some(em(180)), two_valued: false, residual: false, @@ -4532,8 +3530,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -4541,8 +3537,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 29, limit: None, two_valued: false, residual: false, @@ -4550,8 +3544,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 20, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -4559,8 +3551,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 1, limit: None, two_valued: false, residual: true, @@ -4568,8 +3558,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 2, limit: None, two_valued: false, residual: false, @@ -4577,8 +3565,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 3, limit: None, two_valued: false, residual: false, @@ -4586,8 +3572,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 4, limit: None, two_valued: false, residual: false, @@ -4595,8 +3579,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 5, limit: None, two_valued: false, residual: false, @@ -4604,8 +3586,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 6, limit: None, two_valued: false, residual: false, @@ -4613,8 +3593,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 7, limit: None, two_valued: false, residual: false, @@ -4622,8 +3600,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -4631,8 +3607,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 9, limit: Some(em(360)), two_valued: false, residual: false, @@ -4640,8 +3614,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 10, limit: Some(em(360)), two_valued: false, residual: false, @@ -4649,8 +3621,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 11, limit: Some(em(360)), two_valued: false, residual: false, @@ -4658,8 +3628,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -4667,8 +3635,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -4676,8 +3642,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 14, limit: None, two_valued: false, residual: true, @@ -4685,8 +3649,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 15, limit: Some(em(360)), two_valued: false, residual: false, @@ -4694,8 +3656,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 16, limit: Some(em(360)), two_valued: false, residual: false, @@ -4703,8 +3663,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 19, limit: Some(em(360)), two_valued: false, residual: false, @@ -4712,8 +3670,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 20, limit: None, two_valued: false, residual: false, @@ -4721,8 +3677,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 21, limit: Some(em(180)), two_valued: false, residual: false, @@ -4730,8 +3684,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E.2#5", }, RawRangedCell { - before: 21, - after: 22, limit: Some(em(360)), two_valued: false, residual: false, @@ -4739,8 +3691,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 23, limit: Some(em(360)), two_valued: false, residual: false, @@ -4748,8 +3698,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 24, limit: Some(em(180)), two_valued: false, residual: false, @@ -4757,8 +3705,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 25, limit: Some(em(180)), two_valued: false, residual: false, @@ -4766,8 +3712,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 26, limit: None, two_valued: false, residual: true, @@ -4775,8 +3719,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 27, limit: Some(em(180)), two_valued: false, residual: false, @@ -4784,8 +3726,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -4793,8 +3733,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 29, limit: None, two_valued: false, residual: false, @@ -4802,8 +3740,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 21, - after: 30, limit: Some(em(360)), two_valued: false, residual: false, @@ -4811,8 +3747,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 1, limit: None, two_valued: false, residual: true, @@ -4820,8 +3754,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 2, limit: None, two_valued: false, residual: false, @@ -4829,8 +3761,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 3, limit: None, two_valued: false, residual: false, @@ -4838,8 +3768,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 4, limit: None, two_valued: false, residual: false, @@ -4847,8 +3775,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 5, limit: None, two_valued: false, residual: false, @@ -4856,8 +3782,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 6, limit: None, two_valued: false, residual: false, @@ -4865,8 +3789,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 7, limit: None, two_valued: false, residual: false, @@ -4874,8 +3796,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -4883,8 +3803,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -4892,8 +3810,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -4901,8 +3817,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -4910,8 +3824,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -4919,8 +3831,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -4928,8 +3838,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 14, limit: None, two_valued: false, residual: true, @@ -4937,8 +3845,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -4946,8 +3852,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -4955,8 +3859,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -4964,8 +3866,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 20, limit: None, two_valued: false, residual: false, @@ -4973,8 +3873,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -4982,8 +3880,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -4991,8 +3887,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E.2#6", }, RawRangedCell { - before: 22, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -5000,8 +3894,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -5009,8 +3901,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -5018,8 +3908,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 26, limit: None, two_valued: false, residual: true, @@ -5027,8 +3915,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -5036,8 +3922,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -5045,8 +3929,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 29, limit: None, two_valued: false, residual: false, @@ -5054,8 +3936,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 22, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -5063,8 +3943,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 1, limit: None, two_valued: false, residual: true, @@ -5072,8 +3950,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 2, limit: None, two_valued: false, residual: false, @@ -5081,8 +3957,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 3, limit: None, two_valued: false, residual: false, @@ -5090,8 +3964,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 4, limit: None, two_valued: false, residual: false, @@ -5099,8 +3971,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 5, limit: None, two_valued: false, residual: false, @@ -5108,8 +3978,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 6, limit: None, two_valued: false, residual: false, @@ -5117,8 +3985,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 7, limit: None, two_valued: false, residual: false, @@ -5126,8 +3992,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -5135,8 +3999,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -5144,8 +4006,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -5153,8 +4013,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -5162,8 +4020,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -5171,8 +4027,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -5180,8 +4034,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 14, limit: None, two_valued: false, residual: true, @@ -5189,8 +4041,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -5198,8 +4048,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -5207,8 +4055,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -5216,8 +4062,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 20, limit: None, two_valued: false, residual: false, @@ -5225,8 +4069,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -5234,8 +4076,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -5243,8 +4083,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -5252,8 +4090,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E.2#7", }, RawRangedCell { - before: 23, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -5261,8 +4097,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -5270,8 +4104,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 26, limit: None, two_valued: false, residual: true, @@ -5279,8 +4111,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -5288,8 +4118,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -5297,8 +4125,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 29, limit: None, two_valued: false, residual: false, @@ -5306,8 +4132,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 23, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -5315,8 +4139,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 1, limit: None, two_valued: false, residual: true, @@ -5324,8 +4146,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 2, limit: None, two_valued: false, residual: false, @@ -5333,8 +4153,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 3, limit: None, two_valued: false, residual: false, @@ -5342,8 +4160,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 4, limit: None, two_valued: false, residual: false, @@ -5351,8 +4167,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 5, limit: None, two_valued: false, residual: false, @@ -5360,8 +4174,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 6, limit: None, two_valued: false, residual: false, @@ -5369,8 +4181,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 7, limit: None, two_valued: false, residual: false, @@ -5378,8 +4188,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -5387,8 +4195,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 9, limit: Some(em(360)), two_valued: false, residual: false, @@ -5396,8 +4202,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 10, limit: Some(em(360)), two_valued: false, residual: false, @@ -5405,8 +4209,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 11, limit: Some(em(360)), two_valued: false, residual: false, @@ -5414,8 +4216,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -5423,8 +4223,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 13, limit: None, two_valued: false, residual: false, @@ -5432,8 +4230,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E.2#8", }, RawRangedCell { - before: 24, - after: 14, limit: None, two_valued: false, residual: true, @@ -5441,8 +4237,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 15, limit: Some(em(360)), two_valued: false, residual: false, @@ -5450,8 +4244,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 16, limit: Some(em(360)), two_valued: false, residual: false, @@ -5459,8 +4251,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 19, limit: Some(em(360)), two_valued: false, residual: false, @@ -5468,8 +4258,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 20, limit: None, two_valued: false, residual: false, @@ -5477,8 +4265,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 21, limit: Some(em(180)), two_valued: false, residual: false, @@ -5486,8 +4272,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 22, limit: Some(em(360)), two_valued: false, residual: false, @@ -5495,8 +4279,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 23, limit: Some(em(360)), two_valued: false, residual: false, @@ -5504,8 +4286,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 24, limit: None, two_valued: false, residual: false, @@ -5513,8 +4293,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 25, limit: None, two_valued: false, residual: true, @@ -5522,8 +4300,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 26, limit: None, two_valued: false, residual: true, @@ -5531,8 +4307,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 27, limit: None, two_valued: false, residual: false, @@ -5540,8 +4314,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E.2#9", }, RawRangedCell { - before: 24, - after: 28, limit: Some(em(360)), two_valued: false, residual: false, @@ -5549,8 +4321,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 29, limit: None, two_valued: false, residual: false, @@ -5558,8 +4328,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 24, - after: 30, limit: Some(em(360)), two_valued: false, residual: false, @@ -5567,8 +4335,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 1, limit: None, two_valued: false, residual: true, @@ -5576,8 +4342,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 2, limit: None, two_valued: false, residual: false, @@ -5585,8 +4349,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 3, limit: None, two_valued: false, residual: false, @@ -5594,8 +4356,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 4, limit: None, two_valued: false, residual: false, @@ -5603,8 +4363,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 5, limit: None, two_valued: false, residual: false, @@ -5612,8 +4370,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 6, limit: None, two_valued: false, residual: false, @@ -5621,8 +4377,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 7, limit: None, two_valued: false, residual: false, @@ -5630,8 +4384,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -5639,8 +4391,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 9, limit: Some(em(360)), two_valued: false, residual: false, @@ -5648,8 +4398,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 10, limit: Some(em(360)), two_valued: false, residual: false, @@ -5657,8 +4405,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 11, limit: Some(em(360)), two_valued: false, residual: false, @@ -5666,8 +4412,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -5675,8 +4419,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -5684,8 +4426,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 14, limit: None, two_valued: false, residual: true, @@ -5693,8 +4433,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 15, limit: Some(em(360)), two_valued: false, residual: false, @@ -5702,8 +4440,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 16, limit: Some(em(360)), two_valued: false, residual: false, @@ -5711,8 +4447,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 19, limit: Some(em(360)), two_valued: false, residual: false, @@ -5720,8 +4454,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 20, limit: None, two_valued: false, residual: false, @@ -5729,8 +4461,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 21, limit: Some(em(180)), two_valued: false, residual: false, @@ -5738,8 +4468,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 22, limit: Some(em(360)), two_valued: false, residual: false, @@ -5747,8 +4475,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 23, limit: Some(em(360)), two_valued: false, residual: false, @@ -5756,8 +4482,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 24, limit: None, two_valued: false, residual: true, @@ -5765,8 +4489,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 25, limit: None, two_valued: false, residual: false, @@ -5774,8 +4496,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 26, limit: None, two_valued: false, residual: true, @@ -5783,8 +4503,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 27, limit: None, two_valued: false, residual: false, @@ -5792,8 +4510,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 28, limit: Some(em(360)), two_valued: false, residual: false, @@ -5801,8 +4517,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 29, limit: None, two_valued: false, residual: false, @@ -5810,8 +4524,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 25, - after: 30, limit: Some(em(360)), two_valued: false, residual: false, @@ -5819,8 +4531,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 1, limit: None, two_valued: false, residual: true, @@ -5828,8 +4538,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 2, limit: None, two_valued: false, residual: false, @@ -5837,8 +4545,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 3, limit: None, two_valued: false, residual: true, @@ -5846,8 +4552,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 4, limit: None, two_valued: false, residual: true, @@ -5855,8 +4559,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 5, limit: None, two_valued: false, residual: true, @@ -5864,8 +4566,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 6, limit: None, two_valued: false, residual: true, @@ -5873,8 +4573,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 7, limit: None, two_valued: false, residual: true, @@ -5882,8 +4580,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 8, limit: None, two_valued: false, residual: true, @@ -5891,8 +4587,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 9, limit: None, two_valued: false, residual: true, @@ -5900,8 +4594,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 10, limit: None, two_valued: false, residual: true, @@ -5909,8 +4601,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 11, limit: None, two_valued: false, residual: true, @@ -5918,8 +4608,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 12, limit: None, two_valued: false, residual: true, @@ -5927,8 +4615,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 13, limit: None, two_valued: false, residual: true, @@ -5936,8 +4622,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 14, limit: None, two_valued: false, residual: true, @@ -5945,8 +4629,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 15, limit: None, two_valued: false, residual: true, @@ -5954,8 +4636,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 16, limit: None, two_valued: false, residual: true, @@ -5963,8 +4643,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 19, limit: None, two_valued: false, residual: true, @@ -5972,8 +4650,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 20, limit: None, two_valued: false, residual: true, @@ -5981,8 +4657,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 21, limit: None, two_valued: false, residual: true, @@ -5990,8 +4664,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 22, limit: None, two_valued: false, residual: true, @@ -5999,8 +4671,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 23, limit: None, two_valued: false, residual: true, @@ -6008,8 +4678,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 24, limit: None, two_valued: false, residual: true, @@ -6017,8 +4685,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 25, limit: None, two_valued: false, residual: true, @@ -6026,8 +4692,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 26, limit: None, two_valued: false, residual: true, @@ -6035,8 +4699,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 27, limit: None, two_valued: false, residual: true, @@ -6044,8 +4706,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 28, limit: None, two_valued: false, residual: true, @@ -6053,8 +4713,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 29, limit: None, two_valued: false, residual: false, @@ -6062,8 +4720,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 26, - after: 30, limit: None, two_valued: false, residual: true, @@ -6071,8 +4727,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 1, limit: None, two_valued: false, residual: true, @@ -6080,8 +4734,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 2, limit: None, two_valued: false, residual: false, @@ -6089,8 +4741,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 3, limit: None, two_valued: false, residual: false, @@ -6098,8 +4748,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 4, limit: None, two_valued: false, residual: false, @@ -6107,8 +4755,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 5, limit: None, two_valued: false, residual: false, @@ -6116,8 +4762,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 6, limit: None, two_valued: false, residual: false, @@ -6125,8 +4769,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 7, limit: None, two_valued: false, residual: false, @@ -6134,8 +4776,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -6143,8 +4783,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 9, limit: Some(em(360)), two_valued: false, residual: false, @@ -6152,8 +4790,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 10, limit: Some(em(360)), two_valued: false, residual: false, @@ -6161,8 +4797,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 11, limit: Some(em(360)), two_valued: false, residual: false, @@ -6170,8 +4804,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -6179,8 +4811,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -6188,8 +4818,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E.2#10", }, RawRangedCell { - before: 27, - after: 14, limit: None, two_valued: false, residual: true, @@ -6197,8 +4825,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 15, limit: Some(em(360)), two_valued: false, residual: false, @@ -6206,8 +4832,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 16, limit: Some(em(360)), two_valued: false, residual: false, @@ -6215,8 +4839,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 19, limit: Some(em(360)), two_valued: false, residual: false, @@ -6224,8 +4846,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 20, limit: None, two_valued: false, residual: false, @@ -6233,8 +4853,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 21, limit: Some(em(180)), two_valued: false, residual: false, @@ -6242,8 +4860,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 22, limit: Some(em(360)), two_valued: false, residual: false, @@ -6251,8 +4867,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 23, limit: Some(em(360)), two_valued: false, residual: false, @@ -6260,8 +4874,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 24, limit: None, two_valued: false, residual: false, @@ -6269,8 +4881,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 25, limit: None, two_valued: false, residual: false, @@ -6278,8 +4888,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 26, limit: None, two_valued: false, residual: true, @@ -6287,8 +4895,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 27, limit: None, two_valued: false, residual: false, @@ -6296,8 +4902,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E.2#11", }, RawRangedCell { - before: 27, - after: 28, limit: Some(em(360)), two_valued: false, residual: false, @@ -6305,8 +4909,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 29, limit: None, two_valued: false, residual: false, @@ -6314,8 +4916,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 27, - after: 30, limit: Some(em(360)), two_valued: false, residual: false, @@ -6323,8 +4923,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 1, limit: None, two_valued: false, residual: false, @@ -6332,8 +4930,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 2, limit: None, two_valued: false, residual: false, @@ -6341,8 +4937,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 3, limit: None, two_valued: false, residual: false, @@ -6350,8 +4944,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 4, limit: None, two_valued: false, residual: false, @@ -6359,8 +4951,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 5, limit: None, two_valued: false, residual: false, @@ -6368,8 +4958,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 6, limit: None, two_valued: false, residual: false, @@ -6377,8 +4965,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 7, limit: None, two_valued: false, residual: false, @@ -6386,8 +4972,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 8, limit: None, two_valued: false, residual: false, @@ -6395,8 +4979,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 9, limit: None, two_valued: false, residual: false, @@ -6404,8 +4986,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 10, limit: None, two_valued: false, residual: false, @@ -6413,8 +4993,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 11, limit: None, two_valued: false, residual: false, @@ -6422,8 +5000,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 12, limit: None, two_valued: false, residual: false, @@ -6431,8 +5007,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 13, limit: None, two_valued: false, residual: false, @@ -6440,8 +5014,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 14, limit: None, two_valued: false, residual: false, @@ -6449,8 +5021,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 15, limit: None, two_valued: false, residual: false, @@ -6458,8 +5028,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 16, limit: None, two_valued: false, residual: false, @@ -6467,8 +5035,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 19, limit: None, two_valued: false, residual: false, @@ -6476,8 +5042,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 20, limit: None, two_valued: false, residual: false, @@ -6485,8 +5049,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 21, limit: None, two_valued: false, residual: false, @@ -6494,8 +5056,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 22, limit: None, two_valued: false, residual: false, @@ -6503,8 +5063,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 23, limit: None, two_valued: false, residual: false, @@ -6512,8 +5070,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 24, limit: None, two_valued: false, residual: false, @@ -6521,8 +5077,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 25, limit: None, two_valued: false, residual: false, @@ -6530,8 +5084,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 26, limit: None, two_valued: false, residual: false, @@ -6539,8 +5091,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 27, limit: None, two_valued: false, residual: false, @@ -6548,8 +5098,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 28, limit: None, two_valued: false, residual: false, @@ -6557,8 +5105,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 29, limit: None, two_valued: false, residual: false, @@ -6566,8 +5112,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 28, - after: 30, limit: None, two_valued: false, residual: false, @@ -6575,8 +5119,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 1, limit: None, two_valued: false, residual: true, @@ -6584,8 +5126,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 2, limit: None, two_valued: false, residual: false, @@ -6593,8 +5133,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 3, limit: None, two_valued: false, residual: false, @@ -6602,8 +5140,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 4, limit: None, two_valued: false, residual: false, @@ -6611,8 +5147,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 5, limit: None, two_valued: false, residual: false, @@ -6620,8 +5154,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 6, limit: None, two_valued: false, residual: false, @@ -6629,8 +5161,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 7, limit: None, two_valued: false, residual: false, @@ -6638,8 +5168,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -6647,8 +5175,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -6656,8 +5182,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -6665,8 +5189,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -6674,8 +5196,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -6683,8 +5203,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -6692,8 +5210,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 14, limit: None, two_valued: false, residual: true, @@ -6701,8 +5217,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -6710,8 +5224,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -6719,8 +5231,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -6728,8 +5238,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 20, limit: None, two_valued: false, residual: false, @@ -6737,8 +5245,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -6746,8 +5252,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -6755,8 +5259,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -6764,8 +5266,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -6773,8 +5273,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -6782,8 +5280,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 26, limit: None, two_valued: false, residual: true, @@ -6791,8 +5287,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -6800,8 +5294,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -6809,8 +5301,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 29, limit: None, two_valued: false, residual: false, @@ -6818,8 +5308,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 29, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -6827,8 +5315,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 1, limit: None, two_valued: false, residual: true, @@ -6836,8 +5322,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 2, limit: None, two_valued: false, residual: false, @@ -6845,8 +5329,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 3, limit: None, two_valued: false, residual: false, @@ -6854,8 +5336,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 4, limit: None, two_valued: false, residual: false, @@ -6863,8 +5343,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 5, limit: None, two_valued: false, residual: false, @@ -6872,8 +5350,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 6, limit: None, two_valued: false, residual: false, @@ -6881,8 +5357,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 7, limit: None, two_valued: false, residual: false, @@ -6890,8 +5364,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 8, limit: Some(em(180)), two_valued: false, residual: false, @@ -6899,8 +5371,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 9, limit: Some(em(180)), two_valued: false, residual: false, @@ -6908,8 +5378,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 10, limit: Some(em(180)), two_valued: false, residual: false, @@ -6917,8 +5385,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 11, limit: Some(em(180)), two_valued: false, residual: false, @@ -6926,8 +5392,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 12, limit: Some(em(180)), two_valued: false, residual: false, @@ -6935,8 +5399,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 13, limit: Some(em(180)), two_valued: false, residual: false, @@ -6944,8 +5406,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 14, limit: None, two_valued: false, residual: true, @@ -6953,8 +5413,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 15, limit: Some(em(180)), two_valued: false, residual: false, @@ -6962,8 +5420,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 16, limit: Some(em(180)), two_valued: false, residual: false, @@ -6971,8 +5427,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 19, limit: Some(em(180)), two_valued: false, residual: false, @@ -6980,8 +5434,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 20, limit: None, two_valued: false, residual: false, @@ -6989,8 +5441,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 21, limit: Some(em(360)), two_valued: false, residual: false, @@ -6998,8 +5448,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 22, limit: Some(em(180)), two_valued: false, residual: false, @@ -7007,8 +5455,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 23, limit: Some(em(180)), two_valued: false, residual: false, @@ -7016,8 +5462,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 24, limit: Some(em(360)), two_valued: false, residual: false, @@ -7025,8 +5469,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 25, limit: Some(em(360)), two_valued: false, residual: false, @@ -7034,8 +5476,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 26, limit: None, two_valued: false, residual: true, @@ -7043,8 +5483,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 27, limit: Some(em(360)), two_valued: false, residual: false, @@ -7052,8 +5490,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 28, limit: Some(em(180)), two_valued: false, residual: false, @@ -7061,8 +5497,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 29, limit: None, two_valued: false, residual: false, @@ -7070,8 +5504,6 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E", }, RawRangedCell { - before: 30, - after: 30, limit: Some(em(180)), two_valued: false, residual: false, @@ -7079,3 +5511,26 @@ pub(crate) static CELLS: &[RawRangedCell] = &[ rule: "E.2#12", }, ]; + +const ROW_INDEX: [u8; 31] = [ + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, +]; +const COLUMN_INDEX: [u8; 31] = [ + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 255, 255, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, +]; +const COLUMN_COUNT: usize = 28; + +/// Look up one cell directly from its class or line-edge coordinates. +pub(crate) fn cell(before: u8, after: u8) -> Option<&'static RawRangedCell> { + let row = *ROW_INDEX.get(usize::from(before))?; + let column = *COLUMN_INDEX.get(usize::from(after))?; + if row == u8::MAX || column == u8::MAX { + return None; + } + let index = usize::from(row) + .checked_mul(COLUMN_COUNT)? + .checked_add(usize::from(column))?; + CELLS.get(index) +} diff --git a/crates/jlreq-core/src/paragraph.rs b/crates/jlreq-core/src/paragraph.rs index 49027ac..e86272b 100644 --- a/crates/jlreq-core/src/paragraph.rs +++ b/crates/jlreq-core/src/paragraph.rs @@ -7,6 +7,93 @@ use alloc::{vec, vec::Vec}; use crate::construct::{Construct, ConstructKind, is_math_token}; use crate::model::{InputError, ShapedText, WritingMode}; +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct ConstructIndex { + parents: Vec>, + innermost: Vec>, + starts: Vec, + by_start: Vec, +} + +impl ConstructIndex { + fn new(text: &ShapedText, constructs: &[Construct]) -> Self { + if constructs.is_empty() { + return Self { + parents: Vec::new(), + innermost: Vec::new(), + starts: Vec::new(), + by_start: Vec::new(), + }; + } + let mut ranges: Vec<_> = constructs + .iter() + .enumerate() + .filter_map(|(ordinal, construct)| { + let range = construct.range(); + Some(( + text.cluster_ordinal(range.start)?, + text.cluster_ordinal(range.end)?, + ordinal, + )) + }) + .collect(); + ranges.sort_by(|left, right| { + left.0 + .cmp(&right.0) + .then_with(|| right.1.cmp(&left.1)) + .then_with(|| left.2.cmp(&right.2)) + }); + + let mut ends = vec![0_usize; constructs.len()]; + for &(_, end, ordinal) in &ranges { + if let Some(value) = ends.get_mut(ordinal) { + *value = end; + } + } + let mut parents = vec![None; constructs.len()]; + let mut stack = Vec::new(); + for &(start, _, ordinal) in &ranges { + while stack + .last() + .is_some_and(|parent| ends.get(*parent).is_none_or(|end| *end <= start)) + { + stack.pop(); + } + if let Some(parent) = stack.last().copied() { + if let Some(slot) = parents.get_mut(ordinal) { + *slot = Some(parent); + } + } + stack.push(ordinal); + } + + let mut innermost = vec![None; text.clusters().len()]; + stack.clear(); + let mut next = 0_usize; + for (cluster, innermost) in innermost.iter_mut().enumerate() { + while stack + .last() + .is_some_and(|active| ends.get(*active).is_none_or(|end| *end <= cluster)) + { + stack.pop(); + } + while ranges.get(next).is_some_and(|range| range.0 == cluster) { + stack.push(ranges[next].2); + next = next.saturating_add(1); + } + *innermost = stack.last().copied(); + } + let starts = ranges.iter().map(|range| range.0).collect(); + let by_start = ranges.iter().map(|range| range.2).collect(); + Self { + parents, + innermost, + starts, + by_start, + } + } +} + /// The semantic kind of a caller-supplied line-break opportunity. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum BreakKind { @@ -155,6 +242,7 @@ pub struct Paragraph { pub(crate) line_extent: i32, pub(crate) breaks: Vec, pub(crate) constructs: Vec, + pub(crate) construct_index: ConstructIndex, pub(crate) tab_stops: Vec, pub(crate) line_tabs: Vec, pub(crate) first_line_indent: i32, @@ -204,6 +292,90 @@ impl Paragraph { &self.constructs } + pub(crate) fn find_construct_containing( + &self, + cluster: usize, + mut matches: impl FnMut(&Construct) -> bool, + ) -> Option<(usize, &Construct)> { + let mut current = self + .construct_index + .innermost + .get(cluster) + .copied() + .flatten(); + let mut selected = None; + while let Some(ordinal) = current { + if self.constructs.get(ordinal).is_some_and(&mut matches) + && selected.is_none_or(|previous| ordinal < previous) + { + selected = Some(ordinal); + } + current = self.construct_index.parents.get(ordinal).copied().flatten(); + } + selected.and_then(|ordinal| { + self.constructs + .get(ordinal) + .map(|construct| (ordinal, construct)) + }) + } + + pub(crate) fn visit_constructs_containing( + &self, + cluster: usize, + mut visit: impl FnMut(usize, &Construct), + ) { + let mut current = self + .construct_index + .innermost + .get(cluster) + .copied() + .flatten(); + while let Some(ordinal) = current { + if let Some(construct) = self.constructs.get(ordinal) { + visit(ordinal, construct); + } + current = self.construct_index.parents.get(ordinal).copied().flatten(); + } + } + + pub(crate) fn visit_constructs_overlapping( + &self, + start: usize, + end: usize, + mut visit: impl FnMut(usize, &Construct), + ) { + if start >= end { + return; + } + self.visit_constructs_containing(start, &mut visit); + let first = self + .construct_index + .starts + .partition_point(|construct_start| *construct_start <= start); + let last = self + .construct_index + .starts + .partition_point(|construct_start| *construct_start < end); + for index in first..last { + if let Some(ordinal) = self.construct_index.by_start.get(index).copied() { + if let Some(construct) = self.constructs.get(ordinal) { + visit(ordinal, construct); + } + } + } + } + + pub(crate) fn collect_constructs_overlapping( + &self, + start: usize, + end: usize, + ordinals: &mut Vec, + ) { + ordinals.clear(); + self.visit_constructs_overlapping(start, end, |ordinal, _| ordinals.push(ordinal)); + ordinals.sort_unstable(); + } + /// Validated, increasing tab stops. #[must_use] pub fn tab_stops(&self) -> &[TabStop] { @@ -338,11 +510,13 @@ impl ParagraphBuilder { &mut self.tab_stops, )?; + let construct_index = ConstructIndex::new(&self.text, &self.constructs); Ok(Paragraph { text: self.text, line_extent: self.line_extent, breaks: self.breaks, constructs: self.constructs, + construct_index, tab_stops: self.tab_stops, line_tabs, first_line_indent: self.first_line_indent, @@ -896,6 +1070,22 @@ mod tests { .build() .expect_err("crossing constructs"); assert_eq!(crossing.code(), "input.crossing-constructs"); + + let indexed = Paragraph::builder(text("abcde"), 5_000) + .constructs([ + Construct::emphasis_dots(1..4, '・'), + Construct::reference_mark(2..3, text("※")), + Construct::script(4..5, text("x")), + ]) + .build() + .expect("nested and adjacent indexed constructs"); + let mut ordinals = Vec::new(); + indexed.collect_constructs_overlapping(2, 3, &mut ordinals); + assert_eq!(ordinals, [0, 1]); + indexed.collect_constructs_overlapping(3, 5, &mut ordinals); + assert_eq!(ordinals, [0, 2]); + indexed.collect_constructs_overlapping(0, 1, &mut ordinals); + assert!(ordinals.is_empty()); } #[test] diff --git a/crates/jlreq-core/src/pipeline.rs b/crates/jlreq-core/src/pipeline.rs index 9a5213e..f152a96 100644 --- a/crates/jlreq-core/src/pipeline.rs +++ b/crates/jlreq-core/src/pipeline.rs @@ -25,4402 +25,14 @@ use crate::style::{ }; const INFINITE_COST: u128 = u128::MAX; - -#[derive(Debug, Clone, Copy)] -struct Candidate { - offset: usize, - mandatory: bool, - discretionary: bool, -} - -#[derive(Debug, Clone, Copy)] -struct Node { - cost: u128, - previous: usize, - line_count: usize, -} - -#[derive(Debug, Default)] -struct PreparedParagraph { - candidate_ordinals: Vec, - legal_candidates: Vec, - natural_prefix: Vec, - minimum_prefix: Vec, - reduction_prefix: Vec, - line_end_reduction: Vec, - regular: bool, -} - -impl PreparedParagraph { - const fn new() -> Self { - Self { - candidate_ordinals: Vec::new(), - legal_candidates: Vec::new(), - natural_prefix: Vec::new(), - minimum_prefix: Vec::new(), - reduction_prefix: Vec::new(), - line_end_reduction: Vec::new(), - regular: false, - } - } - - fn clear(&mut self) { - self.candidate_ordinals.clear(); - self.legal_candidates.clear(); - self.natural_prefix.clear(); - self.minimum_prefix.clear(); - self.reduction_prefix.clear(); - self.line_end_reduction.clear(); - self.regular = false; - } -} - -#[derive(Debug, Clone)] -struct WarichuSegment { - range: Range, - leading_bracket: Option, - first_lane: Range, - second_lane: Range, - trailing_bracket: Option, - first_width: i32, - second_width: i32, - advance: i32, -} - -#[derive(Debug, Clone)] -struct FurawakeSegment { - range: Range, - lanes: Vec>, - block_extents: Vec, - line_gap: i32, - advance: i32, - block_extent: i32, -} - -#[derive(Debug, Clone)] -struct JidoriPlan { - range: Range, - extra_after: Vec, -} - -impl JidoriPlan { - fn extra_after(&self, ordinal: usize) -> i32 { - ordinal - .checked_sub(self.range.start) - .and_then(|local| self.extra_after.get(local)) - .copied() - .unwrap_or(0) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ComplexKind { - Ornamented, - SimpleRuby, - JukugoRuby, - TateChuYoko, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -struct ComplexIdentity { - kind: ComplexKind, - construct: usize, - member: usize, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ExpansionSite { - None, - Site { - weight: i32, - bounded: Option<(i32, u8)>, - residual: bool, - }, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -struct ReductionSite { - boundary: usize, - weight: i32, - capacity: i32, - stage: u8, - discrete: bool, -} - -#[derive(Debug, Clone, Copy)] -struct LineContext { - start: usize, - end: usize, - index: usize, -} - -/// A reusable whole-paragraph composer. -/// -/// All temporary search memory is retained between calls. The returned Layout owns its -/// placements and never borrows this scratch state. -#[derive(Debug, Default)] -#[non_exhaustive] -pub struct Composer { - limits: CompositionLimits, - transitions: usize, - candidates: Vec, - nodes: Vec, - chosen: Vec, - line_advances: Vec, - line_adjustments: Vec, - prepared: PreparedParagraph, -} - -impl Composer { - /// Build an empty reusable composer. - #[must_use] - pub const fn new() -> Self { - Self { - limits: CompositionLimits::DEFAULT, - transitions: 0, - candidates: Vec::new(), - nodes: Vec::new(), - chosen: Vec::new(), - line_advances: Vec::new(), - line_adjustments: Vec::new(), - prepared: PreparedParagraph::new(), - } - } - - /// Build a reusable composer with explicit deterministic resource limits. - #[must_use] - pub const fn with_limits(limits: CompositionLimits) -> Self { - Self { - limits, - transitions: 0, - candidates: Vec::new(), - nodes: Vec::new(), - chosen: Vec::new(), - line_advances: Vec::new(), - line_adjustments: Vec::new(), - prepared: PreparedParagraph::new(), - } - } - - /// The limits used by subsequent composition calls. - #[must_use] - pub const fn limits(&self) -> CompositionLimits { - self.limits - } - - /// Replace the limits used by subsequent composition calls. - pub const fn set_limits(&mut self, limits: CompositionLimits) { - self.limits = limits; - } - - /// Normalize, choose breaks globally, and place one validated paragraph. - pub fn compose( - &mut self, - paragraph: &Paragraph, - style: &Style, - ) -> Result { - self.reset_for_call(); - if paragraph.text.clusters().is_empty() { - return Ok(Layout::default()); - } - self.check_static_limits(paragraph)?; - self.prepare_candidates(paragraph); - self.prepare_indexes(paragraph, style); - self.search(paragraph, style)?; - self.backtrack(); - Ok(self.place(paragraph, style)) - } - - fn reset_for_call(&mut self) { - self.transitions = 0; - self.candidates.clear(); - self.nodes.clear(); - self.chosen.clear(); - self.line_advances.clear(); - self.line_adjustments.clear(); - self.prepared.clear(); - } - - fn check_static_limits(&self, paragraph: &Paragraph) -> Result<(), ComposeError> { - check_limit( - CompositionResource::Clusters, - self.limits.max_clusters(), - paragraph.text.clusters().len(), - )?; - check_limit( - CompositionResource::BreakCandidates, - self.limits.max_break_candidates(), - paragraph.breaks.len(), - )?; - check_limit( - CompositionResource::Constructs, - self.limits.max_constructs(), - paragraph.constructs.len(), - )?; - check_limit( - CompositionResource::TabStops, - self.limits.max_tab_stops(), - paragraph.tab_stops.len(), - ) - } - - fn charge_transitions(&mut self, amount: usize) -> Result<(), ComposeError> { - let observed = self.transitions.saturating_add(amount); - if observed > self.limits.max_search_transitions() { - return Err(ComposeError::new( - CompositionResource::SearchTransitions, - self.limits.max_search_transitions(), - observed, - )); - } - self.transitions = observed; - Ok(()) - } - - fn prepare_candidates(&mut self, paragraph: &Paragraph) { - self.candidates.clear(); - self.candidates.push(Candidate { - offset: 0, - mandatory: true, - discretionary: false, - }); - self.candidates.extend( - paragraph - .breaks - .iter() - .filter(|opportunity| !is_internal_furawake_offset(paragraph, opportunity.offset())) - .map(|opportunity| Candidate { - offset: opportunity.offset(), - mandatory: opportunity.is_mandatory(), - discretionary: opportunity.is_discretionary(), - }), - ); - } - - fn prepare_indexes(&mut self, paragraph: &Paragraph, style: &Style) { - self.prepared.candidate_ordinals.extend( - self.candidates - .iter() - .map(|candidate| cluster_index_at_or_after(paragraph, candidate.offset)), - ); - self.prepared - .legal_candidates - .extend(self.candidates.iter().map(|candidate| { - candidate.mandatory || break_is_legal(paragraph, style, candidate.offset) - })); - - self.prepared.regular = paragraph.constructs.is_empty() - && !paragraph - .line_tabs - .iter() - .copied() - .any(core::convert::identity); - if !self.prepared.regular { - return; - } - - let cluster_count = paragraph.text.clusters().len(); - self.prepared - .natural_prefix - .reserve(cluster_count.saturating_add(1)); - self.prepared - .minimum_prefix - .reserve(cluster_count.saturating_add(1)); - self.prepared - .reduction_prefix - .reserve(cluster_count.saturating_add(1)); - self.prepared.line_end_reduction.reserve(cluster_count); - self.prepared.natural_prefix.push(0); - self.prepared.minimum_prefix.push(0); - self.prepared.reduction_prefix.push(0); - - for ordinal in 0..cluster_count { - let natural = i64::from(effective_cluster_advance(paragraph, style, ordinal)); - let previous_natural = self.prepared.natural_prefix.last().copied().unwrap_or(0); - self.prepared - .natural_prefix - .push(previous_natural.saturating_add(natural)); - - let minimum = if is_western_word_space(paragraph, ordinal) { - 0 - } else { - i64::from(paragraph.text.clusters()[ordinal].advance()) - }; - let previous_minimum = self.prepared.minimum_prefix.last().copied().unwrap_or(0); - self.prepared - .minimum_prefix - .push(previous_minimum.saturating_add(minimum)); - - let mut sites = Vec::new(); - if paragraph - .text - .clusters() - .get(ordinal.saturating_add(1)) - .is_some() - { - if is_western_word_space(paragraph, ordinal) { - let cluster = ¶graph.text.clusters()[ordinal]; - let capacity = effective_cluster_body_advance(paragraph, ordinal) - .saturating_sub(quarter_inline_size(paragraph, cluster)) - .max(0); - push_reduction_site( - &mut sites, - 0, - cluster - .size_override() - .unwrap_or(paragraph.text.size()) - .inline(), - capacity, - 1, - false, - ); - } - append_table_reduction_sites(paragraph, style, ordinal, 0, &mut sites); - } - let capacity = sites.iter().fold(0_i64, |sum, site| { - sum.saturating_add(i64::from(site.capacity)) - }); - let previous_capacity = self.prepared.reduction_prefix.last().copied().unwrap_or(0); - self.prepared - .reduction_prefix - .push(previous_capacity.saturating_add(capacity)); - - sites.clear(); - append_line_end_reduction_site(paragraph, style, ordinal, 0, &mut sites); - self.prepared - .line_end_reduction - .push(sites.iter().fold(0_i64, |sum, site| { - sum.saturating_add(i64::from(site.capacity)) - })); - } - } - - fn search(&mut self, paragraph: &Paragraph, style: &Style) -> Result<(), ComposeError> { - self.nodes.clear(); - self.nodes.resize( - self.candidates.len(), - Node { - cost: INFINITE_COST, - previous: 0, - line_count: 0, - }, - ); - self.nodes[0] = Node { - cost: 0, - previous: 0, - line_count: 0, - }; - - let mut mandatory_partition_start = 0_usize; - for end in 1..self.candidates.len() { - let candidate = self.candidates[end]; - if !self.prepared.legal_candidates[end] { - continue; - } - for start in (mandatory_partition_start..end).rev() { - self.charge_transitions(1)?; - if self.nodes[start].cost == INFINITE_COST { - continue; - } - let line_number = self.nodes[start].line_count; - let start_ordinal = self.prepared.candidate_ordinals[start]; - let end_ordinal = self.prepared.candidate_ordinals[end]; - if !self.prepared.regular { - self.charge_transitions( - end_ordinal - .saturating_sub(start_ordinal) - .saturating_add(paragraph.constructs.len()), - )?; - } - let measured_width = if self.prepared.regular { - fast_measure_line( - &self.prepared, - paragraph, - style, - start_ordinal, - end_ordinal, - line_number, - ) - } else { - measure_line( - paragraph, - style, - self.candidates[start].offset, - candidate.offset, - line_number, - ) - }; - let available = i64::from(paragraph.line_extent); - let width = if self.prepared.regular { - fast_width_after_available_reduction( - &self.prepared, - paragraph, - style, - start_ordinal, - end_ordinal, - measured_width, - available, - ) - } else { - width_after_available_reduction( - paragraph, - style, - self.candidates[start].offset, - candidate.offset, - measured_width, - available, - ) - }; - let delta = available.saturating_sub(width); - let is_last = end.saturating_add(1) == self.candidates.len(); - let mut edge_cost = - non_negative_cost(line_badness(delta, is_last, style.adjustment_preference())); - if candidate.discretionary { - edge_cost = edge_cost.saturating_add(100_000); - } - edge_cost = edge_cost.saturating_add(non_negative_cost(warichu_break_penalty( - paragraph, - candidate.offset, - ))); - edge_cost = edge_cost.saturating_add(non_negative_cost(formula_break_penalty( - paragraph, - candidate.offset, - ))); - if is_last { - edge_cost = edge_cost.saturating_add(non_negative_cost(widow_penalty( - paragraph, - self.candidates[start].offset, - candidate.offset, - ))); - } - let cost = edge_cost.saturating_add(self.nodes[start].cost); - if search_candidate_precedes(cost, start, self.nodes[end]) { - self.nodes[end] = Node { - cost, - previous: start, - line_count: line_number.saturating_add(1), - }; - } - - if self.prepared.regular { - let minimum_width = - fast_minimum_width(&self.prepared, start_ordinal, end_ordinal); - if search_lower_bound_exceeds( - minimum_width, - available, - is_last, - style.adjustment_preference(), - self.nodes[end].cost, - ) { - break; - } - } - } - if candidate.mandatory { - mandatory_partition_start = end; - } - } - Ok(()) - } - - fn backtrack(&mut self) { - self.chosen.clear(); - let mut cursor = self.nodes.len().saturating_sub(1); - self.chosen.push(cursor); - while cursor != 0 { - cursor = self.nodes[cursor].previous; - self.chosen.push(cursor); - } - self.chosen.reverse(); - } - - fn place(&mut self, paragraph: &Paragraph, style: &Style) -> Layout { - let mut layout = Layout::default(); - let mut block_cursor = 0_i64; - for line_index in 0..self.chosen.len().saturating_sub(1) { - let start_offset = self.candidates[self.chosen[line_index]].offset; - let next_line = line_index.saturating_add(1); - let end_offset = self.candidates[self.chosen[next_line]].offset; - let start_cluster = cluster_index_at_or_after(paragraph, start_offset); - let end_cluster = cluster_index_at_or_after(paragraph, end_offset); - let is_last = line_index.saturating_add(2) == self.chosen.len(); - let block_origin = match paragraph.writing_mode { - WritingMode::HorizontalTb => clamp_i32(block_cursor), - WritingMode::VerticalRl => clamp_i32(block_cursor.saturating_neg()), - }; - let line = self.place_line( - paragraph, - style, - start_cluster..end_cluster, - line_index, - block_origin, - is_last, - ); - if i64::from(line.inline_extent) > i64::from(paragraph.line_extent) { - layout.diagnostics.push(Diagnostic { - code: "layout.overfull", - severity: Severity::Warning, - range: Some(line.range.clone()), - jlreq: "3.8.1", - }); - } - block_cursor = block_cursor.saturating_add(i64::from(line.block_extent.max(1))); - layout.lines.push(line); - } - add_widow_diagnostic(paragraph, &mut layout); - layout - } - - fn place_line( - &mut self, - paragraph: &Paragraph, - style: &Style, - cluster_range: Range, - line_index: usize, - block_origin: i32, - is_last: bool, - ) -> Line { - let start_cluster = cluster_range.start; - let end_cluster = cluster_range.end; - self.line_advances.clear(); - let clusters = ¶graph.text.clusters()[start_cluster..end_cluster]; - self.line_advances - .extend((start_cluster..end_cluster).map(|ordinal| { - effective_cluster_advance_on_line( - paragraph, - style, - ordinal, - start_cluster, - end_cluster, - line_index, - ) - })); - apply_tabs( - paragraph, - style, - start_cluster, - end_cluster, - line_index, - &mut self.line_advances, - ); - - let indent = line_head_indent(paragraph, style, start_cluster, line_index); - let ruby_leading = - ruby_line_leading_separation(paragraph, style, start_cluster, end_cluster, line_index); - let content_width = self.line_advances.iter().fold( - i64::from(indent.saturating_add(ruby_leading)), - |sum, advance| sum.saturating_add(i64::from(*advance)), - ); - let remaining = i64::from(paragraph.line_extent).saturating_sub(content_width); - let alignment_offset = match paragraph.alignment { - Alignment::Start | Alignment::Justify => 0, - Alignment::Center => remaining.max(0) / 2, - Alignment::End => remaining.max(0), - }; - let justify = line_should_justify(paragraph.alignment, is_last, remaining, clusters.len()); - prepare_line_adjustments( - paragraph, - style, - start_cluster, - end_cluster, - line_adjustment_need(remaining, justify), - &mut self.line_adjustments, - ); - - let mut placed = Vec::with_capacity(clusters.len()); - let mut cursor = i64::from(indent) - .saturating_add(i64::from(ruby_leading)) - .saturating_add(alignment_offset); - let mut block_extent = paragraph.text.size().block(); - let mut local = 0; - while local < clusters.len() { - let ordinal = start_cluster.saturating_add(local); - let previous_ordinal; - if let Some((group, columns, line_gap)) = furawake_cluster_range(paragraph, ordinal) - .filter(|(group, _, _)| group.start == ordinal) - { - let segment = furawake_segment(paragraph, group, columns, line_gap, end_cluster); - previous_ordinal = segment.range.end.saturating_sub(1); - block_extent = block_extent.max(segment.block_extent); - place_furawake_segment(paragraph, &segment, cursor, block_origin, &mut placed); - cursor = cursor.saturating_add(i64::from(self.line_advances[local])); - local = local.saturating_add(segment.range.end.saturating_sub(ordinal)); - } else if let Some(group) = warichu_cluster_range(paragraph, ordinal) - .filter(|group| group.start.max(start_cluster) == ordinal) - { - let segment = warichu_segment(paragraph, group, start_cluster, end_cluster); - previous_ordinal = segment.range.end.saturating_sub(1); - place_warichu_segment(paragraph, &segment, cursor, block_origin, &mut placed); - cursor = cursor.saturating_add(i64::from(segment.advance)); - local = local.saturating_add(segment.range.end.saturating_sub(ordinal)); - } else if let Some(group) = tate_chu_yoko_cluster_range(paragraph, ordinal) - .filter(|group| group.start == ordinal) - { - let group_end = group.end.min(end_cluster); - let member_count = group_end.saturating_sub(ordinal); - previous_ordinal = group_end.saturating_sub(1); - let horizontal_width = paragraph.text.clusters()[ordinal..group_end] - .iter() - .fold(0_i64, |sum, cluster| { - sum.saturating_add(i64::from(cluster.advance())) - }); - block_extent = block_extent.max(clamp_i32(horizontal_width)); - let mut member_block = i64::from(block_origin) - .saturating_sub(horizontal_width.checked_div(2).unwrap_or(0)); - for (member_local, cluster) in paragraph.text.clusters()[ordinal..group_end] - .iter() - .enumerate() - { - let member_ordinal = ordinal.saturating_add(member_local); - let size = cluster.size_override().unwrap_or(paragraph.text.size()); - let frame = cluster.frame_override().unwrap_or(paragraph.text.frame()); - let (writing_mode, transform) = - local_orientation(paragraph, member_ordinal, frame); - placed.push(ClusterPlacement { - origin: PlacementOrigin::Cluster(member_ordinal), - range: cluster.range(), - inline: clamp_i32(cursor), - block: clamp_i32(member_block), - advance: cluster.advance(), - size, - frame, - writing_mode, - transform, - }); - member_block = member_block.saturating_add(i64::from(cluster.advance())); - } - cursor = cursor.saturating_add(i64::from(self.line_advances[local])); - local = local.saturating_add(member_count); - } else { - previous_ordinal = ordinal; - let cluster = &clusters[local]; - let advance = self.line_advances[local]; - let size = cluster.size_override().unwrap_or(paragraph.text.size()); - let frame = cluster.frame_override().unwrap_or(paragraph.text.frame()); - block_extent = block_extent.max(size.block()); - let (writing_mode, transform) = local_orientation(paragraph, ordinal, frame); - placed.push(ClusterPlacement { - origin: PlacementOrigin::Cluster(ordinal), - range: cluster.range(), - inline: clamp_i32(cursor), - block: block_origin, - advance, - size, - frame, - writing_mode, - transform, - }); - cursor = cursor.saturating_add(i64::from(advance)); - local = local.saturating_add(1); - } - - let boundary = previous_ordinal.saturating_sub(start_cluster); - cursor = cursor.saturating_add(i64::from( - self.line_adjustments.get(boundary).copied().unwrap_or(0), - )); - } - - let range = if let (Some(first), Some(last)) = (clusters.first(), clusters.last()) { - first.range().start..last.range().end - } else { - 0..0 - }; - let occupied = cursor.saturating_sub(alignment_offset); - let hanging = hanging_amount( - paragraph, - style, - end_cluster, - occupied, - i64::from(paragraph.line_extent), - ); - let mut line = Line { - range, - inline_origin: clamp_i32(alignment_offset), - block_origin, - inline_extent: clamp_i32(occupied.saturating_sub(hanging)), - block_extent, - clusters: placed, - attachments: Vec::new(), - }; - place_attachments(paragraph, style, line_index, &mut line); - line - } -} - -fn check_limit( - resource: CompositionResource, - limit: usize, - observed: usize, -) -> Result<(), ComposeError> { - if observed > limit { - Err(ComposeError::new(resource, limit, observed)) - } else { - Ok(()) - } -} - -fn non_negative_cost(cost: i64) -> u128 { - u128::try_from(cost).unwrap_or(0) -} - -fn search_candidate_precedes(cost: u128, start: usize, current: Node) -> bool { - (cost, start) < (current.cost, current.previous) -} - -fn search_lower_bound_exceeds( - minimum_width: i64, - available: i64, - is_last: bool, - preference: AdjustmentPreference, - best_cost: u128, -) -> bool { - match minimum_width.cmp(&available) { - core::cmp::Ordering::Greater => { - non_negative_cost(line_badness( - available.saturating_sub(minimum_width), - is_last, - preference, - )) > best_cost - }, - core::cmp::Ordering::Less | core::cmp::Ordering::Equal => false, - } -} - -fn line_should_justify( - alignment: Alignment, - is_last: bool, - remaining: i64, - cluster_count: usize, -) -> bool { - alignment == Alignment::Justify && !is_last && remaining.is_positive() && cluster_count > 1 -} - -fn line_adjustment_need(remaining: i64, justify: bool) -> i64 { - if remaining.is_negative() || justify { - remaining - } else { - 0 - } -} - -fn fast_measure_line( - prepared: &PreparedParagraph, - paragraph: &Paragraph, - style: &Style, - start: usize, - end: usize, - line_number: usize, -) -> i64 { - if start >= end { - return i64::from(line_head_indent(paragraph, style, start, line_number)); - } - let mut width = range_sum(&prepared.natural_prefix, start, end); - let last = end.saturating_sub(1); - let last_natural = range_sum(&prepared.natural_prefix, last, end); - if is_western_word_space(paragraph, last) { - width = width.saturating_sub(last_natural); - } else { - width = width - .saturating_sub(last_natural) - .saturating_add(i64::from(effective_cluster_body_advance(paragraph, last))) - .saturating_add(i64::from(line_end_space_after(paragraph, style, last))); - } - if start != last && is_western_word_space(paragraph, start) { - width = width.saturating_sub(range_sum( - &prepared.natural_prefix, - start, - start.saturating_add(1), - )); - } - width.saturating_add(i64::from(line_head_indent( - paragraph, - style, - start, - line_number, - ))) -} - -fn fast_width_after_available_reduction( - prepared: &PreparedParagraph, - paragraph: &Paragraph, - style: &Style, - start: usize, - end: usize, - width: i64, - available: i64, -) -> i64 { - let need = width.saturating_sub(available); - if need <= 0 || start >= end { - return width; - } - let last = end.saturating_sub(1); - let internal_capacity = range_sum(&prepared.reduction_prefix, start, last); - let line_end_capacity = prepared.line_end_reduction.get(last).copied().unwrap_or(0); - let capacity = internal_capacity.saturating_add(line_end_capacity); - let reduced = width.saturating_sub(need.min(capacity)); - reduced.saturating_sub(hanging_amount(paragraph, style, end, reduced, available)) -} - -fn fast_minimum_width(prepared: &PreparedParagraph, start: usize, end: usize) -> i64 { - let Some(last) = end.checked_sub(1) else { - return 0; - }; - range_sum(&prepared.minimum_prefix, start, last) -} - -fn range_sum(prefix: &[i64], start: usize, end: usize) -> i64 { - prefix - .get(end) - .copied() - .unwrap_or(i64::MAX) - .saturating_sub(prefix.get(start).copied().unwrap_or(0)) -} - -fn cluster_index_at_or_after(paragraph: &Paragraph, offset: usize) -> usize { - paragraph - .text - .clusters() - .partition_point(|cluster| cluster.range().start < offset) -} - -fn tate_chu_yoko_cluster_range(paragraph: &Paragraph, ordinal: usize) -> Option> { - if paragraph.writing_mode != WritingMode::VerticalRl { - return None; - } - let cluster = paragraph.text.clusters().get(ordinal)?.range(); - let range = paragraph.constructs.iter().find_map(|construct| { - let ConstructKind::TateChuYoko(range) = construct.kind() else { - return None; - }; - (range.start <= cluster.start && cluster.end <= range.end).then_some(range) - })?; - Some( - cluster_index_at_or_after(paragraph, range.start) - ..cluster_index_at_or_after(paragraph, range.end), - ) -} - -fn warichu_cluster_range(paragraph: &Paragraph, ordinal: usize) -> Option> { - let cluster = paragraph.text.clusters().get(ordinal)?.range(); - let range = paragraph.constructs.iter().find_map(|construct| { - let ConstructKind::Warichu(range) = construct.kind() else { - return None; - }; - (range.start <= cluster.start && cluster.end <= range.end).then_some(range) - })?; - Some( - cluster_index_at_or_after(paragraph, range.start) - ..cluster_index_at_or_after(paragraph, range.end), - ) -} - -fn furawake_cluster_range( - paragraph: &Paragraph, - ordinal: usize, -) -> Option<(Range, u16, i32)> { - let cluster = paragraph.text.clusters().get(ordinal)?.range(); - let (range, columns, line_gap) = paragraph.constructs.iter().find_map(|construct| { - let ConstructKind::Furawake { - range, - columns, - line_gap, - } = construct.kind() - else { - return None; - }; - (range.start <= cluster.start && cluster.end <= range.end) - .then_some((range, *columns, *line_gap)) - })?; - Some(( - cluster_index_at_or_after(paragraph, range.start) - ..cluster_index_at_or_after(paragraph, range.end), - columns, - line_gap, - )) -} - -fn jidori_cluster_range(paragraph: &Paragraph, ordinal: usize) -> Option<(Range, u16)> { - let cluster = paragraph.text.clusters().get(ordinal)?.range(); - let (range, cells) = paragraph.constructs.iter().find_map(|construct| { - let ConstructKind::Jidori { range, cells } = construct.kind() else { - return None; - }; - (range.start <= cluster.start && cluster.end <= range.end).then_some((range, *cells)) - })?; - Some(( - cluster_index_at_or_after(paragraph, range.start) - ..cluster_index_at_or_after(paragraph, range.end), - cells, - )) -} - -fn is_internal_jidori_boundary(paragraph: &Paragraph, ordinal: usize) -> bool { - let Some(cluster) = paragraph.text.clusters().get(ordinal) else { - return false; - }; - let boundary = cluster.range().end; - paragraph.constructs.iter().any(|construct| { - matches!(construct.kind(), ConstructKind::Jidori { range, .. } - if range.start < boundary && boundary < range.end) - }) -} - -/// Whether the boundary after `ordinal` falls inside a warichu or a furawake. -/// -/// Both structures set their text on sublines that run *beside* the line, so a boundary -/// with the clusters on either side of it inside the same structure is no part of what the -/// line was composed from: the space there is the block's own, the seam where two sublines -/// meet carries nothing on the line at all, and §3.8.3's ladder adjusts the spacing of the -/// line (`docs/decisions/stacked-structure-geometry.md`). A boundary with one cluster -/// inside the structure and the next outside it is the line's — that is where the block -/// ends and the line resumes — and this is false there. -fn is_internal_stacked_boundary(paragraph: &Paragraph, ordinal: usize) -> bool { - let Some(cluster) = paragraph.text.clusters().get(ordinal) else { - return false; - }; - let boundary = cluster.range().end; - paragraph.constructs.iter().any(|construct| { - matches!( - construct.kind(), - ConstructKind::Warichu(range) | ConstructKind::Furawake { range, .. } - if range.start < boundary && boundary < range.end - ) - }) -} - -fn formula_cluster_range(paragraph: &Paragraph, ordinal: usize) -> Option> { - let cluster = paragraph.text.clusters().get(ordinal)?.range(); - let range = paragraph.constructs.iter().find_map(|construct| { - let ConstructKind::Formula(range) = construct.kind() else { - return None; - }; - (range.start <= cluster.start && cluster.end <= range.end).then_some(range) - })?; - Some( - cluster_index_at_or_after(paragraph, range.start) - ..cluster_index_at_or_after(paragraph, range.end), - ) -} - -fn is_internal_furawake_offset(paragraph: &Paragraph, offset: usize) -> bool { - paragraph.constructs.iter().any(|construct| { - let ConstructKind::Furawake { range, .. } = construct.kind() else { - return false; - }; - range.start < offset && offset < range.end - }) -} - -fn boundary_expansion_site(paragraph: &Paragraph, style: &Style, before: usize) -> ExpansionSite { - let Some(cluster) = paragraph.text.clusters().get(before) else { - return ExpansionSite::None; - }; - let after = before.saturating_add(1); - if after >= paragraph.text.clusters().len() { - return ExpansionSite::None; - } - if is_western_word_space(paragraph, before) { - let size = cluster - .size_override() - .unwrap_or(paragraph.text.size()) - .inline(); - let cap = half_rounded_up(size) - .saturating_sub(effective_cluster_body_advance(paragraph, before)) - .max(0); - let after_class = class_of_cluster_with_style(paragraph, style, after); - let residual = crate::generated::table6::CELLS - .iter() - .any(|cell| cell.before == 26 && cell.after == after_class && cell.residual); - return ExpansionSite::Site { - weight: size, - bounded: (cap > 0).then_some((cap, 1)), - residual, - }; - } - let before_complex = expansion_complex_at(paragraph, before); - let after_complex = expansion_complex_at(paragraph, after); - if let (Some(before_complex), Some(after_complex)) = (before_complex, after_complex) { - if before_complex == after_complex { - return ExpansionSite::None; - } - } - - let boundary = cluster.range().end; - if paragraph - .constructs - .iter() - .any(|construct| match construct.kind() { - ConstructKind::TateChuYoko(range) - | ConstructKind::Warichu(range) - | ConstructKind::Formula(range) - | ConstructKind::Furawake { range, .. } - | ConstructKind::Jidori { range, .. } - | ConstructKind::Script { range, .. } => range.start < boundary && boundary < range.end, - ConstructKind::Ruby(_) - | ConstructKind::Emphasis { .. } - | ConstructKind::ReferenceMark { .. } => false, - }) - { - return ExpansionSite::None; - } - - let before_class = class_of_cluster_with_style(paragraph, style, before); - let after_class = class_of_cluster_with_style(paragraph, style, after); - let before_character = single_cluster_character(paragraph, cluster); - let after_character = single_cluster_character(paragraph, ¶graph.text.clusters()[after]); - if before_class == 8 && after_class == 8 && cl_08_same_kind(before_character, after_character) { - return ExpansionSite::None; - } - if before_class == 27 && after_class == 13 { - let role = cluster.role(); - if role == Some(ClusterRole::QuantitySymbol) - || before_character.is_some_and(|character| character.is_ascii_digit()) - { - return ExpansionSite::None; - } - } - let Some(cell) = crate::generated::table6::CELLS - .iter() - .find(|cell| cell.before == before_class && cell.after == after_class) - else { - return ExpansionSite::None; - }; - let before_size = cluster.size_override().unwrap_or(paragraph.text.size()); - let after_cluster = ¶graph.text.clusters()[after]; - let after_size = after_cluster - .size_override() - .unwrap_or(paragraph.text.size()); - let before_solid = before_character - .is_some_and(|character| contextual_punctuation_is_solid(paragraph, cluster, character)); - let after_solid = after_character.is_some_and(|character| { - contextual_punctuation_is_solid(paragraph, after_cluster, character) - }); - let components = crate::spec::table_one_space_components( - before_class, - after_class, - before_size, - after_size, - before_solid, - after_solid, - ); - let weight = match components { - [0, amount] if amount > 0 => after_size.inline(), - _ => before_size.inline(), - }; - if cell.residual { - return ExpansionSite::Site { - weight, - bounded: None, - residual: true, - }; - } - let Some(limit) = cell.limit else { - return ExpansionSite::None; - }; - let current = boundary_space_after_with_style(paragraph, style, before); - let ceiling = expansion_ceiling( - style, - before_class, - after_class, - weight, - crate::spec::scale_spec_units(weight, limit), - ); - let cap = ceiling.saturating_sub(current).max(0); - if cap == 0 { - ExpansionSite::None - } else { - ExpansionSite::Site { - weight, - bounded: Some((cap, cell.stage)), - residual: false, - } - } -} - -fn expansion_ceiling(style: &Style, before: u8, after: u8, weight: i32, table: i32) -> i32 { - if !matches!((before, after), (19, 27) | (27, 19)) { - return table; - } - match style.japanese_latin_expansion_ceiling() { - JapaneseLatinExpansionCeiling::HalfEm => half_rounded_up(weight), - JapaneseLatinExpansionCeiling::ThirdEm => { - weight.saturating_add(2).checked_div(3).unwrap_or(0) - }, - JapaneseLatinExpansionCeiling::Rigid => quarter_rounded_up(weight), - } -} - -fn boundary_expansion_site_on_line( - paragraph: &Paragraph, - style: &Style, - before: usize, - line_start: usize, - line_end: usize, -) -> ExpansionSite { - if (before == line_start && is_western_word_space(paragraph, before)) - || (before.saturating_add(2) == line_end - && is_western_word_space(paragraph, before.saturating_add(1))) - { - ExpansionSite::None - } else { - boundary_expansion_site(paragraph, style, before) - } -} - -fn expansion_complex_at(paragraph: &Paragraph, ordinal: usize) -> Option { - let cluster = paragraph.text.clusters().get(ordinal)?.range(); - paragraph - .constructs - .iter() - .enumerate() - .find_map(|(construct, candidate)| match candidate.kind() { - ConstructKind::Script { range, .. } - if range.start <= cluster.start && cluster.end <= range.end => - { - Some(ComplexIdentity { - kind: ComplexKind::Ornamented, - construct, - member: 0, - }) - }, - ConstructKind::Ruby(ruby) - if ruby.base().start <= cluster.start && cluster.end <= ruby.base().end => - { - let (kind, member) = match ruby.kind() { - RubyKind::Mono => ( - ComplexKind::SimpleRuby, - ruby.runs() - .iter() - .position(|run| { - let range = run.base(); - range.start <= cluster.start && cluster.end <= range.end - }) - .unwrap_or(0), - ), - RubyKind::Group => (ComplexKind::SimpleRuby, 0), - RubyKind::Jukugo => (ComplexKind::JukugoRuby, 0), - }; - Some(ComplexIdentity { - kind, - construct, - member, - }) - }, - ConstructKind::TateChuYoko(range) - if paragraph.writing_mode == WritingMode::VerticalRl - && range.start <= cluster.start - && cluster.end <= range.end => - { - Some(ComplexIdentity { - kind: ComplexKind::TateChuYoko, - construct, - member: 0, - }) - }, - _ => None, - }) -} - -fn prepare_line_adjustments( - paragraph: &Paragraph, - style: &Style, - line_start: usize, - line_end: usize, - need: i64, - adjustments: &mut Vec, -) { - adjustments.clear(); - adjustments.resize(line_end.saturating_sub(line_start), 0); - match need.cmp(&0) { - core::cmp::Ordering::Less => { - prepare_line_reductions( - paragraph, - style, - line_start, - line_end, - need.saturating_abs(), - adjustments, - ); - return; - }, - core::cmp::Ordering::Equal => return, - core::cmp::Ordering::Greater => {}, - } - - let sites: Vec<_> = (line_start..line_end.saturating_sub(1)) - .map(|before| { - boundary_expansion_site_on_line(paragraph, style, before, line_start, line_end) - }) - .collect(); - let mut remaining = need; - for stage in 1_u8..=3 { - if remaining == 0 { - return; - } - let stage_sites: Vec<_> = sites - .iter() - .enumerate() - .filter_map(|(index, site)| match site { - ExpansionSite::Site { - weight, - bounded: Some((cap, site_stage)), - .. - } if *site_stage == stage => Some((index, *weight, Some(*cap))), - _ => None, - }) - .collect(); - let capacity = stage_sites.iter().fold(0_i64, |sum, (_, _, cap)| { - sum.saturating_add(i64::from(cap.unwrap_or(0))) - }); - let take = remaining.min(capacity); - distribute_adjustment(take, &stage_sites, style.remainder(), adjustments); - remaining = remaining.saturating_sub(take); - } - - if remaining == 0 { - return; - } - let union: Vec<_> = sites - .iter() - .enumerate() - .filter_map(|(index, site)| match site { - ExpansionSite::Site { - weight, - bounded, - residual, - } if *residual || bounded.is_some_and(|(_, stage)| (2..=3).contains(&stage)) => { - Some((index, *weight, None)) - }, - ExpansionSite::None | ExpansionSite::Site { .. } => None, - }) - .collect(); - distribute_adjustment(remaining, &union, style.remainder(), adjustments); -} - -fn prepare_line_reductions( - paragraph: &Paragraph, - style: &Style, - line_start: usize, - line_end: usize, - mut need: i64, - adjustments: &mut [i32], -) { - let sites = reduction_sites(paragraph, style, line_start, line_end); - for stage in 1_u8..=6 { - if need <= 0 { - break; - } - let discrete: Vec<_> = sites - .iter() - .copied() - .filter(|site| site.stage == stage && site.discrete) - .collect(); - for site in discrete { - if need <= 0 { - break; - } - apply_reduction(site.boundary, i64::from(site.capacity), adjustments); - need = need.saturating_sub(i64::from(site.capacity)); - } - - if need <= 0 { - break; - } - let continuous: Vec<_> = sites - .iter() - .copied() - .filter(|site| site.stage == stage && !site.discrete) - .collect(); - let capacity = continuous.iter().fold(0_i64, |sum, site| { - sum.saturating_add(i64::from(site.capacity)) - }); - let take = need.min(capacity); - distribute_reduction(take, &continuous, style.remainder(), adjustments); - need = need.saturating_sub(take); - } -} - -fn reduction_sites( - paragraph: &Paragraph, - style: &Style, - line_start: usize, - line_end: usize, -) -> Vec { - let mut sites = Vec::new(); - for ordinal in line_start..line_end.saturating_sub(1) { - if is_internal_jidori_boundary(paragraph, ordinal) - || is_internal_stacked_boundary(paragraph, ordinal) - { - continue; - } - if is_western_word_space(paragraph, ordinal) { - let cluster = ¶graph.text.clusters()[ordinal]; - let minimum = quarter_inline_size(paragraph, cluster); - let capacity = effective_cluster_body_advance(paragraph, ordinal) - .saturating_sub(minimum) - .max(0); - push_reduction_site( - &mut sites, - ordinal.saturating_sub(line_start), - paragraph.text.clusters()[ordinal] - .size_override() - .unwrap_or(paragraph.text.size()) - .inline(), - capacity, - 1, - false, - ); - } - append_table_reduction_sites( - paragraph, - style, - ordinal, - ordinal.saturating_sub(line_start), - &mut sites, - ); - } - if let Some(ordinal) = line_end.checked_sub(1).filter(|_| line_start < line_end) { - append_line_end_reduction_site( - paragraph, - style, - ordinal, - ordinal.saturating_sub(line_start), - &mut sites, - ); - } - sites -} - -fn width_after_available_reduction( - paragraph: &Paragraph, - style: &Style, - start: usize, - end: usize, - width: i64, - available: i64, -) -> i64 { - let need = width.saturating_sub(available); - if need <= 0 { - return width; - } - let line_start = cluster_index_at_or_after(paragraph, start); - let line_end = cluster_index_at_or_after(paragraph, end); - let capacity = reduction_sites(paragraph, style, line_start, line_end) - .iter() - .fold(0_i64, |sum, site| { - sum.saturating_add(i64::from(site.capacity)) - }); - let reduced = width.saturating_sub(need.min(capacity)); - reduced.saturating_sub(hanging_amount( - paragraph, style, line_end, reduced, available, - )) -} - -fn append_line_end_reduction_site( - paragraph: &Paragraph, - style: &Style, - ordinal: usize, - boundary: usize, - sites: &mut Vec, -) { - let Some(cluster) = paragraph.text.clusters().get(ordinal) else { - return; - }; - let before = class_of_cluster_with_style(paragraph, style, ordinal); - let cells = match style.reduction_table() { - ReductionTable::Table3 => crate::generated::table3::CELLS, - ReductionTable::Table4 => crate::generated::table4::CELLS, - ReductionTable::Table5 => crate::generated::table5::CELLS, - }; - let Some(cell) = cells - .iter() - .find(|cell| cell.before == before && cell.after == 0) - else { - return; - }; - let Some(limit) = cell.limit.filter(|_| cell.stage != 0) else { - return; - }; - let size = cluster.size_override().unwrap_or(paragraph.text.size()); - let current = line_end_space_after(paragraph, style, ordinal); - let floor = crate::spec::scale_spec_units(size.inline(), limit); - push_reduction_site( - sites, - boundary, - size.inline(), - current.saturating_sub(floor), - cell.stage, - cell.two_valued, - ); -} - -fn hanging_amount( - paragraph: &Paragraph, - style: &Style, - line_end: usize, - occupied: i64, - available: i64, -) -> i64 { - if style.hanging_punctuation() != HangingPunctuation::Hanging || occupied <= available { - return 0; - } - let Some(ordinal) = line_end.checked_sub(1) else { - return 0; - }; - if !matches!( - class_of_cluster_with_style(paragraph, style, ordinal), - crate::spec::FULL_STOP | crate::spec::COMMA - ) { - return 0; - } - occupied - .saturating_sub(available) - .min(i64::from(effective_cluster_body_advance(paragraph, ordinal)).max(0)) -} - -fn append_table_reduction_sites( - paragraph: &Paragraph, - style: &Style, - ordinal: usize, - boundary: usize, - sites: &mut Vec, -) { - let clusters = paragraph.text.clusters(); - let Some(before_cluster) = clusters.get(ordinal) else { - return; - }; - let Some(after_cluster) = clusters.get(ordinal.saturating_add(1)) else { - return; - }; - let before = class_of_cluster_with_style(paragraph, style, ordinal); - let after = class_of_cluster_with_style(paragraph, style, ordinal.saturating_add(1)); - let before_size = before_cluster - .size_override() - .unwrap_or(paragraph.text.size()); - let after_size = after_cluster - .size_override() - .unwrap_or(paragraph.text.size()); - let before_character = single_cluster_character(paragraph, before_cluster); - let after_character = single_cluster_character(paragraph, after_cluster); - let before_solid = before_character.is_some_and(|character| { - contextual_punctuation_is_solid(paragraph, before_cluster, character) - }); - let after_solid = after_character.is_some_and(|character| { - contextual_punctuation_is_solid(paragraph, after_cluster, character) - }); - let components = crate::spec::table_one_space_components( - before, - after, - before_size, - after_size, - before_solid, - after_solid, - ); - - if append_special_reduction_sites( - style.reduction_table(), - before, - after, - components, - [before_size.inline(), after_size.inline()], - boundary, - sites, - ) { - return; - } - - let cells = match style.reduction_table() { - ReductionTable::Table3 => crate::generated::table3::CELLS, - ReductionTable::Table4 => crate::generated::table4::CELLS, - ReductionTable::Table5 => crate::generated::table5::CELLS, - }; - let Some(cell) = cells - .iter() - .find(|cell| cell.before == before && cell.after == after) - else { - return; - }; - let active = match components { - [amount, 0] => Some((amount, before_size.inline())), - [0, amount] => Some((amount, after_size.inline())), - _ => None, - }; - let (Some((amount, weight)), Some(_)) = (active, cell.limit) else { - return; - }; - let floor = crate::spec::scale_spec_units(weight, cell.limit.unwrap_or(0)); - push_reduction_site( - sites, - boundary, - weight, - amount.saturating_sub(floor), - cell.stage, - cell.two_valued, - ); -} - -fn append_special_reduction_sites( - table: ReductionTable, - before: u8, - after: u8, - components: [i32; 2], - weights: [i32; 2], - boundary: usize, - sites: &mut Vec, -) -> bool { - let mut push = |component: usize, floor: i32, stage: u8| { - push_reduction_site( - sites, - boundary, - weights[component], - components[component].saturating_sub(floor), - stage, - false, - ); - }; - match (before, after, table) { - (5, 5, ReductionTable::Table3) => { - push(0, 0, 4); - push(1, 0, 4); - }, - (5, 5, ReductionTable::Table4) => { - push(0, 0, 2); - push(1, 0, 2); - }, - (5 | 6, 5, ReductionTable::Table5) => {}, - (6, 5, ReductionTable::Table3) => push(1, 0, 4), - (6 | 7, 5, ReductionTable::Table4) => push(1, 0, 2), - (7, 5, ReductionTable::Table3) => { - push(0, 0, 5); - push(1, 0, 4); - }, - (7, 5, ReductionTable::Table5) => { - push(0, quarter_rounded_up(weights[0]), 3); - }, - _ => return false, - } - true -} - -fn quarter_rounded_up(value: i32) -> i32 { - (value / 4).saturating_add(i32::from(value % 4 != 0)) -} - -fn push_reduction_site( - sites: &mut Vec, - boundary: usize, - weight: i32, - capacity: i32, - stage: u8, - discrete: bool, -) { - if capacity > 0 && stage != 0 { - sites.push(ReductionSite { - boundary, - weight, - capacity, - stage, - discrete, - }); - } -} - -fn distribute_reduction( - amount: i64, - sites: &[ReductionSite], - remainder: Remainder, - adjustments: &mut [i32], -) { - if amount <= 0 { - return; - } - let weight_sum = sites.iter().fold(0_i64, |sum, site| { - sum.saturating_add(i64::from(site.weight.max(1))) - }); - let mut assigned: Vec = sites - .iter() - .map(|site| { - amount - .saturating_mul(i64::from(site.weight.max(1))) - .checked_div(weight_sum.max(1)) - .unwrap_or(0) - .min(i64::from(site.capacity)) - }) - .collect(); - let left = amount.saturating_sub( - assigned - .iter() - .fold(0_i64, |sum, take| sum.saturating_add(*take)), - ); - let capacities: Vec<_> = sites - .iter() - .zip(&assigned) - .map(|(site, take)| i64::from(site.capacity).saturating_sub(*take).max(0)) - .collect(); - let extra = capped_round_robin(left, &capacities, remainder); - for (take, addition) in assigned.iter_mut().zip(extra) { - *take = take.saturating_add(addition); - } - for (site, take) in sites.iter().zip(assigned) { - apply_reduction(site.boundary, take, adjustments); - } -} - -fn capped_round_robin(amount: i64, capacities: &[i64], remainder: Remainder) -> Vec { - let total_capacity = capacities.iter().fold(0_i64, |sum, capacity| { - sum.saturating_add((*capacity).max(0)) - }); - let target = amount.max(0).min(total_capacity); - let maximum_rounds = capacities - .iter() - .copied() - .map(|capacity| capacity.max(0)) - .max() - .unwrap_or(0) - .min(target); - - let mut lower = 0_i64; - let mut upper = maximum_rounds; - for _ in 0..64 { - let distance = upper.saturating_sub(lower); - if distance == 0 { - break; - } - let previous = (lower, upper); - let rounds = lower - .saturating_add(distance / 2) - .saturating_add(distance % 2); - let consumed = capacities.iter().fold(0_i64, |sum, capacity| { - sum.saturating_add((*capacity).max(0).min(rounds)) - }); - if consumed <= target { - lower = rounds; - } else { - upper = rounds.saturating_sub(1); - } - if (lower, upper) == previous { - break; - } - } - - let mut shares: Vec<_> = capacities - .iter() - .map(|capacity| (*capacity).max(0).min(lower)) - .collect(); - let placed = shares - .iter() - .fold(0_i64, |sum, share| sum.saturating_add(*share)); - let mut left = target.saturating_sub(placed); - match remainder { - Remainder::Leading => { - for (capacity, share) in capacities.iter().zip(&mut shares) { - if left == 0 { - break; - } - if (*capacity).max(0) > lower { - *share = share.saturating_add(1); - left = left.saturating_sub(1); - } - } - }, - Remainder::Trailing => { - for (capacity, share) in capacities.iter().zip(&mut shares).rev() { - if left == 0 { - break; - } - if (*capacity).max(0) > lower { - *share = share.saturating_add(1); - left = left.saturating_sub(1); - } - } - }, - } - shares -} - -fn apply_reduction(boundary: usize, amount: i64, adjustments: &mut [i32]) { - if let Some(adjustment) = adjustments.get_mut(boundary) { - *adjustment = adjustment.saturating_sub(clamp_i32(amount)); - } -} - -fn distribute_adjustment( - amount: i64, - sites: &[(usize, i32, Option)], - remainder: Remainder, - adjustments: &mut [i32], -) { - if amount <= 0 { - return; - } - let weight_sum = sites.iter().fold(0_i64, |sum, (_, weight, _)| { - sum.saturating_add(i64::from((*weight).max(1))) - }); - let assigned: Vec<_> = sites - .iter() - .map(|&(_, weight, cap)| { - let proportional = amount - .saturating_mul(i64::from(weight.max(1))) - .checked_div(weight_sum.max(1)) - .unwrap_or(0); - cap.map_or(proportional, |cap| proportional.min(i64::from(cap))) - }) - .collect(); - let mut placed = 0_i64; - for (&(index, _, _), share) in sites.iter().zip(&assigned) { - if let Some(adjustment) = adjustments.get_mut(index) { - *adjustment = adjustment.saturating_add(clamp_i32(*share)); - placed = placed.saturating_add(*share); - } - } - - let left = amount.saturating_sub(placed); - let capacities: Vec<_> = sites - .iter() - .map(|&(index, _, cap)| { - adjustments.get(index).map_or(0, |adjustment| { - cap.map_or(left, |cap| { - i64::from(cap).saturating_sub(i64::from(*adjustment)).max(0) - }) - }) - }) - .collect(); - let extra = capped_round_robin(left, &capacities, remainder); - for (&(index, _, _), addition) in sites.iter().zip(extra) { - if let Some(adjustment) = adjustments.get_mut(index) { - *adjustment = adjustment.saturating_add(clamp_i32(addition)); - } - } -} - -fn effective_cluster_advance(paragraph: &Paragraph, style: &Style, ordinal: usize) -> i32 { - effective_cluster_body_advance(paragraph, ordinal) - .saturating_add(boundary_space_after_with_style(paragraph, style, ordinal)) -} - -fn effective_cluster_body_advance(paragraph: &Paragraph, ordinal: usize) -> i32 { - if let Some((group, columns, line_gap)) = furawake_cluster_range(paragraph, ordinal) { - if group.start != ordinal { - return 0; - } - furawake_segment( - paragraph, - group, - columns, - line_gap, - paragraph.text.clusters().len(), - ) - .advance - } else { - nested_cluster_body_advance(paragraph, ordinal) - } -} - -fn nested_cluster_body_advance(paragraph: &Paragraph, ordinal: usize) -> i32 { - if let Some(group) = tate_chu_yoko_cluster_range(paragraph, ordinal) { - if group.start != ordinal { - return 0; - } - paragraph.text.clusters()[group] - .iter() - .map(|cluster| { - cluster - .size_override() - .unwrap_or(paragraph.text.size()) - .block() - }) - .max() - .unwrap_or(0) - } else if formula_cluster_range(paragraph, ordinal).is_some() - && single_cluster_character(paragraph, ¶graph.text.clusters()[ordinal]) - .is_some_and(is_math_token) - { - paragraph.text.clusters()[ordinal] - .size_override() - .unwrap_or(paragraph.text.size()) - .inline() - } else { - paragraph.text.clusters()[ordinal].advance() - } -} - -fn effective_cluster_advance_on_line( - paragraph: &Paragraph, - style: &Style, - ordinal: usize, - line_start: usize, - line_end: usize, - line_index: usize, -) -> i32 { - ordinary_cluster_advance_on_line(paragraph, style, ordinal, line_start, line_end, line_index) - .saturating_add(jidori_extra_after( - paragraph, style, ordinal, line_start, line_end, line_index, - )) -} - -fn ordinary_cluster_advance_on_line( - paragraph: &Paragraph, - style: &Style, - ordinal: usize, - line_start: usize, - line_end: usize, - line_index: usize, -) -> i32 { - effective_cluster_advance_on_line_without_ruby(paragraph, style, ordinal, line_start, line_end) - .saturating_add(ruby_boundary_separation_after( - paragraph, style, ordinal, line_start, line_end, line_index, - )) -} - -fn jidori_extra_after( - paragraph: &Paragraph, - style: &Style, - ordinal: usize, - line_start: usize, - line_end: usize, - line_index: usize, -) -> i32 { - let Some((range, cells)) = jidori_cluster_range(paragraph, ordinal) else { - return 0; - }; - if range.start < line_start || range.end > line_end || range.start >= range.end { - return 0; - } - jidori_plan( - paragraph, style, range, cells, line_start, line_end, line_index, - ) - .extra_after(ordinal) -} - -fn jidori_plan( - paragraph: &Paragraph, - style: &Style, - range: Range, - cells: u16, - line_start: usize, - line_end: usize, - line_index: usize, -) -> JidoriPlan { - let natural = range.clone().fold(0_i64, |sum, member| { - let advance = if member.saturating_add(1) == range.end { - effective_cluster_body_advance(paragraph, member) - } else { - ordinary_cluster_advance_on_line( - paragraph, style, member, line_start, line_end, line_index, - ) - }; - sum.saturating_add(i64::from(advance)) - }); - let target = i64::from(paragraph.text.size().inline()).saturating_mul(i64::from(cells)); - let surplus = target.saturating_sub(natural).max(0); - let mut extra_after = vec![0; range.end.saturating_sub(range.start)]; - let mut eligible = Vec::new(); - for before in range.start..range.end.saturating_sub(1) { - if paragraph - .text - .clusters() - .get(before) - .is_some_and(|cluster| break_is_legal(paragraph, style, cluster.range().end)) - { - eligible.push(before); - } - } - if eligible.is_empty() { - if let Some(last) = extra_after.last_mut() { - *last = clamp_i32(surplus); - } - } else { - let divisor = i64::try_from(eligible.len()).unwrap_or(i64::MAX).max(1); - let quotient = surplus.checked_div(divisor).unwrap_or(0); - let remainder = usize::try_from(surplus.rem_euclid(divisor)).unwrap_or(usize::MAX); - for (position, boundary) in eligible.iter().enumerate() { - let receives_remainder = match style.remainder() { - Remainder::Leading => position < remainder, - Remainder::Trailing => { - eligible.len().saturating_sub(position.saturating_add(1)) < remainder - }, - }; - if let Some(extra) = extra_after.get_mut(boundary.saturating_sub(range.start)) { - *extra = clamp_i32(quotient.saturating_add(i64::from(receives_remainder))); - } - } - } - JidoriPlan { range, extra_after } -} - -fn effective_cluster_advance_on_line_without_ruby( - paragraph: &Paragraph, - style: &Style, - ordinal: usize, - line_start: usize, - line_end: usize, -) -> i32 { - if let Some((group, columns, line_gap)) = furawake_cluster_range(paragraph, ordinal) { - if ordinal != group.start { - return 0; - } - return furawake_segment(paragraph, group, columns, line_gap, line_end).advance; - } - if let Some(group) = warichu_cluster_range(paragraph, ordinal) { - let segment_start = group.start.max(line_start); - if ordinal != segment_start { - return 0; - } - return warichu_segment(paragraph, group, line_start, line_end).advance; - } - if is_western_word_space(paragraph, ordinal) - && (ordinal == line_start || ordinal.saturating_add(1) == line_end) - { - return 0; - } - if ordinal.saturating_add(1) == line_end { - return effective_cluster_body_advance(paragraph, ordinal) - .saturating_add(line_end_space_after(paragraph, style, ordinal)); - } - effective_cluster_advance(paragraph, style, ordinal) -} - -fn line_end_space_after(paragraph: &Paragraph, style: &Style, ordinal: usize) -> i32 { - let Some(cluster) = paragraph.text.clusters().get(ordinal) else { - return 0; - }; - let class = class_of_cluster_with_style(paragraph, style, ordinal); - if (class == crate::spec::CLOSING_BRACKET - && style.line_end_punctuation() == LineEndPunctuation::Solid) - || (class == crate::spec::COMMA - && style.line_end_full_stop_comma() == LineEndFullStopComma::Jis) - { - return 0; - } - let size = cluster.size_override().unwrap_or(paragraph.text.size()); - let character = single_cluster_character(paragraph, cluster); - let solid = character - .is_some_and(|character| contextual_punctuation_is_solid(paragraph, cluster, character)); - crate::spec::table_one_space(class, 0, size, size, solid, false) -} - -#[derive(Debug, Clone)] -struct RubyOverhang { - base: Range, - leading: i32, - trailing: i32, - ruby_em: i32, -} - -#[derive(Debug, Clone)] -struct GroupRubyBasePlan { - base: Range, - leading: i32, - trailing: i32, - gaps_after: Vec<(usize, i32)>, -} - -impl GroupRubyBasePlan { - fn gap_after(&self, ordinal: usize) -> i32 { - self.gaps_after - .iter() - .find_map(|(boundary, gap)| (*boundary == ordinal).then_some(*gap)) - .unwrap_or(0) - } -} - -#[derive(Debug, Clone)] -struct PhoneticJukugoRun { - base: Range, - annotation: Range, - base_start: i64, - base_end: i64, - annotation_start: i64, - annotation_width: i64, - ruby_em: i32, - annotation_count: usize, -} - -#[derive(Debug, Clone)] -struct PhoneticJukugoPlan { - runs: Vec, - leading_gap: i32, - gaps_after: Vec<(usize, i32)>, -} - -impl PhoneticJukugoPlan { - fn gap_after(&self, ordinal: usize) -> i32 { - self.gaps_after - .iter() - .find_map(|(boundary, gap)| (*boundary == ordinal).then_some(*gap)) - .unwrap_or(0) - } -} - -#[derive(Debug, Clone, Copy)] -struct PhoneticEdges { - line: LineContext, - leading_allowance: i32, - trailing_allowance: i32, -} - -#[derive(Debug, Clone, Copy)] -enum RubySide { - Leading, - Trailing, -} - -fn visit_ruby_spans( - paragraph: &Paragraph, - style: &Style, - mut visit: impl FnMut(&Ruby, Range, Range), -) { - for construct in ¶graph.constructs { - let ConstructKind::Ruby(ruby) = construct.kind() else { - continue; - }; - match ruby.kind() { - RubyKind::Group => visit(ruby, ruby.base(), 0..ruby.annotation().source().len()), - RubyKind::Mono => { - for run in ruby.runs() { - visit(ruby, run.base(), run.annotation()); - } - }, - RubyKind::Jukugo => { - if style.jukugo_ruby_layout() == JukugoRubyLayout::Phonetic { - continue; - } - let per_base = ruby - .runs() - .iter() - .all(|run| annotation_cluster_count(ruby.annotation(), &run.annotation()) <= 2); - if per_base { - for run in ruby.runs() { - visit(ruby, run.base(), run.annotation()); - } - } else { - visit(ruby, ruby.base(), 0..ruby.annotation().source().len()); - } - }, - } - } -} - -fn phonetic_jukugo_plan( - paragraph: &Paragraph, - style: &Style, - ruby: &Ruby, - line_start: usize, - line_end: usize, - line_index: usize, -) -> Option { - if ruby.kind() != RubyKind::Jukugo || style.jukugo_ruby_layout() != JukugoRubyLayout::Phonetic { - return None; - } - - let mut runs = Vec::new(); - for run in ruby.runs() { - let base = cluster_index_at_or_after(paragraph, run.base().start) - ..cluster_index_at_or_after(paragraph, run.base().end); - if base.start < line_start || base.end > line_end { - continue; - } - let (annotation_count, annotation_width, ruby_em) = - ruby_annotation_metrics(ruby.annotation(), &run.annotation()); - let base_width = ruby_base_width(paragraph, style, &base); - runs.push(PhoneticJukugoRun { - base, - annotation: run.annotation(), - base_start: 0, - base_end: base_width, - annotation_start: 0, - annotation_width, - ruby_em, - annotation_count, - }); - } - let (first, last) = (runs.first()?, runs.last()?); - let leading_allowance = if first.base.start == line_start { - if line_index == 0 && style.ruby_overhang_indent() == RubyOverhangIndent::Permitted { - line_head_indent(paragraph, style, line_start, line_index).min(first.ruby_em) - } else { - 0 - } - } else { - ruby_neighbor_overhang_allowance( - paragraph, - style, - first.base.start.saturating_sub(1), - RubySide::Leading, - first.ruby_em, - ) - }; - let trailing_allowance = if last.base.end == line_end { - 0 - } else { - ruby_neighbor_overhang_allowance( - paragraph, - style, - last.base.end, - RubySide::Trailing, - last.ruby_em, - ) - }; - let edges = PhoneticEdges { - line: LineContext { - start: line_start, - end: line_end, - index: line_index, - }, - leading_allowance, - trailing_allowance, - }; - let maximum_expansion = runs.iter().fold(0_i64, |sum, run| { - let base_width = run.base_end.saturating_sub(run.base_start); - sum.saturating_add(run.annotation_width.saturating_sub(base_width).max(0)) - }); - - build_phonetic_jukugo_plan(paragraph, style, &runs, 0, edges).or_else(|| { - let mut lower = 1_i64; - let mut upper = maximum_expansion; - let upper_plan = build_phonetic_jukugo_plan(paragraph, style, &runs, upper, edges)?; - while lower < upper { - let previous = (lower, upper); - let middle = lower.saturating_add(upper.saturating_sub(lower) / 2); - if build_phonetic_jukugo_plan(paragraph, style, &runs, middle, edges).is_some() { - upper = middle; - } else { - lower = middle.saturating_add(1); - } - if (lower, upper) == previous { - return Some(upper_plan); - } - } - if lower == maximum_expansion { - Some(upper_plan) - } else { - build_phonetic_jukugo_plan(paragraph, style, &runs, lower, edges) - } - }) -} - -fn build_phonetic_jukugo_plan( - paragraph: &Paragraph, - style: &Style, - raw_runs: &[PhoneticJukugoRun], - expansion: i64, - edges: PhoneticEdges, -) -> Option { - let assigned = apportion_phonetic_expansion(raw_runs, expansion, style.remainder()); - let mut before = alloc::vec![0_i64; raw_runs.len()]; - let mut after = alloc::vec![0_i64; raw_runs.len()]; - for (index, (run, amount)) in raw_runs.iter().zip(assigned).enumerate() { - if amount == 0 { - continue; - } - if run.base.start == edges.line.start && run.base.end != edges.line.end { - after[index] = amount; - } else if run.base.end == edges.line.end && run.base.start != edges.line.start { - before[index] = amount; - } else { - let half = amount / 2; - let odd = amount % 2; - match style.remainder() { - Remainder::Leading => { - before[index] = half.saturating_add(odd); - after[index] = half; - }, - Remainder::Trailing => { - before[index] = half; - after[index] = half.saturating_add(odd); - }, - } - } - } - - let mut leading_gap = 0_i64; - let mut gaps_after = Vec::<(usize, i64)>::new(); - for (index, run) in raw_runs.iter().enumerate() { - if before[index] != 0 { - if run.base.start == edges.line.start { - leading_gap = leading_gap.saturating_add(before[index]); - } else { - add_phonetic_gap( - &mut gaps_after, - run.base.start.saturating_sub(1), - before[index], - ); - } - } - if after[index] != 0 { - add_phonetic_gap( - &mut gaps_after, - run.base.end.saturating_sub(1), - after[index], - ); - } - } - - let first = raw_runs.first()?; - let mut cursor = if first.base.start == edges.line.start { - leading_gap - } else { - phonetic_gap_after(&gaps_after, first.base.start.saturating_sub(1)) - }; - let mut runs = Vec::with_capacity(raw_runs.len()); - let mut inter_run_boundary = None; - for raw in raw_runs { - if let Some(boundary) = inter_run_boundary { - cursor = cursor - .saturating_add(i64::from(boundary_space_after(paragraph, boundary))) - .saturating_add(phonetic_gap_after(&gaps_after, boundary)); - } - let base_start = cursor; - let mut base_end = base_start; - for ordinal in raw.base.clone() { - cursor = cursor.saturating_add(i64::from(effective_cluster_body_advance( - paragraph, ordinal, - ))); - base_end = cursor; - if ordinal.saturating_add(1) < raw.base.end { - cursor = cursor - .saturating_add(i64::from(boundary_space_after(paragraph, ordinal))) - .saturating_add(phonetic_gap_after(&gaps_after, ordinal)); - } - } - inter_run_boundary = Some(raw.base.end.saturating_sub(1)); - runs.push(PhoneticJukugoRun { - base: raw.base.clone(), - annotation: raw.annotation.clone(), - base_start, - base_end, - annotation_start: 0, - annotation_width: raw.annotation_width, - ruby_em: raw.ruby_em, - annotation_count: raw.annotation_count, - }); - } - - let mut lower = Vec::with_capacity(runs.len()); - let mut upper = Vec::with_capacity(runs.len()); - for (index, run) in runs.iter().enumerate() { - let minimum = if index == 0 { - run.base_start - .saturating_sub(before[index]) - .saturating_sub(i64::from(edges.leading_allowance)) - } else { - runs[index.saturating_sub(1)] - .base_end - .saturating_sub(i64::from(run.ruby_em)) - }; - let maximum_end = if index.saturating_add(1) == runs.len() { - run.base_end - .saturating_add(after[index]) - .saturating_add(i64::from(edges.trailing_allowance)) - } else { - runs[index.saturating_add(1)] - .base_start - .saturating_add(i64::from(run.ruby_em)) - }; - lower.push(minimum); - upper.push(maximum_end.saturating_sub(run.annotation_width)); - } - - let mut latest = upper.clone(); - for index in (0..latest.len().saturating_sub(1)).rev() { - latest[index] = latest[index] - .min(latest[index.saturating_add(1)].saturating_sub(runs[index].annotation_width)); - } - if latest - .iter() - .zip(&lower) - .any(|(latest, lower)| latest < lower) - { - return None; - } - - let mut previous_end: Option = None; - for (index, run) in runs.iter_mut().enumerate() { - let minimum = previous_end.map_or(lower[index], |end| end.max(lower[index])); - let preferred = run.base_start.max(minimum); - let start = preferred.min(latest[index]); - if start < minimum { - return None; - } - run.annotation_start = start; - previous_end = Some(start.saturating_add(run.annotation_width)); - } - - Some(PhoneticJukugoPlan { - runs, - leading_gap: clamp_i32(leading_gap), - gaps_after: gaps_after - .into_iter() - .map(|(ordinal, gap)| (ordinal, clamp_i32(gap))) - .collect(), - }) -} - -fn apportion_phonetic_expansion( - runs: &[PhoneticJukugoRun], - total: i64, - remainder: Remainder, -) -> Vec { - let weight = runs - .iter() - .filter(|run| run.annotation_count > 2) - .fold(0_i64, |sum, run| { - sum.saturating_add(run.annotation_width.max(1)) - }); - let mut assigned: Vec<_> = runs - .iter() - .map(|run| { - if run.annotation_count > 2 && weight != 0 { - total - .saturating_mul(run.annotation_width.max(1)) - .checked_div(weight) - .unwrap_or(0) - } else { - 0 - } - }) - .collect(); - let mut remainder_units = total.saturating_sub( - assigned - .iter() - .fold(0_i64, |sum, amount| sum.saturating_add(*amount)), - ); - let indices: Vec<_> = match remainder { - Remainder::Leading => (0..runs.len()).collect(), - Remainder::Trailing => (0..runs.len()).rev().collect(), - }; - for index in indices { - if remainder_units == 0 { - break; - } - if runs[index].annotation_count > 2 { - assigned[index] = assigned[index].saturating_add(1); - remainder_units = remainder_units.saturating_sub(1); - } - } - assigned -} - -fn add_phonetic_gap(gaps: &mut Vec<(usize, i64)>, ordinal: usize, amount: i64) { - if let Some((_, gap)) = gaps.iter_mut().find(|(boundary, _)| *boundary == ordinal) { - *gap = gap.saturating_add(amount); - } else { - gaps.push((ordinal, amount)); - } -} - -fn phonetic_gap_after(gaps: &[(usize, i64)], ordinal: usize) -> i64 { - gaps.iter() - .find_map(|(boundary, gap)| (*boundary == ordinal).then_some(*gap)) - .unwrap_or(0) -} - -fn ruby_annotation_metrics( - annotation: &crate::ShapedText, - range: &Range, -) -> (usize, i64, i32) { - annotation - .clusters() - .iter() - .filter(|cluster| { - let cluster = cluster.range(); - range.start <= cluster.start && cluster.end <= range.end - }) - .fold((0_usize, 0_i64, 0_i32), |(count, width, em), cluster| { - ( - count.saturating_add(1), - width.saturating_add(i64::from(cluster.advance())), - em.max( - cluster - .size_override() - .unwrap_or(annotation.size()) - .inline(), - ), - ) - }) -} - -fn ruby_base_width(paragraph: &Paragraph, style: &Style, base: &Range) -> i64 { - base.clone().fold(0_i64, |sum, ordinal| { - let boundary = if ordinal.saturating_add(1) < base.end { - boundary_space_after_with_style(paragraph, style, ordinal) - } else { - 0 - }; - sum.saturating_add(i64::from(effective_cluster_body_advance( - paragraph, ordinal, - ))) - .saturating_add(i64::from(boundary)) - }) -} - -fn base_distribution_plan( - paragraph: &Paragraph, - style: &Style, - base_range: &Range, - annotation: &crate::ShapedText, - annotation_range: &Range, - distribution: GroupRubyDistribution, -) -> Option { - let base = cluster_index_at_or_after(paragraph, base_range.start) - ..cluster_index_at_or_after(paragraph, base_range.end); - let count = base.end.saturating_sub(base.start); - if count < 2 { - return None; - } - let (_, annotation_width, _) = ruby_annotation_metrics(annotation, annotation_range); - let surplus = annotation_width.saturating_sub(ruby_base_width(paragraph, style, &base)); - if surplus <= 0 { - return None; - } - - let weights = match distribution { - GroupRubyDistribution::Jis => { - let mut weights = Vec::with_capacity(count.saturating_add(1)); - weights.push(1); - weights.extend((1..count).map(|_| 2)); - weights.push(1); - weights - }, - GroupRubyDistribution::Flush => vec![1; count.saturating_sub(1)], - }; - let shares = proportional_shares(surplus, &weights, style.remainder()); - let (leading, trailing, interior) = match distribution { - GroupRubyDistribution::Jis => ( - shares.first().copied().unwrap_or(0), - shares.last().copied().unwrap_or(0), - shares - .iter() - .skip(1) - .take(count.saturating_sub(1)) - .copied() - .collect::>(), - ), - GroupRubyDistribution::Flush => (0, 0, shares), - }; - let gaps_after = interior - .into_iter() - .enumerate() - .map(|(offset, gap)| (base.start.saturating_add(offset), gap)) - .collect(); - Some(GroupRubyBasePlan { - base, - leading, - trailing, - gaps_after, - }) -} - -fn group_ruby_base_plan( - paragraph: &Paragraph, - style: &Style, - ruby: &Ruby, - base: &Range, - annotation: &Range, -) -> Option { - let distribution = match ruby.kind() { - RubyKind::Group => style.group_ruby_distribution(), - RubyKind::Jukugo - if style.jukugo_ruby_layout() != JukugoRubyLayout::Phonetic && *base == ruby.base() => - { - GroupRubyDistribution::Jis - }, - RubyKind::Mono | RubyKind::Jukugo => return None, - }; - base_distribution_plan( - paragraph, - style, - base, - ruby.annotation(), - annotation, - distribution, - ) -} - -fn ruby_span_overhang( - paragraph: &Paragraph, - style: &Style, - ruby: &Ruby, - base: Range, - annotation: Range, - line_start: usize, - line_end: usize, -) -> Option { - if group_ruby_base_plan(paragraph, style, ruby, &base, &annotation).is_some() { - return None; - } - let base = cluster_index_at_or_after(paragraph, base.start) - ..cluster_index_at_or_after(paragraph, base.end); - if base.start < line_start || base.end > line_end { - return None; - } - let base_width = ruby_base_width(paragraph, style, &base); - let mut annotation_width = 0_i64; - let mut ruby_em = 0_i32; - for cluster in ruby.annotation().clusters().iter().filter(|cluster| { - let cluster = cluster.range(); - annotation.start <= cluster.start && cluster.end <= annotation.end - }) { - annotation_width = annotation_width.saturating_add(i64::from(cluster.advance())); - ruby_em = ruby_em.max( - cluster - .size_override() - .unwrap_or(ruby.annotation().size()) - .inline(), - ); - } - let surplus = annotation_width.saturating_sub(base_width).max(0); - if surplus == 0 { - return None; - } - let half = surplus / 2; - let odd = surplus % 2; - let (leading, trailing) = match style.remainder() { - Remainder::Leading => (half.saturating_add(odd), half), - Remainder::Trailing => (half, half.saturating_add(odd)), - }; - Some(RubyOverhang { - base, - leading: clamp_i32(leading), - trailing: clamp_i32(trailing), - ruby_em, - }) -} - -fn ruby_boundary_separation_after( - paragraph: &Paragraph, - style: &Style, - before: usize, - line_start: usize, - line_end: usize, - line_index: usize, -) -> i32 { - let mut required = 0_i32; - let mut distributed = 0_i32; - visit_ruby_spans(paragraph, style, |ruby, base, annotation| { - if let Some(plan) = group_ruby_base_plan(paragraph, style, ruby, &base, &annotation) { - if plan.base.start < line_start || plan.base.end > line_end { - return; - } - if plan.base.start == before.saturating_add(1) && before >= line_start { - distributed = distributed.saturating_add(plan.leading); - } - distributed = distributed.saturating_add(plan.gap_after(before)); - if plan.base.end == before.saturating_add(1) { - distributed = distributed.saturating_add(plan.trailing); - } - return; - } - let Some(overhang) = ruby_span_overhang( - paragraph, style, ruby, base, annotation, line_start, line_end, - ) else { - return; - }; - if overhang.base.start == before.saturating_add(1) && before >= line_start { - let allowance = ruby_neighbor_overhang_allowance( - paragraph, - style, - before, - RubySide::Leading, - overhang.ruby_em, - ); - required = required.max(overhang.leading.saturating_sub(allowance)); - } - if overhang.base.end == before.saturating_add(1) { - let allowance = if overhang.base.end < line_end { - ruby_neighbor_overhang_allowance( - paragraph, - style, - overhang.base.end, - RubySide::Trailing, - overhang.ruby_em, - ) - } else { - 0 - }; - required = required.max(overhang.trailing.saturating_sub(allowance)); - } - }); - for construct in ¶graph.constructs { - let ConstructKind::Ruby(ruby) = construct.kind() else { - continue; - }; - if let Some(plan) = - phonetic_jukugo_plan(paragraph, style, ruby, line_start, line_end, line_index) - { - required = required.max(plan.gap_after(before)); - } - } - required.saturating_add(distributed) -} - -fn ruby_line_leading_separation( - paragraph: &Paragraph, - style: &Style, - line_start: usize, - line_end: usize, - line_index: usize, -) -> i32 { - let mut required = 0_i32; - let mut distributed = 0_i32; - visit_ruby_spans(paragraph, style, |ruby, base, annotation| { - if let Some(plan) = group_ruby_base_plan(paragraph, style, ruby, &base, &annotation) { - if plan.base.start == line_start && plan.base.end <= line_end { - distributed = distributed.saturating_add(plan.leading); - } - return; - } - let Some(overhang) = ruby_span_overhang( - paragraph, style, ruby, base, annotation, line_start, line_end, - ) else { - return; - }; - if overhang.base.start != line_start { - return; - } - let allowance = - if line_index == 0 && style.ruby_overhang_indent() == RubyOverhangIndent::Permitted { - line_head_indent(paragraph, style, line_start, line_index).min(overhang.ruby_em) - } else { - 0 - }; - required = required.max(overhang.leading.saturating_sub(allowance)); - }); - for construct in ¶graph.constructs { - let ConstructKind::Ruby(ruby) = construct.kind() else { - continue; - }; - if let Some(plan) = - phonetic_jukugo_plan(paragraph, style, ruby, line_start, line_end, line_index) - { - required = required.max(plan.leading_gap); - } - } - required.saturating_add(distributed) -} - -fn ruby_neighbor_overhang_allowance( - paragraph: &Paragraph, - style: &Style, - ordinal: usize, - side: RubySide, - ruby_em: i32, -) -> i32 { - let Some(cluster) = paragraph.text.clusters().get(ordinal) else { - return 0; - }; - let Some(character) = single_cluster_character(paragraph, cluster) else { - return 0; - }; - let adjacent_space = match side { - RubySide::Leading => boundary_space_after_with_style(paragraph, style, ordinal), - RubySide::Trailing => ordinal.checked_sub(1).map_or(0, |before| { - boundary_space_after_with_style(paragraph, style, before) - }), - }; - let punctuation_allowance = match side { - RubySide::Leading if is_opening_bracket(character) => ruby_em, - RubySide::Leading - if is_closing_bracket(character) || is_full_stop(character) || is_comma(character) => - { - ruby_em.min(adjacent_space) - }, - RubySide::Trailing if is_opening_bracket(character) => ruby_em.min(adjacent_space), - RubySide::Trailing - if is_closing_bracket(character) || is_full_stop(character) || is_comma(character) => - { - ruby_em - }, - _ => 0, - }; - let kana = match style.ruby_overhang_kana() { - RubyOverhangKana::Kana => is_hiragana(character) || is_katakana(character), - RubyOverhangKana::Jis => is_hiragana(character), - RubyOverhangKana::Any => true, - RubyOverhangKana::None => false, - }; - if punctuation_allowance != 0 { - punctuation_allowance - } else if is_middle_dot(character) { - ruby_em.min(adjacent_space.saturating_add(half_rounded_up(ruby_em))) - } else if character == '\u{3000}' || is_inseparable_character(character) || kana { - ruby_em - } else { - 0 - } -} - -fn half_rounded_up(value: i32) -> i32 { - (value / 2).saturating_add(value % 2) -} - -fn is_hiragana(character: char) -> bool { - crate::spec::is_hiragana(character) -} - -fn is_katakana(character: char) -> bool { - crate::spec::is_katakana(character) -} - -fn is_inseparable_character(character: char) -> bool { - crate::spec::single_has_class(character, crate::spec::INSEPARABLE) -} - -fn is_western_word_space(paragraph: &Paragraph, ordinal: usize) -> bool { - let Some(cluster) = paragraph.text.clusters().get(ordinal) else { - return false; - }; - ¶graph.text.source()[cluster.range()] == " " - && cluster.frame_override().unwrap_or(paragraph.text.frame()) == Frame::Proportional - && matches!(cluster.role(), None | Some(ClusterRole::Text)) -} - -fn boundary_space_after(paragraph: &Paragraph, ordinal: usize) -> i32 { - tate_chu_yoko_boundary_space_after(paragraph, ordinal) - .or_else(|| formula_boundary_space_after(paragraph, ordinal)) - .unwrap_or_else(|| ordinary_boundary_space_after(paragraph, ordinal)) -} - -fn boundary_space_after_with_style(paragraph: &Paragraph, style: &Style, ordinal: usize) -> i32 { - tate_chu_yoko_boundary_space_after(paragraph, ordinal) - .or_else(|| formula_boundary_space_after(paragraph, ordinal)) - .unwrap_or_else(|| ordinary_boundary_space_after_with_style(paragraph, style, ordinal)) -} - -fn tate_chu_yoko_boundary_space_after(paragraph: &Paragraph, ordinal: usize) -> Option { - if paragraph.writing_mode != WritingMode::VerticalRl { - return None; - } - let clusters = paragraph.text.clusters(); - let current_group = tate_chu_yoko_cluster_range(paragraph, ordinal); - if let Some(group) = current_group { - if group.start != ordinal || group.end >= clusters.len() { - return Some(0); - } - let following = &clusters[group.end]; - let character = single_cluster_character(paragraph, following); - if character.is_some_and(is_opening_bracket) { - return Some(half_inline_size(paragraph, following)); - } - return Some(0); - } - - let following_ordinal = ordinal.saturating_add(1); - if following_ordinal >= clusters.len() - || tate_chu_yoko_cluster_range(paragraph, following_ordinal) - .is_none_or(|group| group.start != following_ordinal) - { - return None; - } - let current = &clusters[ordinal]; - let character = single_cluster_character(paragraph, current); - if character.is_some_and(|character| { - is_closing_bracket(character) || is_full_stop(character) || is_comma(character) - }) { - Some(half_inline_size(paragraph, current)) - } else { - Some(0) - } -} - -fn ordinary_boundary_space_after(paragraph: &Paragraph, ordinal: usize) -> i32 { - let clusters = paragraph.text.clusters(); - let Some(current) = clusters.get(ordinal) else { - return 0; - }; - let Some(following) = clusters.get(ordinal.saturating_add(1)) else { - return 0; - }; - let current_character = single_cluster_character(paragraph, current); - let following_character = single_cluster_character(paragraph, following); - let current_solid = current_character - .is_some_and(|character| contextual_punctuation_is_solid(paragraph, current, character)); - let following_solid = following_character - .is_some_and(|character| contextual_punctuation_is_solid(paragraph, following, character)); - let current_size = current.size_override().unwrap_or(paragraph.text.size()); - let following_size = following.size_override().unwrap_or(paragraph.text.size()); - crate::spec::table_one_space( - class_of_cluster(paragraph, ordinal), - class_of_cluster(paragraph, ordinal.saturating_add(1)), - current_size, - following_size, - current_solid, - following_solid, - ) -} - -fn ordinary_boundary_space_after_with_style( - paragraph: &Paragraph, - style: &Style, - ordinal: usize, -) -> i32 { - let clusters = paragraph.text.clusters(); - let Some(current) = clusters.get(ordinal) else { - return 0; - }; - let Some(following) = clusters.get(ordinal.saturating_add(1)) else { - return 0; - }; - let current_character = single_cluster_character(paragraph, current); - let following_character = single_cluster_character(paragraph, following); - let current_solid = current_character - .is_some_and(|character| contextual_punctuation_is_solid(paragraph, current, character)); - let following_solid = following_character - .is_some_and(|character| contextual_punctuation_is_solid(paragraph, following, character)); - let current_size = current.size_override().unwrap_or(paragraph.text.size()); - let following_size = following.size_override().unwrap_or(paragraph.text.size()); - let before = class_of_cluster_with_style(paragraph, style, ordinal); - let after = class_of_cluster_with_style(paragraph, style, ordinal.saturating_add(1)); - if before == 4 && current.role() == Some(ClusterRole::SentenceTerminator) { - return if after == crate::spec::CLOSING_BRACKET { - 0 - } else { - current_size.inline() - }; - } - let table = crate::spec::table_one_space( - before, - after, - current_size, - following_size, - current_solid, - following_solid, - ); - let table_is_blank = table_one_cell_is_blank(before, after); - if !table_is_blank - || style.sentence_medial_dividing_mark() != SentenceMedialDividingMark::QuarterEm - { - return table; - } - let before_quarter = if before == 4 && current.role() == Some(ClusterRole::SentenceMedial) { - quarter_inline_size(paragraph, current) - } else { - 0 - }; - let after_quarter = if after == 4 && following.role() == Some(ClusterRole::SentenceMedial) { - quarter_inline_size(paragraph, following) - } else { - 0 - }; - table - .saturating_add(before_quarter) - .saturating_add(after_quarter) -} - -fn table_one_cell_is_blank(before: u8, after: u8) -> bool { - crate::generated::table1::CELLS - .iter() - .find(|cell| cell.before == before && cell.after == after) - .is_none_or(|cell| cell.terms.is_empty()) -} - -fn class_of_cluster(paragraph: &Paragraph, ordinal: usize) -> u8 { - class_of_cluster_impl(paragraph, None, ordinal) -} - -fn class_of_cluster_with_style(paragraph: &Paragraph, style: &Style, ordinal: usize) -> u8 { - class_of_cluster_impl(paragraph, Some(style), ordinal) -} - -fn class_of_cluster_impl(paragraph: &Paragraph, style: Option<&Style>, ordinal: usize) -> u8 { - if tate_chu_yoko_cluster_range(paragraph, ordinal).is_some() { - return 30; - } - let cluster = ¶graph.text.clusters()[ordinal]; - let range = cluster.range(); - for construct in ¶graph.constructs { - if !ranges_overlap(&construct.range(), &range) { - continue; - } - match construct.kind() { - ConstructKind::Ruby(ruby) => { - return if ruby.kind() == RubyKind::Jukugo { - 23 - } else { - 22 - }; - }, - ConstructKind::Emphasis { .. } | ConstructKind::Script { .. } => return 21, - ConstructKind::ReferenceMark { .. } => return 20, - _ => {}, - } - } - let frame = cluster.frame_override().unwrap_or(paragraph.text.frame()); - crate::spec::class_of( - ¶graph.text.source()[range], - frame, - cluster.role(), - paragraph.writing_mode, - style.is_some_and(|style| style.unlisted_code_point() == UnlistedCodePoint::Ideographic), - style.is_some_and(|style| style.ambiguous_context() == AmbiguousContext::HighestClass), - style.is_some_and(|style| { - style.grouped_numeral_qualification() == GroupedNumeralQualification::ByRole - }), - ) -} - -fn contextual_punctuation_is_solid( - paragraph: &Paragraph, - cluster: &crate::Cluster, - character: char, -) -> bool { - match cluster.role() { - Some(ClusterRole::WarichuBracket) => { - is_opening_bracket(character) || is_closing_bracket(character) - }, - Some(ClusterRole::DecimalPoint) => { - character == '・' && paragraph.writing_mode == WritingMode::VerticalRl - }, - Some(ClusterRole::DigitGroupSeparator) => { - character == '、' && paragraph.writing_mode == WritingMode::VerticalRl - }, - Some(ClusterRole::GroupedNumeral | ClusterRole::UnitSymbol | ClusterRole::Formula) => { - character == '・' - }, - _ => false, - } -} - -fn formula_boundary_space_after(paragraph: &Paragraph, ordinal: usize) -> Option { - let clusters = paragraph.text.clusters(); - let following_ordinal = ordinal.saturating_add(1); - let current_formula = formula_cluster_range(paragraph, ordinal); - let following_formula = formula_cluster_range(paragraph, following_ordinal); - if current_formula.is_none() && following_formula.is_none() { - return None; - } - let current = clusters.get(ordinal)?; - let following = clusters.get(following_ordinal)?; - - match (current_formula, following_formula) { - (Some(current_range), Some(_)) => { - if current_range.start == 0 && current_range.end == clusters.len() { - let current_character = single_cluster_character(paragraph, current); - let following_character = single_cluster_character(paragraph, following); - let symbol_boundary = current_character.is_some_and(is_math_symbol) - ^ following_character.is_some_and(is_math_symbol); - if symbol_boundary { - let symbol = if current_character.is_some_and(is_math_symbol) { - current - } else { - following - }; - return Some(quarter_inline_size(paragraph, symbol)); - } - } - Some(0) - }, - (None, Some(_)) => Some(formula_outer_boundary_space(paragraph, current, following)), - (Some(_), None) => Some(formula_outer_boundary_space(paragraph, following, current)), - _ => Some(0), - } -} - -fn formula_outer_boundary_space( - paragraph: &Paragraph, - outside: &crate::Cluster, - endpoint: &crate::Cluster, -) -> i32 { - if is_japanese_formula_neighbor(paragraph, outside) - && formula_endpoint_needs_quarter(paragraph, endpoint) - { - quarter_inline_size(paragraph, endpoint) - } else { - 0 - } -} - -fn formula_endpoint_needs_quarter(paragraph: &Paragraph, cluster: &crate::Cluster) -> bool { - let character = single_cluster_character(paragraph, cluster); - !character.is_some_and(is_math_token) - && (cluster.frame_override().unwrap_or(paragraph.text.frame()) == Frame::Proportional - || cluster.role() == Some(ClusterRole::GroupedNumeral)) -} - -fn is_japanese_formula_neighbor(paragraph: &Paragraph, cluster: &crate::Cluster) -> bool { - if cluster.frame_override().unwrap_or(paragraph.text.frame()) == Frame::Proportional { - return false; - } - single_cluster_character(paragraph, cluster).is_some_and(|character| { - !character.is_whitespace() - && !is_opening_bracket(character) - && !is_closing_bracket(character) - && !is_full_stop(character) - && !is_comma(character) - && !is_middle_dot(character) - && !is_math_token(character) - }) -} - -fn furawake_segment( - paragraph: &Paragraph, - range: Range, - columns: u16, - line_gap: i32, - line_end: usize, -) -> FurawakeSegment { - let mut lanes = Vec::with_capacity(usize::from(columns)); - let start_offset = paragraph.text.clusters()[range.start].range().start; - let end_offset = paragraph.text.clusters()[range.end.saturating_sub(1)] - .range() - .end; - let mut start = range.start; - for opportunity in paragraph.breaks.iter().filter(|opportunity| { - start_offset < opportunity.offset() && opportunity.offset() < end_offset - }) { - let split = cluster_index_at_or_after(paragraph, opportunity.offset()); - lanes.push(start..split); - start = split; - } - lanes.push(start..range.end); - - let widths: Vec<_> = lanes - .iter() - .map(|lane| construct_lane_width(paragraph, lane)) - .collect(); - let block_extents: Vec<_> = lanes - .iter() - .map(|lane| construct_lane_block_extent(paragraph, lane)) - .collect(); - let block_extent = block_extents - .iter() - .copied() - .fold(0_i32, i32::saturating_add) - .saturating_add( - line_gap - .saturating_mul(i32::try_from(lanes.len().saturating_sub(1)).unwrap_or(i32::MAX)), - ); - let outer_space = if range.end < line_end { - boundary_space_after(paragraph, range.end.saturating_sub(1)) - } else { - 0 - }; - let advance = widths - .iter() - .copied() - .max() - .unwrap_or(0) - .saturating_add(outer_space); - FurawakeSegment { - range, - lanes, - block_extents, - line_gap, - advance, - block_extent, - } -} - -fn construct_lane_width(paragraph: &Paragraph, lane: &Range) -> i32 { - lane.clone().fold(0_i32, |sum, ordinal| { - let logical_end = tate_chu_yoko_cluster_range(paragraph, ordinal) - .map_or_else(|| ordinal.saturating_add(1), |group| group.end); - let boundary = if logical_end < lane.end { - boundary_space_after(paragraph, ordinal) - } else { - 0 - }; - sum.saturating_add(nested_cluster_body_advance(paragraph, ordinal)) - .saturating_add(boundary) - }) -} - -fn construct_lane_block_extent(paragraph: &Paragraph, lane: &Range) -> i32 { - paragraph.text.clusters()[lane.clone()] - .iter() - .map(|cluster| { - cluster - .size_override() - .unwrap_or(paragraph.text.size()) - .block() - }) - .max() - .unwrap_or(0) -} - -fn place_furawake_segment( - paragraph: &Paragraph, - segment: &FurawakeSegment, - inline: i64, - block_origin: i32, - placed: &mut Vec, -) { - let main_block_extent = paragraph.text.size().block(); - let mut block = match paragraph.writing_mode { - WritingMode::HorizontalTb => i64::from(block_origin).saturating_add(i64::from( - main_block_extent.saturating_sub(segment.block_extent) / 2, - )), - WritingMode::VerticalRl => i64::from(block_origin).saturating_add(i64::from( - segment.block_extent.saturating_sub(main_block_extent) / 2, - )), - }; - for (lane_index, lane) in segment.lanes.iter().enumerate() { - let mut cursor = inline; - for ordinal in lane.clone() { - let logical_end = tate_chu_yoko_cluster_range(paragraph, ordinal) - .map_or_else(|| ordinal.saturating_add(1), |group| group.end); - let boundary = if logical_end < lane.end { - boundary_space_after(paragraph, ordinal) - } else { - 0 - }; - let advance = nested_cluster_body_advance(paragraph, ordinal).saturating_add(boundary); - place_construct_member(paragraph, ordinal, cursor, block, advance, placed); - cursor = cursor.saturating_add(i64::from(advance)); - } - let step = segment.block_extents[lane_index].saturating_add(segment.line_gap); - match paragraph.writing_mode { - WritingMode::HorizontalTb => { - block = block.saturating_add(i64::from(step)); - }, - WritingMode::VerticalRl => { - block = block.saturating_sub(i64::from(step)); - }, - } - } -} - -fn place_construct_member( - paragraph: &Paragraph, - ordinal: usize, - inline: i64, - block: i64, - advance: i32, - placed: &mut Vec, -) { - let cluster = ¶graph.text.clusters()[ordinal]; - let size = cluster.size_override().unwrap_or(paragraph.text.size()); - let frame = cluster.frame_override().unwrap_or(paragraph.text.frame()); - let (writing_mode, transform) = local_orientation(paragraph, ordinal, frame); - placed.push(ClusterPlacement { - origin: PlacementOrigin::Cluster(ordinal), - range: cluster.range(), - inline: clamp_i32(inline), - block: clamp_i32(block), - advance, - size, - frame, - writing_mode, - transform, - }); -} - -fn warichu_segment( - paragraph: &Paragraph, - full_range: Range, - line_start: usize, - line_end: usize, -) -> WarichuSegment { - let range = full_range.start.max(line_start)..full_range.end.min(line_end); - let clusters = paragraph.text.clusters(); - let leading_bracket = (range.start == full_range.start) - .then_some(range.start) - .filter(|ordinal| { - clusters - .get(*ordinal) - .is_some_and(|cluster| cluster.role() == Some(ClusterRole::WarichuBracket)) - }); - let trailing_ordinal = range.end.saturating_sub(1); - let trailing_bracket = (range.end == full_range.end) - .then_some(trailing_ordinal) - .filter(|ordinal| { - clusters - .get(*ordinal) - .is_some_and(|cluster| cluster.role() == Some(ClusterRole::WarichuBracket)) - }); - let interior_start = leading_bracket.map_or(range.start, |ordinal| ordinal.saturating_add(1)); - let interior_end = trailing_bracket.unwrap_or(range.end); - let split = choose_warichu_split(paragraph, interior_start..interior_end); - let first_lane = interior_start..split; - let second_lane = split..interior_end; - let first_width = warichu_lane_width(paragraph, &first_lane); - let second_width = warichu_lane_width(paragraph, &second_lane); - let leading_width = - leading_bracket.map_or(0, |ordinal| paragraph.text.clusters()[ordinal].advance()); - let trailing_width = - trailing_bracket.map_or(0, |ordinal| paragraph.text.clusters()[ordinal].advance()); - let outer_space = if range.end < line_end { - boundary_space_after(paragraph, range.end.saturating_sub(1)) - } else { - 0 - }; - let advance = leading_width - .saturating_add(first_width.max(second_width)) - .saturating_add(trailing_width) - .saturating_add(outer_space); - WarichuSegment { - range, - leading_bracket, - first_lane, - second_lane, - trailing_bracket, - first_width, - second_width, - advance, - } -} - -fn choose_warichu_split(paragraph: &Paragraph, interior: Range) -> usize { - if interior.end.saturating_sub(interior.start) < 2 { - return interior.end; - } - let mut best = None; - for require_declared in [true, false] { - for split in interior.start.saturating_add(1)..interior.end { - let offset = paragraph.text.clusters()[split].range().start; - let declared = paragraph - .breaks - .iter() - .any(|opportunity| opportunity.offset() == offset); - if require_declared != declared { - continue; - } - let first = warichu_lane_width(paragraph, &(interior.start..split)); - let second = warichu_lane_width(paragraph, &(split..interior.end)); - let score = ( - i32::from(second > first), - first.saturating_sub(second).saturating_abs(), - ); - if best.is_none_or(|(_, best_score)| score < best_score) { - best = Some((split, score)); - } - } - if best.is_some() { - break; - } - } - best.map_or(interior.end, |(split, _)| split) -} - -fn warichu_lane_width(paragraph: &Paragraph, lane: &Range) -> i32 { - lane.clone().fold(0_i32, |sum, ordinal| { - sum.saturating_add(warichu_member_advance(paragraph, ordinal, lane)) - }) -} - -fn warichu_member_advance(paragraph: &Paragraph, ordinal: usize, lane: &Range) -> i32 { - if is_western_word_space(paragraph, ordinal) - && (ordinal == lane.start || ordinal.saturating_add(1) == lane.end) - { - return 0; - } - let logical_end = tate_chu_yoko_cluster_range(paragraph, ordinal) - .map_or_else(|| ordinal.saturating_add(1), |group| group.end); - debug_assert!(logical_end <= lane.end); - let boundary = if logical_end == lane.end { - 0 - } else { - boundary_space_after(paragraph, ordinal) - }; - effective_cluster_body_advance(paragraph, ordinal).saturating_add(boundary) -} - -fn place_warichu_segment( - paragraph: &Paragraph, - segment: &WarichuSegment, - cursor: i64, - block_origin: i32, - placed: &mut Vec, -) { - let leading_width = segment - .leading_bracket - .map_or(0, |ordinal| paragraph.text.clusters()[ordinal].advance()); - if let Some(ordinal) = segment.leading_bracket { - place_warichu_member( - paragraph, - ordinal, - cursor, - i64::from(block_origin), - paragraph.text.clusters()[ordinal].advance(), - placed, - ); - } - - let lane_inline = cursor.saturating_add(i64::from(leading_width)); - let first_block_extent = warichu_lane_block_extent(paragraph, &segment.first_lane); - let second_block_extent = warichu_lane_block_extent(paragraph, &segment.second_lane); - let total_block_extent = first_block_extent.saturating_add(second_block_extent); - let main_block_extent = paragraph.text.size().block(); - let first_block = match paragraph.writing_mode { - WritingMode::HorizontalTb => i64::from(block_origin).saturating_add(i64::from( - main_block_extent.saturating_sub(total_block_extent) / 2, - )), - WritingMode::VerticalRl => i64::from(block_origin).saturating_add(i64::from( - total_block_extent.saturating_sub(main_block_extent) / 2, - )), - }; - let second_block = match paragraph.writing_mode { - WritingMode::HorizontalTb => first_block.saturating_add(i64::from(first_block_extent)), - WritingMode::VerticalRl => first_block.saturating_sub(i64::from(first_block_extent)), - }; - place_warichu_lane( - paragraph, - &segment.first_lane, - lane_inline, - first_block, - placed, - ); - place_warichu_lane( - paragraph, - &segment.second_lane, - lane_inline, - second_block, - placed, - ); - - if let Some(ordinal) = segment.trailing_bracket { - let inline = - lane_inline.saturating_add(i64::from(segment.first_width.max(segment.second_width))); - place_warichu_member( - paragraph, - ordinal, - inline, - i64::from(block_origin), - paragraph.text.clusters()[ordinal].advance(), - placed, - ); - } -} - -fn place_warichu_lane( - paragraph: &Paragraph, - lane: &Range, - inline: i64, - block: i64, - placed: &mut Vec, -) { - let mut cursor = inline; - for ordinal in lane.clone() { - let advance = warichu_member_advance(paragraph, ordinal, lane); - place_warichu_member(paragraph, ordinal, cursor, block, advance, placed); - cursor = cursor.saturating_add(i64::from(advance)); - } -} - -fn place_warichu_member( - paragraph: &Paragraph, - ordinal: usize, - inline: i64, - block: i64, - advance: i32, - placed: &mut Vec, -) { - let cluster = ¶graph.text.clusters()[ordinal]; - let size = cluster.size_override().unwrap_or(paragraph.text.size()); - let frame = cluster.frame_override().unwrap_or(paragraph.text.frame()); - let (writing_mode, transform) = local_orientation(paragraph, ordinal, frame); - placed.push(ClusterPlacement { - origin: PlacementOrigin::Cluster(ordinal), - range: cluster.range(), - inline: clamp_i32(inline), - block: clamp_i32(block), - advance, - size, - frame, - writing_mode, - transform, - }); -} - -fn warichu_lane_block_extent(paragraph: &Paragraph, lane: &Range) -> i32 { - paragraph.text.clusters()[lane.clone()] - .iter() - .map(|cluster| { - cluster - .size_override() - .unwrap_or(paragraph.text.size()) - .block() - }) - .max() - .unwrap_or(0) -} - -fn warichu_break_penalty(paragraph: &Paragraph, offset: usize) -> i64 { - paragraph - .constructs - .iter() - .filter_map(|construct| { - let ConstructKind::Warichu(range) = construct.kind() else { - return None; - }; - if !(range.start < offset && offset < range.end) { - return None; - } - let start = cluster_index_at_or_after(paragraph, range.start); - let split = cluster_index_at_or_after(paragraph, offset); - let end = cluster_index_at_or_after(paragraph, range.end); - let before = split.saturating_sub(start); - let after = end.saturating_sub(split); - Some( - i64::try_from(before.abs_diff(after)) - .unwrap_or(i64::MAX) - .saturating_mul(1_000_000), - ) - }) - .fold(0_i64, i64::saturating_add) -} - -fn formula_break_penalty(paragraph: &Paragraph, offset: usize) -> i64 { - let is_independent_formula = paragraph.constructs.iter().any(|construct| { - matches!( - construct.kind(), - ConstructKind::Formula(range) - if range.start == 0 - && range.end == paragraph.text.source().len() - && range.start < offset - && offset < range.end - ) - }); - if !is_independent_formula { - return 0; - } - let after = paragraph.text.source()[offset..].chars().next(); - if after.is_some_and(is_math_symbol) { - 0 - } else if after.is_some_and(is_math_operator) { - 100_000_000 - } else { - 200_000_000 - } -} - -fn single_cluster_character(paragraph: &Paragraph, cluster: &crate::Cluster) -> Option { - let mut characters = paragraph.text.source()[cluster.range()].chars(); - let character = characters.next()?; - characters.next().is_none().then_some(character) -} - -fn half_inline_size(paragraph: &Paragraph, cluster: &crate::Cluster) -> i32 { - let size = cluster - .size_override() - .unwrap_or(paragraph.text.size()) - .inline(); - (size / 2).saturating_add(size % 2) -} - -fn quarter_inline_size(paragraph: &Paragraph, cluster: &crate::Cluster) -> i32 { - let size = cluster - .size_override() - .unwrap_or(paragraph.text.size()) - .inline(); - (size / 4).saturating_add(i32::from(size % 4 != 0)) -} - -fn is_opening_bracket(character: char) -> bool { - crate::spec::single_has_class(character, crate::spec::OPENING_BRACKET) -} - -fn is_closing_bracket(character: char) -> bool { - crate::spec::single_has_class(character, crate::spec::CLOSING_BRACKET) -} - -fn is_full_stop(character: char) -> bool { - crate::spec::single_has_class(character, crate::spec::FULL_STOP) -} - -fn is_comma(character: char) -> bool { - crate::spec::single_has_class(character, crate::spec::COMMA) -} - -fn is_middle_dot(character: char) -> bool { - crate::spec::single_has_class(character, crate::spec::MIDDLE_DOT) -} - -fn line_head_indent( - paragraph: &Paragraph, - style: &Style, - line_start: usize, - line_index: usize, -) -> i32 { - let ordinary = if line_index == 0 { - paragraph.first_line_indent - } else { - 0 - }; - let Some(cluster) = paragraph.text.clusters().get(line_start) else { - return ordinary; - }; - if class_of_cluster_with_style(paragraph, style, line_start) != crate::spec::OPENING_BRACKET { - return ordinary; - } - let half = half_inline_size(paragraph, cluster); - match (line_index == 0, style.line_head_opening_bracket()) { - (_, LineHeadOpeningBracket::Pattern2) => ordinary.saturating_add(half), - (true, LineHeadOpeningBracket::Pattern3) => ordinary.saturating_sub(half), - _ => ordinary, - } -} - -/// Whether the cluster at `ordinal` is a tab sign **of the line** in §3.6.3's sense. -/// -/// §3.6.3 corresponds the signs of a line with the stops of that line, and both halves -/// of that sentence are about the line's own inline axis. A tate-chu-yoko run runs -/// across that axis and a warichu's and a furawake's sublines run beside it, so each of -/// those structures holds one position on the line however many characters it holds, -/// and a coordinate inside one is not a position a stop can name. A sign standing there -/// is therefore not a sign of the line: it takes no stop, sets the advance it was shaped -/// with, and never chooses §3.6.3's cut — including where it is the structure's first -/// character, which is set in the structure like every other one -/// (`docs/decisions/tab-line-correspondence.md`). -fn is_line_tab_sign(paragraph: &Paragraph, ordinal: usize) -> bool { - paragraph.line_tabs.get(ordinal).copied().unwrap_or(false) -} - -fn measure_line( - paragraph: &Paragraph, - style: &Style, - start: usize, - end: usize, - line_number: usize, -) -> i64 { - let start_cluster = cluster_index_at_or_after(paragraph, start); - let end_cluster = cluster_index_at_or_after(paragraph, end); - let indent = i64::from(line_head_indent( - paragraph, - style, - start_cluster, - line_number, - )); - let mut cursor = indent.saturating_add(i64::from(ruby_line_leading_separation( - paragraph, - style, - start_cluster, - end_cluster, - line_number, - ))); - let mut tab_index = 0; - for local in 0..end_cluster.saturating_sub(start_cluster) { - let ordinal = start_cluster.saturating_add(local); - if is_line_tab_sign(paragraph, ordinal) { - let after_tab = ordinal.saturating_add(1); - let segment_width = segment_width( - paragraph, - style, - after_tab, - end_cluster, - LineContext { - start: start_cluster, - end: end_cluster, - index: line_number, - }, - ); - if let Some(stop) = paragraph - .tab_stops - .iter() - .skip(tab_index) - .find(|stop| i64::from(stop.position()) > cursor) - { - tab_index = tab_index.saturating_add(1); - let target = tab_target( - paragraph, - style, - *stop, - after_tab, - end_cluster, - LineContext { - start: start_cluster, - end: end_cluster, - index: line_number, - }, - segment_width, - ); - cursor = cursor.max(target); - } else { - return i64::MAX; - } - } else { - cursor = cursor.saturating_add(i64::from(effective_cluster_advance_on_line( - paragraph, - style, - ordinal, - start_cluster, - end_cluster, - line_number, - ))); - } - } - cursor -} - -fn segment_width( - paragraph: &Paragraph, - style: &Style, - start: usize, - end: usize, - line: LineContext, -) -> i64 { - (start..end) - .take_while(|ordinal| !is_line_tab_sign(paragraph, *ordinal)) - .fold(0_i64, |sum, ordinal| { - sum.saturating_add(i64::from(effective_cluster_advance_on_line( - paragraph, style, ordinal, line.start, line.end, line.index, - ))) - }) -} - -fn tab_target( - paragraph: &Paragraph, - style: &Style, - stop: TabStop, - start: usize, - end: usize, - line: LineContext, - segment_width: i64, -) -> i64 { - let position = i64::from(stop.position()); - match stop.alignment() { - TabAlignment::Start => position, - TabAlignment::Center => position.saturating_sub(segment_width / 2), - TabAlignment::End => position.saturating_sub(segment_width), - TabAlignment::Character(character) => { - let before = (start..end) - .take_while(|ordinal| { - let cluster = paragraph.text.clusters()[*ordinal].range(); - !paragraph.text.source()[cluster].contains(character) - && !is_line_tab_sign(paragraph, *ordinal) - }) - .fold(0_i64, |sum, ordinal| { - sum.saturating_add(i64::from(effective_cluster_advance_on_line( - paragraph, style, ordinal, line.start, line.end, line.index, - ))) - }); - position.saturating_sub(before) - }, - } -} - -fn apply_tabs( - paragraph: &Paragraph, - style: &Style, - start: usize, - end: usize, - line_number: usize, - advances: &mut [i32], -) { - let mut cursor = i64::from(line_head_indent(paragraph, style, start, line_number)); - let mut tab_index = 0; - for local in 0..advances.len() { - let ordinal = start.saturating_add(local); - let after_tab = local.saturating_add(1); - if is_line_tab_sign(paragraph, ordinal) { - let width = advances[after_tab..] - .iter() - .enumerate() - .take_while(|(following, _)| { - !is_line_tab_sign( - paragraph, - ordinal.saturating_add(1).saturating_add(*following), - ) - }) - .fold(0_i64, |sum, (_, advance)| { - sum.saturating_add(i64::from(*advance)) - }); - if let Some(stop) = paragraph - .tab_stops - .iter() - .skip(tab_index) - .find(|stop| i64::from(stop.position()) > cursor) - { - tab_index = tab_index.saturating_add(1); - let target = tab_target( - paragraph, - style, - *stop, - ordinal.saturating_add(1), - end, - LineContext { - start, - end, - index: line_number, - }, - width, - ); - advances[local] = clamp_i32(target.saturating_sub(cursor).max(0)); - } else { - advances[local] = paragraph.text.size().inline(); - } - } - cursor = cursor.saturating_add(i64::from(advances[local])); - } -} - -fn line_badness(delta: i64, is_last: bool, preference: AdjustmentPreference) -> i64 { - let magnitude = delta.saturating_abs().min(1_000_000); - let square = magnitude.saturating_mul(magnitude); - if delta < 0 { - return square.saturating_mul(1_000).saturating_add(10_000_000); - } - if is_last { - return square / 100; - } - match preference { - AdjustmentPreference::LeastAdjustment => square, - AdjustmentPreference::EvenTexture => square.saturating_mul(2), - } -} - -fn widow_penalty(paragraph: &Paragraph, start: usize, end: usize) -> i64 { - let Widow::MinimumClusters(minimum) = paragraph.widow else { - return 0; - }; - let count = cluster_index_at_or_after(paragraph, end) - .saturating_sub(cluster_index_at_or_after(paragraph, start)); - if count < usize::from(minimum) { - 1_000_000_000 - } else { - 0 - } -} - -fn add_widow_diagnostic(paragraph: &Paragraph, layout: &mut Layout) { - let Widow::MinimumClusters(minimum) = paragraph.widow else { - return; - }; - let Some(last) = layout.lines.last() else { - return; - }; - if last.clusters.len() < usize::from(minimum) { - layout.diagnostics.push(Diagnostic { - code: "layout.widow", - severity: Severity::Warning, - range: Some(last.range.clone()), - jlreq: "3.1.9", - }); - } -} - -fn break_is_legal(paragraph: &Paragraph, style: &Style, offset: usize) -> bool { - if offset == paragraph.text.source().len() { - return true; - } - let after_ordinal = cluster_index_at_or_after(paragraph, offset); - let Some(before_ordinal) = after_ordinal.checked_sub(1) else { - return true; - }; - if after_ordinal >= paragraph.text.clusters().len() { - return true; - } - // §3.6.3's cut answers to no character class, but it is available only where the - // sign is a sign of the line: inside a structure that stacks its text off the line - // there is no line boundary to cut at, and the boundary before such a structure is - // an ordinary one Table 2 decides. - if is_line_tab_sign(paragraph, after_ordinal) { - return true; - } - - let raw_before = class_of_cluster_with_style(paragraph, style, before_ordinal); - let raw_after = class_of_cluster_with_style(paragraph, style, after_ordinal); - let before_character = - single_cluster_character(paragraph, ¶graph.text.clusters()[before_ordinal]); - let after_character = - single_cluster_character(paragraph, ¶graph.text.clusters()[after_ordinal]); - - // §C.3 states these four prohibitions are common to every convention level. - if raw_before == crate::spec::OPENING_BRACKET - || matches!( - raw_after, - crate::spec::CLOSING_BRACKET | crate::spec::FULL_STOP | crate::spec::COMMA - ) - { - return false; - } - - if c_3_relaxes_boundary( - style, - raw_before, - raw_after, - before_character, - after_character, - ) { - return true; - } - - let before = reclassified_break_class(style, raw_before, before_character); - let after = reclassified_break_class(style, raw_after, after_character); - let Some(cell) = crate::spec::table_two_cell(before, after) else { - return true; - }; - if cell.prohibited { - return false; - } - - match (before, after) { - (8, 8) => !inseparable_member_pair(before_character, after_character), - (24, 27) => { - style.grouped_numeral_before_western() == GroupedNumeralBeforeWestern::Breakable - }, - (27, 13) => { - let role = paragraph.text.clusters()[before_ordinal].role(); - role != Some(ClusterRole::QuantitySymbol) - && !before_character.is_some_and(|character| character.is_ascii_digit()) - }, - _ => cell.levels & kinsoku_level_bit(style.kinsoku_level()) == 0, - } -} - -fn kinsoku_level_bit(level: KinsokuLevel) -> u8 { - match level { - KinsokuLevel::VeryLoose => 0b0001, - KinsokuLevel::Loose => 0b0010, - KinsokuLevel::Strict => 0b0100, - KinsokuLevel::VeryStrict => 0b1000, - } -} - -fn reclassified_break_class(style: &Style, class: u8, character: Option) -> u8 { - if character == Some('々') - && style.iteration_mark_at_line_head() != IterationMarkAtLineHead::Prohibited - && style.kinsoku_level() != KinsokuLevel::VeryStrict - { - return 19; - } - if style.relaxation_mechanism() == RelaxationMechanism::Reclassify - && style.kinsoku_level() != KinsokuLevel::VeryStrict - { - if class == 10 { - return 16; - } - if class == 11 { - return if character.is_some_and(crate::spec::is_hiragana) { - 15 - } else { - 16 - }; - } - } - class -} - -fn c_3_relaxes_boundary( - style: &Style, - before: u8, - after: u8, - before_character: Option, - after_character: Option, -) -> bool { - let either_class = |classes: &[u8]| classes.contains(&before) || classes.contains(&after); - let either_character = - |character| before_character == Some(character) || after_character == Some(character); - let iteration_relaxed = style.iteration_mark_at_line_head() - != IterationMarkAtLineHead::Prohibited - && either_character('々'); - let matrix_kana = - style.relaxation_mechanism() == RelaxationMechanism::Matrix && either_class(&[10, 11]); - - match style.kinsoku_level() { - KinsokuLevel::VeryLoose => { - either_class(&[3, 4, 5, 9, 12, 13]) - || matrix_kana - || cl_08_same_kind(before_character, after_character) - }, - KinsokuLevel::Loose => { - either_class(&[3]) - || either_character('・') - || matches!( - (before_character, after_character), - (Some('…'), Some('…')) | (Some('‥'), Some('‥')) - ) - || iteration_relaxed - || matrix_kana - || either_character('%') - || either_character('%') - }, - KinsokuLevel::Strict => iteration_relaxed || matrix_kana, - KinsokuLevel::VeryStrict => false, - } -} - -fn inseparable_member_pair(before: Option, after: Option) -> bool { - matches!( - (before, after), - (Some('—'), Some('—')) - | (Some('…'), Some('…')) - | (Some('‥'), Some('‥')) - | (Some('〳' | '〴'), Some('〵')) - ) -} - -fn cl_08_same_kind(before: Option, after: Option) -> bool { - match (before, after) { - (Some(before), Some(after)) if before == after => true, - (Some(before), Some(after)) => "〳〴〵".contains(before) && "〳〴〵".contains(after), - _ => false, - } -} - -fn local_orientation( - paragraph: &Paragraph, - ordinal: usize, - frame: Frame, -) -> (WritingMode, CoordinateTransform) { - if paragraph.writing_mode == WritingMode::HorizontalTb { - return (WritingMode::HorizontalTb, CoordinateTransform::Identity); - } - let range = paragraph.text.clusters()[ordinal].range(); - if paragraph.constructs.iter().any(|construct| { - matches!(construct.kind(), ConstructKind::TateChuYoko(_)) - && ranges_overlap(&construct.range(), &range) - }) { - return (WritingMode::HorizontalTb, CoordinateTransform::TateChuYoko); - } - if frame == Frame::Proportional { - ( - WritingMode::VerticalRl, - CoordinateTransform::RotateClockwise, - ) - } else { - (WritingMode::VerticalRl, CoordinateTransform::Identity) - } -} - -fn place_attachments(paragraph: &Paragraph, style: &Style, line_index: usize, line: &mut Line) { - let mut attachment_extent = 0; - for (ordinal, construct) in paragraph.constructs.iter().enumerate() { - if !ranges_overlap(&construct.range(), &line.range) { - continue; - } - match construct.kind() { - ConstructKind::Ruby(ruby) => { - place_ruby_attachments( - paragraph, - style, - line_index, - line, - ordinal, - ruby, - &mut attachment_extent, - ); - }, - ConstructKind::Emphasis { range, mark } => { - for placement in line - .clusters - .iter() - .filter(|placement| ranges_overlap(&placement.range, range)) - { - let size = placement.size.half_rounded_up(); - let inline = i64::from(placement.inline).saturating_add( - i64::from(placement.advance).saturating_sub(i64::from(size.inline())) / 2, - ); - line.attachments.push(Attachment { - construct: ordinal, - range: 0..0, - inline: clamp_i32(inline), - block: attachment_block(paragraph, line, size), - advance: 0, - size, - writing_mode: paragraph.writing_mode, - transform: CoordinateTransform::Identity, - symbol: Some(*mark), - }); - attachment_extent = attachment_extent.max(size.block()); - } - }, - ConstructKind::ReferenceMark { range, mark } - | ConstructKind::Script { - range, - annotation: mark, - } => { - if let Some((start, end)) = bounds_for_range(line, range) { - let width = mark.clusters().iter().fold(0_i64, |sum, cluster| { - sum.saturating_add(i64::from(cluster.advance())) - }); - let mut inline = i64::from(start).saturating_add( - (i64::from(end) - .saturating_sub(i64::from(start)) - .saturating_sub(width)) - / 2, - ); - for cluster in mark.clusters() { - let size = cluster.size_override().unwrap_or(mark.size()); - line.attachments.push(Attachment { - construct: ordinal, - range: cluster.range(), - inline: clamp_i32(inline), - block: attachment_block(paragraph, line, size), - advance: cluster.advance(), - size, - writing_mode: paragraph.writing_mode, - transform: CoordinateTransform::Identity, - symbol: None, - }); - attachment_extent = attachment_extent.max(size.block()); - inline = inline.saturating_add(i64::from(cluster.advance())); - } - } - }, - ConstructKind::TateChuYoko(_) - | ConstructKind::Warichu(_) - | ConstructKind::Furawake { .. } - | ConstructKind::Jidori { .. } - | ConstructKind::Formula(_) => {}, - } - } - line.block_extent = line.block_extent.saturating_add(attachment_extent); -} - -fn place_ruby_attachments( - paragraph: &Paragraph, - style: &Style, - line_index: usize, - line: &mut Line, - construct: usize, - ruby: &Ruby, - attachment_extent: &mut i32, -) { - match ruby.kind() { - RubyKind::Group => { - *attachment_extent = (*attachment_extent).max(place_ruby_span( - paragraph, - style, - line, - construct, - &RubySpan { - annotation: ruby.annotation(), - base_range: ruby.base(), - annotation_range: 0..ruby.annotation().source().len(), - distribution: Some(style.group_ruby_distribution()), - }, - )); - }, - RubyKind::Mono => { - for run in ruby.runs() { - if range_fits_line(&run.base(), line) { - *attachment_extent = (*attachment_extent).max(place_ruby_span( - paragraph, - style, - line, - construct, - &RubySpan { - annotation: ruby.annotation(), - base_range: run.base(), - annotation_range: run.annotation(), - distribution: None, - }, - )); - } - } - }, - RubyKind::Jukugo => { - if style.jukugo_ruby_layout() == JukugoRubyLayout::Phonetic { - if let Some(extent) = - place_phonetic_jukugo(paragraph, style, line_index, line, construct, ruby) - { - *attachment_extent = (*attachment_extent).max(extent); - return; - } - } - let per_base = ruby - .runs() - .iter() - .all(|run| annotation_cluster_count(ruby.annotation(), &run.annotation()) <= 2); - if !per_base && range_fits_line(&ruby.base(), line) { - *attachment_extent = (*attachment_extent).max(place_ruby_span( - paragraph, - style, - line, - construct, - &RubySpan { - annotation: ruby.annotation(), - base_range: ruby.base(), - annotation_range: 0..ruby.annotation().source().len(), - distribution: Some(GroupRubyDistribution::Jis), - }, - )); - } else { - for run in ruby.runs() { - if range_fits_line(&run.base(), line) { - *attachment_extent = (*attachment_extent).max(place_ruby_span( - paragraph, - style, - line, - construct, - &RubySpan { - annotation: ruby.annotation(), - base_range: run.base(), - annotation_range: run.annotation(), - distribution: None, - }, - )); - } - } - } - }, - } -} - -fn place_phonetic_jukugo( - paragraph: &Paragraph, - style: &Style, - line_index: usize, - line: &mut Line, - construct: usize, - ruby: &Ruby, -) -> Option { - let line_start = cluster_index_at_or_after(paragraph, line.range.start); - let line_end = cluster_index_at_or_after(paragraph, line.range.end); - let plan = phonetic_jukugo_plan(paragraph, style, ruby, line_start, line_end, line_index)?; - let first = plan.runs.first()?; - let actual_start = line.clusters.iter().find_map(|placement| { - (placement.origin == PlacementOrigin::Cluster(first.base.start)) - .then_some(i64::from(placement.inline)) - })?; - let offset = actual_start.saturating_sub(first.base_start); - let mut attachment_extent = 0; - for run in &plan.runs { - let mut inline = offset.saturating_add(run.annotation_start); - for cluster in ruby.annotation().clusters().iter().filter(|cluster| { - let cluster = cluster.range(); - run.annotation.start <= cluster.start && cluster.end <= run.annotation.end - }) { - let size = cluster.size_override().unwrap_or(ruby.annotation().size()); - line.attachments.push(Attachment { - construct, - range: cluster.range(), - inline: clamp_i32(inline), - block: attachment_block(paragraph, line, size), - advance: cluster.advance(), - size, - writing_mode: paragraph.writing_mode, - transform: CoordinateTransform::Identity, - symbol: None, - }); - attachment_extent = attachment_extent.max(size.block()); - inline = inline.saturating_add(i64::from(cluster.advance())); - } - } - Some(attachment_extent) -} - -fn annotation_cluster_count(annotation: &crate::ShapedText, range: &Range) -> usize { - annotation - .clusters() - .iter() - .filter(|cluster| { - let cluster = cluster.range(); - range.start <= cluster.start && cluster.end <= range.end - }) - .count() -} - -fn range_fits_line(range: &Range, line: &Line) -> bool { - line.range.start <= range.start && range.end <= line.range.end -} - -struct RubySpan<'a> { - annotation: &'a crate::ShapedText, - base_range: Range, - annotation_range: Range, - distribution: Option, -} - -fn place_ruby_span( - paragraph: &Paragraph, - style: &Style, - line: &mut Line, - construct: usize, - span: &RubySpan<'_>, -) -> i32 { - let Some((base_start, base_end)) = bounds_for_ruby_range(paragraph, line, &span.base_range) - else { - return 0; - }; - let (annotation_count, annotation_width, _) = - ruby_annotation_metrics(span.annotation, &span.annotation_range); - let base_width = i64::from(base_end).saturating_sub(i64::from(base_start)); - let surplus = base_width.saturating_sub(annotation_width).max(0); - let mut gaps = Vec::new(); - let base_plan = span.distribution.and_then(|distribution| { - base_distribution_plan( - paragraph, - style, - &span.base_range, - span.annotation, - &span.annotation_range, - distribution, - ) - }); - let mut inline = if let Some(plan) = base_plan { - i64::from(base_start).saturating_sub(i64::from(plan.leading)) - } else if matches!( - annotation_width.cmp(&base_width), - core::cmp::Ordering::Greater - ) { - i64::from(base_start).saturating_add((base_width.saturating_sub(annotation_width)) / 2) - } else if let Some(distribution) = span.distribution { - let weights = match distribution { - GroupRubyDistribution::Jis => { - let mut weights = Vec::with_capacity(annotation_count.saturating_add(1)); - weights.push(1); - weights.extend((1..annotation_count).map(|_| 2)); - weights.push(1); - weights - }, - GroupRubyDistribution::Flush => vec![1; annotation_count.saturating_sub(1)], - }; - let shares = proportional_shares(surplus, &weights, style.remainder()); - match distribution { - GroupRubyDistribution::Jis => { - gaps.extend( - shares - .iter() - .skip(1) - .take(annotation_count.saturating_sub(1)), - ); - i64::from(base_start) - .saturating_add(i64::from(shares.first().copied().unwrap_or(0))) - }, - GroupRubyDistribution::Flush => { - gaps.extend(shares); - i64::from(base_start) - }, - } - } else { - match style.ruby_alignment() { - RubyAlignment::Nakatsuki => i64::from(base_start).saturating_add(surplus / 2), - RubyAlignment::Katatsuki => i64::from(base_start), - } - }; - let mut attachment_extent = 0; - for (index, cluster) in span - .annotation - .clusters() - .iter() - .filter(|cluster| { - let cluster = cluster.range(); - span.annotation_range.start <= cluster.start && cluster.end <= span.annotation_range.end - }) - .enumerate() - { - let size = cluster.size_override().unwrap_or(span.annotation.size()); - line.attachments.push(Attachment { - construct, - range: cluster.range(), - inline: clamp_i32(inline), - block: attachment_block(paragraph, line, size), - advance: cluster.advance(), - size, - writing_mode: paragraph.writing_mode, - transform: CoordinateTransform::Identity, - symbol: None, - }); - attachment_extent = attachment_extent.max(size.block()); - inline = inline.saturating_add(i64::from(cluster.advance())); - inline = inline.saturating_add(i64::from(gaps.get(index).copied().unwrap_or(0))); - } - attachment_extent -} - -fn proportional_shares(total: i64, weights: &[i32], remainder: Remainder) -> Vec { - if total <= 0 { - return vec![0; weights.len()]; - } - let weight_sum = weights.iter().fold(0_i64, |sum, weight| { - sum.saturating_add(i64::from((*weight).max(0))) - }); - if weight_sum == 0 { - return vec![0; weights.len()]; - } - let total = u128::try_from(total).unwrap_or(0); - let weight_sum = u128::try_from(weight_sum).unwrap_or(0); - let mut shares: Vec<_> = weights - .iter() - .map(|weight| { - let weight = u128::try_from((*weight).max(0)).unwrap_or(0); - let share = total - .saturating_mul(weight) - .checked_div(weight_sum) - .unwrap_or(0); - i64::try_from(share).unwrap_or(i64::MAX) - }) - .collect(); - let assigned = shares.iter().fold(0_u128, |sum, share| { - sum.saturating_add(u128::try_from(*share).unwrap_or(0)) - }); - let left = usize::try_from(total.saturating_sub(assigned)).unwrap_or(usize::MAX); - debug_assert!(left <= weights.len()); - match remainder { - Remainder::Leading => { - for share in shares.iter_mut().take(left) { - *share = share.saturating_add(1); - } - }, - Remainder::Trailing => { - for share in shares.iter_mut().rev().take(left) { - *share = share.saturating_add(1); - } - }, - } - shares.into_iter().map(clamp_i32).collect() -} - -fn attachment_block(paragraph: &Paragraph, line: &Line, size: Size) -> i32 { - match paragraph.writing_mode { - WritingMode::HorizontalTb => line.block_origin.saturating_sub(size.block()), - WritingMode::VerticalRl => line.block_origin.saturating_add(size.block()), - } -} - -fn bounds_for_range(line: &Line, range: &Range) -> Option<(i32, i32)> { - let mut matching = line - .clusters - .iter() - .filter(|placement| ranges_overlap(&placement.range, range)); - let first = matching.next()?; - let mut end = placement_inline_end(first); - for placement in matching { - end = end.max(placement_inline_end(placement)); - } - Some((first.inline, end)) -} - -fn bounds_for_ruby_range( - paragraph: &Paragraph, - line: &Line, - range: &Range, -) -> Option<(i32, i32)> { - let mut matching = line - .clusters - .iter() - .filter(|placement| ranges_overlap(&placement.range, range)); - let first = matching.next()?; - let mut end = ruby_base_inline_end(paragraph, first); - for placement in matching { - end = end.max(ruby_base_inline_end(paragraph, placement)); - } - Some((first.inline, end)) -} - -fn ruby_base_inline_end(paragraph: &Paragraph, placement: &ClusterPlacement) -> i32 { - let advance = match placement.origin { - PlacementOrigin::Cluster(ordinal) => effective_cluster_body_advance(paragraph, ordinal), - PlacementOrigin::Construct(_) => placement.advance, - }; - placement.inline.saturating_add(advance) -} - -fn placement_inline_end(placement: &ClusterPlacement) -> i32 { - let advance = if placement.transform == CoordinateTransform::TateChuYoko { - placement.size.block() - } else { - placement.advance - }; - placement.inline.saturating_add(advance) -} - -fn ranges_overlap(left: &Range, right: &Range) -> bool { - left.start < right.end && right.start < left.end -} - -fn clamp_i32(value: i64) -> i32 { - i32::try_from(value).unwrap_or_else(|_| { - if value.is_negative() { - i32::MIN - } else { - i32::MAX - } - }) -} - +// These stage files expand in this module so the private pipeline contract and public API +// remain unchanged while each composition phase can be maintained independently. +include!("pipeline/composition.rs"); +include!("pipeline/search.rs"); +include!("pipeline/adjustment.rs"); +include!("pipeline/ruby.rs"); +include!("pipeline/special.rs"); +include!("pipeline/placement.rs"); #[cfg(test)] mod tests { use alloc::{string::String, vec, vec::Vec}; @@ -4643,6 +255,12 @@ mod tests { composer.chosen.push(1); composer.line_advances.push(2); composer.line_adjustments.push(3); + composer + .line_scratch + .expansion_sites + .push(super::ExpansionSite::None); + composer.line_scratch.construct_ordinals.push(4); + composer.line_scratch.distribution.assigned.push(5); composer.prepared.regular = true; composer.reset_for_call(); assert_eq!(composer.transitions, 0); @@ -4651,7 +269,11 @@ mod tests { assert!(composer.chosen.is_empty()); assert!(composer.line_advances.is_empty()); assert!(composer.line_adjustments.is_empty()); + assert!(composer.line_scratch.expansion_sites.is_empty()); + assert!(composer.line_scratch.construct_ordinals.is_empty()); + assert!(composer.line_scratch.distribution.assigned.is_empty()); assert!(!composer.prepared.regular); + assert!(!composer.prepared.fast_measure); } #[test] @@ -4806,6 +428,68 @@ mod tests { assert_fast_matches_full(" A", 1_000); } + #[test] + fn static_construct_prefixes_match_full_measurement_and_keep_legacy_charges() { + let cases = [ + Paragraph::builder(text("ABC"), 4_000) + .constructs([Construct::emphasis_dots(1..2, '・')]) + .build() + .expect("valid emphasis paragraph"), + Paragraph::builder(text("ABC"), 4_000) + .constructs([Construct::reference_mark(1..2, text("*"))]) + .build() + .expect("valid reference-mark paragraph"), + Paragraph::builder(text("ABC"), 4_000) + .constructs([Construct::script(1..2, text("x"))]) + .build() + .expect("valid script paragraph"), + Paragraph::builder(text("A=B"), 4_000) + .constructs([Construct::formula(0..3)]) + .build() + .expect("valid formula paragraph"), + Paragraph::builder(text("ABC"), 4_000) + .constructs([Construct::tate_chu_yoko(1..3)]) + .writing_mode(WritingMode::VerticalRl) + .build() + .expect("valid tate-chu-yoko paragraph"), + ]; + let style = Style::default(); + for paragraph in cases { + let mut composer = super::Composer::new(); + composer.prepare_candidates(¶graph); + composer.prepare_indexes(¶graph, &style); + assert!(composer.prepared.fast_measure); + assert!(!composer.prepared.regular); + let cluster_end = paragraph.text.clusters().len(); + let measured = + super::measure_line(¶graph, &style, 0, paragraph.text.source().len(), 0); + let fast = + super::fast_measure_line(&composer.prepared, ¶graph, &style, 0, cluster_end, 0); + assert_eq!(fast, measured); + for available in [1_000_i64, 4_000] { + assert_eq!( + super::fast_width_after_available_reduction( + &composer.prepared, + ¶graph, + &style, + 0, + cluster_end, + fast, + available, + ), + super::width_after_available_reduction( + ¶graph, + &style, + 0, + paragraph.text.source().len(), + measured, + available, + ) + ); + } + } + } + #[test] fn construct_cluster_ranges_and_internal_boundaries_are_exact() { let furawake = Paragraph::builder(text("abc"), 3_000) @@ -5545,14 +1229,16 @@ mod tests { #[test] fn table_one_blank_lookup_matches_every_generated_cell() { - for cell in crate::generated::table1::CELLS { - assert_eq!( - super::table_one_cell_is_blank(cell.before, cell.after), - cell.terms.is_empty(), - "cl-{:02}/cl-{:02}", - cell.before, - cell.after - ); + for before in (0_u8..=30).filter(|class| *class != 17 && *class != 18) { + for after in (0_u8..=30).filter(|class| *class != 17 && *class != 18) { + let cell = crate::generated::table1::cell(before, after) + .expect("every transcribed table-one coordinate is indexed"); + assert_eq!( + super::table_one_cell_is_blank(before, after), + cell.terms.is_empty(), + "cl-{before:02}/cl-{after:02}" + ); + } } assert!(super::table_one_cell_is_blank(0, 0)); } @@ -6909,7 +2595,16 @@ mod tests { placement(1, 1..2, 1_000, 1_000, crate::CoordinateTransform::Identity), ], ); - super::place_attachments(&script, &Style::default(), 0, &mut script_line); + let mut construct_ordinals = Vec::new(); + super::place_attachments( + &script, + &Style::default(), + 0, + 0, + 2, + &mut script_line, + &mut construct_ordinals, + ); assert_eq!(script_line.attachments.len(), 1); assert_eq!(script_line.attachments[0].inline(), 500); assert_eq!(script_line.attachments[0].block(), 0); @@ -6920,7 +2615,15 @@ mod tests { .build() .expect("valid emphasis paragraph"); let mut emphasis_line = line(0..2, script_line.clusters.clone()); - super::place_attachments(&emphasis, &Style::default(), 0, &mut emphasis_line); + super::place_attachments( + &emphasis, + &Style::default(), + 0, + 0, + 2, + &mut emphasis_line, + &mut construct_ordinals, + ); assert_eq!( emphasis_line .attachments diff --git a/crates/jlreq-core/src/pipeline/adjustment.rs b/crates/jlreq-core/src/pipeline/adjustment.rs new file mode 100644 index 0000000..72eb889 --- /dev/null +++ b/crates/jlreq-core/src/pipeline/adjustment.rs @@ -0,0 +1,1093 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +fn boundary_expansion_site(paragraph: &Paragraph, style: &Style, before: usize) -> ExpansionSite { + let Some(cluster) = paragraph.text.clusters().get(before) else { + return ExpansionSite::None; + }; + let after = before.saturating_add(1); + if after >= paragraph.text.clusters().len() { + return ExpansionSite::None; + } + if is_western_word_space(paragraph, before) { + let size = cluster + .size_override() + .unwrap_or(paragraph.text.size()) + .inline(); + let cap = half_rounded_up(size) + .saturating_sub(effective_cluster_body_advance(paragraph, before)) + .max(0); + let after_class = class_of_cluster_with_style(paragraph, style, after); + let residual = + crate::generated::table6::cell(26, after_class).is_some_and(|cell| cell.residual); + return ExpansionSite::Site { + weight: size, + bounded: (cap > 0).then_some((cap, 1)), + residual, + }; + } + let before_complex = expansion_complex_at(paragraph, before); + let after_complex = expansion_complex_at(paragraph, after); + if let (Some(before_complex), Some(after_complex)) = (before_complex, after_complex) { + if before_complex == after_complex { + return ExpansionSite::None; + } + } + + let boundary = cluster.range().end; + if paragraph + .find_construct_containing(before, |construct| match construct.kind() { + ConstructKind::TateChuYoko(range) + | ConstructKind::Warichu(range) + | ConstructKind::Formula(range) + | ConstructKind::Furawake { range, .. } + | ConstructKind::Jidori { range, .. } + | ConstructKind::Script { range, .. } => range.start < boundary && boundary < range.end, + ConstructKind::Ruby(_) + | ConstructKind::Emphasis { .. } + | ConstructKind::ReferenceMark { .. } => false, + }) + .is_some() + { + return ExpansionSite::None; + } + + let before_class = class_of_cluster_with_style(paragraph, style, before); + let after_class = class_of_cluster_with_style(paragraph, style, after); + let before_character = single_cluster_character(paragraph, cluster); + let after_character = single_cluster_character(paragraph, ¶graph.text.clusters()[after]); + if before_class == 8 && after_class == 8 && cl_08_same_kind(before_character, after_character) { + return ExpansionSite::None; + } + if before_class == 27 && after_class == 13 { + let role = cluster.role(); + if role == Some(ClusterRole::QuantitySymbol) + || before_character.is_some_and(|character| character.is_ascii_digit()) + { + return ExpansionSite::None; + } + } + let Some(cell) = crate::generated::table6::cell(before_class, after_class) else { + return ExpansionSite::None; + }; + let before_size = cluster.size_override().unwrap_or(paragraph.text.size()); + let after_cluster = ¶graph.text.clusters()[after]; + let after_size = after_cluster + .size_override() + .unwrap_or(paragraph.text.size()); + let before_solid = before_character + .is_some_and(|character| contextual_punctuation_is_solid(paragraph, cluster, character)); + let after_solid = after_character.is_some_and(|character| { + contextual_punctuation_is_solid(paragraph, after_cluster, character) + }); + let components = crate::spec::table_one_space_components( + before_class, + after_class, + before_size, + after_size, + before_solid, + after_solid, + ); + let weight = match components { + [0, amount] if amount > 0 => after_size.inline(), + _ => before_size.inline(), + }; + if cell.residual { + return ExpansionSite::Site { + weight, + bounded: None, + residual: true, + }; + } + let Some(limit) = cell.limit else { + return ExpansionSite::None; + }; + let current = boundary_space_after_with_style(paragraph, style, before); + let ceiling = expansion_ceiling( + style, + before_class, + after_class, + weight, + crate::spec::scale_spec_units(weight, limit), + ); + let cap = ceiling.saturating_sub(current).max(0); + if cap == 0 { + ExpansionSite::None + } else { + ExpansionSite::Site { + weight, + bounded: Some((cap, cell.stage)), + residual: false, + } + } +} + +fn expansion_ceiling(style: &Style, before: u8, after: u8, weight: i32, table: i32) -> i32 { + if !matches!((before, after), (19, 27) | (27, 19)) { + return table; + } + match style.japanese_latin_expansion_ceiling() { + JapaneseLatinExpansionCeiling::HalfEm => half_rounded_up(weight), + JapaneseLatinExpansionCeiling::ThirdEm => { + weight.saturating_add(2).checked_div(3).unwrap_or(0) + }, + JapaneseLatinExpansionCeiling::Rigid => quarter_rounded_up(weight), + } +} + +fn boundary_expansion_site_on_line( + paragraph: &Paragraph, + style: &Style, + before: usize, + line_start: usize, + line_end: usize, +) -> ExpansionSite { + if (before == line_start && is_western_word_space(paragraph, before)) + || (before.saturating_add(2) == line_end + && is_western_word_space(paragraph, before.saturating_add(1))) + { + ExpansionSite::None + } else { + boundary_expansion_site(paragraph, style, before) + } +} + +fn expansion_complex_at(paragraph: &Paragraph, ordinal: usize) -> Option { + let cluster = paragraph.text.clusters().get(ordinal)?.range(); + let (construct, candidate) = paragraph.find_construct_containing(ordinal, |candidate| { + matches!( + candidate.kind(), + ConstructKind::Script { .. } | ConstructKind::Ruby(_) + ) || (paragraph.writing_mode == WritingMode::VerticalRl + && matches!(candidate.kind(), ConstructKind::TateChuYoko(_))) + })?; + match candidate.kind() { + ConstructKind::Script { .. } => Some(ComplexIdentity { + kind: ComplexKind::Ornamented, + construct, + member: 0, + }), + ConstructKind::Ruby(ruby) => { + let (kind, member) = match ruby.kind() { + RubyKind::Mono => ( + ComplexKind::SimpleRuby, + ruby.runs() + .iter() + .position(|run| { + let range = run.base(); + range.start <= cluster.start && cluster.end <= range.end + }) + .unwrap_or(0), + ), + RubyKind::Group => (ComplexKind::SimpleRuby, 0), + RubyKind::Jukugo => (ComplexKind::JukugoRuby, 0), + }; + Some(ComplexIdentity { + kind, + construct, + member, + }) + }, + ConstructKind::TateChuYoko(_) => Some(ComplexIdentity { + kind: ComplexKind::TateChuYoko, + construct, + member: 0, + }), + _ => None, + } +} + +fn prepare_line_adjustments_with_scratch( + paragraph: &Paragraph, + style: &Style, + line_start: usize, + line_end: usize, + need: i64, + adjustments: &mut Vec, + scratch: &mut LineScratch, +) { + adjustments.clear(); + adjustments.resize(line_end.saturating_sub(line_start), 0); + match need.cmp(&0) { + core::cmp::Ordering::Less => { + prepare_line_reductions_with_scratch( + paragraph, + style, + line_start, + line_end, + need.saturating_abs(), + adjustments, + scratch, + ); + return; + }, + core::cmp::Ordering::Equal => return, + core::cmp::Ordering::Greater => {}, + } + + scratch.expansion_sites.clear(); + scratch + .expansion_sites + .extend((line_start..line_end.saturating_sub(1)).map(|before| { + boundary_expansion_site_on_line(paragraph, style, before, line_start, line_end) + })); + let mut remaining = need; + for stage in 1_u8..=3 { + if remaining == 0 { + return; + } + scratch.distribution_sites.clear(); + scratch + .distribution_sites + .extend(scratch.expansion_sites.iter().enumerate().filter_map( + |(index, site)| match site { + ExpansionSite::Site { + weight, + bounded: Some((cap, site_stage)), + .. + } if *site_stage == stage => Some((index, *weight, Some(*cap))), + _ => None, + }, + )); + let capacity = scratch + .distribution_sites + .iter() + .fold(0_i64, |sum, (_, _, cap)| { + sum.saturating_add(i64::from(cap.unwrap_or(0))) + }); + let take = remaining.min(capacity); + distribute_adjustment_with_scratch( + take, + &scratch.distribution_sites, + style.remainder(), + adjustments, + &mut scratch.distribution, + ); + remaining = remaining.saturating_sub(take); + } + + if remaining == 0 { + return; + } + scratch.distribution_sites.clear(); + scratch + .distribution_sites + .extend(scratch.expansion_sites.iter().enumerate().filter_map( + |(index, site)| match site { + ExpansionSite::Site { + weight, + bounded, + residual, + } if *residual || bounded.is_some_and(|(_, stage)| (2..=3).contains(&stage)) => { + Some((index, *weight, None)) + }, + ExpansionSite::None | ExpansionSite::Site { .. } => None, + }, + )); + distribute_adjustment_with_scratch( + remaining, + &scratch.distribution_sites, + style.remainder(), + adjustments, + &mut scratch.distribution, + ); +} + +#[cfg(test)] +fn prepare_line_adjustments( + paragraph: &Paragraph, + style: &Style, + line_start: usize, + line_end: usize, + need: i64, + adjustments: &mut Vec, +) { + let mut scratch = LineScratch::new(); + prepare_line_adjustments_with_scratch( + paragraph, + style, + line_start, + line_end, + need, + adjustments, + &mut scratch, + ); +} + +fn prepare_line_reductions_with_scratch( + paragraph: &Paragraph, + style: &Style, + line_start: usize, + line_end: usize, + mut need: i64, + adjustments: &mut [i32], + scratch: &mut LineScratch, +) { + collect_reduction_sites( + paragraph, + style, + line_start, + line_end, + &mut scratch.reduction_sites, + ); + for stage in 1_u8..=6 { + if need <= 0 { + break; + } + for site in scratch + .reduction_sites + .iter() + .copied() + .filter(|site| site.stage == stage && site.discrete) + { + if need <= 0 { + break; + } + apply_reduction(site.boundary, i64::from(site.capacity), adjustments); + need = need.saturating_sub(i64::from(site.capacity)); + } + + if need <= 0 { + break; + } + scratch.stage_reductions.clear(); + scratch.stage_reductions.extend( + scratch + .reduction_sites + .iter() + .copied() + .filter(|site| site.stage == stage && !site.discrete), + ); + let capacity = scratch.stage_reductions.iter().fold(0_i64, |sum, site| { + sum.saturating_add(i64::from(site.capacity)) + }); + let take = need.min(capacity); + distribute_reduction_with_scratch( + take, + &scratch.stage_reductions, + style.remainder(), + adjustments, + &mut scratch.distribution, + ); + need = need.saturating_sub(take); + } +} + +fn collect_reduction_sites( + paragraph: &Paragraph, + style: &Style, + line_start: usize, + line_end: usize, + sites: &mut Vec, +) { + sites.clear(); + for ordinal in line_start..line_end.saturating_sub(1) { + if is_internal_jidori_boundary(paragraph, ordinal) + || is_internal_stacked_boundary(paragraph, ordinal) + { + continue; + } + if is_western_word_space(paragraph, ordinal) { + let cluster = ¶graph.text.clusters()[ordinal]; + let minimum = quarter_inline_size(paragraph, cluster); + let capacity = effective_cluster_body_advance(paragraph, ordinal) + .saturating_sub(minimum) + .max(0); + push_reduction_site( + sites, + ordinal.saturating_sub(line_start), + paragraph.text.clusters()[ordinal] + .size_override() + .unwrap_or(paragraph.text.size()) + .inline(), + capacity, + 1, + false, + ); + } + append_table_reduction_sites( + paragraph, + style, + ordinal, + ordinal.saturating_sub(line_start), + sites, + ); + } + if let Some(ordinal) = line_end.checked_sub(1).filter(|_| line_start < line_end) { + append_line_end_reduction_site( + paragraph, + style, + ordinal, + ordinal.saturating_sub(line_start), + sites, + ); + } +} + +#[cfg(test)] +fn reduction_sites( + paragraph: &Paragraph, + style: &Style, + line_start: usize, + line_end: usize, +) -> Vec { + let mut sites = Vec::new(); + collect_reduction_sites(paragraph, style, line_start, line_end, &mut sites); + sites +} + +fn width_after_available_reduction_with_scratch( + paragraph: &Paragraph, + style: &Style, + start: usize, + end: usize, + width: i64, + available: i64, + sites: &mut Vec, +) -> i64 { + let need = width.saturating_sub(available); + if need <= 0 { + return width; + } + let line_start = cluster_index_at_or_after(paragraph, start); + let line_end = cluster_index_at_or_after(paragraph, end); + collect_reduction_sites(paragraph, style, line_start, line_end, sites); + let capacity = sites.iter().fold(0_i64, |sum, site| { + sum.saturating_add(i64::from(site.capacity)) + }); + let reduced = width.saturating_sub(need.min(capacity)); + reduced.saturating_sub(hanging_amount( + paragraph, style, line_end, reduced, available, + )) +} + +#[cfg(test)] +fn width_after_available_reduction( + paragraph: &Paragraph, + style: &Style, + start: usize, + end: usize, + width: i64, + available: i64, +) -> i64 { + let mut sites = Vec::new(); + width_after_available_reduction_with_scratch( + paragraph, style, start, end, width, available, &mut sites, + ) +} + +fn append_line_end_reduction_site( + paragraph: &Paragraph, + style: &Style, + ordinal: usize, + boundary: usize, + sites: &mut Vec, +) { + let Some(cluster) = paragraph.text.clusters().get(ordinal) else { + return; + }; + let before = class_of_cluster_with_style(paragraph, style, ordinal); + let cell = match style.reduction_table() { + ReductionTable::Table3 => crate::generated::table3::cell(before, 0), + ReductionTable::Table4 => crate::generated::table4::cell(before, 0), + ReductionTable::Table5 => crate::generated::table5::cell(before, 0), + }; + let Some(cell) = cell else { + return; + }; + let Some(limit) = cell.limit.filter(|_| cell.stage != 0) else { + return; + }; + let size = cluster.size_override().unwrap_or(paragraph.text.size()); + let current = line_end_space_after(paragraph, style, ordinal); + let floor = crate::spec::scale_spec_units(size.inline(), limit); + push_reduction_site( + sites, + boundary, + size.inline(), + current.saturating_sub(floor), + cell.stage, + cell.two_valued, + ); +} + +fn hanging_amount( + paragraph: &Paragraph, + style: &Style, + line_end: usize, + occupied: i64, + available: i64, +) -> i64 { + if style.hanging_punctuation() != HangingPunctuation::Hanging || occupied <= available { + return 0; + } + let Some(ordinal) = line_end.checked_sub(1) else { + return 0; + }; + if !matches!( + class_of_cluster_with_style(paragraph, style, ordinal), + crate::spec::FULL_STOP | crate::spec::COMMA + ) { + return 0; + } + occupied + .saturating_sub(available) + .min(i64::from(effective_cluster_body_advance(paragraph, ordinal)).max(0)) +} + +fn append_table_reduction_sites( + paragraph: &Paragraph, + style: &Style, + ordinal: usize, + boundary: usize, + sites: &mut Vec, +) { + let clusters = paragraph.text.clusters(); + let Some(before_cluster) = clusters.get(ordinal) else { + return; + }; + let Some(after_cluster) = clusters.get(ordinal.saturating_add(1)) else { + return; + }; + let before = class_of_cluster_with_style(paragraph, style, ordinal); + let after = class_of_cluster_with_style(paragraph, style, ordinal.saturating_add(1)); + let before_size = before_cluster + .size_override() + .unwrap_or(paragraph.text.size()); + let after_size = after_cluster + .size_override() + .unwrap_or(paragraph.text.size()); + let before_character = single_cluster_character(paragraph, before_cluster); + let after_character = single_cluster_character(paragraph, after_cluster); + let before_solid = before_character.is_some_and(|character| { + contextual_punctuation_is_solid(paragraph, before_cluster, character) + }); + let after_solid = after_character.is_some_and(|character| { + contextual_punctuation_is_solid(paragraph, after_cluster, character) + }); + let components = crate::spec::table_one_space_components( + before, + after, + before_size, + after_size, + before_solid, + after_solid, + ); + + if append_special_reduction_sites( + style.reduction_table(), + before, + after, + components, + [before_size.inline(), after_size.inline()], + boundary, + sites, + ) { + return; + } + + let cell = match style.reduction_table() { + ReductionTable::Table3 => crate::generated::table3::cell(before, after), + ReductionTable::Table4 => crate::generated::table4::cell(before, after), + ReductionTable::Table5 => crate::generated::table5::cell(before, after), + }; + let Some(cell) = cell else { + return; + }; + let active = match components { + [amount, 0] => Some((amount, before_size.inline())), + [0, amount] => Some((amount, after_size.inline())), + _ => None, + }; + let (Some((amount, weight)), Some(_)) = (active, cell.limit) else { + return; + }; + let floor = crate::spec::scale_spec_units(weight, cell.limit.unwrap_or(0)); + push_reduction_site( + sites, + boundary, + weight, + amount.saturating_sub(floor), + cell.stage, + cell.two_valued, + ); +} + +fn append_special_reduction_sites( + table: ReductionTable, + before: u8, + after: u8, + components: [i32; 2], + weights: [i32; 2], + boundary: usize, + sites: &mut Vec, +) -> bool { + let mut push = |component: usize, floor: i32, stage: u8| { + push_reduction_site( + sites, + boundary, + weights[component], + components[component].saturating_sub(floor), + stage, + false, + ); + }; + match (before, after, table) { + (5, 5, ReductionTable::Table3) => { + push(0, 0, 4); + push(1, 0, 4); + }, + (5, 5, ReductionTable::Table4) => { + push(0, 0, 2); + push(1, 0, 2); + }, + (5 | 6, 5, ReductionTable::Table5) => {}, + (6, 5, ReductionTable::Table3) => push(1, 0, 4), + (6 | 7, 5, ReductionTable::Table4) => push(1, 0, 2), + (7, 5, ReductionTable::Table3) => { + push(0, 0, 5); + push(1, 0, 4); + }, + (7, 5, ReductionTable::Table5) => { + push(0, quarter_rounded_up(weights[0]), 3); + }, + _ => return false, + } + true +} + +fn quarter_rounded_up(value: i32) -> i32 { + (value / 4).saturating_add(i32::from(value % 4 != 0)) +} + +fn push_reduction_site( + sites: &mut Vec, + boundary: usize, + weight: i32, + capacity: i32, + stage: u8, + discrete: bool, +) { + if capacity > 0 && stage != 0 { + sites.push(ReductionSite { + boundary, + weight, + capacity, + stage, + discrete, + }); + } +} + +fn distribute_reduction_with_scratch( + amount: i64, + sites: &[ReductionSite], + remainder: Remainder, + adjustments: &mut [i32], + scratch: &mut DistributionScratch, +) { + if amount <= 0 { + return; + } + let weight_sum = sites.iter().fold(0_i64, |sum, site| { + sum.saturating_add(i64::from(site.weight.max(1))) + }); + scratch.assigned.clear(); + scratch.assigned.extend(sites.iter().map(|site| { + amount + .saturating_mul(i64::from(site.weight.max(1))) + .checked_div(weight_sum.max(1)) + .unwrap_or(0) + .min(i64::from(site.capacity)) + })); + let left = amount.saturating_sub( + scratch + .assigned + .iter() + .fold(0_i64, |sum, take| sum.saturating_add(*take)), + ); + scratch.capacities.clear(); + scratch.capacities.extend( + sites + .iter() + .zip(&scratch.assigned) + .map(|(site, take)| i64::from(site.capacity).saturating_sub(*take).max(0)), + ); + capped_round_robin_into(left, &scratch.capacities, remainder, &mut scratch.extra); + for (take, addition) in scratch.assigned.iter_mut().zip(&scratch.extra) { + *take = take.saturating_add(*addition); + } + for (site, take) in sites.iter().zip(&scratch.assigned) { + apply_reduction(site.boundary, *take, adjustments); + } +} + +#[cfg(test)] +fn distribute_reduction( + amount: i64, + sites: &[ReductionSite], + remainder: Remainder, + adjustments: &mut [i32], +) { + let mut scratch = DistributionScratch::new(); + distribute_reduction_with_scratch(amount, sites, remainder, adjustments, &mut scratch); +} + +fn capped_round_robin_into( + amount: i64, + capacities: &[i64], + remainder: Remainder, + shares: &mut Vec, +) { + let total_capacity = capacities.iter().fold(0_i64, |sum, capacity| { + sum.saturating_add((*capacity).max(0)) + }); + let target = amount.max(0).min(total_capacity); + let maximum_rounds = capacities + .iter() + .copied() + .map(|capacity| capacity.max(0)) + .max() + .unwrap_or(0) + .min(target); + + let mut lower = 0_i64; + let mut upper = maximum_rounds; + for _ in 0..64 { + let distance = upper.saturating_sub(lower); + if distance == 0 { + break; + } + let previous = (lower, upper); + let rounds = lower + .saturating_add(distance / 2) + .saturating_add(distance % 2); + let consumed = capacities.iter().fold(0_i64, |sum, capacity| { + sum.saturating_add((*capacity).max(0).min(rounds)) + }); + if consumed <= target { + lower = rounds; + } else { + upper = rounds.saturating_sub(1); + } + if (lower, upper) == previous { + break; + } + } + + shares.clear(); + shares.extend( + capacities + .iter() + .map(|capacity| (*capacity).max(0).min(lower)), + ); + let placed = shares + .iter() + .fold(0_i64, |sum, share| sum.saturating_add(*share)); + let mut left = target.saturating_sub(placed); + match remainder { + Remainder::Leading => { + for (capacity, share) in capacities.iter().zip(&mut *shares) { + if left == 0 { + break; + } + if (*capacity).max(0) > lower { + *share = share.saturating_add(1); + left = left.saturating_sub(1); + } + } + }, + Remainder::Trailing => { + for (capacity, share) in capacities.iter().zip(&mut *shares).rev() { + if left == 0 { + break; + } + if (*capacity).max(0) > lower { + *share = share.saturating_add(1); + left = left.saturating_sub(1); + } + } + }, + } +} + +#[cfg(test)] +fn capped_round_robin(amount: i64, capacities: &[i64], remainder: Remainder) -> Vec { + let mut shares = Vec::new(); + capped_round_robin_into(amount, capacities, remainder, &mut shares); + shares +} + +fn apply_reduction(boundary: usize, amount: i64, adjustments: &mut [i32]) { + if let Some(adjustment) = adjustments.get_mut(boundary) { + *adjustment = adjustment.saturating_sub(clamp_i32(amount)); + } +} + +fn distribute_adjustment_with_scratch( + amount: i64, + sites: &[(usize, i32, Option)], + remainder: Remainder, + adjustments: &mut [i32], + scratch: &mut DistributionScratch, +) { + if amount <= 0 { + return; + } + let weight_sum = sites.iter().fold(0_i64, |sum, (_, weight, _)| { + sum.saturating_add(i64::from((*weight).max(1))) + }); + scratch.assigned.clear(); + scratch + .assigned + .extend(sites.iter().map(|&(_, weight, cap)| { + let proportional = amount + .saturating_mul(i64::from(weight.max(1))) + .checked_div(weight_sum.max(1)) + .unwrap_or(0); + cap.map_or(proportional, |cap| proportional.min(i64::from(cap))) + })); + let mut placed = 0_i64; + for (&(index, _, _), share) in sites.iter().zip(&scratch.assigned) { + if let Some(adjustment) = adjustments.get_mut(index) { + *adjustment = adjustment.saturating_add(clamp_i32(*share)); + placed = placed.saturating_add(*share); + } + } + + let left = amount.saturating_sub(placed); + scratch.capacities.clear(); + scratch + .capacities + .extend(sites.iter().map(|&(index, _, cap)| { + adjustments.get(index).map_or(0, |adjustment| { + cap.map_or(left, |cap| { + i64::from(cap).saturating_sub(i64::from(*adjustment)).max(0) + }) + }) + })); + capped_round_robin_into(left, &scratch.capacities, remainder, &mut scratch.extra); + for (&(index, _, _), addition) in sites.iter().zip(&scratch.extra) { + if let Some(adjustment) = adjustments.get_mut(index) { + *adjustment = adjustment.saturating_add(clamp_i32(*addition)); + } + } +} + +#[cfg(test)] +fn distribute_adjustment( + amount: i64, + sites: &[(usize, i32, Option)], + remainder: Remainder, + adjustments: &mut [i32], +) { + let mut scratch = DistributionScratch::new(); + distribute_adjustment_with_scratch(amount, sites, remainder, adjustments, &mut scratch); +} + +fn effective_cluster_advance(paragraph: &Paragraph, style: &Style, ordinal: usize) -> i32 { + effective_cluster_body_advance(paragraph, ordinal) + .saturating_add(boundary_space_after_with_style(paragraph, style, ordinal)) +} + +fn effective_cluster_body_advance(paragraph: &Paragraph, ordinal: usize) -> i32 { + if let Some((group, columns, line_gap)) = furawake_cluster_range(paragraph, ordinal) { + if group.start != ordinal { + return 0; + } + furawake_segment( + paragraph, + group, + columns, + line_gap, + paragraph.text.clusters().len(), + ) + .advance + } else { + nested_cluster_body_advance(paragraph, ordinal) + } +} + +fn nested_cluster_body_advance(paragraph: &Paragraph, ordinal: usize) -> i32 { + if let Some(group) = tate_chu_yoko_cluster_range(paragraph, ordinal) { + if group.start != ordinal { + return 0; + } + paragraph.text.clusters()[group] + .iter() + .map(|cluster| { + cluster + .size_override() + .unwrap_or(paragraph.text.size()) + .block() + }) + .max() + .unwrap_or(0) + } else if formula_cluster_range(paragraph, ordinal).is_some() + && single_cluster_character(paragraph, ¶graph.text.clusters()[ordinal]) + .is_some_and(is_math_token) + { + paragraph.text.clusters()[ordinal] + .size_override() + .unwrap_or(paragraph.text.size()) + .inline() + } else { + paragraph.text.clusters()[ordinal].advance() + } +} + +fn effective_cluster_advance_on_line( + paragraph: &Paragraph, + style: &Style, + ordinal: usize, + line_start: usize, + line_end: usize, + line_index: usize, +) -> i32 { + ordinary_cluster_advance_on_line(paragraph, style, ordinal, line_start, line_end, line_index) + .saturating_add(jidori_extra_after( + paragraph, style, ordinal, line_start, line_end, line_index, + )) +} + +fn ordinary_cluster_advance_on_line( + paragraph: &Paragraph, + style: &Style, + ordinal: usize, + line_start: usize, + line_end: usize, + line_index: usize, +) -> i32 { + effective_cluster_advance_on_line_without_ruby(paragraph, style, ordinal, line_start, line_end) + .saturating_add(ruby_boundary_separation_after( + paragraph, style, ordinal, line_start, line_end, line_index, + )) +} + +fn jidori_extra_after( + paragraph: &Paragraph, + style: &Style, + ordinal: usize, + line_start: usize, + line_end: usize, + line_index: usize, +) -> i32 { + let Some((range, cells)) = jidori_cluster_range(paragraph, ordinal) else { + return 0; + }; + if range.start < line_start || range.end > line_end || range.start >= range.end { + return 0; + } + jidori_plan( + paragraph, style, range, cells, line_start, line_end, line_index, + ) + .extra_after(ordinal) +} + +fn jidori_plan( + paragraph: &Paragraph, + style: &Style, + range: Range, + cells: u16, + line_start: usize, + line_end: usize, + line_index: usize, +) -> JidoriPlan { + let natural = range.clone().fold(0_i64, |sum, member| { + let advance = if member.saturating_add(1) == range.end { + effective_cluster_body_advance(paragraph, member) + } else { + ordinary_cluster_advance_on_line( + paragraph, style, member, line_start, line_end, line_index, + ) + }; + sum.saturating_add(i64::from(advance)) + }); + let target = i64::from(paragraph.text.size().inline()).saturating_mul(i64::from(cells)); + let surplus = target.saturating_sub(natural).max(0); + let mut extra_after = vec![0; range.end.saturating_sub(range.start)]; + let mut eligible = Vec::new(); + for before in range.start..range.end.saturating_sub(1) { + if paragraph + .text + .clusters() + .get(before) + .is_some_and(|cluster| break_is_legal(paragraph, style, cluster.range().end)) + { + eligible.push(before); + } + } + if eligible.is_empty() { + if let Some(last) = extra_after.last_mut() { + *last = clamp_i32(surplus); + } + } else { + let divisor = i64::try_from(eligible.len()).unwrap_or(i64::MAX).max(1); + let quotient = surplus.checked_div(divisor).unwrap_or(0); + let remainder = usize::try_from(surplus.rem_euclid(divisor)).unwrap_or(usize::MAX); + for (position, boundary) in eligible.iter().enumerate() { + let receives_remainder = match style.remainder() { + Remainder::Leading => position < remainder, + Remainder::Trailing => { + eligible.len().saturating_sub(position.saturating_add(1)) < remainder + }, + }; + if let Some(extra) = extra_after.get_mut(boundary.saturating_sub(range.start)) { + *extra = clamp_i32(quotient.saturating_add(i64::from(receives_remainder))); + } + } + } + JidoriPlan { range, extra_after } +} + +fn effective_cluster_advance_on_line_without_ruby( + paragraph: &Paragraph, + style: &Style, + ordinal: usize, + line_start: usize, + line_end: usize, +) -> i32 { + if let Some((group, columns, line_gap)) = furawake_cluster_range(paragraph, ordinal) { + if ordinal != group.start { + return 0; + } + return furawake_segment(paragraph, group, columns, line_gap, line_end).advance; + } + if let Some(group) = warichu_cluster_range(paragraph, ordinal) { + let segment_start = group.start.max(line_start); + if ordinal != segment_start { + return 0; + } + return warichu_segment(paragraph, group, line_start, line_end).advance; + } + if is_western_word_space(paragraph, ordinal) + && (ordinal == line_start || ordinal.saturating_add(1) == line_end) + { + return 0; + } + if ordinal.saturating_add(1) == line_end { + return effective_cluster_body_advance(paragraph, ordinal) + .saturating_add(line_end_space_after(paragraph, style, ordinal)); + } + effective_cluster_advance(paragraph, style, ordinal) +} + +fn line_end_space_after(paragraph: &Paragraph, style: &Style, ordinal: usize) -> i32 { + let Some(cluster) = paragraph.text.clusters().get(ordinal) else { + return 0; + }; + let class = class_of_cluster_with_style(paragraph, style, ordinal); + if (class == crate::spec::CLOSING_BRACKET + && style.line_end_punctuation() == LineEndPunctuation::Solid) + || (class == crate::spec::COMMA + && style.line_end_full_stop_comma() == LineEndFullStopComma::Jis) + { + return 0; + } + let size = cluster.size_override().unwrap_or(paragraph.text.size()); + let character = single_cluster_character(paragraph, cluster); + let solid = character + .is_some_and(|character| contextual_punctuation_is_solid(paragraph, cluster, character)); + crate::spec::table_one_space(class, 0, size, size, solid, false) +} diff --git a/crates/jlreq-core/src/pipeline/composition.rs b/crates/jlreq-core/src/pipeline/composition.rs new file mode 100644 index 0000000..2decc4a --- /dev/null +++ b/crates/jlreq-core/src/pipeline/composition.rs @@ -0,0 +1,831 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[derive(Debug, Clone, Copy)] +struct Candidate { + offset: usize, + mandatory: bool, + discretionary: bool, +} + +#[derive(Debug, Clone, Copy)] +struct Node { + cost: u128, + previous: usize, + line_count: usize, +} + +#[derive(Debug, Default)] +struct PreparedParagraph { + candidate_ordinals: Vec, + legal_candidates: Vec, + natural_prefix: Vec, + minimum_prefix: Vec, + reduction_prefix: Vec, + line_end_reduction: Vec, + regular: bool, + fast_measure: bool, +} + +impl PreparedParagraph { + const fn new() -> Self { + Self { + candidate_ordinals: Vec::new(), + legal_candidates: Vec::new(), + natural_prefix: Vec::new(), + minimum_prefix: Vec::new(), + reduction_prefix: Vec::new(), + line_end_reduction: Vec::new(), + regular: false, + fast_measure: false, + } + } + + fn clear(&mut self) { + self.candidate_ordinals.clear(); + self.legal_candidates.clear(); + self.natural_prefix.clear(); + self.minimum_prefix.clear(); + self.reduction_prefix.clear(); + self.line_end_reduction.clear(); + self.regular = false; + self.fast_measure = false; + } +} + +#[derive(Debug, Clone)] +struct WarichuSegment { + range: Range, + leading_bracket: Option, + first_lane: Range, + second_lane: Range, + trailing_bracket: Option, + first_width: i32, + second_width: i32, + advance: i32, +} + +#[derive(Debug, Clone)] +struct FurawakeSegment { + range: Range, + lanes: Vec>, + block_extents: Vec, + line_gap: i32, + advance: i32, + block_extent: i32, +} + +#[derive(Debug, Clone)] +struct JidoriPlan { + range: Range, + extra_after: Vec, +} + +impl JidoriPlan { + fn extra_after(&self, ordinal: usize) -> i32 { + ordinal + .checked_sub(self.range.start) + .and_then(|local| self.extra_after.get(local)) + .copied() + .unwrap_or(0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ComplexKind { + Ornamented, + SimpleRuby, + JukugoRuby, + TateChuYoko, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct ComplexIdentity { + kind: ComplexKind, + construct: usize, + member: usize, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ExpansionSite { + None, + Site { + weight: i32, + bounded: Option<(i32, u8)>, + residual: bool, + }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct ReductionSite { + boundary: usize, + weight: i32, + capacity: i32, + stage: u8, + discrete: bool, +} + +#[derive(Debug, Default)] +struct DistributionScratch { + assigned: Vec, + capacities: Vec, + extra: Vec, +} + +impl DistributionScratch { + const fn new() -> Self { + Self { + assigned: Vec::new(), + capacities: Vec::new(), + extra: Vec::new(), + } + } + + fn clear(&mut self) { + self.assigned.clear(); + self.capacities.clear(); + self.extra.clear(); + } +} + +#[derive(Debug, Default)] +struct LineScratch { + reduction_sites: Vec, + stage_reductions: Vec, + expansion_sites: Vec, + distribution_sites: Vec<(usize, i32, Option)>, + construct_ordinals: Vec, + distribution: DistributionScratch, +} + +impl LineScratch { + const fn new() -> Self { + Self { + reduction_sites: Vec::new(), + stage_reductions: Vec::new(), + expansion_sites: Vec::new(), + distribution_sites: Vec::new(), + construct_ordinals: Vec::new(), + distribution: DistributionScratch::new(), + } + } + + fn clear(&mut self) { + self.reduction_sites.clear(); + self.stage_reductions.clear(); + self.expansion_sites.clear(); + self.distribution_sites.clear(); + self.construct_ordinals.clear(); + self.distribution.clear(); + } +} + +#[derive(Debug, Clone, Copy)] +struct LineContext { + start: usize, + end: usize, + index: usize, +} + +/// A reusable whole-paragraph composer. +/// +/// All temporary search memory is retained between calls. The returned Layout owns its +/// placements and never borrows this scratch state. +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct Composer { + limits: CompositionLimits, + transitions: usize, + candidates: Vec, + nodes: Vec, + chosen: Vec, + line_advances: Vec, + line_adjustments: Vec, + line_scratch: LineScratch, + prepared: PreparedParagraph, +} + +impl Composer { + /// Build an empty reusable composer. + #[must_use] + pub const fn new() -> Self { + Self { + limits: CompositionLimits::DEFAULT, + transitions: 0, + candidates: Vec::new(), + nodes: Vec::new(), + chosen: Vec::new(), + line_advances: Vec::new(), + line_adjustments: Vec::new(), + line_scratch: LineScratch::new(), + prepared: PreparedParagraph::new(), + } + } + + /// Build a reusable composer with explicit deterministic resource limits. + #[must_use] + pub const fn with_limits(limits: CompositionLimits) -> Self { + Self { + limits, + transitions: 0, + candidates: Vec::new(), + nodes: Vec::new(), + chosen: Vec::new(), + line_advances: Vec::new(), + line_adjustments: Vec::new(), + line_scratch: LineScratch::new(), + prepared: PreparedParagraph::new(), + } + } + + /// The limits used by subsequent composition calls. + #[must_use] + pub const fn limits(&self) -> CompositionLimits { + self.limits + } + + /// Replace the limits used by subsequent composition calls. + pub const fn set_limits(&mut self, limits: CompositionLimits) { + self.limits = limits; + } + + /// Normalize, choose breaks globally, and place one validated paragraph. + pub fn compose( + &mut self, + paragraph: &Paragraph, + style: &Style, + ) -> Result { + self.reset_for_call(); + if paragraph.text.clusters().is_empty() { + return Ok(Layout::default()); + } + self.check_static_limits(paragraph)?; + self.prepare_candidates(paragraph); + self.prepare_indexes(paragraph, style); + self.search(paragraph, style)?; + self.backtrack(); + Ok(self.place(paragraph, style)) + } + + fn reset_for_call(&mut self) { + self.transitions = 0; + self.candidates.clear(); + self.nodes.clear(); + self.chosen.clear(); + self.line_advances.clear(); + self.line_adjustments.clear(); + self.line_scratch.clear(); + self.prepared.clear(); + } + + fn check_static_limits(&self, paragraph: &Paragraph) -> Result<(), ComposeError> { + check_limit( + CompositionResource::Clusters, + self.limits.max_clusters(), + paragraph.text.clusters().len(), + )?; + check_limit( + CompositionResource::BreakCandidates, + self.limits.max_break_candidates(), + paragraph.breaks.len(), + )?; + check_limit( + CompositionResource::Constructs, + self.limits.max_constructs(), + paragraph.constructs.len(), + )?; + check_limit( + CompositionResource::TabStops, + self.limits.max_tab_stops(), + paragraph.tab_stops.len(), + ) + } + + fn charge_transitions(&mut self, amount: usize) -> Result<(), ComposeError> { + let observed = self.transitions.saturating_add(amount); + if observed > self.limits.max_search_transitions() { + return Err(ComposeError::new( + CompositionResource::SearchTransitions, + self.limits.max_search_transitions(), + observed, + )); + } + self.transitions = observed; + Ok(()) + } + + fn prepare_candidates(&mut self, paragraph: &Paragraph) { + self.candidates.clear(); + self.candidates.push(Candidate { + offset: 0, + mandatory: true, + discretionary: false, + }); + self.candidates.extend( + paragraph + .breaks + .iter() + .filter(|opportunity| !is_internal_furawake_offset(paragraph, opportunity.offset())) + .map(|opportunity| Candidate { + offset: opportunity.offset(), + mandatory: opportunity.is_mandatory(), + discretionary: opportunity.is_discretionary(), + }), + ); + } + + fn prepare_indexes(&mut self, paragraph: &Paragraph, style: &Style) { + self.prepared.candidate_ordinals.extend( + self.candidates + .iter() + .map(|candidate| cluster_index_at_or_after(paragraph, candidate.offset)), + ); + self.prepared + .legal_candidates + .extend(self.candidates.iter().map(|candidate| { + candidate.mandatory || break_is_legal(paragraph, style, candidate.offset) + })); + + self.prepared.regular = paragraph.constructs.is_empty() + && !paragraph + .line_tabs + .iter() + .copied() + .any(core::convert::identity); + self.prepared.fast_measure = !paragraph + .line_tabs + .iter() + .copied() + .any(core::convert::identity) + && paragraph.constructs.iter().all(|construct| { + matches!( + construct.kind(), + ConstructKind::TateChuYoko(_) + | ConstructKind::Emphasis { .. } + | ConstructKind::ReferenceMark { .. } + | ConstructKind::Script { .. } + | ConstructKind::Formula(_) + ) + }); + if !self.prepared.fast_measure { + return; + } + + let cluster_count = paragraph.text.clusters().len(); + self.prepared + .natural_prefix + .reserve(cluster_count.saturating_add(1)); + self.prepared + .minimum_prefix + .reserve(cluster_count.saturating_add(1)); + self.prepared + .reduction_prefix + .reserve(cluster_count.saturating_add(1)); + self.prepared.line_end_reduction.reserve(cluster_count); + self.prepared.natural_prefix.push(0); + self.prepared.minimum_prefix.push(0); + self.prepared.reduction_prefix.push(0); + + for ordinal in 0..cluster_count { + let natural = i64::from(effective_cluster_advance(paragraph, style, ordinal)); + let previous_natural = self.prepared.natural_prefix.last().copied().unwrap_or(0); + self.prepared + .natural_prefix + .push(previous_natural.saturating_add(natural)); + + let minimum = if is_western_word_space(paragraph, ordinal) { + 0 + } else { + i64::from(paragraph.text.clusters()[ordinal].advance()) + }; + let previous_minimum = self.prepared.minimum_prefix.last().copied().unwrap_or(0); + self.prepared + .minimum_prefix + .push(previous_minimum.saturating_add(minimum)); + + self.line_scratch.reduction_sites.clear(); + if paragraph + .text + .clusters() + .get(ordinal.saturating_add(1)) + .is_some() + { + if is_western_word_space(paragraph, ordinal) { + let cluster = ¶graph.text.clusters()[ordinal]; + let capacity = effective_cluster_body_advance(paragraph, ordinal) + .saturating_sub(quarter_inline_size(paragraph, cluster)) + .max(0); + push_reduction_site( + &mut self.line_scratch.reduction_sites, + 0, + cluster + .size_override() + .unwrap_or(paragraph.text.size()) + .inline(), + capacity, + 1, + false, + ); + } + append_table_reduction_sites( + paragraph, + style, + ordinal, + 0, + &mut self.line_scratch.reduction_sites, + ); + } + let capacity = self + .line_scratch + .reduction_sites + .iter() + .fold(0_i64, |sum, site| { + sum.saturating_add(i64::from(site.capacity)) + }); + let previous_capacity = self.prepared.reduction_prefix.last().copied().unwrap_or(0); + self.prepared + .reduction_prefix + .push(previous_capacity.saturating_add(capacity)); + + self.line_scratch.reduction_sites.clear(); + append_line_end_reduction_site( + paragraph, + style, + ordinal, + 0, + &mut self.line_scratch.reduction_sites, + ); + self.prepared.line_end_reduction.push( + self.line_scratch + .reduction_sites + .iter() + .fold(0_i64, |sum, site| { + sum.saturating_add(i64::from(site.capacity)) + }), + ); + } + } + + fn search(&mut self, paragraph: &Paragraph, style: &Style) -> Result<(), ComposeError> { + self.nodes.clear(); + self.nodes.resize( + self.candidates.len(), + Node { + cost: INFINITE_COST, + previous: 0, + line_count: 0, + }, + ); + self.nodes[0] = Node { + cost: 0, + previous: 0, + line_count: 0, + }; + + let mut mandatory_partition_start = 0_usize; + for end in 1..self.candidates.len() { + let candidate = self.candidates[end]; + if !self.prepared.legal_candidates[end] { + continue; + } + for start in (mandatory_partition_start..end).rev() { + self.charge_transitions(1)?; + if self.nodes[start].cost == INFINITE_COST { + continue; + } + let line_number = self.nodes[start].line_count; + let start_ordinal = self.prepared.candidate_ordinals[start]; + let end_ordinal = self.prepared.candidate_ordinals[end]; + if !self.prepared.regular { + self.charge_transitions( + end_ordinal + .saturating_sub(start_ordinal) + .saturating_add(paragraph.constructs.len()), + )?; + } + let measured_width = if self.prepared.fast_measure { + fast_measure_line( + &self.prepared, + paragraph, + style, + start_ordinal, + end_ordinal, + line_number, + ) + } else { + measure_line( + paragraph, + style, + self.candidates[start].offset, + candidate.offset, + line_number, + ) + }; + let available = i64::from(paragraph.line_extent); + let width = if self.prepared.fast_measure { + fast_width_after_available_reduction( + &self.prepared, + paragraph, + style, + start_ordinal, + end_ordinal, + measured_width, + available, + ) + } else { + width_after_available_reduction_with_scratch( + paragraph, + style, + self.candidates[start].offset, + candidate.offset, + measured_width, + available, + &mut self.line_scratch.reduction_sites, + ) + }; + let delta = available.saturating_sub(width); + let is_last = end.saturating_add(1) == self.candidates.len(); + let mut edge_cost = + non_negative_cost(line_badness(delta, is_last, style.adjustment_preference())); + if candidate.discretionary { + edge_cost = edge_cost.saturating_add(100_000); + } + edge_cost = edge_cost.saturating_add(non_negative_cost(warichu_break_penalty( + paragraph, + candidate.offset, + ))); + edge_cost = edge_cost.saturating_add(non_negative_cost(formula_break_penalty( + paragraph, + candidate.offset, + ))); + if is_last { + edge_cost = edge_cost.saturating_add(non_negative_cost(widow_penalty( + paragraph, + self.candidates[start].offset, + candidate.offset, + ))); + } + let cost = edge_cost.saturating_add(self.nodes[start].cost); + if search_candidate_precedes(cost, start, self.nodes[end]) { + self.nodes[end] = Node { + cost, + previous: start, + line_count: line_number.saturating_add(1), + }; + } + + if self.prepared.regular { + let minimum_width = + fast_minimum_width(&self.prepared, start_ordinal, end_ordinal); + if search_lower_bound_exceeds( + minimum_width, + available, + is_last, + style.adjustment_preference(), + self.nodes[end].cost, + ) { + break; + } + } + } + if candidate.mandatory { + mandatory_partition_start = end; + } + } + Ok(()) + } + + fn backtrack(&mut self) { + self.chosen.clear(); + let mut cursor = self.nodes.len().saturating_sub(1); + self.chosen.push(cursor); + while cursor != 0 { + cursor = self.nodes[cursor].previous; + self.chosen.push(cursor); + } + self.chosen.reverse(); + } + + fn place(&mut self, paragraph: &Paragraph, style: &Style) -> Layout { + let mut layout = Layout::default(); + let mut block_cursor = 0_i64; + for line_index in 0..self.chosen.len().saturating_sub(1) { + let start_offset = self.candidates[self.chosen[line_index]].offset; + let next_line = line_index.saturating_add(1); + let end_offset = self.candidates[self.chosen[next_line]].offset; + let start_cluster = cluster_index_at_or_after(paragraph, start_offset); + let end_cluster = cluster_index_at_or_after(paragraph, end_offset); + let is_last = line_index.saturating_add(2) == self.chosen.len(); + let block_origin = match paragraph.writing_mode { + WritingMode::HorizontalTb => clamp_i32(block_cursor), + WritingMode::VerticalRl => clamp_i32(block_cursor.saturating_neg()), + }; + let line = self.place_line( + paragraph, + style, + start_cluster..end_cluster, + line_index, + block_origin, + is_last, + ); + if i64::from(line.inline_extent) > i64::from(paragraph.line_extent) { + layout.diagnostics.push(Diagnostic { + code: "layout.overfull", + severity: Severity::Warning, + range: Some(line.range.clone()), + jlreq: "3.8.1", + }); + } + block_cursor = block_cursor.saturating_add(i64::from(line.block_extent.max(1))); + layout.lines.push(line); + } + add_widow_diagnostic(paragraph, &mut layout); + layout + } + + fn place_line( + &mut self, + paragraph: &Paragraph, + style: &Style, + cluster_range: Range, + line_index: usize, + block_origin: i32, + is_last: bool, + ) -> Line { + let start_cluster = cluster_range.start; + let end_cluster = cluster_range.end; + self.line_advances.clear(); + let clusters = ¶graph.text.clusters()[start_cluster..end_cluster]; + self.line_advances + .extend((start_cluster..end_cluster).map(|ordinal| { + effective_cluster_advance_on_line( + paragraph, + style, + ordinal, + start_cluster, + end_cluster, + line_index, + ) + })); + apply_tabs( + paragraph, + style, + start_cluster, + end_cluster, + line_index, + &mut self.line_advances, + ); + + let indent = line_head_indent(paragraph, style, start_cluster, line_index); + let ruby_leading = + ruby_line_leading_separation(paragraph, style, start_cluster, end_cluster, line_index); + let content_width = self.line_advances.iter().fold( + i64::from(indent.saturating_add(ruby_leading)), + |sum, advance| sum.saturating_add(i64::from(*advance)), + ); + let remaining = i64::from(paragraph.line_extent).saturating_sub(content_width); + let alignment_offset = match paragraph.alignment { + Alignment::Start | Alignment::Justify => 0, + Alignment::Center => remaining.max(0) / 2, + Alignment::End => remaining.max(0), + }; + let justify = line_should_justify(paragraph.alignment, is_last, remaining, clusters.len()); + prepare_line_adjustments_with_scratch( + paragraph, + style, + start_cluster, + end_cluster, + line_adjustment_need(remaining, justify), + &mut self.line_adjustments, + &mut self.line_scratch, + ); + + let mut placed = Vec::with_capacity(clusters.len()); + let mut cursor = i64::from(indent) + .saturating_add(i64::from(ruby_leading)) + .saturating_add(alignment_offset); + let mut block_extent = paragraph.text.size().block(); + let mut local = 0; + while local < clusters.len() { + let ordinal = start_cluster.saturating_add(local); + let previous_ordinal; + if let Some((group, columns, line_gap)) = furawake_cluster_range(paragraph, ordinal) + .filter(|(group, _, _)| group.start == ordinal) + { + let segment = furawake_segment(paragraph, group, columns, line_gap, end_cluster); + previous_ordinal = segment.range.end.saturating_sub(1); + block_extent = block_extent.max(segment.block_extent); + place_furawake_segment(paragraph, &segment, cursor, block_origin, &mut placed); + cursor = cursor.saturating_add(i64::from(self.line_advances[local])); + local = local.saturating_add(segment.range.end.saturating_sub(ordinal)); + } else if let Some(group) = warichu_cluster_range(paragraph, ordinal) + .filter(|group| group.start.max(start_cluster) == ordinal) + { + let segment = warichu_segment(paragraph, group, start_cluster, end_cluster); + previous_ordinal = segment.range.end.saturating_sub(1); + place_warichu_segment(paragraph, &segment, cursor, block_origin, &mut placed); + cursor = cursor.saturating_add(i64::from(segment.advance)); + local = local.saturating_add(segment.range.end.saturating_sub(ordinal)); + } else if let Some(group) = tate_chu_yoko_cluster_range(paragraph, ordinal) + .filter(|group| group.start == ordinal) + { + let group_end = group.end.min(end_cluster); + let member_count = group_end.saturating_sub(ordinal); + previous_ordinal = group_end.saturating_sub(1); + let horizontal_width = paragraph.text.clusters()[ordinal..group_end] + .iter() + .fold(0_i64, |sum, cluster| { + sum.saturating_add(i64::from(cluster.advance())) + }); + block_extent = block_extent.max(clamp_i32(horizontal_width)); + let mut member_block = i64::from(block_origin) + .saturating_sub(horizontal_width.checked_div(2).unwrap_or(0)); + for (member_local, cluster) in paragraph.text.clusters()[ordinal..group_end] + .iter() + .enumerate() + { + let member_ordinal = ordinal.saturating_add(member_local); + let size = cluster.size_override().unwrap_or(paragraph.text.size()); + let frame = cluster.frame_override().unwrap_or(paragraph.text.frame()); + let (writing_mode, transform) = + local_orientation(paragraph, member_ordinal, frame); + placed.push(ClusterPlacement { + origin: PlacementOrigin::Cluster(member_ordinal), + range: cluster.range(), + inline: clamp_i32(cursor), + block: clamp_i32(member_block), + advance: cluster.advance(), + size, + frame, + writing_mode, + transform, + }); + member_block = member_block.saturating_add(i64::from(cluster.advance())); + } + cursor = cursor.saturating_add(i64::from(self.line_advances[local])); + local = local.saturating_add(member_count); + } else { + previous_ordinal = ordinal; + let cluster = &clusters[local]; + let advance = self.line_advances[local]; + let size = cluster.size_override().unwrap_or(paragraph.text.size()); + let frame = cluster.frame_override().unwrap_or(paragraph.text.frame()); + block_extent = block_extent.max(size.block()); + let (writing_mode, transform) = local_orientation(paragraph, ordinal, frame); + placed.push(ClusterPlacement { + origin: PlacementOrigin::Cluster(ordinal), + range: cluster.range(), + inline: clamp_i32(cursor), + block: block_origin, + advance, + size, + frame, + writing_mode, + transform, + }); + cursor = cursor.saturating_add(i64::from(advance)); + local = local.saturating_add(1); + } + + let boundary = previous_ordinal.saturating_sub(start_cluster); + cursor = cursor.saturating_add(i64::from( + self.line_adjustments.get(boundary).copied().unwrap_or(0), + )); + } + + let range = if let (Some(first), Some(last)) = (clusters.first(), clusters.last()) { + first.range().start..last.range().end + } else { + 0..0 + }; + let occupied = cursor.saturating_sub(alignment_offset); + let hanging = hanging_amount( + paragraph, + style, + end_cluster, + occupied, + i64::from(paragraph.line_extent), + ); + let mut line = Line { + range, + inline_origin: clamp_i32(alignment_offset), + block_origin, + inline_extent: clamp_i32(occupied.saturating_sub(hanging)), + block_extent, + clusters: placed, + attachments: Vec::new(), + }; + place_attachments( + paragraph, + style, + line_index, + start_cluster, + end_cluster, + &mut line, + &mut self.line_scratch.construct_ordinals, + ); + line + } +} + diff --git a/crates/jlreq-core/src/pipeline/placement.rs b/crates/jlreq-core/src/pipeline/placement.rs new file mode 100644 index 0000000..2ba4d27 --- /dev/null +++ b/crates/jlreq-core/src/pipeline/placement.rs @@ -0,0 +1,909 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +fn line_head_indent( + paragraph: &Paragraph, + style: &Style, + line_start: usize, + line_index: usize, +) -> i32 { + let ordinary = if line_index == 0 { + paragraph.first_line_indent + } else { + 0 + }; + let Some(cluster) = paragraph.text.clusters().get(line_start) else { + return ordinary; + }; + if class_of_cluster_with_style(paragraph, style, line_start) != crate::spec::OPENING_BRACKET { + return ordinary; + } + let half = half_inline_size(paragraph, cluster); + match (line_index == 0, style.line_head_opening_bracket()) { + (_, LineHeadOpeningBracket::Pattern2) => ordinary.saturating_add(half), + (true, LineHeadOpeningBracket::Pattern3) => ordinary.saturating_sub(half), + _ => ordinary, + } +} + +/// Whether the cluster at `ordinal` is a tab sign **of the line** in §3.6.3's sense. +/// +/// §3.6.3 corresponds the signs of a line with the stops of that line, and both halves +/// of that sentence are about the line's own inline axis. A tate-chu-yoko run runs +/// across that axis and a warichu's and a furawake's sublines run beside it, so each of +/// those structures holds one position on the line however many characters it holds, +/// and a coordinate inside one is not a position a stop can name. A sign standing there +/// is therefore not a sign of the line: it takes no stop, sets the advance it was shaped +/// with, and never chooses §3.6.3's cut — including where it is the structure's first +/// character, which is set in the structure like every other one +/// (`docs/decisions/tab-line-correspondence.md`). +fn is_line_tab_sign(paragraph: &Paragraph, ordinal: usize) -> bool { + paragraph.line_tabs.get(ordinal).copied().unwrap_or(false) +} + +fn measure_line( + paragraph: &Paragraph, + style: &Style, + start: usize, + end: usize, + line_number: usize, +) -> i64 { + let start_cluster = cluster_index_at_or_after(paragraph, start); + let end_cluster = cluster_index_at_or_after(paragraph, end); + let indent = i64::from(line_head_indent( + paragraph, + style, + start_cluster, + line_number, + )); + let mut cursor = indent.saturating_add(i64::from(ruby_line_leading_separation( + paragraph, + style, + start_cluster, + end_cluster, + line_number, + ))); + let mut tab_index = 0; + for local in 0..end_cluster.saturating_sub(start_cluster) { + let ordinal = start_cluster.saturating_add(local); + if is_line_tab_sign(paragraph, ordinal) { + let after_tab = ordinal.saturating_add(1); + let segment_width = segment_width( + paragraph, + style, + after_tab, + end_cluster, + LineContext { + start: start_cluster, + end: end_cluster, + index: line_number, + }, + ); + if let Some(stop) = paragraph + .tab_stops + .iter() + .skip(tab_index) + .find(|stop| i64::from(stop.position()) > cursor) + { + tab_index = tab_index.saturating_add(1); + let target = tab_target( + paragraph, + style, + *stop, + after_tab, + end_cluster, + LineContext { + start: start_cluster, + end: end_cluster, + index: line_number, + }, + segment_width, + ); + cursor = cursor.max(target); + } else { + return i64::MAX; + } + } else { + cursor = cursor.saturating_add(i64::from(effective_cluster_advance_on_line( + paragraph, + style, + ordinal, + start_cluster, + end_cluster, + line_number, + ))); + } + } + cursor +} + +fn segment_width( + paragraph: &Paragraph, + style: &Style, + start: usize, + end: usize, + line: LineContext, +) -> i64 { + (start..end) + .take_while(|ordinal| !is_line_tab_sign(paragraph, *ordinal)) + .fold(0_i64, |sum, ordinal| { + sum.saturating_add(i64::from(effective_cluster_advance_on_line( + paragraph, style, ordinal, line.start, line.end, line.index, + ))) + }) +} + +fn tab_target( + paragraph: &Paragraph, + style: &Style, + stop: TabStop, + start: usize, + end: usize, + line: LineContext, + segment_width: i64, +) -> i64 { + let position = i64::from(stop.position()); + match stop.alignment() { + TabAlignment::Start => position, + TabAlignment::Center => position.saturating_sub(segment_width / 2), + TabAlignment::End => position.saturating_sub(segment_width), + TabAlignment::Character(character) => { + let before = (start..end) + .take_while(|ordinal| { + let cluster = paragraph.text.clusters()[*ordinal].range(); + !paragraph.text.source()[cluster].contains(character) + && !is_line_tab_sign(paragraph, *ordinal) + }) + .fold(0_i64, |sum, ordinal| { + sum.saturating_add(i64::from(effective_cluster_advance_on_line( + paragraph, style, ordinal, line.start, line.end, line.index, + ))) + }); + position.saturating_sub(before) + }, + } +} + +fn apply_tabs( + paragraph: &Paragraph, + style: &Style, + start: usize, + end: usize, + line_number: usize, + advances: &mut [i32], +) { + let mut cursor = i64::from(line_head_indent(paragraph, style, start, line_number)); + let mut tab_index = 0; + for local in 0..advances.len() { + let ordinal = start.saturating_add(local); + let after_tab = local.saturating_add(1); + if is_line_tab_sign(paragraph, ordinal) { + let width = advances[after_tab..] + .iter() + .enumerate() + .take_while(|(following, _)| { + !is_line_tab_sign( + paragraph, + ordinal.saturating_add(1).saturating_add(*following), + ) + }) + .fold(0_i64, |sum, (_, advance)| { + sum.saturating_add(i64::from(*advance)) + }); + if let Some(stop) = paragraph + .tab_stops + .iter() + .skip(tab_index) + .find(|stop| i64::from(stop.position()) > cursor) + { + tab_index = tab_index.saturating_add(1); + let target = tab_target( + paragraph, + style, + *stop, + ordinal.saturating_add(1), + end, + LineContext { + start, + end, + index: line_number, + }, + width, + ); + advances[local] = clamp_i32(target.saturating_sub(cursor).max(0)); + } else { + advances[local] = paragraph.text.size().inline(); + } + } + cursor = cursor.saturating_add(i64::from(advances[local])); + } +} + +fn line_badness(delta: i64, is_last: bool, preference: AdjustmentPreference) -> i64 { + let magnitude = delta.saturating_abs().min(1_000_000); + let square = magnitude.saturating_mul(magnitude); + if delta < 0 { + return square.saturating_mul(1_000).saturating_add(10_000_000); + } + if is_last { + return square / 100; + } + match preference { + AdjustmentPreference::LeastAdjustment => square, + AdjustmentPreference::EvenTexture => square.saturating_mul(2), + } +} + +fn widow_penalty(paragraph: &Paragraph, start: usize, end: usize) -> i64 { + let Widow::MinimumClusters(minimum) = paragraph.widow else { + return 0; + }; + let count = cluster_index_at_or_after(paragraph, end) + .saturating_sub(cluster_index_at_or_after(paragraph, start)); + if count < usize::from(minimum) { + 1_000_000_000 + } else { + 0 + } +} + +fn add_widow_diagnostic(paragraph: &Paragraph, layout: &mut Layout) { + let Widow::MinimumClusters(minimum) = paragraph.widow else { + return; + }; + let Some(last) = layout.lines.last() else { + return; + }; + if last.clusters.len() < usize::from(minimum) { + layout.diagnostics.push(Diagnostic { + code: "layout.widow", + severity: Severity::Warning, + range: Some(last.range.clone()), + jlreq: "3.1.9", + }); + } +} + +fn break_is_legal(paragraph: &Paragraph, style: &Style, offset: usize) -> bool { + if offset == paragraph.text.source().len() { + return true; + } + let after_ordinal = cluster_index_at_or_after(paragraph, offset); + let Some(before_ordinal) = after_ordinal.checked_sub(1) else { + return true; + }; + if after_ordinal >= paragraph.text.clusters().len() { + return true; + } + // §3.6.3's cut answers to no character class, but it is available only where the + // sign is a sign of the line: inside a structure that stacks its text off the line + // there is no line boundary to cut at, and the boundary before such a structure is + // an ordinary one Table 2 decides. + if is_line_tab_sign(paragraph, after_ordinal) { + return true; + } + + let raw_before = class_of_cluster_with_style(paragraph, style, before_ordinal); + let raw_after = class_of_cluster_with_style(paragraph, style, after_ordinal); + let before_character = + single_cluster_character(paragraph, ¶graph.text.clusters()[before_ordinal]); + let after_character = + single_cluster_character(paragraph, ¶graph.text.clusters()[after_ordinal]); + + // §C.3 states these four prohibitions are common to every convention level. + if raw_before == crate::spec::OPENING_BRACKET + || matches!( + raw_after, + crate::spec::CLOSING_BRACKET | crate::spec::FULL_STOP | crate::spec::COMMA + ) + { + return false; + } + + if c_3_relaxes_boundary( + style, + raw_before, + raw_after, + before_character, + after_character, + ) { + return true; + } + + let before = reclassified_break_class(style, raw_before, before_character); + let after = reclassified_break_class(style, raw_after, after_character); + let Some(cell) = crate::spec::table_two_cell(before, after) else { + return true; + }; + if cell.prohibited { + return false; + } + + match (before, after) { + (8, 8) => !inseparable_member_pair(before_character, after_character), + (24, 27) => { + style.grouped_numeral_before_western() == GroupedNumeralBeforeWestern::Breakable + }, + (27, 13) => { + let role = paragraph.text.clusters()[before_ordinal].role(); + role != Some(ClusterRole::QuantitySymbol) + && !before_character.is_some_and(|character| character.is_ascii_digit()) + }, + _ => cell.levels & kinsoku_level_bit(style.kinsoku_level()) == 0, + } +} + +fn kinsoku_level_bit(level: KinsokuLevel) -> u8 { + match level { + KinsokuLevel::VeryLoose => 0b0001, + KinsokuLevel::Loose => 0b0010, + KinsokuLevel::Strict => 0b0100, + KinsokuLevel::VeryStrict => 0b1000, + } +} + +fn reclassified_break_class(style: &Style, class: u8, character: Option) -> u8 { + if character == Some('々') + && style.iteration_mark_at_line_head() != IterationMarkAtLineHead::Prohibited + && style.kinsoku_level() != KinsokuLevel::VeryStrict + { + return 19; + } + if style.relaxation_mechanism() == RelaxationMechanism::Reclassify + && style.kinsoku_level() != KinsokuLevel::VeryStrict + { + if class == 10 { + return 16; + } + if class == 11 { + return if character.is_some_and(crate::spec::is_hiragana) { + 15 + } else { + 16 + }; + } + } + class +} + +fn c_3_relaxes_boundary( + style: &Style, + before: u8, + after: u8, + before_character: Option, + after_character: Option, +) -> bool { + let either_class = |classes: &[u8]| classes.contains(&before) || classes.contains(&after); + let either_character = + |character| before_character == Some(character) || after_character == Some(character); + let iteration_relaxed = style.iteration_mark_at_line_head() + != IterationMarkAtLineHead::Prohibited + && either_character('々'); + let matrix_kana = + style.relaxation_mechanism() == RelaxationMechanism::Matrix && either_class(&[10, 11]); + + match style.kinsoku_level() { + KinsokuLevel::VeryLoose => { + either_class(&[3, 4, 5, 9, 12, 13]) + || matrix_kana + || cl_08_same_kind(before_character, after_character) + }, + KinsokuLevel::Loose => { + either_class(&[3]) + || either_character('・') + || matches!( + (before_character, after_character), + (Some('…'), Some('…')) | (Some('‥'), Some('‥')) + ) + || iteration_relaxed + || matrix_kana + || either_character('%') + || either_character('%') + }, + KinsokuLevel::Strict => iteration_relaxed || matrix_kana, + KinsokuLevel::VeryStrict => false, + } +} + +fn inseparable_member_pair(before: Option, after: Option) -> bool { + matches!( + (before, after), + (Some('—'), Some('—')) + | (Some('…'), Some('…')) + | (Some('‥'), Some('‥')) + | (Some('〳' | '〴'), Some('〵')) + ) +} + +fn cl_08_same_kind(before: Option, after: Option) -> bool { + match (before, after) { + (Some(before), Some(after)) if before == after => true, + (Some(before), Some(after)) => "〳〴〵".contains(before) && "〳〴〵".contains(after), + _ => false, + } +} + +fn local_orientation( + paragraph: &Paragraph, + ordinal: usize, + frame: Frame, +) -> (WritingMode, CoordinateTransform) { + if paragraph.writing_mode == WritingMode::HorizontalTb { + return (WritingMode::HorizontalTb, CoordinateTransform::Identity); + } + if paragraph + .find_construct_containing(ordinal, |construct| { + matches!(construct.kind(), ConstructKind::TateChuYoko(_)) + }) + .is_some() + { + return (WritingMode::HorizontalTb, CoordinateTransform::TateChuYoko); + } + if frame == Frame::Proportional { + ( + WritingMode::VerticalRl, + CoordinateTransform::RotateClockwise, + ) + } else { + (WritingMode::VerticalRl, CoordinateTransform::Identity) + } +} + +fn place_attachments( + paragraph: &Paragraph, + style: &Style, + line_index: usize, + line_start: usize, + line_end: usize, + line: &mut Line, + construct_ordinals: &mut Vec, +) { + let mut attachment_extent = 0; + paragraph.collect_constructs_overlapping(line_start, line_end, construct_ordinals); + for ordinal in construct_ordinals.iter().copied() { + let Some(construct) = paragraph.constructs.get(ordinal) else { + continue; + }; + match construct.kind() { + ConstructKind::Ruby(ruby) => { + place_ruby_attachments( + paragraph, + style, + line_index, + line, + ordinal, + ruby, + &mut attachment_extent, + ); + }, + ConstructKind::Emphasis { range, mark } => { + for placement in line + .clusters + .iter() + .filter(|placement| ranges_overlap(&placement.range, range)) + { + let size = placement.size.half_rounded_up(); + let inline = i64::from(placement.inline).saturating_add( + i64::from(placement.advance).saturating_sub(i64::from(size.inline())) / 2, + ); + line.attachments.push(Attachment { + construct: ordinal, + range: 0..0, + inline: clamp_i32(inline), + block: attachment_block(paragraph, line, size), + advance: 0, + size, + writing_mode: paragraph.writing_mode, + transform: CoordinateTransform::Identity, + symbol: Some(*mark), + }); + attachment_extent = attachment_extent.max(size.block()); + } + }, + ConstructKind::ReferenceMark { range, mark } + | ConstructKind::Script { + range, + annotation: mark, + } => { + if let Some((start, end)) = bounds_for_range(line, range) { + let width = mark.clusters().iter().fold(0_i64, |sum, cluster| { + sum.saturating_add(i64::from(cluster.advance())) + }); + let mut inline = i64::from(start).saturating_add( + (i64::from(end) + .saturating_sub(i64::from(start)) + .saturating_sub(width)) + / 2, + ); + for cluster in mark.clusters() { + let size = cluster.size_override().unwrap_or(mark.size()); + line.attachments.push(Attachment { + construct: ordinal, + range: cluster.range(), + inline: clamp_i32(inline), + block: attachment_block(paragraph, line, size), + advance: cluster.advance(), + size, + writing_mode: paragraph.writing_mode, + transform: CoordinateTransform::Identity, + symbol: None, + }); + attachment_extent = attachment_extent.max(size.block()); + inline = inline.saturating_add(i64::from(cluster.advance())); + } + } + }, + ConstructKind::TateChuYoko(_) + | ConstructKind::Warichu(_) + | ConstructKind::Furawake { .. } + | ConstructKind::Jidori { .. } + | ConstructKind::Formula(_) => {}, + } + } + line.block_extent = line.block_extent.saturating_add(attachment_extent); +} + +fn place_ruby_attachments( + paragraph: &Paragraph, + style: &Style, + line_index: usize, + line: &mut Line, + construct: usize, + ruby: &Ruby, + attachment_extent: &mut i32, +) { + match ruby.kind() { + RubyKind::Group => { + *attachment_extent = (*attachment_extent).max(place_ruby_span( + paragraph, + style, + line, + construct, + &RubySpan { + annotation: ruby.annotation(), + base_range: ruby.base(), + annotation_range: 0..ruby.annotation().source().len(), + distribution: Some(style.group_ruby_distribution()), + }, + )); + }, + RubyKind::Mono => { + for run in ruby.runs() { + if range_fits_line(&run.base(), line) { + *attachment_extent = (*attachment_extent).max(place_ruby_span( + paragraph, + style, + line, + construct, + &RubySpan { + annotation: ruby.annotation(), + base_range: run.base(), + annotation_range: run.annotation(), + distribution: None, + }, + )); + } + } + }, + RubyKind::Jukugo => { + if style.jukugo_ruby_layout() == JukugoRubyLayout::Phonetic { + if let Some(extent) = + place_phonetic_jukugo(paragraph, style, line_index, line, construct, ruby) + { + *attachment_extent = (*attachment_extent).max(extent); + return; + } + } + let per_base = ruby + .runs() + .iter() + .all(|run| annotation_cluster_count(ruby.annotation(), &run.annotation()) <= 2); + if !per_base && range_fits_line(&ruby.base(), line) { + *attachment_extent = (*attachment_extent).max(place_ruby_span( + paragraph, + style, + line, + construct, + &RubySpan { + annotation: ruby.annotation(), + base_range: ruby.base(), + annotation_range: 0..ruby.annotation().source().len(), + distribution: Some(GroupRubyDistribution::Jis), + }, + )); + } else { + for run in ruby.runs() { + if range_fits_line(&run.base(), line) { + *attachment_extent = (*attachment_extent).max(place_ruby_span( + paragraph, + style, + line, + construct, + &RubySpan { + annotation: ruby.annotation(), + base_range: run.base(), + annotation_range: run.annotation(), + distribution: None, + }, + )); + } + } + } + }, + } +} + +fn place_phonetic_jukugo( + paragraph: &Paragraph, + style: &Style, + line_index: usize, + line: &mut Line, + construct: usize, + ruby: &Ruby, +) -> Option { + let line_start = cluster_index_at_or_after(paragraph, line.range.start); + let line_end = cluster_index_at_or_after(paragraph, line.range.end); + let plan = phonetic_jukugo_plan(paragraph, style, ruby, line_start, line_end, line_index)?; + let first = plan.runs.first()?; + let actual_start = line.clusters.iter().find_map(|placement| { + (placement.origin == PlacementOrigin::Cluster(first.base.start)) + .then_some(i64::from(placement.inline)) + })?; + let offset = actual_start.saturating_sub(first.base_start); + let mut attachment_extent = 0; + for run in &plan.runs { + let mut inline = offset.saturating_add(run.annotation_start); + for cluster in ruby.annotation().clusters().iter().filter(|cluster| { + let cluster = cluster.range(); + run.annotation.start <= cluster.start && cluster.end <= run.annotation.end + }) { + let size = cluster.size_override().unwrap_or(ruby.annotation().size()); + line.attachments.push(Attachment { + construct, + range: cluster.range(), + inline: clamp_i32(inline), + block: attachment_block(paragraph, line, size), + advance: cluster.advance(), + size, + writing_mode: paragraph.writing_mode, + transform: CoordinateTransform::Identity, + symbol: None, + }); + attachment_extent = attachment_extent.max(size.block()); + inline = inline.saturating_add(i64::from(cluster.advance())); + } + } + Some(attachment_extent) +} + +fn annotation_cluster_count(annotation: &crate::ShapedText, range: &Range) -> usize { + annotation + .clusters() + .iter() + .filter(|cluster| { + let cluster = cluster.range(); + range.start <= cluster.start && cluster.end <= range.end + }) + .count() +} + +fn range_fits_line(range: &Range, line: &Line) -> bool { + line.range.start <= range.start && range.end <= line.range.end +} + +struct RubySpan<'a> { + annotation: &'a crate::ShapedText, + base_range: Range, + annotation_range: Range, + distribution: Option, +} + +fn place_ruby_span( + paragraph: &Paragraph, + style: &Style, + line: &mut Line, + construct: usize, + span: &RubySpan<'_>, +) -> i32 { + let Some((base_start, base_end)) = bounds_for_ruby_range(paragraph, line, &span.base_range) + else { + return 0; + }; + let (annotation_count, annotation_width, _) = + ruby_annotation_metrics(span.annotation, &span.annotation_range); + let base_width = i64::from(base_end).saturating_sub(i64::from(base_start)); + let surplus = base_width.saturating_sub(annotation_width).max(0); + let mut gaps = Vec::new(); + let base_plan = span.distribution.and_then(|distribution| { + base_distribution_plan( + paragraph, + style, + &span.base_range, + span.annotation, + &span.annotation_range, + distribution, + ) + }); + let mut inline = if let Some(plan) = base_plan { + i64::from(base_start).saturating_sub(i64::from(plan.leading)) + } else if matches!( + annotation_width.cmp(&base_width), + core::cmp::Ordering::Greater + ) { + i64::from(base_start).saturating_add((base_width.saturating_sub(annotation_width)) / 2) + } else if let Some(distribution) = span.distribution { + let weights = match distribution { + GroupRubyDistribution::Jis => { + let mut weights = Vec::with_capacity(annotation_count.saturating_add(1)); + weights.push(1); + weights.extend((1..annotation_count).map(|_| 2)); + weights.push(1); + weights + }, + GroupRubyDistribution::Flush => vec![1; annotation_count.saturating_sub(1)], + }; + let shares = proportional_shares(surplus, &weights, style.remainder()); + match distribution { + GroupRubyDistribution::Jis => { + gaps.extend( + shares + .iter() + .skip(1) + .take(annotation_count.saturating_sub(1)), + ); + i64::from(base_start) + .saturating_add(i64::from(shares.first().copied().unwrap_or(0))) + }, + GroupRubyDistribution::Flush => { + gaps.extend(shares); + i64::from(base_start) + }, + } + } else { + match style.ruby_alignment() { + RubyAlignment::Nakatsuki => i64::from(base_start).saturating_add(surplus / 2), + RubyAlignment::Katatsuki => i64::from(base_start), + } + }; + let mut attachment_extent = 0; + for (index, cluster) in span + .annotation + .clusters() + .iter() + .filter(|cluster| { + let cluster = cluster.range(); + span.annotation_range.start <= cluster.start && cluster.end <= span.annotation_range.end + }) + .enumerate() + { + let size = cluster.size_override().unwrap_or(span.annotation.size()); + line.attachments.push(Attachment { + construct, + range: cluster.range(), + inline: clamp_i32(inline), + block: attachment_block(paragraph, line, size), + advance: cluster.advance(), + size, + writing_mode: paragraph.writing_mode, + transform: CoordinateTransform::Identity, + symbol: None, + }); + attachment_extent = attachment_extent.max(size.block()); + inline = inline.saturating_add(i64::from(cluster.advance())); + inline = inline.saturating_add(i64::from(gaps.get(index).copied().unwrap_or(0))); + } + attachment_extent +} + +fn proportional_shares(total: i64, weights: &[i32], remainder: Remainder) -> Vec { + if total <= 0 { + return vec![0; weights.len()]; + } + let weight_sum = weights.iter().fold(0_i64, |sum, weight| { + sum.saturating_add(i64::from((*weight).max(0))) + }); + if weight_sum == 0 { + return vec![0; weights.len()]; + } + let total = u128::try_from(total).unwrap_or(0); + let weight_sum = u128::try_from(weight_sum).unwrap_or(0); + let mut shares: Vec<_> = weights + .iter() + .map(|weight| { + let weight = u128::try_from((*weight).max(0)).unwrap_or(0); + let share = total + .saturating_mul(weight) + .checked_div(weight_sum) + .unwrap_or(0); + i64::try_from(share).unwrap_or(i64::MAX) + }) + .collect(); + let assigned = shares.iter().fold(0_u128, |sum, share| { + sum.saturating_add(u128::try_from(*share).unwrap_or(0)) + }); + let left = usize::try_from(total.saturating_sub(assigned)).unwrap_or(usize::MAX); + debug_assert!(left <= weights.len()); + match remainder { + Remainder::Leading => { + for share in shares.iter_mut().take(left) { + *share = share.saturating_add(1); + } + }, + Remainder::Trailing => { + for share in shares.iter_mut().rev().take(left) { + *share = share.saturating_add(1); + } + }, + } + shares.into_iter().map(clamp_i32).collect() +} + +fn attachment_block(paragraph: &Paragraph, line: &Line, size: Size) -> i32 { + match paragraph.writing_mode { + WritingMode::HorizontalTb => line.block_origin.saturating_sub(size.block()), + WritingMode::VerticalRl => line.block_origin.saturating_add(size.block()), + } +} + +fn bounds_for_range(line: &Line, range: &Range) -> Option<(i32, i32)> { + let mut matching = line + .clusters + .iter() + .filter(|placement| ranges_overlap(&placement.range, range)); + let first = matching.next()?; + let mut end = placement_inline_end(first); + for placement in matching { + end = end.max(placement_inline_end(placement)); + } + Some((first.inline, end)) +} + +fn bounds_for_ruby_range( + paragraph: &Paragraph, + line: &Line, + range: &Range, +) -> Option<(i32, i32)> { + let mut matching = line + .clusters + .iter() + .filter(|placement| ranges_overlap(&placement.range, range)); + let first = matching.next()?; + let mut end = ruby_base_inline_end(paragraph, first); + for placement in matching { + end = end.max(ruby_base_inline_end(paragraph, placement)); + } + Some((first.inline, end)) +} + +fn ruby_base_inline_end(paragraph: &Paragraph, placement: &ClusterPlacement) -> i32 { + let advance = match placement.origin { + PlacementOrigin::Cluster(ordinal) => effective_cluster_body_advance(paragraph, ordinal), + PlacementOrigin::Construct(_) => placement.advance, + }; + placement.inline.saturating_add(advance) +} + +fn placement_inline_end(placement: &ClusterPlacement) -> i32 { + let advance = if placement.transform == CoordinateTransform::TateChuYoko { + placement.size.block() + } else { + placement.advance + }; + placement.inline.saturating_add(advance) +} + +fn ranges_overlap(left: &Range, right: &Range) -> bool { + left.start < right.end && right.start < left.end +} + +fn clamp_i32(value: i64) -> i32 { + i32::try_from(value).unwrap_or_else(|_| { + if value.is_negative() { + i32::MIN + } else { + i32::MAX + } + }) +} + diff --git a/crates/jlreq-core/src/pipeline/ruby.rs b/crates/jlreq-core/src/pipeline/ruby.rs new file mode 100644 index 0000000..61c1832 --- /dev/null +++ b/crates/jlreq-core/src/pipeline/ruby.rs @@ -0,0 +1,1099 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[derive(Debug, Clone)] +struct RubyOverhang { + base: Range, + leading: i32, + trailing: i32, + ruby_em: i32, +} + +#[derive(Debug, Clone)] +struct GroupRubyBasePlan { + base: Range, + leading: i32, + trailing: i32, + gaps_after: Vec<(usize, i32)>, +} + +impl GroupRubyBasePlan { + fn gap_after(&self, ordinal: usize) -> i32 { + self.gaps_after + .iter() + .find_map(|(boundary, gap)| (*boundary == ordinal).then_some(*gap)) + .unwrap_or(0) + } +} + +#[derive(Debug, Clone)] +struct PhoneticJukugoRun { + base: Range, + annotation: Range, + base_start: i64, + base_end: i64, + annotation_start: i64, + annotation_width: i64, + ruby_em: i32, + annotation_count: usize, +} + +#[derive(Debug, Clone)] +struct PhoneticJukugoPlan { + runs: Vec, + leading_gap: i32, + gaps_after: Vec<(usize, i32)>, +} + +impl PhoneticJukugoPlan { + fn gap_after(&self, ordinal: usize) -> i32 { + self.gaps_after + .iter() + .find_map(|(boundary, gap)| (*boundary == ordinal).then_some(*gap)) + .unwrap_or(0) + } +} + +#[derive(Debug, Clone, Copy)] +struct PhoneticEdges { + line: LineContext, + leading_allowance: i32, + trailing_allowance: i32, +} + +#[derive(Debug, Clone, Copy)] +enum RubySide { + Leading, + Trailing, +} + +#[cfg(test)] +fn visit_ruby_spans( + paragraph: &Paragraph, + style: &Style, + mut visit: impl FnMut(&Ruby, Range, Range), +) { + for construct in ¶graph.constructs { + let ConstructKind::Ruby(ruby) = construct.kind() else { + continue; + }; + visit_ruby_spans_for(style, ruby, &mut visit); + } +} + +fn visit_ruby_spans_for( + style: &Style, + ruby: &Ruby, + mut visit: impl FnMut(&Ruby, Range, Range), +) { + match ruby.kind() { + RubyKind::Group => visit(ruby, ruby.base(), 0..ruby.annotation().source().len()), + RubyKind::Mono => { + for run in ruby.runs() { + visit(ruby, run.base(), run.annotation()); + } + }, + RubyKind::Jukugo => { + if style.jukugo_ruby_layout() == JukugoRubyLayout::Phonetic { + return; + } + let per_base = ruby + .runs() + .iter() + .all(|run| annotation_cluster_count(ruby.annotation(), &run.annotation()) <= 2); + if per_base { + for run in ruby.runs() { + visit(ruby, run.base(), run.annotation()); + } + } else { + visit(ruby, ruby.base(), 0..ruby.annotation().source().len()); + } + }, + } +} + +fn visit_ruby_spans_overlapping( + paragraph: &Paragraph, + style: &Style, + start: usize, + end: usize, + mut visit: impl FnMut(&Ruby, Range, Range), +) { + paragraph.visit_constructs_overlapping(start, end, |_, construct| { + if let ConstructKind::Ruby(ruby) = construct.kind() { + visit_ruby_spans_for(style, ruby, &mut visit); + } + }); +} + +fn visit_rubies_overlapping( + paragraph: &Paragraph, + start: usize, + end: usize, + mut visit: impl FnMut(&Ruby), +) { + paragraph.visit_constructs_overlapping(start, end, |_, construct| { + if let ConstructKind::Ruby(ruby) = construct.kind() { + visit(ruby); + } + }); +} + +fn phonetic_jukugo_plan( + paragraph: &Paragraph, + style: &Style, + ruby: &Ruby, + line_start: usize, + line_end: usize, + line_index: usize, +) -> Option { + if ruby.kind() != RubyKind::Jukugo || style.jukugo_ruby_layout() != JukugoRubyLayout::Phonetic { + return None; + } + + let mut runs = Vec::new(); + for run in ruby.runs() { + let base = cluster_index_at_or_after(paragraph, run.base().start) + ..cluster_index_at_or_after(paragraph, run.base().end); + if base.start < line_start || base.end > line_end { + continue; + } + let (annotation_count, annotation_width, ruby_em) = + ruby_annotation_metrics(ruby.annotation(), &run.annotation()); + let base_width = ruby_base_width(paragraph, style, &base); + runs.push(PhoneticJukugoRun { + base, + annotation: run.annotation(), + base_start: 0, + base_end: base_width, + annotation_start: 0, + annotation_width, + ruby_em, + annotation_count, + }); + } + let (first, last) = (runs.first()?, runs.last()?); + let leading_allowance = if first.base.start == line_start { + if line_index == 0 && style.ruby_overhang_indent() == RubyOverhangIndent::Permitted { + line_head_indent(paragraph, style, line_start, line_index).min(first.ruby_em) + } else { + 0 + } + } else { + ruby_neighbor_overhang_allowance( + paragraph, + style, + first.base.start.saturating_sub(1), + RubySide::Leading, + first.ruby_em, + ) + }; + let trailing_allowance = if last.base.end == line_end { + 0 + } else { + ruby_neighbor_overhang_allowance( + paragraph, + style, + last.base.end, + RubySide::Trailing, + last.ruby_em, + ) + }; + let edges = PhoneticEdges { + line: LineContext { + start: line_start, + end: line_end, + index: line_index, + }, + leading_allowance, + trailing_allowance, + }; + let maximum_expansion = runs.iter().fold(0_i64, |sum, run| { + let base_width = run.base_end.saturating_sub(run.base_start); + sum.saturating_add(run.annotation_width.saturating_sub(base_width).max(0)) + }); + + build_phonetic_jukugo_plan(paragraph, style, &runs, 0, edges).or_else(|| { + let mut lower = 1_i64; + let mut upper = maximum_expansion; + let upper_plan = build_phonetic_jukugo_plan(paragraph, style, &runs, upper, edges)?; + while lower < upper { + let previous = (lower, upper); + let middle = lower.saturating_add(upper.saturating_sub(lower) / 2); + if build_phonetic_jukugo_plan(paragraph, style, &runs, middle, edges).is_some() { + upper = middle; + } else { + lower = middle.saturating_add(1); + } + if (lower, upper) == previous { + return Some(upper_plan); + } + } + if lower == maximum_expansion { + Some(upper_plan) + } else { + build_phonetic_jukugo_plan(paragraph, style, &runs, lower, edges) + } + }) +} + +fn build_phonetic_jukugo_plan( + paragraph: &Paragraph, + style: &Style, + raw_runs: &[PhoneticJukugoRun], + expansion: i64, + edges: PhoneticEdges, +) -> Option { + let assigned = apportion_phonetic_expansion(raw_runs, expansion, style.remainder()); + let mut before = alloc::vec![0_i64; raw_runs.len()]; + let mut after = alloc::vec![0_i64; raw_runs.len()]; + for (index, (run, amount)) in raw_runs.iter().zip(assigned).enumerate() { + if amount == 0 { + continue; + } + if run.base.start == edges.line.start && run.base.end != edges.line.end { + after[index] = amount; + } else if run.base.end == edges.line.end && run.base.start != edges.line.start { + before[index] = amount; + } else { + let half = amount / 2; + let odd = amount % 2; + match style.remainder() { + Remainder::Leading => { + before[index] = half.saturating_add(odd); + after[index] = half; + }, + Remainder::Trailing => { + before[index] = half; + after[index] = half.saturating_add(odd); + }, + } + } + } + + let mut leading_gap = 0_i64; + let mut gaps_after = Vec::<(usize, i64)>::new(); + for (index, run) in raw_runs.iter().enumerate() { + if before[index] != 0 { + if run.base.start == edges.line.start { + leading_gap = leading_gap.saturating_add(before[index]); + } else { + add_phonetic_gap( + &mut gaps_after, + run.base.start.saturating_sub(1), + before[index], + ); + } + } + if after[index] != 0 { + add_phonetic_gap( + &mut gaps_after, + run.base.end.saturating_sub(1), + after[index], + ); + } + } + + let first = raw_runs.first()?; + let mut cursor = if first.base.start == edges.line.start { + leading_gap + } else { + phonetic_gap_after(&gaps_after, first.base.start.saturating_sub(1)) + }; + let mut runs = Vec::with_capacity(raw_runs.len()); + let mut inter_run_boundary = None; + for raw in raw_runs { + if let Some(boundary) = inter_run_boundary { + cursor = cursor + .saturating_add(i64::from(boundary_space_after(paragraph, boundary))) + .saturating_add(phonetic_gap_after(&gaps_after, boundary)); + } + let base_start = cursor; + let mut base_end = base_start; + for ordinal in raw.base.clone() { + cursor = cursor.saturating_add(i64::from(effective_cluster_body_advance( + paragraph, ordinal, + ))); + base_end = cursor; + if ordinal.saturating_add(1) < raw.base.end { + cursor = cursor + .saturating_add(i64::from(boundary_space_after(paragraph, ordinal))) + .saturating_add(phonetic_gap_after(&gaps_after, ordinal)); + } + } + inter_run_boundary = Some(raw.base.end.saturating_sub(1)); + runs.push(PhoneticJukugoRun { + base: raw.base.clone(), + annotation: raw.annotation.clone(), + base_start, + base_end, + annotation_start: 0, + annotation_width: raw.annotation_width, + ruby_em: raw.ruby_em, + annotation_count: raw.annotation_count, + }); + } + + let mut lower = Vec::with_capacity(runs.len()); + let mut upper = Vec::with_capacity(runs.len()); + for (index, run) in runs.iter().enumerate() { + let minimum = if index == 0 { + run.base_start + .saturating_sub(before[index]) + .saturating_sub(i64::from(edges.leading_allowance)) + } else { + runs[index.saturating_sub(1)] + .base_end + .saturating_sub(i64::from(run.ruby_em)) + }; + let maximum_end = if index.saturating_add(1) == runs.len() { + run.base_end + .saturating_add(after[index]) + .saturating_add(i64::from(edges.trailing_allowance)) + } else { + runs[index.saturating_add(1)] + .base_start + .saturating_add(i64::from(run.ruby_em)) + }; + lower.push(minimum); + upper.push(maximum_end.saturating_sub(run.annotation_width)); + } + + let mut latest = upper.clone(); + for index in (0..latest.len().saturating_sub(1)).rev() { + latest[index] = latest[index] + .min(latest[index.saturating_add(1)].saturating_sub(runs[index].annotation_width)); + } + if latest + .iter() + .zip(&lower) + .any(|(latest, lower)| latest < lower) + { + return None; + } + + let mut previous_end: Option = None; + for (index, run) in runs.iter_mut().enumerate() { + let minimum = previous_end.map_or(lower[index], |end| end.max(lower[index])); + let preferred = run.base_start.max(minimum); + let start = preferred.min(latest[index]); + if start < minimum { + return None; + } + run.annotation_start = start; + previous_end = Some(start.saturating_add(run.annotation_width)); + } + + Some(PhoneticJukugoPlan { + runs, + leading_gap: clamp_i32(leading_gap), + gaps_after: gaps_after + .into_iter() + .map(|(ordinal, gap)| (ordinal, clamp_i32(gap))) + .collect(), + }) +} + +fn apportion_phonetic_expansion( + runs: &[PhoneticJukugoRun], + total: i64, + remainder: Remainder, +) -> Vec { + let weight = runs + .iter() + .filter(|run| run.annotation_count > 2) + .fold(0_i64, |sum, run| { + sum.saturating_add(run.annotation_width.max(1)) + }); + let mut assigned: Vec<_> = runs + .iter() + .map(|run| { + if run.annotation_count > 2 && weight != 0 { + total + .saturating_mul(run.annotation_width.max(1)) + .checked_div(weight) + .unwrap_or(0) + } else { + 0 + } + }) + .collect(); + let mut remainder_units = total.saturating_sub( + assigned + .iter() + .fold(0_i64, |sum, amount| sum.saturating_add(*amount)), + ); + let indices: Vec<_> = match remainder { + Remainder::Leading => (0..runs.len()).collect(), + Remainder::Trailing => (0..runs.len()).rev().collect(), + }; + for index in indices { + if remainder_units == 0 { + break; + } + if runs[index].annotation_count > 2 { + assigned[index] = assigned[index].saturating_add(1); + remainder_units = remainder_units.saturating_sub(1); + } + } + assigned +} + +fn add_phonetic_gap(gaps: &mut Vec<(usize, i64)>, ordinal: usize, amount: i64) { + if let Some((_, gap)) = gaps.iter_mut().find(|(boundary, _)| *boundary == ordinal) { + *gap = gap.saturating_add(amount); + } else { + gaps.push((ordinal, amount)); + } +} + +fn phonetic_gap_after(gaps: &[(usize, i64)], ordinal: usize) -> i64 { + gaps.iter() + .find_map(|(boundary, gap)| (*boundary == ordinal).then_some(*gap)) + .unwrap_or(0) +} + +fn ruby_annotation_metrics( + annotation: &crate::ShapedText, + range: &Range, +) -> (usize, i64, i32) { + annotation + .clusters() + .iter() + .filter(|cluster| { + let cluster = cluster.range(); + range.start <= cluster.start && cluster.end <= range.end + }) + .fold((0_usize, 0_i64, 0_i32), |(count, width, em), cluster| { + ( + count.saturating_add(1), + width.saturating_add(i64::from(cluster.advance())), + em.max( + cluster + .size_override() + .unwrap_or(annotation.size()) + .inline(), + ), + ) + }) +} + +fn ruby_base_width(paragraph: &Paragraph, style: &Style, base: &Range) -> i64 { + base.clone().fold(0_i64, |sum, ordinal| { + let boundary = if ordinal.saturating_add(1) < base.end { + boundary_space_after_with_style(paragraph, style, ordinal) + } else { + 0 + }; + sum.saturating_add(i64::from(effective_cluster_body_advance( + paragraph, ordinal, + ))) + .saturating_add(i64::from(boundary)) + }) +} + +fn base_distribution_plan( + paragraph: &Paragraph, + style: &Style, + base_range: &Range, + annotation: &crate::ShapedText, + annotation_range: &Range, + distribution: GroupRubyDistribution, +) -> Option { + let base = cluster_index_at_or_after(paragraph, base_range.start) + ..cluster_index_at_or_after(paragraph, base_range.end); + let count = base.end.saturating_sub(base.start); + if count < 2 { + return None; + } + let (_, annotation_width, _) = ruby_annotation_metrics(annotation, annotation_range); + let surplus = annotation_width.saturating_sub(ruby_base_width(paragraph, style, &base)); + if surplus <= 0 { + return None; + } + + let weights = match distribution { + GroupRubyDistribution::Jis => { + let mut weights = Vec::with_capacity(count.saturating_add(1)); + weights.push(1); + weights.extend((1..count).map(|_| 2)); + weights.push(1); + weights + }, + GroupRubyDistribution::Flush => vec![1; count.saturating_sub(1)], + }; + let shares = proportional_shares(surplus, &weights, style.remainder()); + let (leading, trailing, interior) = match distribution { + GroupRubyDistribution::Jis => ( + shares.first().copied().unwrap_or(0), + shares.last().copied().unwrap_or(0), + shares + .iter() + .skip(1) + .take(count.saturating_sub(1)) + .copied() + .collect::>(), + ), + GroupRubyDistribution::Flush => (0, 0, shares), + }; + let gaps_after = interior + .into_iter() + .enumerate() + .map(|(offset, gap)| (base.start.saturating_add(offset), gap)) + .collect(); + Some(GroupRubyBasePlan { + base, + leading, + trailing, + gaps_after, + }) +} + +fn group_ruby_base_plan( + paragraph: &Paragraph, + style: &Style, + ruby: &Ruby, + base: &Range, + annotation: &Range, +) -> Option { + let distribution = match ruby.kind() { + RubyKind::Group => style.group_ruby_distribution(), + RubyKind::Jukugo + if style.jukugo_ruby_layout() != JukugoRubyLayout::Phonetic && *base == ruby.base() => + { + GroupRubyDistribution::Jis + }, + RubyKind::Mono | RubyKind::Jukugo => return None, + }; + base_distribution_plan( + paragraph, + style, + base, + ruby.annotation(), + annotation, + distribution, + ) +} + +fn ruby_span_overhang( + paragraph: &Paragraph, + style: &Style, + ruby: &Ruby, + base: Range, + annotation: Range, + line_start: usize, + line_end: usize, +) -> Option { + if group_ruby_base_plan(paragraph, style, ruby, &base, &annotation).is_some() { + return None; + } + let base = cluster_index_at_or_after(paragraph, base.start) + ..cluster_index_at_or_after(paragraph, base.end); + if base.start < line_start || base.end > line_end { + return None; + } + let base_width = ruby_base_width(paragraph, style, &base); + let mut annotation_width = 0_i64; + let mut ruby_em = 0_i32; + for cluster in ruby.annotation().clusters().iter().filter(|cluster| { + let cluster = cluster.range(); + annotation.start <= cluster.start && cluster.end <= annotation.end + }) { + annotation_width = annotation_width.saturating_add(i64::from(cluster.advance())); + ruby_em = ruby_em.max( + cluster + .size_override() + .unwrap_or(ruby.annotation().size()) + .inline(), + ); + } + let surplus = annotation_width.saturating_sub(base_width).max(0); + if surplus == 0 { + return None; + } + let half = surplus / 2; + let odd = surplus % 2; + let (leading, trailing) = match style.remainder() { + Remainder::Leading => (half.saturating_add(odd), half), + Remainder::Trailing => (half, half.saturating_add(odd)), + }; + Some(RubyOverhang { + base, + leading: clamp_i32(leading), + trailing: clamp_i32(trailing), + ruby_em, + }) +} + +fn ruby_boundary_separation_after( + paragraph: &Paragraph, + style: &Style, + before: usize, + line_start: usize, + line_end: usize, + line_index: usize, +) -> i32 { + let mut required = 0_i32; + let mut distributed = 0_i32; + let boundary_end = before + .saturating_add(2) + .min(paragraph.text.clusters().len()); + visit_ruby_spans_overlapping( + paragraph, + style, + before, + boundary_end, + |ruby, base, annotation| { + if let Some(plan) = group_ruby_base_plan(paragraph, style, ruby, &base, &annotation) { + if plan.base.start < line_start || plan.base.end > line_end { + return; + } + if plan.base.start == before.saturating_add(1) && before >= line_start { + distributed = distributed.saturating_add(plan.leading); + } + distributed = distributed.saturating_add(plan.gap_after(before)); + if plan.base.end == before.saturating_add(1) { + distributed = distributed.saturating_add(plan.trailing); + } + return; + } + let Some(overhang) = ruby_span_overhang( + paragraph, style, ruby, base, annotation, line_start, line_end, + ) else { + return; + }; + if overhang.base.start == before.saturating_add(1) && before >= line_start { + let allowance = ruby_neighbor_overhang_allowance( + paragraph, + style, + before, + RubySide::Leading, + overhang.ruby_em, + ); + required = required.max(overhang.leading.saturating_sub(allowance)); + } + if overhang.base.end == before.saturating_add(1) { + let allowance = if overhang.base.end < line_end { + ruby_neighbor_overhang_allowance( + paragraph, + style, + overhang.base.end, + RubySide::Trailing, + overhang.ruby_em, + ) + } else { + 0 + }; + required = required.max(overhang.trailing.saturating_sub(allowance)); + } + }, + ); + visit_rubies_overlapping(paragraph, before, boundary_end, |ruby| { + if let Some(plan) = + phonetic_jukugo_plan(paragraph, style, ruby, line_start, line_end, line_index) + { + required = required.max(plan.gap_after(before)); + } + }); + required.saturating_add(distributed) +} + +fn ruby_line_leading_separation( + paragraph: &Paragraph, + style: &Style, + line_start: usize, + line_end: usize, + line_index: usize, +) -> i32 { + let mut required = 0_i32; + let mut distributed = 0_i32; + visit_ruby_spans_overlapping( + paragraph, + style, + line_start, + line_end, + |ruby, base, annotation| { + if let Some(plan) = group_ruby_base_plan(paragraph, style, ruby, &base, &annotation) { + if plan.base.start == line_start && plan.base.end <= line_end { + distributed = distributed.saturating_add(plan.leading); + } + return; + } + let Some(overhang) = ruby_span_overhang( + paragraph, style, ruby, base, annotation, line_start, line_end, + ) else { + return; + }; + if overhang.base.start != line_start { + return; + } + let allowance = if line_index == 0 + && style.ruby_overhang_indent() == RubyOverhangIndent::Permitted + { + line_head_indent(paragraph, style, line_start, line_index).min(overhang.ruby_em) + } else { + 0 + }; + required = required.max(overhang.leading.saturating_sub(allowance)); + }, + ); + visit_rubies_overlapping(paragraph, line_start, line_end, |ruby| { + if let Some(plan) = + phonetic_jukugo_plan(paragraph, style, ruby, line_start, line_end, line_index) + { + required = required.max(plan.leading_gap); + } + }); + required.saturating_add(distributed) +} + +fn ruby_neighbor_overhang_allowance( + paragraph: &Paragraph, + style: &Style, + ordinal: usize, + side: RubySide, + ruby_em: i32, +) -> i32 { + let Some(cluster) = paragraph.text.clusters().get(ordinal) else { + return 0; + }; + let Some(character) = single_cluster_character(paragraph, cluster) else { + return 0; + }; + let adjacent_space = match side { + RubySide::Leading => boundary_space_after_with_style(paragraph, style, ordinal), + RubySide::Trailing => ordinal.checked_sub(1).map_or(0, |before| { + boundary_space_after_with_style(paragraph, style, before) + }), + }; + let punctuation_allowance = match side { + RubySide::Leading if is_opening_bracket(character) => ruby_em, + RubySide::Leading + if is_closing_bracket(character) || is_full_stop(character) || is_comma(character) => + { + ruby_em.min(adjacent_space) + }, + RubySide::Trailing if is_opening_bracket(character) => ruby_em.min(adjacent_space), + RubySide::Trailing + if is_closing_bracket(character) || is_full_stop(character) || is_comma(character) => + { + ruby_em + }, + _ => 0, + }; + let kana = match style.ruby_overhang_kana() { + RubyOverhangKana::Kana => is_hiragana(character) || is_katakana(character), + RubyOverhangKana::Jis => is_hiragana(character), + RubyOverhangKana::Any => true, + RubyOverhangKana::None => false, + }; + if punctuation_allowance != 0 { + punctuation_allowance + } else if is_middle_dot(character) { + ruby_em.min(adjacent_space.saturating_add(half_rounded_up(ruby_em))) + } else if character == '\u{3000}' || is_inseparable_character(character) || kana { + ruby_em + } else { + 0 + } +} + +fn half_rounded_up(value: i32) -> i32 { + (value / 2).saturating_add(value % 2) +} + +fn is_hiragana(character: char) -> bool { + crate::spec::is_hiragana(character) +} + +fn is_katakana(character: char) -> bool { + crate::spec::is_katakana(character) +} + +fn is_inseparable_character(character: char) -> bool { + crate::spec::single_has_class(character, crate::spec::INSEPARABLE) +} + +fn is_western_word_space(paragraph: &Paragraph, ordinal: usize) -> bool { + let Some(cluster) = paragraph.text.clusters().get(ordinal) else { + return false; + }; + ¶graph.text.source()[cluster.range()] == " " + && cluster.frame_override().unwrap_or(paragraph.text.frame()) == Frame::Proportional + && matches!(cluster.role(), None | Some(ClusterRole::Text)) +} + +fn boundary_space_after(paragraph: &Paragraph, ordinal: usize) -> i32 { + tate_chu_yoko_boundary_space_after(paragraph, ordinal) + .or_else(|| formula_boundary_space_after(paragraph, ordinal)) + .unwrap_or_else(|| ordinary_boundary_space_after(paragraph, ordinal)) +} + +fn boundary_space_after_with_style(paragraph: &Paragraph, style: &Style, ordinal: usize) -> i32 { + tate_chu_yoko_boundary_space_after(paragraph, ordinal) + .or_else(|| formula_boundary_space_after(paragraph, ordinal)) + .unwrap_or_else(|| ordinary_boundary_space_after_with_style(paragraph, style, ordinal)) +} + +fn tate_chu_yoko_boundary_space_after(paragraph: &Paragraph, ordinal: usize) -> Option { + if paragraph.writing_mode != WritingMode::VerticalRl { + return None; + } + let clusters = paragraph.text.clusters(); + let current_group = tate_chu_yoko_cluster_range(paragraph, ordinal); + if let Some(group) = current_group { + if group.start != ordinal || group.end >= clusters.len() { + return Some(0); + } + let following = &clusters[group.end]; + let character = single_cluster_character(paragraph, following); + if character.is_some_and(is_opening_bracket) { + return Some(half_inline_size(paragraph, following)); + } + return Some(0); + } + + let following_ordinal = ordinal.saturating_add(1); + if following_ordinal >= clusters.len() + || tate_chu_yoko_cluster_range(paragraph, following_ordinal) + .is_none_or(|group| group.start != following_ordinal) + { + return None; + } + let current = &clusters[ordinal]; + let character = single_cluster_character(paragraph, current); + if character.is_some_and(|character| { + is_closing_bracket(character) || is_full_stop(character) || is_comma(character) + }) { + Some(half_inline_size(paragraph, current)) + } else { + Some(0) + } +} + +fn ordinary_boundary_space_after(paragraph: &Paragraph, ordinal: usize) -> i32 { + let clusters = paragraph.text.clusters(); + let Some(current) = clusters.get(ordinal) else { + return 0; + }; + let Some(following) = clusters.get(ordinal.saturating_add(1)) else { + return 0; + }; + let current_character = single_cluster_character(paragraph, current); + let following_character = single_cluster_character(paragraph, following); + let current_solid = current_character + .is_some_and(|character| contextual_punctuation_is_solid(paragraph, current, character)); + let following_solid = following_character + .is_some_and(|character| contextual_punctuation_is_solid(paragraph, following, character)); + let current_size = current.size_override().unwrap_or(paragraph.text.size()); + let following_size = following.size_override().unwrap_or(paragraph.text.size()); + crate::spec::table_one_space( + class_of_cluster(paragraph, ordinal), + class_of_cluster(paragraph, ordinal.saturating_add(1)), + current_size, + following_size, + current_solid, + following_solid, + ) +} + +fn ordinary_boundary_space_after_with_style( + paragraph: &Paragraph, + style: &Style, + ordinal: usize, +) -> i32 { + let clusters = paragraph.text.clusters(); + let Some(current) = clusters.get(ordinal) else { + return 0; + }; + let Some(following) = clusters.get(ordinal.saturating_add(1)) else { + return 0; + }; + let current_character = single_cluster_character(paragraph, current); + let following_character = single_cluster_character(paragraph, following); + let current_solid = current_character + .is_some_and(|character| contextual_punctuation_is_solid(paragraph, current, character)); + let following_solid = following_character + .is_some_and(|character| contextual_punctuation_is_solid(paragraph, following, character)); + let current_size = current.size_override().unwrap_or(paragraph.text.size()); + let following_size = following.size_override().unwrap_or(paragraph.text.size()); + let before = class_of_cluster_with_style(paragraph, style, ordinal); + let after = class_of_cluster_with_style(paragraph, style, ordinal.saturating_add(1)); + if before == 4 && current.role() == Some(ClusterRole::SentenceTerminator) { + return if after == crate::spec::CLOSING_BRACKET { + 0 + } else { + current_size.inline() + }; + } + let table = crate::spec::table_one_space( + before, + after, + current_size, + following_size, + current_solid, + following_solid, + ); + let table_is_blank = table_one_cell_is_blank(before, after); + if !table_is_blank + || style.sentence_medial_dividing_mark() != SentenceMedialDividingMark::QuarterEm + { + return table; + } + let before_quarter = if before == 4 && current.role() == Some(ClusterRole::SentenceMedial) { + quarter_inline_size(paragraph, current) + } else { + 0 + }; + let after_quarter = if after == 4 && following.role() == Some(ClusterRole::SentenceMedial) { + quarter_inline_size(paragraph, following) + } else { + 0 + }; + table + .saturating_add(before_quarter) + .saturating_add(after_quarter) +} + +fn table_one_cell_is_blank(before: u8, after: u8) -> bool { + crate::generated::table1::cell(before, after).is_none_or(|cell| cell.terms.is_empty()) +} + +fn class_of_cluster(paragraph: &Paragraph, ordinal: usize) -> u8 { + class_of_cluster_impl(paragraph, None, ordinal) +} + +fn class_of_cluster_with_style(paragraph: &Paragraph, style: &Style, ordinal: usize) -> u8 { + class_of_cluster_impl(paragraph, Some(style), ordinal) +} + +fn class_of_cluster_impl(paragraph: &Paragraph, style: Option<&Style>, ordinal: usize) -> u8 { + if tate_chu_yoko_cluster_range(paragraph, ordinal).is_some() { + return 30; + } + let cluster = ¶graph.text.clusters()[ordinal]; + let range = cluster.range(); + if let Some((_, construct)) = paragraph.find_construct_containing(ordinal, |construct| { + matches!( + construct.kind(), + ConstructKind::Ruby(_) + | ConstructKind::Emphasis { .. } + | ConstructKind::Script { .. } + | ConstructKind::ReferenceMark { .. } + ) + }) { + return match construct.kind() { + ConstructKind::Ruby(ruby) if ruby.kind() == RubyKind::Jukugo => 23, + ConstructKind::Ruby(_) => 22, + ConstructKind::Emphasis { .. } | ConstructKind::Script { .. } => 21, + ConstructKind::ReferenceMark { .. } => 20, + _ => 19, + }; + } + let frame = cluster.frame_override().unwrap_or(paragraph.text.frame()); + crate::spec::class_of( + ¶graph.text.source()[range], + frame, + cluster.role(), + paragraph.writing_mode, + style.is_some_and(|style| style.unlisted_code_point() == UnlistedCodePoint::Ideographic), + style.is_some_and(|style| style.ambiguous_context() == AmbiguousContext::HighestClass), + style.is_some_and(|style| { + style.grouped_numeral_qualification() == GroupedNumeralQualification::ByRole + }), + ) +} + +fn contextual_punctuation_is_solid( + paragraph: &Paragraph, + cluster: &crate::Cluster, + character: char, +) -> bool { + match cluster.role() { + Some(ClusterRole::WarichuBracket) => { + is_opening_bracket(character) || is_closing_bracket(character) + }, + Some(ClusterRole::DecimalPoint) => { + character == '・' && paragraph.writing_mode == WritingMode::VerticalRl + }, + Some(ClusterRole::DigitGroupSeparator) => { + character == '、' && paragraph.writing_mode == WritingMode::VerticalRl + }, + Some(ClusterRole::GroupedNumeral | ClusterRole::UnitSymbol | ClusterRole::Formula) => { + character == '・' + }, + _ => false, + } +} + +fn formula_boundary_space_after(paragraph: &Paragraph, ordinal: usize) -> Option { + let clusters = paragraph.text.clusters(); + let following_ordinal = ordinal.saturating_add(1); + let current_formula = formula_cluster_range(paragraph, ordinal); + let following_formula = formula_cluster_range(paragraph, following_ordinal); + if current_formula.is_none() && following_formula.is_none() { + return None; + } + let current = clusters.get(ordinal)?; + let following = clusters.get(following_ordinal)?; + + match (current_formula, following_formula) { + (Some(current_range), Some(_)) => { + if current_range.start == 0 && current_range.end == clusters.len() { + let current_character = single_cluster_character(paragraph, current); + let following_character = single_cluster_character(paragraph, following); + let symbol_boundary = current_character.is_some_and(is_math_symbol) + ^ following_character.is_some_and(is_math_symbol); + if symbol_boundary { + let symbol = if current_character.is_some_and(is_math_symbol) { + current + } else { + following + }; + return Some(quarter_inline_size(paragraph, symbol)); + } + } + Some(0) + }, + (None, Some(_)) => Some(formula_outer_boundary_space(paragraph, current, following)), + (Some(_), None) => Some(formula_outer_boundary_space(paragraph, following, current)), + _ => Some(0), + } +} + +fn formula_outer_boundary_space( + paragraph: &Paragraph, + outside: &crate::Cluster, + endpoint: &crate::Cluster, +) -> i32 { + if is_japanese_formula_neighbor(paragraph, outside) + && formula_endpoint_needs_quarter(paragraph, endpoint) + { + quarter_inline_size(paragraph, endpoint) + } else { + 0 + } +} + +fn formula_endpoint_needs_quarter(paragraph: &Paragraph, cluster: &crate::Cluster) -> bool { + let character = single_cluster_character(paragraph, cluster); + !character.is_some_and(is_math_token) + && (cluster.frame_override().unwrap_or(paragraph.text.frame()) == Frame::Proportional + || cluster.role() == Some(ClusterRole::GroupedNumeral)) +} + +fn is_japanese_formula_neighbor(paragraph: &Paragraph, cluster: &crate::Cluster) -> bool { + if cluster.frame_override().unwrap_or(paragraph.text.frame()) == Frame::Proportional { + return false; + } + single_cluster_character(paragraph, cluster).is_some_and(|character| { + !character.is_whitespace() + && !is_opening_bracket(character) + && !is_closing_bracket(character) + && !is_full_stop(character) + && !is_comma(character) + && !is_middle_dot(character) + && !is_math_token(character) + }) +} diff --git a/crates/jlreq-core/src/pipeline/search.rs b/crates/jlreq-core/src/pipeline/search.rs new file mode 100644 index 0000000..1ad5dbb --- /dev/null +++ b/crates/jlreq-core/src/pipeline/search.rs @@ -0,0 +1,274 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +fn check_limit( + resource: CompositionResource, + limit: usize, + observed: usize, +) -> Result<(), ComposeError> { + if observed > limit { + Err(ComposeError::new(resource, limit, observed)) + } else { + Ok(()) + } +} + +fn non_negative_cost(cost: i64) -> u128 { + u128::try_from(cost).unwrap_or(0) +} + +fn search_candidate_precedes(cost: u128, start: usize, current: Node) -> bool { + (cost, start) < (current.cost, current.previous) +} + +fn search_lower_bound_exceeds( + minimum_width: i64, + available: i64, + is_last: bool, + preference: AdjustmentPreference, + best_cost: u128, +) -> bool { + match minimum_width.cmp(&available) { + core::cmp::Ordering::Greater => { + non_negative_cost(line_badness( + available.saturating_sub(minimum_width), + is_last, + preference, + )) > best_cost + }, + core::cmp::Ordering::Less | core::cmp::Ordering::Equal => false, + } +} + +fn line_should_justify( + alignment: Alignment, + is_last: bool, + remaining: i64, + cluster_count: usize, +) -> bool { + alignment == Alignment::Justify && !is_last && remaining.is_positive() && cluster_count > 1 +} + +fn line_adjustment_need(remaining: i64, justify: bool) -> i64 { + if remaining.is_negative() || justify { + remaining + } else { + 0 + } +} + +fn fast_measure_line( + prepared: &PreparedParagraph, + paragraph: &Paragraph, + style: &Style, + start: usize, + end: usize, + line_number: usize, +) -> i64 { + if start >= end { + return i64::from(line_head_indent(paragraph, style, start, line_number)); + } + let mut width = range_sum(&prepared.natural_prefix, start, end); + let last = end.saturating_sub(1); + let last_natural = range_sum(&prepared.natural_prefix, last, end); + if is_western_word_space(paragraph, last) { + width = width.saturating_sub(last_natural); + } else { + width = width + .saturating_sub(last_natural) + .saturating_add(i64::from(effective_cluster_body_advance(paragraph, last))) + .saturating_add(i64::from(line_end_space_after(paragraph, style, last))); + } + if start != last && is_western_word_space(paragraph, start) { + width = width.saturating_sub(range_sum( + &prepared.natural_prefix, + start, + start.saturating_add(1), + )); + } + width.saturating_add(i64::from(line_head_indent( + paragraph, + style, + start, + line_number, + ))) +} + +fn fast_width_after_available_reduction( + prepared: &PreparedParagraph, + paragraph: &Paragraph, + style: &Style, + start: usize, + end: usize, + width: i64, + available: i64, +) -> i64 { + let need = width.saturating_sub(available); + if need <= 0 || start >= end { + return width; + } + let last = end.saturating_sub(1); + let internal_capacity = range_sum(&prepared.reduction_prefix, start, last); + let line_end_capacity = prepared.line_end_reduction.get(last).copied().unwrap_or(0); + let capacity = internal_capacity.saturating_add(line_end_capacity); + let reduced = width.saturating_sub(need.min(capacity)); + reduced.saturating_sub(hanging_amount(paragraph, style, end, reduced, available)) +} + +fn fast_minimum_width(prepared: &PreparedParagraph, start: usize, end: usize) -> i64 { + let Some(last) = end.checked_sub(1) else { + return 0; + }; + range_sum(&prepared.minimum_prefix, start, last) +} + +fn range_sum(prefix: &[i64], start: usize, end: usize) -> i64 { + prefix + .get(end) + .copied() + .unwrap_or(i64::MAX) + .saturating_sub(prefix.get(start).copied().unwrap_or(0)) +} + +fn cluster_index_at_or_after(paragraph: &Paragraph, offset: usize) -> usize { + paragraph + .text + .clusters() + .partition_point(|cluster| cluster.range().start < offset) +} + +fn tate_chu_yoko_cluster_range(paragraph: &Paragraph, ordinal: usize) -> Option> { + if paragraph.writing_mode != WritingMode::VerticalRl { + return None; + } + paragraph.text.clusters().get(ordinal)?; + let (_, construct) = paragraph.find_construct_containing(ordinal, |construct| { + matches!(construct.kind(), ConstructKind::TateChuYoko(_)) + })?; + let ConstructKind::TateChuYoko(range) = construct.kind() else { + return None; + }; + Some( + cluster_index_at_or_after(paragraph, range.start) + ..cluster_index_at_or_after(paragraph, range.end), + ) +} + +fn warichu_cluster_range(paragraph: &Paragraph, ordinal: usize) -> Option> { + paragraph.text.clusters().get(ordinal)?; + let (_, construct) = paragraph.find_construct_containing(ordinal, |construct| { + matches!(construct.kind(), ConstructKind::Warichu(_)) + })?; + let ConstructKind::Warichu(range) = construct.kind() else { + return None; + }; + Some( + cluster_index_at_or_after(paragraph, range.start) + ..cluster_index_at_or_after(paragraph, range.end), + ) +} + +fn furawake_cluster_range( + paragraph: &Paragraph, + ordinal: usize, +) -> Option<(Range, u16, i32)> { + paragraph.text.clusters().get(ordinal)?; + let (_, construct) = paragraph.find_construct_containing(ordinal, |construct| { + matches!(construct.kind(), ConstructKind::Furawake { .. }) + })?; + let ConstructKind::Furawake { + range, + columns, + line_gap, + } = construct.kind() + else { + return None; + }; + Some(( + cluster_index_at_or_after(paragraph, range.start) + ..cluster_index_at_or_after(paragraph, range.end), + *columns, + *line_gap, + )) +} + +fn jidori_cluster_range(paragraph: &Paragraph, ordinal: usize) -> Option<(Range, u16)> { + paragraph.text.clusters().get(ordinal)?; + let (_, construct) = paragraph.find_construct_containing(ordinal, |construct| { + matches!(construct.kind(), ConstructKind::Jidori { .. }) + })?; + let ConstructKind::Jidori { range, cells } = construct.kind() else { + return None; + }; + Some(( + cluster_index_at_or_after(paragraph, range.start) + ..cluster_index_at_or_after(paragraph, range.end), + *cells, + )) +} + +fn is_internal_jidori_boundary(paragraph: &Paragraph, ordinal: usize) -> bool { + let Some(cluster) = paragraph.text.clusters().get(ordinal) else { + return false; + }; + let boundary = cluster.range().end; + paragraph + .find_construct_containing(ordinal, |construct| { + matches!(construct.kind(), ConstructKind::Jidori { range, .. } + if range.start < boundary && boundary < range.end) + }) + .is_some() +} + +/// Whether the boundary after `ordinal` falls inside a warichu or a furawake. +/// +/// Both structures set their text on sublines that run *beside* the line, so a boundary +/// with the clusters on either side of it inside the same structure is no part of what the +/// line was composed from: the space there is the block's own, the seam where two sublines +/// meet carries nothing on the line at all, and §3.8.3's ladder adjusts the spacing of the +/// line (`docs/decisions/stacked-structure-geometry.md`). A boundary with one cluster +/// inside the structure and the next outside it is the line's — that is where the block +/// ends and the line resumes — and this is false there. +fn is_internal_stacked_boundary(paragraph: &Paragraph, ordinal: usize) -> bool { + let Some(cluster) = paragraph.text.clusters().get(ordinal) else { + return false; + }; + let boundary = cluster.range().end; + paragraph + .find_construct_containing(ordinal, |construct| { + matches!( + construct.kind(), + ConstructKind::Warichu(range) | ConstructKind::Furawake { range, .. } + if range.start < boundary && boundary < range.end + ) + }) + .is_some() +} + +fn formula_cluster_range(paragraph: &Paragraph, ordinal: usize) -> Option> { + paragraph.text.clusters().get(ordinal)?; + let (_, construct) = paragraph.find_construct_containing(ordinal, |construct| { + matches!(construct.kind(), ConstructKind::Formula(_)) + })?; + let ConstructKind::Formula(range) = construct.kind() else { + return None; + }; + Some( + cluster_index_at_or_after(paragraph, range.start) + ..cluster_index_at_or_after(paragraph, range.end), + ) +} + +fn is_internal_furawake_offset(paragraph: &Paragraph, offset: usize) -> bool { + let boundary = cluster_index_at_or_after(paragraph, offset); + boundary.checked_sub(1).is_some_and(|ordinal| { + paragraph + .find_construct_containing(ordinal, |construct| { + matches!(construct.kind(), ConstructKind::Furawake { range, .. } + if range.start < offset && offset < range.end) + }) + .is_some() + }) +} + diff --git a/crates/jlreq-core/src/pipeline/special.rs b/crates/jlreq-core/src/pipeline/special.rs new file mode 100644 index 0000000..b65f6d8 --- /dev/null +++ b/crates/jlreq-core/src/pipeline/special.rs @@ -0,0 +1,485 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +fn furawake_segment( + paragraph: &Paragraph, + range: Range, + columns: u16, + line_gap: i32, + line_end: usize, +) -> FurawakeSegment { + let mut lanes = Vec::with_capacity(usize::from(columns)); + let start_offset = paragraph.text.clusters()[range.start].range().start; + let end_offset = paragraph.text.clusters()[range.end.saturating_sub(1)] + .range() + .end; + let mut start = range.start; + for opportunity in paragraph.breaks.iter().filter(|opportunity| { + start_offset < opportunity.offset() && opportunity.offset() < end_offset + }) { + let split = cluster_index_at_or_after(paragraph, opportunity.offset()); + lanes.push(start..split); + start = split; + } + lanes.push(start..range.end); + + let widths: Vec<_> = lanes + .iter() + .map(|lane| construct_lane_width(paragraph, lane)) + .collect(); + let block_extents: Vec<_> = lanes + .iter() + .map(|lane| construct_lane_block_extent(paragraph, lane)) + .collect(); + let block_extent = block_extents + .iter() + .copied() + .fold(0_i32, i32::saturating_add) + .saturating_add( + line_gap + .saturating_mul(i32::try_from(lanes.len().saturating_sub(1)).unwrap_or(i32::MAX)), + ); + let outer_space = if range.end < line_end { + boundary_space_after(paragraph, range.end.saturating_sub(1)) + } else { + 0 + }; + let advance = widths + .iter() + .copied() + .max() + .unwrap_or(0) + .saturating_add(outer_space); + FurawakeSegment { + range, + lanes, + block_extents, + line_gap, + advance, + block_extent, + } +} + +fn construct_lane_width(paragraph: &Paragraph, lane: &Range) -> i32 { + lane.clone().fold(0_i32, |sum, ordinal| { + let logical_end = tate_chu_yoko_cluster_range(paragraph, ordinal) + .map_or_else(|| ordinal.saturating_add(1), |group| group.end); + let boundary = if logical_end < lane.end { + boundary_space_after(paragraph, ordinal) + } else { + 0 + }; + sum.saturating_add(nested_cluster_body_advance(paragraph, ordinal)) + .saturating_add(boundary) + }) +} + +fn construct_lane_block_extent(paragraph: &Paragraph, lane: &Range) -> i32 { + paragraph.text.clusters()[lane.clone()] + .iter() + .map(|cluster| { + cluster + .size_override() + .unwrap_or(paragraph.text.size()) + .block() + }) + .max() + .unwrap_or(0) +} + +fn place_furawake_segment( + paragraph: &Paragraph, + segment: &FurawakeSegment, + inline: i64, + block_origin: i32, + placed: &mut Vec, +) { + let main_block_extent = paragraph.text.size().block(); + let mut block = match paragraph.writing_mode { + WritingMode::HorizontalTb => i64::from(block_origin).saturating_add(i64::from( + main_block_extent.saturating_sub(segment.block_extent) / 2, + )), + WritingMode::VerticalRl => i64::from(block_origin).saturating_add(i64::from( + segment.block_extent.saturating_sub(main_block_extent) / 2, + )), + }; + for (lane_index, lane) in segment.lanes.iter().enumerate() { + let mut cursor = inline; + for ordinal in lane.clone() { + let logical_end = tate_chu_yoko_cluster_range(paragraph, ordinal) + .map_or_else(|| ordinal.saturating_add(1), |group| group.end); + let boundary = if logical_end < lane.end { + boundary_space_after(paragraph, ordinal) + } else { + 0 + }; + let advance = nested_cluster_body_advance(paragraph, ordinal).saturating_add(boundary); + place_construct_member(paragraph, ordinal, cursor, block, advance, placed); + cursor = cursor.saturating_add(i64::from(advance)); + } + let step = segment.block_extents[lane_index].saturating_add(segment.line_gap); + match paragraph.writing_mode { + WritingMode::HorizontalTb => { + block = block.saturating_add(i64::from(step)); + }, + WritingMode::VerticalRl => { + block = block.saturating_sub(i64::from(step)); + }, + } + } +} + +fn place_construct_member( + paragraph: &Paragraph, + ordinal: usize, + inline: i64, + block: i64, + advance: i32, + placed: &mut Vec, +) { + let cluster = ¶graph.text.clusters()[ordinal]; + let size = cluster.size_override().unwrap_or(paragraph.text.size()); + let frame = cluster.frame_override().unwrap_or(paragraph.text.frame()); + let (writing_mode, transform) = local_orientation(paragraph, ordinal, frame); + placed.push(ClusterPlacement { + origin: PlacementOrigin::Cluster(ordinal), + range: cluster.range(), + inline: clamp_i32(inline), + block: clamp_i32(block), + advance, + size, + frame, + writing_mode, + transform, + }); +} + +fn warichu_segment( + paragraph: &Paragraph, + full_range: Range, + line_start: usize, + line_end: usize, +) -> WarichuSegment { + let range = full_range.start.max(line_start)..full_range.end.min(line_end); + let clusters = paragraph.text.clusters(); + let leading_bracket = (range.start == full_range.start) + .then_some(range.start) + .filter(|ordinal| { + clusters + .get(*ordinal) + .is_some_and(|cluster| cluster.role() == Some(ClusterRole::WarichuBracket)) + }); + let trailing_ordinal = range.end.saturating_sub(1); + let trailing_bracket = (range.end == full_range.end) + .then_some(trailing_ordinal) + .filter(|ordinal| { + clusters + .get(*ordinal) + .is_some_and(|cluster| cluster.role() == Some(ClusterRole::WarichuBracket)) + }); + let interior_start = leading_bracket.map_or(range.start, |ordinal| ordinal.saturating_add(1)); + let interior_end = trailing_bracket.unwrap_or(range.end); + let split = choose_warichu_split(paragraph, interior_start..interior_end); + let first_lane = interior_start..split; + let second_lane = split..interior_end; + let first_width = warichu_lane_width(paragraph, &first_lane); + let second_width = warichu_lane_width(paragraph, &second_lane); + let leading_width = + leading_bracket.map_or(0, |ordinal| paragraph.text.clusters()[ordinal].advance()); + let trailing_width = + trailing_bracket.map_or(0, |ordinal| paragraph.text.clusters()[ordinal].advance()); + let outer_space = if range.end < line_end { + boundary_space_after(paragraph, range.end.saturating_sub(1)) + } else { + 0 + }; + let advance = leading_width + .saturating_add(first_width.max(second_width)) + .saturating_add(trailing_width) + .saturating_add(outer_space); + WarichuSegment { + range, + leading_bracket, + first_lane, + second_lane, + trailing_bracket, + first_width, + second_width, + advance, + } +} + +fn choose_warichu_split(paragraph: &Paragraph, interior: Range) -> usize { + if interior.end.saturating_sub(interior.start) < 2 { + return interior.end; + } + let mut best = None; + for require_declared in [true, false] { + for split in interior.start.saturating_add(1)..interior.end { + let offset = paragraph.text.clusters()[split].range().start; + let declared = paragraph + .breaks + .iter() + .any(|opportunity| opportunity.offset() == offset); + if require_declared != declared { + continue; + } + let first = warichu_lane_width(paragraph, &(interior.start..split)); + let second = warichu_lane_width(paragraph, &(split..interior.end)); + let score = ( + i32::from(second > first), + first.saturating_sub(second).saturating_abs(), + ); + if best.is_none_or(|(_, best_score)| score < best_score) { + best = Some((split, score)); + } + } + if best.is_some() { + break; + } + } + best.map_or(interior.end, |(split, _)| split) +} + +fn warichu_lane_width(paragraph: &Paragraph, lane: &Range) -> i32 { + lane.clone().fold(0_i32, |sum, ordinal| { + sum.saturating_add(warichu_member_advance(paragraph, ordinal, lane)) + }) +} + +fn warichu_member_advance(paragraph: &Paragraph, ordinal: usize, lane: &Range) -> i32 { + if is_western_word_space(paragraph, ordinal) + && (ordinal == lane.start || ordinal.saturating_add(1) == lane.end) + { + return 0; + } + let logical_end = tate_chu_yoko_cluster_range(paragraph, ordinal) + .map_or_else(|| ordinal.saturating_add(1), |group| group.end); + debug_assert!(logical_end <= lane.end); + let boundary = if logical_end == lane.end { + 0 + } else { + boundary_space_after(paragraph, ordinal) + }; + effective_cluster_body_advance(paragraph, ordinal).saturating_add(boundary) +} + +fn place_warichu_segment( + paragraph: &Paragraph, + segment: &WarichuSegment, + cursor: i64, + block_origin: i32, + placed: &mut Vec, +) { + let leading_width = segment + .leading_bracket + .map_or(0, |ordinal| paragraph.text.clusters()[ordinal].advance()); + if let Some(ordinal) = segment.leading_bracket { + place_warichu_member( + paragraph, + ordinal, + cursor, + i64::from(block_origin), + paragraph.text.clusters()[ordinal].advance(), + placed, + ); + } + + let lane_inline = cursor.saturating_add(i64::from(leading_width)); + let first_block_extent = warichu_lane_block_extent(paragraph, &segment.first_lane); + let second_block_extent = warichu_lane_block_extent(paragraph, &segment.second_lane); + let total_block_extent = first_block_extent.saturating_add(second_block_extent); + let main_block_extent = paragraph.text.size().block(); + let first_block = match paragraph.writing_mode { + WritingMode::HorizontalTb => i64::from(block_origin).saturating_add(i64::from( + main_block_extent.saturating_sub(total_block_extent) / 2, + )), + WritingMode::VerticalRl => i64::from(block_origin).saturating_add(i64::from( + total_block_extent.saturating_sub(main_block_extent) / 2, + )), + }; + let second_block = match paragraph.writing_mode { + WritingMode::HorizontalTb => first_block.saturating_add(i64::from(first_block_extent)), + WritingMode::VerticalRl => first_block.saturating_sub(i64::from(first_block_extent)), + }; + place_warichu_lane( + paragraph, + &segment.first_lane, + lane_inline, + first_block, + placed, + ); + place_warichu_lane( + paragraph, + &segment.second_lane, + lane_inline, + second_block, + placed, + ); + + if let Some(ordinal) = segment.trailing_bracket { + let inline = + lane_inline.saturating_add(i64::from(segment.first_width.max(segment.second_width))); + place_warichu_member( + paragraph, + ordinal, + inline, + i64::from(block_origin), + paragraph.text.clusters()[ordinal].advance(), + placed, + ); + } +} + +fn place_warichu_lane( + paragraph: &Paragraph, + lane: &Range, + inline: i64, + block: i64, + placed: &mut Vec, +) { + let mut cursor = inline; + for ordinal in lane.clone() { + let advance = warichu_member_advance(paragraph, ordinal, lane); + place_warichu_member(paragraph, ordinal, cursor, block, advance, placed); + cursor = cursor.saturating_add(i64::from(advance)); + } +} + +fn place_warichu_member( + paragraph: &Paragraph, + ordinal: usize, + inline: i64, + block: i64, + advance: i32, + placed: &mut Vec, +) { + let cluster = ¶graph.text.clusters()[ordinal]; + let size = cluster.size_override().unwrap_or(paragraph.text.size()); + let frame = cluster.frame_override().unwrap_or(paragraph.text.frame()); + let (writing_mode, transform) = local_orientation(paragraph, ordinal, frame); + placed.push(ClusterPlacement { + origin: PlacementOrigin::Cluster(ordinal), + range: cluster.range(), + inline: clamp_i32(inline), + block: clamp_i32(block), + advance, + size, + frame, + writing_mode, + transform, + }); +} + +fn warichu_lane_block_extent(paragraph: &Paragraph, lane: &Range) -> i32 { + paragraph.text.clusters()[lane.clone()] + .iter() + .map(|cluster| { + cluster + .size_override() + .unwrap_or(paragraph.text.size()) + .block() + }) + .max() + .unwrap_or(0) +} + +fn warichu_break_penalty(paragraph: &Paragraph, offset: usize) -> i64 { + let boundary = cluster_index_at_or_after(paragraph, offset); + let Some(cluster) = boundary.checked_sub(1) else { + return 0; + }; + let mut penalty = 0_i64; + paragraph.visit_constructs_containing(cluster, |_, construct| { + let ConstructKind::Warichu(range) = construct.kind() else { + return; + }; + if !(range.start < offset && offset < range.end) { + return; + } + let start = cluster_index_at_or_after(paragraph, range.start); + let split = cluster_index_at_or_after(paragraph, offset); + let end = cluster_index_at_or_after(paragraph, range.end); + let before = split.saturating_sub(start); + let after = end.saturating_sub(split); + penalty = penalty.saturating_add( + i64::try_from(before.abs_diff(after)) + .unwrap_or(i64::MAX) + .saturating_mul(1_000_000), + ); + }); + penalty +} + +fn formula_break_penalty(paragraph: &Paragraph, offset: usize) -> i64 { + let boundary = cluster_index_at_or_after(paragraph, offset); + let is_independent_formula = boundary.checked_sub(1).is_some_and(|ordinal| { + paragraph + .find_construct_containing(ordinal, |construct| { + matches!( + construct.kind(), + ConstructKind::Formula(range) + if range.start == 0 + && range.end == paragraph.text.source().len() + && range.start < offset + && offset < range.end + ) + }) + .is_some() + }); + if !is_independent_formula { + return 0; + } + let after = paragraph.text.source()[offset..].chars().next(); + if after.is_some_and(is_math_symbol) { + 0 + } else if after.is_some_and(is_math_operator) { + 100_000_000 + } else { + 200_000_000 + } +} + +fn single_cluster_character(paragraph: &Paragraph, cluster: &crate::Cluster) -> Option { + let mut characters = paragraph.text.source()[cluster.range()].chars(); + let character = characters.next()?; + characters.next().is_none().then_some(character) +} + +fn half_inline_size(paragraph: &Paragraph, cluster: &crate::Cluster) -> i32 { + let size = cluster + .size_override() + .unwrap_or(paragraph.text.size()) + .inline(); + (size / 2).saturating_add(size % 2) +} + +fn quarter_inline_size(paragraph: &Paragraph, cluster: &crate::Cluster) -> i32 { + let size = cluster + .size_override() + .unwrap_or(paragraph.text.size()) + .inline(); + (size / 4).saturating_add(i32::from(size % 4 != 0)) +} + +fn is_opening_bracket(character: char) -> bool { + crate::spec::single_has_class(character, crate::spec::OPENING_BRACKET) +} + +fn is_closing_bracket(character: char) -> bool { + crate::spec::single_has_class(character, crate::spec::CLOSING_BRACKET) +} + +fn is_full_stop(character: char) -> bool { + crate::spec::single_has_class(character, crate::spec::FULL_STOP) +} + +fn is_comma(character: char) -> bool { + crate::spec::single_has_class(character, crate::spec::COMMA) +} + +fn is_middle_dot(character: char) -> bool { + crate::spec::single_has_class(character, crate::spec::MIDDLE_DOT) +} + diff --git a/crates/jlreq-core/src/spec.rs b/crates/jlreq-core/src/spec.rs index 0162a88..1000cac 100644 --- a/crates/jlreq-core/src/spec.rs +++ b/crates/jlreq-core/src/spec.rs @@ -31,8 +31,6 @@ pub(crate) struct RawTerm { #[derive(Debug, Clone, Copy)] pub(crate) struct RawSpacingCell { - pub(crate) before: u8, - pub(crate) after: u8, pub(crate) prohibited: bool, pub(crate) hang: RawHang, pub(crate) rule: &'static str, @@ -41,8 +39,6 @@ pub(crate) struct RawSpacingCell { #[derive(Debug, Clone, Copy)] pub(crate) struct RawBreakCell { - pub(crate) before: u8, - pub(crate) after: u8, pub(crate) prohibited: bool, pub(crate) levels: u8, pub(crate) rule: &'static str, @@ -50,8 +46,6 @@ pub(crate) struct RawBreakCell { #[derive(Debug, Clone, Copy)] pub(crate) struct RawRangedCell { - pub(crate) before: u8, - pub(crate) after: u8, pub(crate) limit: Option, pub(crate) two_valued: bool, pub(crate) residual: bool, @@ -151,10 +145,7 @@ pub(crate) fn table_one_space_components( before_solid: bool, after_solid: bool, ) -> [i32; 2] { - let Some(cell) = crate::generated::table1::CELLS - .iter() - .find(|cell| cell.before == before && cell.after == after) - else { + let Some(cell) = crate::generated::table1::cell(before, after) else { return [0, 0]; }; let mut components = [0_i32; 2]; @@ -174,10 +165,7 @@ pub(crate) fn table_one_space_components( } pub(crate) fn table_two_cell(before: u8, after: u8) -> Option { - crate::generated::table2::CELLS - .iter() - .copied() - .find(|cell| cell.before == before && cell.after == after) + crate::generated::table2::cell(before, after).copied() } const fn class_bit(class: u8) -> u32 { diff --git a/crates/jlreq/examples/benchmark.rs b/crates/jlreq/examples/benchmark.rs new file mode 100644 index 0000000..62497df --- /dev/null +++ b/crates/jlreq/examples/benchmark.rs @@ -0,0 +1,271 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Reproducible median-time smoke benchmarks for the complete layout stack. +//! +//! Run with `cargo run -p jlreq --release --example benchmark`. Set +//! `JLREQ_BENCH_SAMPLES` to an odd positive sample count when collecting a +//! comparison; the default is nine measured samples after two warmups. + +use std::{ + env, + error::Error, + hint::black_box, + sync::Arc, + time::{Duration, Instant}, +}; + +use jlreq::{ + BaseDirection, Document, DocumentBuilder, FontLibrary, FontStyle, LayoutEngine, LayoutOptions, + ScriptPosition, SpanStyle, WritingMode, + core::{ + Break, Cluster, Composer, CompositionLimits, Construct, Frame, Paragraph, ShapedText, Size, + Style, + }, +}; + +type BenchResult = Result>; + +fn main() -> BenchResult<()> { + let samples = sample_count(); + println!("jlreq median benchmark ({samples} measured samples, release recommended)"); + + let fonts = fixture_fonts()?; + let plain = "日本語組版では、行長と約物の間隔を整える。".repeat(64); + let plain_options = LayoutOptions::try_new(640.0, 16.0)?; + + measure("layout.one-shot.japanese", samples, || { + let layout = jlreq::layout(black_box(&plain), &fonts, plain_options.clone())?; + Ok(layout.glyphs().count()) + })?; + + let mut engine = LayoutEngine::new(); + measure("layout.reused-engine.japanese", samples, || { + let layout = engine.layout(black_box(&plain), &fonts, plain_options.clone())?; + Ok(layout.glyphs().count()) + })?; + + let bidi = "日本語 abc مرحبا 🇪🇨 ".repeat(48); + let bidi_options = LayoutOptions::try_new(640.0, 18.0)?.base_direction(BaseDirection::Auto); + measure("layout.reused-engine.bidi-fallback", samples, || { + let layout = engine.layout(black_box(&bidi), &fonts, bidi_options.clone())?; + Ok(layout.glyphs().count()) + })?; + + let span_text = "日本語ABC".repeat(96); + measure("document.build.many-spans", samples, || { + Ok(many_span_document(black_box(&span_text))?.text().len()) + })?; + let span_document = many_span_document(&span_text)?; + measure("layout.reused-engine.many-spans", samples, || { + let layout = + engine.layout_document(black_box(&span_document), &fonts, plain_options.clone())?; + Ok(layout.glyphs().count()) + })?; + + let complex_document = complex_document(32)?; + let vertical_options = LayoutOptions::try_new(640.0, 18.0)? + .writing_mode(WritingMode::VerticalRl) + .tab_width(4)?; + measure("layout.reused-engine.vertical-constructs", samples, || { + let layout = engine.layout_document( + black_box(&complex_document), + &fonts, + vertical_options.clone(), + )?; + Ok(layout.glyphs().count()) + })?; + + let query_layout = engine.layout(&plain, &fonts, plain_options)?; + let points: Vec<_> = query_layout + .lines() + .iter() + .map(jlreq::TextLine::origin) + .collect(); + let boundaries: Vec<_> = plain + .char_indices() + .map(|(offset, _)| offset) + .chain([plain.len()]) + .collect(); + measure("result.hit-caret-selection", samples, || { + let mut observed = 0_usize; + for (ordinal, point) in points.iter().enumerate() { + let hit = query_layout.hit_test(black_box(*point)); + observed = observed.saturating_add(hit.byte_offset()); + let boundary = cyclic_value(&boundaries, ordinal)?; + observed = observed.saturating_add(usize::from( + query_layout.caret_rect(black_box(boundary)).is_some(), + )); + let next = cyclic_value(&boundaries, ordinal.saturating_add(1))?; + let range = boundary.min(next)..boundary.max(next); + observed = + observed.saturating_add(query_layout.selection_rects(black_box(range)).len()); + } + Ok(observed) + })?; + + let regular = core_paragraph(10_000, false)?; + let core_style = Style::default(); + measure("core.compose-fresh.10k", samples, || { + Ok(jlreq::core::compose(black_box(®ular), &core_style)? + .lines() + .len()) + })?; + + let mut composer = Composer::new(); + measure("core.composer-reused.10k", samples, || { + Ok(composer + .compose(black_box(®ular), &core_style)? + .lines() + .len()) + })?; + + let static_constructs = core_paragraph(1_000, true)?; + composer.set_limits(CompositionLimits::DEFAULT.with_max_search_transitions(1_000_000_000)); + measure("core.static-construct-index.1k", samples, || { + Ok(composer + .compose(black_box(&static_constructs), &core_style)? + .lines() + .len()) + })?; + Ok(()) +} + +fn sample_count() -> usize { + let parsed = env::var("JLREQ_BENCH_SAMPLES") + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(9); + if parsed.is_multiple_of(2) { + parsed.saturating_add(1) + } else { + parsed + } +} + +fn measure( + name: &str, + samples: usize, + mut operation: impl FnMut() -> BenchResult, +) -> BenchResult { + for _ in 0..2 { + black_box(operation()?); + } + let mut elapsed = Vec::with_capacity(samples); + for _ in 0..samples { + let started = Instant::now(); + black_box(operation()?); + elapsed.push(started.elapsed()); + } + elapsed.sort_unstable(); + let median = elapsed[elapsed.len() / 2]; + println!("{name:40} {:>12} ns", median.as_nanos()); + Ok(median) +} + +fn cyclic_value(values: &[usize], index: usize) -> BenchResult { + let wrapped = index + .checked_rem(values.len()) + .ok_or_else(|| std::io::Error::other("a benchmark value cycle is empty"))?; + values + .get(wrapped) + .copied() + .ok_or_else(|| std::io::Error::other("a benchmark value index is outside its cycle").into()) +} + +fn fixture_fonts() -> BenchResult { + let mut fonts = FontLibrary::new(); + let _ = fonts.register_face( + Arc::<[u8]>::from(font_test_data::NOTO_SANS_JP_CFF), + 0, + "Noto Sans JP", + FontStyle::default(), + )?; + let _ = fonts.register_face( + Arc::<[u8]>::from(rwml_fonts::noto_sans_arabic_subset()), + 0, + "Vazirmatn", + FontStyle::default(), + )?; + let _ = fonts.register_face( + Arc::<[u8]>::from(font_test_data::NOTO_COLOR_EMOJI_FLAGS), + 0, + "Noto Color Emoji", + FontStyle::default(), + )?; + let _ = fonts.register_face( + Arc::<[u8]>::from(font_test_data::TINOS_SUBSET), + 0, + "Tinos", + FontStyle::default(), + )?; + Ok(fonts) +} + +fn many_span_document(text: &str) -> BenchResult { + let mut boundaries: Vec<_> = text.char_indices().map(|(offset, _)| offset).collect(); + boundaries.push(text.len()); + let mut builder = DocumentBuilder::new(text); + for ordinal in (0..boundaries.len().saturating_sub(1)).step_by(2) { + let style = if ordinal.is_multiple_of(4) { + SpanStyle::new().family("Noto Sans JP") + } else { + SpanStyle::new().family("Tinos") + }; + let next = ordinal.saturating_add(1); + if let Some((&start, &end)) = boundaries.get(ordinal).zip(boundaries.get(next)) { + let _ = builder.span(start..end, style)?; + } + } + Ok(builder.build()?) +} + +fn complex_document(repetitions: usize) -> BenchResult { + const SEGMENT: &str = "漢12強注式\t\n"; + let mut builder = DocumentBuilder::new(SEGMENT.repeat(repetitions)); + for repetition in 0..repetitions { + let base = repetition.saturating_mul(SEGMENT.len()); + let _ = builder.group_ruby(base..base.saturating_add(3), "かん")?; + let _ = builder.tate_chu_yoko(base.saturating_add(3)..base.saturating_add(5))?; + let _ = builder.emphasis_dots(base.saturating_add(5)..base.saturating_add(8), '・')?; + let _ = builder.warichu(base.saturating_add(8)..base.saturating_add(11))?; + let _ = builder.script( + base.saturating_add(11)..base.saturating_add(14), + "2", + ScriptPosition::Superscript, + )?; + } + Ok(builder.build()?) +} + +fn core_paragraph(cluster_count: usize, with_constructs: bool) -> BenchResult { + let source = "日".repeat(cluster_count); + let ranges: Vec<_> = source + .char_indices() + .map(|(start, character)| start..start.saturating_add(character.len_utf8())) + .collect(); + let clusters = ranges + .iter() + .cloned() + .map(|range| Cluster::new(range, 1_000)); + let shaped = ShapedText::new(source, Size::square(1_000)?, Frame::FullEm, clusters)?; + let breaks = ranges + .iter() + .skip(1) + .map(|range| Break::allowed(range.start)); + let constructs: Vec<_> = if with_constructs { + ranges + .iter() + .step_by(20) + .cloned() + .map(|range| Construct::emphasis_dots(range, '・')) + .collect() + } else { + Vec::new() + }; + Ok(Paragraph::builder(shaped, 20_000) + .breaks(breaks) + .constructs(constructs) + .build()?) +} diff --git a/crates/jlreq/src/document.rs b/crates/jlreq/src/document.rs index 80b4e29..b7ea4b1 100644 --- a/crates/jlreq/src/document.rs +++ b/crates/jlreq/src/document.rs @@ -297,17 +297,24 @@ impl DocumentBuilder { style: SpanStyle, ) -> Result<&mut Self, LayoutError> { self.validate_non_empty_range(&range, "document.invalid-span-range")?; - if self + let insertion = self .spans - .iter() - .any(|(other, _)| ranges_overlap(other, &range)) - { + .partition_point(|(other, _)| other.start < range.start); + let overlaps_previous = insertion + .checked_sub(1) + .and_then(|index| self.spans.get(index)) + .is_some_and(|(other, _)| ranges_overlap(other, &range)); + let overlaps_next = self + .spans + .get(insertion) + .is_some_and(|(other, _)| ranges_overlap(other, &range)); + if overlaps_previous || overlaps_next { return Err(LayoutError::invalid_document( "document.overlapping-spans", Some(range), )); } - self.spans.push((range, style)); + self.spans.insert(insertion, (range, style)); Ok(self) } diff --git a/crates/jlreq/src/engine.rs b/crates/jlreq/src/engine.rs index 8078399..120d350 100644 --- a/crates/jlreq/src/engine.rs +++ b/crates/jlreq/src/engine.rs @@ -16,1527 +16,16 @@ use unicode_bidi::{BidiInfo, Level, ParagraphBidiInfo}; use crate::document::{DocumentConstruct, TextRole}; use crate::{ Alignment, AnnotationSource, BaseDirection, Diagnostic, DiagnosticSeverity, Document, - DocumentBuilder, FontId, FontLibrary, FontResource, FontStyle, FontVariation, GlyphPlacement, - GlyphTransform, LayoutError, LayoutOptions, OpenTypeFeature, Point, Resource, SpanStyle, - TextLayout, TextLine, WritingMode, + DocumentBuilder, FontId, FontLibrary, FontResource, FontSlant, FontStyle, FontVariation, + GlyphPlacement, GlyphTransform, LayoutError, LayoutOptions, OpenTypeFeature, Point, Resource, + SpanStyle, TextLayout, TextLine, WritingMode, }; - -struct FontCache { - bytes: Arc<[u8]>, - face_index: u32, - shaper_data: ShaperData, -} - -struct ShapeRequest<'a> { - source: &'a str, - range: Range, - resource: &'a FontResource, - size: i32, - direction: Direction, - language: &'a str, - features: &'a [OpenTypeFeature], - variations: &'a [FontVariation], -} - -struct PrepareRequest<'a> { - source: &'a str, - global_offset: usize, - spans: &'a [(Range, SpanStyle)], - fonts: &'a FontLibrary, - options: &'a LayoutOptions, - diagnostic_range: Option>, -} - -/// Reusable high-level layout engine. -/// -/// Font parsing and shaping caches are retained between calls. Returned layouts never borrow -/// the engine, and an error leaves it immediately reusable. -pub struct LayoutEngine { - fonts: BTreeMap, - composer: jlreq_core::Composer, -} - -impl fmt::Debug for LayoutEngine { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter - .debug_struct("LayoutEngine") - .field("cached_fonts", &self.fonts.len()) - .finish_non_exhaustive() - } -} - -impl Default for LayoutEngine { - fn default() -> Self { - Self::new() - } -} - -impl LayoutEngine { - /// Build an empty reusable engine. - #[must_use] - pub fn new() -> Self { - Self { - fonts: BTreeMap::new(), - composer: jlreq_core::Composer::new(), - } - } - - /// Shape, compose, reorder, and physically place plain UTF-8 text. - pub fn layout( - &mut self, - text: &str, - fonts: &FontLibrary, - options: LayoutOptions, - ) -> Result { - let document = DocumentBuilder::new(text).build()?; - self.layout_document(&document, fonts, options) - } - - /// Shape, compose, reorder, and physically place a typed document. - pub fn layout_document( - &mut self, - document: &Document, - fonts: &FontLibrary, - options: LayoutOptions, - ) -> Result { - // The public API intentionally takes an owned option set so callers can configure and - // submit it in one expression. Moving it through this single-element container makes - // that ownership boundary explicit while the pipeline borrows the stable value. - let options = [options]; - let options = &options[0]; - validate_call(document, fonts, options)?; - if document.text.is_empty() { - return Ok(TextLayout { - source: String::new(), - lines: Vec::new(), - fonts: Vec::new(), - diagnostics: Vec::new(), - writing_mode: options.writing_mode, - }); - } - - let segments = paragraph_segments(&document.text); - check_limit( - Resource::Paragraphs, - options.limits.paragraphs, - segments.len(), - )?; - let mut call = CallState::new(options); - let mut lines = Vec::new(); - let mut block_offset = 0_i32; - - for segment in segments { - let content = &document.text[segment.content.clone()]; - if content.is_empty() { - let origin = match options.writing_mode { - WritingMode::HorizontalTb => Point::from_fixed(0, block_offset), - WritingMode::VerticalRl => Point::from_fixed(block_offset, 0), - }; - lines.push(TextLine { - range: segment.content.clone(), - origin, - inline_extent: 0, - block_extent: options.font_size, - writing_mode: options.writing_mode, - glyphs: Vec::new(), - }); - block_offset = advance_block( - block_offset, - options.font_size.saturating_add(options.line_gap), - options.writing_mode, - ); - continue; - } - - let prepared = self.prepare_text( - PrepareRequest { - source: content, - global_offset: segment.content.start, - spans: &document.spans, - fonts, - options, - diagnostic_range: None, - }, - &mut call, - )?; - let shaped = prepared.to_core(content, options.font_size)?; - let (constructs, attachments) = self.lower_constructs( - document, - &segment.content, - &prepared, - fonts, - options, - &mut call, - )?; - let breaks = - collect_breaks(document, &segment.content, content, &prepared, &constructs); - let tabs = collect_tab_stops(content, options)?; - let paragraph = jlreq_core::Paragraph::builder(shaped, options.line_extent) - .breaks(breaks) - .constructs(constructs) - .tab_stops(tabs) - .alignment(options.alignment.core()) - .writing_mode(options.writing_mode.core()) - .build()?; - - let core_limits = jlreq_core::CompositionLimits::default() - .with_max_clusters(options.limits.glyphs) - .with_max_break_candidates(options.limits.runs) - .with_max_constructs(options.limits.constructs) - .with_max_tab_stops(options.limits.constructs) - .with_max_search_transitions(options.limits.core_operations); - self.composer.set_limits(core_limits); - let core_layout = self - .composer - .compose(¶graph, &options.style) - .map_err(map_core_resource_error)?; - - for diagnostic in core_layout.diagnostics() { - call.diagnostics.push(Diagnostic { - code: diagnostic.code(), - // Keep every public core severity distinct at the facade boundary. - // The helper is independently tested even when a particular composer - // release does not currently emit every declared severity. - severity: diagnostic_severity(diagnostic.severity()), - range: diagnostic.range().map(|range| { - range.start.saturating_add(segment.content.start) - ..range.end.saturating_add(segment.content.start) - }), - message: "the core composer produced a recoverable layout diagnostic", - jlreq: Some(diagnostic.jlreq()), - }); - } - - let paragraph_lines = map_core_lines( - &core_layout, - &prepared, - &attachments, - segment.content.start, - block_offset, - options, - ); - let consumed = paragraph_lines.len().max(1); - lines.extend(paragraph_lines); - let line_count = i32::try_from(consumed).unwrap_or(i32::MAX); - let block_advance = line_count - .saturating_mul(options.font_size.saturating_add(options.line_gap).max(0)); - block_offset = advance_block(block_offset, block_advance, options.writing_mode); - } - - call.diagnostics.sort_by_key(|diagnostic| { - diagnostic - .range - .as_ref() - .map_or((usize::MAX, usize::MAX), |range| (range.start, range.end)) - }); - let retained_fonts = call - .used_fonts - .iter() - .filter_map(|id| fonts.get(*id).cloned()) - .collect(); - Ok(TextLayout { - source: document.text.clone(), - lines, - fonts: retained_fonts, - diagnostics: call.diagnostics, - writing_mode: options.writing_mode, - }) - } - - fn ensure_cache(&mut self, resource: &FontResource) -> Result<(), LayoutError> { - let needs_replacement = self.fonts.get(&resource.id()).is_none_or(|cached| { - cached.face_index != resource.face_index() - || !Arc::ptr_eq(&cached.bytes, &resource.bytes) - }); - if needs_replacement { - let font = harfrust::FontRef::from_index(resource.bytes(), resource.face_index()) - .map_err(|_| LayoutError::invalid_font(resource.face_index()))?; - let shaper_data = ShaperData::new(&font); - self.fonts.insert( - resource.id(), - FontCache { - bytes: resource.bytes.clone(), - face_index: resource.face_index(), - shaper_data, - }, - ); - } - Ok(()) - } - - fn shape_font(&mut self, request: ShapeRequest<'_>) -> Result, LayoutError> { - let ShapeRequest { - source, - range, - resource, - size, - direction, - language, - features, - variations, - } = request; - self.ensure_cache(resource)?; - let cached = self - .fonts - .get(&resource.id()) - .ok_or_else(|| LayoutError::invalid_font(resource.face_index()))?; - let font = harfrust::FontRef::from_index(&cached.bytes, cached.face_index) - .map_err(|_| LayoutError::invalid_font(cached.face_index))?; - let variations: Vec<_> = variations - .iter() - .map(|variation| Variation { - tag: harfrust::Tag::new(&variation.tag().bytes()), - value: variation.value(), - }) - .collect(); - let instance = ShaperInstance::from_variations(&font, variations); - let shaper = cached - .shaper_data - .shaper(&font) - .instance(Some(&instance)) - .build(); - let mut buffer = UnicodeBuffer::new(); - for (relative, character) in source[range.clone()].char_indices() { - let cluster = u32::try_from(range.start.saturating_add(relative)).map_err(|_| { - LayoutError::resource(Resource::InputBytes, u32::MAX as usize, source.len()) - })?; - buffer.add(character, cluster); - } - buffer.set_direction(direction); - if let Some(language) = Language::new(language) { - buffer.set_language(language); - } - buffer.guess_segment_properties(); - let features: Vec<_> = features - .iter() - .map(|feature| { - Feature::new( - harfrust::Tag::new(&feature.tag().bytes()), - feature.value(), - .., - ) - }) - .collect(); - let glyphs = shaper.shape( - buffer, - ShapeOptions::new().scale(Some(size)).features(&features), - ); - Ok(glyphs - .glyph_infos() - .iter() - .zip(glyphs.glyph_positions()) - .map(|(info, position)| RawGlyph { - font_id: resource.id(), - glyph_id: info.glyph_id, - cluster: info.cluster as usize, - x_advance: position.x_advance, - y_advance: position.y_advance, - x_offset: position.x_offset, - y_offset: position.y_offset, - }) - .collect()) - } - - fn prepare_text( - &mut self, - request: PrepareRequest<'_>, - call: &mut CallState, - ) -> Result { - let PrepareRequest { - source, - global_offset, - spans, - fonts, - options, - diagnostic_range, - } = request; - if source.is_empty() { - return Ok(PreparedText { - clusters: Vec::new(), - }); - } - let base_level = match options.base_direction { - BaseDirection::Auto => None, - BaseDirection::LeftToRight => Some(Level::ltr()), - BaseDirection::RightToLeft => Some(Level::rtl()), - }; - let bidi = ParagraphBidiInfo::new(source, base_level); - let boundaries: Vec<_> = GraphemeClusterSegmenter::new() - .segment_str(source) - .collect(); - let mut graphemes = Vec::with_capacity(boundaries.len().saturating_sub(1)); - for pair in boundaries.windows(2) { - let range = pair[0]..pair[1]; - let global = - range.start.saturating_add(global_offset)..range.end.saturating_add(global_offset); - let effective = effective_style(&global, spans, options)?; - let level = bidi - .levels - .get(range.start) - .copied() - .unwrap_or(bidi.paragraph_level); - let script = script_class(&source[range.clone()]); - let direction = shape_direction(options.writing_mode, level, script); - let is_tab = &source[range.clone()] == "\t"; - let (font_id, missing) = if is_tab { - (fonts.primary().ok_or(LayoutError::NoFonts)?, false) - } else { - self.select_font(source, range.clone(), fonts, &effective, direction)? - }; - if missing { - call.diagnostics.push(Diagnostic { - code: "font.missing-glyph", - severity: DiagnosticSeverity::Warning, - range: Some(diagnostic_range.clone().unwrap_or(global)), - message: "no fallback face covers the complete grapheme; primary .notdef was retained", - jlreq: None, - }); - } - graphemes.push(GraphemeItem { - range, - level, - script, - direction, - font_id, - effective, - is_tab, - }); - } - - let mut clusters = Vec::new(); - let mut index = 0; - while index < graphemes.len() { - if graphemes[index].is_tab { - let item = &graphemes[index]; - clusters.push(PreparedCluster { - range: item.range.clone(), - advance: 0, - size: item.effective.size, - frame: jlreq_core::Frame::Proportional, - role: None, - bidi_level: item.level.number(), - glyphs: Vec::new(), - }); - index = index.saturating_add(1); - continue; - } - let start = index; - index = index.saturating_add(1); - while index < graphemes.len() - && !graphemes[index].is_tab - && graphemes[index].same_run(&graphemes[start]) - { - index = index.saturating_add(1); - } - call.charge_run()?; - let first = &graphemes[start]; - let run_range = first.range.start..graphemes[index.saturating_sub(1)].range.end; - let resource = fonts - .get(first.font_id) - .ok_or_else(|| LayoutError::invalid_document("font.unknown-id", None))?; - let raw = self.shape_font(ShapeRequest { - source, - range: run_range.clone(), - resource, - size: first.effective.size, - direction: first.direction, - language: &first.effective.language, - features: &first.effective.features, - variations: &first.effective.variations, - })?; - call.used_fonts.insert(first.font_id); - call.charge_glyphs(raw.len())?; - clusters.extend(aggregate_run( - source, - run_range, - &raw, - first.effective.size, - first.effective.role, - first.level.number(), - first.direction, - )); - } - Ok(PreparedText { clusters }) - } - - fn select_font( - &mut self, - source: &str, - range: Range, - fonts: &FontLibrary, - style: &EffectiveStyle, - direction: Direction, - ) -> Result<(FontId, bool), LayoutError> { - for id in fonts.ordered_candidates(&style.families, style.font_style) { - let resource = fonts - .get(id) - .ok_or_else(|| LayoutError::invalid_document("font.unknown-id", None))?; - let glyphs = self.shape_font(ShapeRequest { - source, - range: range.clone(), - resource, - size: style.size, - direction, - language: &style.language, - features: &style.features, - variations: &style.variations, - })?; - if !glyphs.is_empty() && glyphs.iter().all(|glyph| glyph.glyph_id != 0) { - return Ok((id, false)); - } - } - Ok((fonts.primary().ok_or(LayoutError::NoFonts)?, true)) - } - - fn lower_constructs( - &mut self, - document: &Document, - paragraph_range: &Range, - prepared: &PreparedText, - fonts: &FontLibrary, - options: &LayoutOptions, - call: &mut CallState, - ) -> Result<(Vec, Vec), LayoutError> { - let mut constructs = Vec::new(); - let mut attachments = Vec::new(); - for (global_ordinal, construct) in document.constructs.iter().enumerate() { - let global_range = construct.range(); - if !ranges_overlap(&global_range, paragraph_range) { - continue; - } - if global_range.start < paragraph_range.start || global_range.end > paragraph_range.end - { - return Err(LayoutError::invalid_document( - "document.construct-crosses-paragraph", - Some(global_range), - )); - } - let local_range = global_range.start.saturating_sub(paragraph_range.start) - ..global_range.end.saturating_sub(paragraph_range.start); - let local_ordinal = constructs.len(); - match construct { - DocumentConstruct::Ruby { - kind, - annotation, - runs, - .. - } => { - let annotation_options = annotation_options(options); - let annotation_prepared = self.prepare_text( - PrepareRequest { - source: annotation, - global_offset: 0, - spans: &[], - fonts, - options: &annotation_options, - diagnostic_range: Some(global_range.clone()), - }, - call, - )?; - let shaped = - annotation_prepared.to_core(annotation, annotation_options.font_size)?; - let core_runs = ruby_runs( - *kind, - &local_range, - paragraph_range.start, - runs, - prepared, - &annotation_prepared, - annotation.len(), - )?; - let ruby = - jlreq_core::Ruby::new(kind.core(), local_range.clone(), shaped, core_runs)?; - constructs.push(jlreq_core::Construct::ruby(ruby)); - attachments.push(AttachmentShape { - local_ordinal, - global_ordinal, - base: global_range, - prepared: annotation_prepared, - }); - }, - DocumentConstruct::TateChuYoko(_) => { - constructs.push(jlreq_core::Construct::tate_chu_yoko(local_range)); - }, - DocumentConstruct::Emphasis { mark, .. } => { - constructs.push(jlreq_core::Construct::emphasis_dots(local_range, *mark)); - let mark_text = mark.to_string(); - let annotation_options = annotation_options(options); - let mark_prepared = self.prepare_text( - PrepareRequest { - source: &mark_text, - global_offset: 0, - spans: &[], - fonts, - options: &annotation_options, - diagnostic_range: Some(global_range.clone()), - }, - call, - )?; - attachments.push(AttachmentShape { - local_ordinal, - global_ordinal, - base: global_range, - prepared: mark_prepared, - }); - }, - DocumentConstruct::Warichu(_) => { - constructs.push(jlreq_core::Construct::warichu(local_range)); - }, - DocumentConstruct::Furawake { - columns, line_gap, .. - } => { - constructs.push(jlreq_core::Construct::furawake( - local_range, - *columns, - *line_gap, - )); - }, - DocumentConstruct::Jidori { cells, .. } => { - constructs.push(jlreq_core::Construct::jidori(local_range, *cells)); - }, - DocumentConstruct::ReferenceMark { mark, .. } => { - let annotation_options = annotation_options(options); - let annotation_prepared = self.prepare_text( - PrepareRequest { - source: mark, - global_offset: 0, - spans: &[], - fonts, - options: &annotation_options, - diagnostic_range: Some(global_range.clone()), - }, - call, - )?; - let shaped = annotation_prepared.to_core(mark, annotation_options.font_size)?; - constructs.push(jlreq_core::Construct::reference_mark(local_range, shaped)); - attachments.push(AttachmentShape { - local_ordinal, - global_ordinal, - base: global_range, - prepared: annotation_prepared, - }); - }, - DocumentConstruct::Script { annotation, .. } => { - let annotation_options = annotation_options(options); - let annotation_prepared = self.prepare_text( - PrepareRequest { - source: annotation, - global_offset: 0, - spans: &[], - fonts, - options: &annotation_options, - diagnostic_range: Some(global_range.clone()), - }, - call, - )?; - let shaped = - annotation_prepared.to_core(annotation, annotation_options.font_size)?; - constructs.push(jlreq_core::Construct::script(local_range, shaped)); - attachments.push(AttachmentShape { - local_ordinal, - global_ordinal, - base: global_range, - prepared: annotation_prepared, - }); - }, - DocumentConstruct::Formula(_) => { - constructs.push(jlreq_core::Construct::formula(local_range)); - }, - } - } - Ok((constructs, attachments)) - } -} - -#[derive(Debug)] -struct CallState { - runs: usize, - glyphs: usize, - max_runs: usize, - max_glyphs: usize, - used_fonts: BTreeSet, - diagnostics: Vec, -} - -impl CallState { - fn new(options: &LayoutOptions) -> Self { - Self { - runs: 0, - glyphs: 0, - max_runs: options.limits.runs, - max_glyphs: options.limits.glyphs, - used_fonts: BTreeSet::new(), - diagnostics: Vec::new(), - } - } - - fn charge_run(&mut self) -> Result<(), LayoutError> { - self.runs = self.runs.saturating_add(1); - check_limit(Resource::Runs, self.max_runs, self.runs) - } - - fn charge_glyphs(&mut self, amount: usize) -> Result<(), LayoutError> { - self.glyphs = self.glyphs.saturating_add(amount); - check_limit(Resource::Glyphs, self.max_glyphs, self.glyphs) - } -} - -#[derive(Debug, Clone, PartialEq)] -struct EffectiveStyle { - families: Vec, - font_style: FontStyle, - size: i32, - language: String, - features: Vec, - variations: Vec, - role: TextRole, -} - -#[derive(Debug)] -struct GraphemeItem { - range: Range, - level: Level, - script: ScriptClass, - direction: Direction, - font_id: FontId, - effective: EffectiveStyle, - is_tab: bool, -} - -impl GraphemeItem { - fn same_run(&self, other: &Self) -> bool { - self.font_id == other.font_id - && self.level.is_rtl() == other.level.is_rtl() - && self.script == other.script - && self.direction == other.direction - && self.effective == other.effective - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ScriptClass { - Japanese, - Latin, - Rtl, - Emoji, - Other, -} - -#[derive(Debug, Clone)] -struct RawGlyph { - font_id: FontId, - glyph_id: u32, - cluster: usize, - x_advance: i32, - y_advance: i32, - x_offset: i32, - y_offset: i32, -} - -impl RawGlyph { - fn inline_advance(&self, direction: Direction) -> i32 { - match direction { - Direction::TopToBottom | Direction::BottomToTop => self.y_advance.abs(), - _ => self.x_advance.abs(), - } - } -} - -#[derive(Debug, Clone)] -struct PreparedCluster { - range: Range, - advance: i32, - size: i32, - frame: jlreq_core::Frame, - role: Option, - bidi_level: u8, - glyphs: Vec, -} - -#[derive(Debug, Clone)] -struct PreparedText { - clusters: Vec, -} - -impl PreparedText { - fn to_core( - &self, - source: &str, - default_size: i32, - ) -> Result { - let size = jlreq_core::Size::square(default_size)?; - let mut clusters = Vec::with_capacity(self.clusters.len()); - for cluster in &self.clusters { - let local_size = jlreq_core::Size::square(cluster.size)?; - let mut core = jlreq_core::Cluster::new(cluster.range.clone(), cluster.advance) - .with_size(local_size) - .with_frame(cluster.frame); - if let Some(role) = cluster.role { - core = core.with_role(role); - } - clusters.push(core); - } - Ok(jlreq_core::ShapedText::new( - source, - size, - jlreq_core::Frame::FullEm, - clusters, - )?) - } - - fn is_boundary(&self, offset: usize, source_len: usize) -> bool { - offset == 0 - || offset == source_len - || self - .clusters - .iter() - .any(|cluster| cluster.range.start == offset) - } -} - -#[derive(Debug, Clone)] -struct AttachmentShape { - local_ordinal: usize, - global_ordinal: usize, - base: Range, - prepared: PreparedText, -} - -#[derive(Debug, Clone)] -struct ParagraphSegment { - content: Range, -} - -fn validate_call( - document: &Document, - fonts: &FontLibrary, - options: &LayoutOptions, -) -> Result<(), LayoutError> { - if fonts.is_empty() { - return Err(LayoutError::NoFonts); - } - check_limit( - Resource::InputBytes, - options.limits.input_bytes, - document.text.len(), - )?; - check_limit(Resource::Fonts, options.limits.fonts, fonts.len())?; - let font_bytes = fonts - .fonts() - .iter() - .fold(0_usize, |sum, font| sum.saturating_add(font.bytes().len())); - check_limit(Resource::FontBytes, options.limits.font_bytes, font_bytes)?; - check_limit( - Resource::Constructs, - options.limits.constructs, - document.constructs.len(), - ) -} - -fn check_limit(resource: Resource, limit: usize, observed: usize) -> Result<(), LayoutError> { - if observed > limit { - Err(LayoutError::resource(resource, limit, observed)) - } else { - Ok(()) - } -} - -fn map_core_resource_error(error: jlreq_core::ComposeError) -> LayoutError { - let Some(resource) = high_level_resource(error.resource()) else { - return LayoutError::CoreComposition(error); - }; - LayoutError::resource(resource, error.limit(), error.observed()) -} - -fn high_level_resource(resource: jlreq_core::CompositionResource) -> Option { - match resource { - jlreq_core::CompositionResource::Clusters => Some(Resource::Glyphs), - jlreq_core::CompositionResource::BreakCandidates => Some(Resource::Runs), - jlreq_core::CompositionResource::Constructs | jlreq_core::CompositionResource::TabStops => { - Some(Resource::Constructs) - }, - jlreq_core::CompositionResource::SearchTransitions => Some(Resource::CoreOperations), - _ => None, - } -} - -fn effective_style( - global: &Range, - spans: &[(Range, SpanStyle)], - options: &LayoutOptions, -) -> Result { - let mut selected = None; - for (range, style) in spans { - if ranges_overlap(range, global) { - if range.start > global.start || range.end < global.end { - return Err(LayoutError::invalid_document( - "document.span-splits-grapheme", - Some(global.clone()), - )); - } - selected = Some(style); - break; - } - } - let mut result = EffectiveStyle { - families: Vec::new(), - font_style: FontStyle::default(), - size: options.font_size, - language: options.language.clone(), - features: options.features.clone(), - variations: options.variations.clone(), - role: TextRole::Text, - }; - if let Some(style) = selected { - result.families.clone_from(&style.families); - result.font_style = style.font_style; - result.size = style.font_size.unwrap_or(options.font_size); - if let Some(language) = &style.language { - result.language.clone_from(language); - } - result.features.extend_from_slice(&style.features); - result.variations.extend_from_slice(&style.variations); - result.role = style.role; - } - Ok(result) -} - -fn aggregate_run( - source: &str, - range: Range, - glyphs: &[RawGlyph], - size: i32, - role: TextRole, - bidi_level: u8, - direction: Direction, -) -> Vec { - let mut starts: Vec<_> = glyphs - .iter() - .map(|glyph| glyph.cluster) - .filter(|start| range.contains(start)) - .collect(); - starts.push(range.start); - starts.sort_unstable(); - starts.dedup(); - let mut result = Vec::with_capacity(starts.len()); - for (ordinal, start) in starts.iter().copied().enumerate() { - let end = starts - .get(ordinal.saturating_add(1)) - .copied() - .unwrap_or(range.end); - let members: Vec<_> = glyphs - .iter() - .filter(|glyph| glyph.cluster == start) - .cloned() - .collect(); - let advance = members.iter().fold(0_i32, |sum, glyph| { - sum.saturating_add(glyph.inline_advance(direction)) - }); - let piece = &source[start..end]; - result.push(PreparedCluster { - range: start..end, - advance, - size, - frame: frame_for(piece), - role: classify_role(source, start..end, role), - bidi_level, - glyphs: members, - }); - } - result -} - -fn frame_for(piece: &str) -> jlreq_core::Frame { - if piece.chars().count() == 1 - && piece - .chars() - .next() - .is_some_and(|character| is_japanese(character) || is_emoji(character)) - { - jlreq_core::Frame::FullEm - } else { - jlreq_core::Frame::Proportional - } -} - -fn classify_role( - source: &str, - range: Range, - asserted: TextRole, -) -> Option { - if asserted != TextRole::Text { - return Some(asserted.core()); - } - let mut characters = source[range.clone()].chars(); - let character = characters.next()?; - if characters.next().is_some() { - return None; - } - let previous = source[..range.start].chars().next_back(); - let next = source[range.end..].chars().next(); - if matches!(character, '.' | '.' | '・') - && previous.is_some_and(char::is_numeric) - && next.is_some_and(char::is_numeric) - { - return Some(jlreq_core::ClusterRole::DecimalPoint); - } - if matches!(character, ',' | ',' | '、') - && previous.is_some_and(char::is_numeric) - && next.is_some_and(char::is_numeric) - { - return Some(jlreq_core::ClusterRole::DigitGroupSeparator); - } - if matches!(character, '!' | '?' | '!' | '?') { - return Some(if source[range.end..].trim().is_empty() { - jlreq_core::ClusterRole::SentenceTerminator - } else { - jlreq_core::ClusterRole::SentenceMedial - }); - } - None -} - -fn script_class(text: &str) -> ScriptClass { - for character in text.chars() { - let value = character as u32; - if is_japanese(character) { - return ScriptClass::Japanese; - } - if is_emoji(character) { - return ScriptClass::Emoji; - } - if matches!(value, 0x0590..=0x08ff | 0xfb1d..=0xfdff | 0xfe70..=0xfeff) { - return ScriptClass::Rtl; - } - if character.is_ascii_alphanumeric() || matches!(value, 0x00c0..=0x024f | 0x1e00..=0x1eff) { - return ScriptClass::Latin; - } - } - ScriptClass::Other -} - -fn is_japanese(character: char) -> bool { - matches!( - character as u32, - 0x2e80..=0x2fff - | 0x3000..=0x30ff - | 0x31f0..=0x31ff - | 0x3400..=0x4dbf - | 0x4e00..=0x9fff - | 0xf900..=0xfaff - | 0x20000..=0x323af - ) -} - -fn is_emoji(character: char) -> bool { - matches!(character as u32, 0x1f000..=0x1faff | 0x2600..=0x27bf) -} - -fn shape_direction(mode: WritingMode, level: Level, script: ScriptClass) -> Direction { - if mode == WritingMode::VerticalRl - && matches!(script, ScriptClass::Japanese | ScriptClass::Emoji) - { - Direction::TopToBottom - } else if level.is_rtl() { - Direction::RightToLeft - } else { - Direction::LeftToRight - } -} - -fn paragraph_segments(text: &str) -> Vec { - let mut result = Vec::new(); - let mut start = 0; - let mut indices = text.char_indices().peekable(); - while let Some((offset, character)) = indices.next() { - let mut separator_end = offset.saturating_add(character.len_utf8()); - let separator = match character { - '\r' => { - if indices.peek().is_some_and(|(_, next)| *next == '\n') - && let Some((next_offset, next)) = indices.next() - { - separator_end = next_offset.saturating_add(next.len_utf8()); - } - true - }, - '\n' | '\u{2028}' | '\u{2029}' => true, - _ => false, - }; - if separator { - result.push(ParagraphSegment { - content: start..offset, - }); - start = separator_end; - } - } - result.push(ParagraphSegment { - content: start..text.len(), - }); - result -} - -fn collect_breaks( - document: &Document, - paragraph_range: &Range, - source: &str, - prepared: &PreparedText, - constructs: &[jlreq_core::Construct], -) -> Vec { - let prohibited: BTreeSet<_> = document - .prohibited_breaks - .iter() - .filter(|offset| paragraph_range.contains(offset)) - .map(|offset| offset.saturating_sub(paragraph_range.start)) - .collect(); - let mandatory: BTreeSet<_> = document - .mandatory_breaks - .iter() - .filter(|offset| paragraph_range.contains(offset)) - .map(|offset| offset.saturating_sub(paragraph_range.start)) - .collect(); - let inside_construct = |offset: usize| { - constructs.iter().any(|construct| { - let range = construct.range(); - range.start < offset && offset < range.end - }) - }; - let mut breaks = BTreeMap::new(); - for offset in LineSegmenter::new_auto(LineBreakOptions::default()).segment_str(source) { - if automatic_break_allowed( - offset, - source.len(), - prepared.is_boundary(offset, source.len()), - prohibited.contains(&offset), - inside_construct(offset), - ) { - breaks.insert(offset, false); - } - } - for offset in mandatory { - if prepared.is_boundary(offset, source.len()) { - breaks.insert(offset, true); - } - } - breaks - .into_iter() - .map(|(offset, required)| { - if required { - jlreq_core::Break::mandatory(offset) - } else { - jlreq_core::Break::allowed(offset) - } - }) - .collect() -} - -fn automatic_break_allowed( - offset: usize, - source_len: usize, - is_cluster_boundary: bool, - is_prohibited: bool, - is_inside_construct: bool, -) -> bool { - offset > 0 - && offset < source_len - && is_cluster_boundary - && !is_prohibited - && !is_inside_construct -} - -fn collect_tab_stops( - source: &str, - options: &LayoutOptions, -) -> Result, LayoutError> { - if !source.contains('\t') { - return Ok(Vec::new()); - } - let interval = options - .font_size - .saturating_mul(i32::from(options.tab_width)); - let mut position = interval; - let mut result = Vec::new(); - while position < options.line_extent && result.len() < options.limits.constructs { - result.push(jlreq_core::TabStop::new( - position, - jlreq_core::TabAlignment::Start, - )?); - position = position.saturating_add(interval); - } - Ok(result) -} - -fn annotation_options(options: &LayoutOptions) -> LayoutOptions { - let mut result = options.clone(); - result.font_size = (options.font_size / 2).max(1); - result.alignment = Alignment::Start; - result -} - -fn ruby_runs( - kind: crate::RubyKind, - local_base: &Range, - paragraph_offset: usize, - declared: &[crate::RubyRun], - base: &PreparedText, - annotation: &PreparedText, - annotation_len: usize, -) -> Result, LayoutError> { - if !declared.is_empty() { - return Ok(declared - .iter() - .map(|run| { - let base = run.base(); - jlreq_core::RubyRun::new( - base.start.saturating_sub(paragraph_offset) - ..base.end.saturating_sub(paragraph_offset), - run.annotation(), - ) - }) - .collect()); - } - if kind != crate::RubyKind::Mono { - return Ok(vec![jlreq_core::RubyRun::new( - local_base.clone(), - 0..annotation_len, - )]); - } - let base_clusters: Vec<_> = base - .clusters - .iter() - .filter(|cluster| { - local_base.start <= cluster.range.start && cluster.range.end <= local_base.end - }) - .collect(); - if base_clusters.len() != annotation.clusters.len() || base_clusters.is_empty() { - return Err(LayoutError::invalid_document( - "document.mono-ruby-cluster-count", - Some( - local_base.start.saturating_add(paragraph_offset) - ..local_base.end.saturating_add(paragraph_offset), - ), - )); - } - Ok(base_clusters - .iter() - .zip(&annotation.clusters) - .map(|(base, annotation)| { - jlreq_core::RubyRun::new(base.range.clone(), annotation.range.clone()) - }) - .collect()) -} - -fn map_core_lines( - layout: &jlreq_core::Layout, - prepared: &PreparedText, - attachments: &[AttachmentShape], - global_offset: usize, - block_offset: i32, - options: &LayoutOptions, -) -> Vec { - let mut result = Vec::with_capacity(layout.lines().len()); - for (line_index, line) in layout.lines().iter().enumerate() { - let mut cells = Vec::new(); - let mut used = BTreeSet::new(); - for placement in line.clusters() { - let range = placement.range(); - let cluster_indices = placement_cluster_indices(placement.origin(), prepared, &range); - let cluster_indices: Vec<_> = cluster_indices - .into_iter() - .filter(|index| used.insert(*index)) - .collect(); - if cluster_indices.is_empty() { - continue; - } - let level = prepared.clusters[cluster_indices[0]].bidi_level; - cells.push(Cell { - clusters: cluster_indices, - block: placement.block(), - advance: placement.advance().max(0), - level, - transform: core_transform(placement.transform()), - }); - } - let levels: Vec<_> = cells - .iter() - .map(|cell| Level::new(cell.level).unwrap_or_else(|_| Level::ltr())) - .collect(); - let visual = BidiInfo::reorder_visual(&levels); - let mut cursor = line.inline_origin(); - let mut glyphs = Vec::new(); - for visual_index in visual { - let cell = &cells[visual_index]; - let mut cluster_cursor = 0_i32; - let indices = logical_cluster_order(cell.clusters.clone(), cell.level); - for cluster_index in indices { - let cluster = &prepared.clusters[cluster_index]; - let mut glyph_cursor = 0_i32; - for raw in &cluster.glyphs { - glyphs.push(place_raw_glyph( - raw, - cluster, - PlacementContext { - source_range: global_offset.saturating_add(cluster.range.start) - ..global_offset.saturating_add(cluster.range.end), - annotation: None, - inline: cursor - .saturating_add(cluster_cursor) - .saturating_add(glyph_cursor), - block: adjusted_block(cell.block, line_index, block_offset, options), - transform: cell.transform, - writing_mode: options.writing_mode, - }, - )); - glyph_cursor = glyph_cursor.saturating_add(raw.inline_advance( - direction_from_geometry(raw, options.writing_mode, cell.transform), - )); - } - cluster_cursor = cluster_cursor.saturating_add(cluster.advance); - } - cursor = cursor.saturating_add(cell.advance.max(cluster_cursor)); - } - append_attachments( - &mut glyphs, - line, - attachments, - line_index, - block_offset, - options, - ); - let physical_origin = match options.writing_mode { - WritingMode::HorizontalTb => Point::from_fixed( - line.inline_origin(), - adjusted_block(line.block_origin(), line_index, block_offset, options), - ), - WritingMode::VerticalRl => Point::from_fixed( - adjusted_block(line.block_origin(), line_index, block_offset, options), - line.inline_origin(), - ), - }; - result.push(TextLine { - range: line.range().start.saturating_add(global_offset) - ..line.range().end.saturating_add(global_offset), - origin: physical_origin, - inline_extent: line.inline_extent(), - block_extent: line.block_extent(), - writing_mode: options.writing_mode, - glyphs, - }); - } - result -} - -fn placement_cluster_indices( - origin: jlreq_core::PlacementOrigin, - prepared: &PreparedText, - range: &Range, -) -> Vec { - match origin { - jlreq_core::PlacementOrigin::Cluster(index) => vec![index], - jlreq_core::PlacementOrigin::Construct(_) => prepared - .clusters - .iter() - .enumerate() - .filter(|(_, cluster)| ranges_overlap(&cluster.range, range)) - .map(|(index, _)| index) - .collect(), - _ => Vec::new(), - } -} - -fn logical_cluster_order(mut indices: Vec, level: u8) -> Vec { - if level % 2 == 1 { - indices.reverse(); - } - indices -} - -#[derive(Debug)] -struct Cell { - clusters: Vec, - block: i32, - advance: i32, - level: u8, - transform: GlyphTransform, -} - -struct PlacementContext { - source_range: Range, - annotation: Option, - inline: i32, - block: i32, - transform: GlyphTransform, - writing_mode: WritingMode, -} - -fn place_raw_glyph( - raw: &RawGlyph, - cluster: &PreparedCluster, - placement: PlacementContext, -) -> GlyphPlacement { - let PlacementContext { - source_range, - annotation, - inline, - block, - transform, - writing_mode, - } = placement; - let horizontal = - writing_mode == WritingMode::HorizontalTb || transform == GlyphTransform::TateChuYoko; - let (x, y, advance_x, advance_y, offset_x, offset_y) = if horizontal { - ( - inline, - block.saturating_add(cluster.size), - raw.x_advance.abs(), - 0, - raw.x_offset, - raw.y_offset.saturating_neg(), - ) - } else { - ( - block, - inline, - 0, - raw.y_advance.abs().max(raw.x_advance.abs()), - raw.x_offset, - raw.y_offset.saturating_neg(), - ) - }; - GlyphPlacement { - font_id: raw.font_id, - glyph_id: raw.glyph_id, - source_range, - annotation, - x, - y, - advance_x, - advance_y, - offset_x, - offset_y, - font_size: cluster.size, - transform, - bidi_level: cluster.bidi_level, - writing_mode, - } -} - -fn append_attachments( - glyphs: &mut Vec, - line: &jlreq_core::Line, - shapes: &[AttachmentShape], - line_index: usize, - block_offset: i32, - options: &LayoutOptions, -) { - for attachment in line.attachments() { - let Some(shape) = shapes - .iter() - .find(|shape| shape.local_ordinal == attachment.construct()) - else { - continue; - }; - let requested = attachment.range(); - let clusters: Vec<_> = if requested.is_empty() { - shape.prepared.clusters.iter().collect() - } else { - shape - .prepared - .clusters - .iter() - .filter(|cluster| ranges_overlap(&cluster.range, &requested)) - .collect() - }; - let transform = core_transform(attachment.transform()); - let mut inline = attachment.inline(); - for cluster in clusters { - let mut glyph_cursor = 0_i32; - for raw in &cluster.glyphs { - glyphs.push(place_raw_glyph( - raw, - cluster, - PlacementContext { - source_range: shape.base.clone(), - annotation: Some(AnnotationSource::new( - shape.global_ordinal, - cluster.range.clone(), - )), - inline: inline.saturating_add(glyph_cursor), - block: adjusted_block( - attachment.block(), - line_index, - block_offset, - options, - ), - transform, - writing_mode: options.writing_mode, - }, - )); - glyph_cursor = glyph_cursor.saturating_add(raw.inline_advance( - direction_from_geometry(raw, options.writing_mode, transform), - )); - } - inline = inline.saturating_add(cluster.advance); - } - } -} - -fn adjusted_block( - core_block: i32, - line_index: usize, - block_offset: i32, - options: &LayoutOptions, -) -> i32 { - let gap = i32::try_from(line_index) - .unwrap_or(i32::MAX) - .saturating_mul(options.line_gap); - match options.writing_mode { - WritingMode::HorizontalTb => block_offset.saturating_add(core_block).saturating_add(gap), - WritingMode::VerticalRl => block_offset.saturating_add(core_block).saturating_sub(gap), - } -} - -fn core_transform(value: jlreq_core::CoordinateTransform) -> GlyphTransform { - match value { - jlreq_core::CoordinateTransform::RotateClockwise => GlyphTransform::RotateClockwise, - jlreq_core::CoordinateTransform::TateChuYoko => GlyphTransform::TateChuYoko, - _ => GlyphTransform::Identity, - } -} - -fn direction_from_geometry( - raw: &RawGlyph, - mode: WritingMode, - transform: GlyphTransform, -) -> Direction { - if mode == WritingMode::VerticalRl - && transform != GlyphTransform::TateChuYoko - && raw.y_advance != 0 - { - Direction::TopToBottom - } else { - Direction::LeftToRight - } -} - -fn advance_block(value: i32, amount: i32, mode: WritingMode) -> i32 { - match mode { - WritingMode::HorizontalTb => value.saturating_add(amount), - WritingMode::VerticalRl => value.saturating_sub(amount), - } -} - -fn ranges_overlap(left: &Range, right: &Range) -> bool { - left.start < right.end && right.start < left.end -} - -fn diagnostic_severity(value: jlreq_core::Severity) -> DiagnosticSeverity { - match value { - jlreq_core::Severity::Info => DiagnosticSeverity::Info, - jlreq_core::Severity::Error => DiagnosticSeverity::Error, - _ => DiagnosticSeverity::Warning, - } -} - +// Keep the engine's private data flow in one module while maintaining each phase separately. +include!("engine/layout.rs"); +include!("engine/preparation.rs"); +include!("engine/shaping.rs"); +include!("engine/lowering.rs"); +include!("engine/result.rs"); #[cfg(test)] mod tests { use super::*; @@ -1606,7 +95,7 @@ mod tests { script: ScriptClass::Latin, direction: Direction::LeftToRight, font_id, - effective: effective(1024), + effective: Arc::new(effective(1024)), is_tab: false, } } @@ -1679,6 +168,83 @@ mod tests { assert!(prepared.clusters[1].glyphs.is_empty()); } + #[test] + fn repeated_graphemes_reuse_exact_fallback_shaping_within_each_call() { + let mut fonts = FontLibrary::new(); + fonts + .register_face( + Arc::<[u8]>::from(font_test_data::NOTO_SANS_JP_CFF), + 0, + "Japanese", + FontStyle::default(), + ) + .unwrap(); + let emoji = fonts + .register_face( + Arc::<[u8]>::from(font_test_data::NOTO_COLOR_EMOJI_FLAGS), + 0, + "Emoji", + FontStyle::default(), + ) + .unwrap(); + let options = LayoutOptions::try_new(300.0, 16.0).unwrap(); + let mut engine = LayoutEngine::new(); + + for _ in 0..2 { + let mut call = CallState::new(&options); + let prepared = engine + .prepare_text( + PrepareRequest { + source: "🇪🇨🇪🇨🇪🇨🇪🇨", + global_offset: 0, + spans: &[], + fonts: &fonts, + options: &options, + diagnostic_range: None, + }, + &mut call, + ) + .unwrap(); + assert_eq!(prepared.clusters.len(), 4); + assert_eq!(call.font_candidates.len(), 1); + assert_eq!(call.font_selections.len(), 1); + assert_eq!(call.shape_calls, 3, "two fallbacks plus one shaping run"); + assert!( + call.shape_calls < 4_usize.saturating_mul(2).saturating_add(1), + "uncached fallback would shape both faces for every grapheme" + ); + assert!( + prepared + .clusters + .iter() + .flat_map(|cluster| &cluster.glyphs) + .all(|glyph| glyph.font_id == emoji) + ); + } + } + + #[test] + fn style_resolver_scans_forward_and_shares_one_effective_style_per_span() { + let options = LayoutOptions::try_new(300.0, 16.0).unwrap(); + let spans = vec![ + (1..3, SpanStyle::new().family("First")), + (4..6, SpanStyle::new().family("Second")), + ]; + let mut resolver = StyleResolver::new(&spans, &options, 0); + + let base = resolver.resolve(&(0..1)).unwrap(); + let first_left = resolver.resolve(&(1..2)).unwrap(); + let first_right = resolver.resolve(&(2..3)).unwrap(); + let between = resolver.resolve(&(3..4)).unwrap(); + let second = resolver.resolve(&(4..5)).unwrap(); + + assert!(Arc::ptr_eq(&first_left, &first_right)); + assert!(Arc::ptr_eq(&base, &between)); + assert_eq!(first_left.families, ["First"]); + assert_eq!(second.families, ["Second"]); + assert_eq!(resolver.next, 1); + } + #[test] fn call_charges_are_inclusive_and_accumulate() { let mut options = LayoutOptions::try_new(100.0, 16.0).unwrap(); @@ -1712,7 +278,7 @@ mod tests { changed.direction = Direction::RightToLeft; assert!(!base.same_run(&changed)); changed = grapheme(first); - changed.effective.size = 2048; + Arc::make_mut(&mut changed.effective).size = 2048; assert!(!base.same_run(&changed)); } @@ -1918,6 +484,9 @@ mod tests { .collect::>(), [0..0, 2..2] ); + let offsets = [0, 1, 3, 4, 6]; + assert_eq!(sorted_offsets_in_range(&offsets, &(1..4)), [1, 3]); + assert!(sorted_offsets_in_range(&offsets, &(2..3)).is_empty()); } #[test] @@ -2096,7 +665,7 @@ mod tests { }; assert_eq!( placement_cluster_indices(jlreq_core::PlacementOrigin::Cluster(2), &prepared, &(0..6)), - [2] + 2..3 ); assert_eq!( placement_cluster_indices( @@ -2104,10 +673,10 @@ mod tests { &prepared, &(1..5) ), - [0, 1, 2] + 0..3 ); - assert_eq!(logical_cluster_order(vec![0, 1, 2], 0), [0, 1, 2]); - assert_eq!(logical_cluster_order(vec![0, 1, 2], 1), [2, 1, 0]); + assert_eq!(logical_cluster_order(&[0, 1, 2], 0), [0, 1, 2]); + assert_eq!(logical_cluster_order(&[0, 1, 2], 1), [2, 1, 0]); let raw = raw(id); let horizontal = place_raw_glyph( diff --git a/crates/jlreq/src/engine/layout.rs b/crates/jlreq/src/engine/layout.rs new file mode 100644 index 0000000..0ec6844 --- /dev/null +++ b/crates/jlreq/src/engine/layout.rs @@ -0,0 +1,681 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +struct FontCache { + bytes: Arc<[u8]>, + face_index: u32, + shaper_data: Arc, +} + +struct ShapeRequest<'a> { + source: &'a str, + range: Range, + resource: &'a FontResource, + size: i32, + direction: Direction, + language: &'a str, + features: &'a [OpenTypeFeature], + variations: &'a [FontVariation], +} + +struct PrepareRequest<'a> { + source: &'a str, + global_offset: usize, + spans: &'a [(Range, SpanStyle)], + fonts: &'a FontLibrary, + options: &'a LayoutOptions, + diagnostic_range: Option>, +} + +struct ConstructParagraph<'a> { + range: &'a Range, + next_construct: &'a mut usize, +} + +/// Reusable high-level layout engine. +/// +/// Font parsing and shaping caches are retained between calls. Returned layouts never borrow +/// the engine, and an error leaves it immediately reusable. +pub struct LayoutEngine { + fonts: BTreeMap, + composer: jlreq_core::Composer, + unicode_buffer: Option, + shape_features: Vec, + shape_variations: Vec, +} + +impl fmt::Debug for LayoutEngine { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter + .debug_struct("LayoutEngine") + .field("cached_fonts", &self.fonts.len()) + .finish_non_exhaustive() + } +} + +impl Default for LayoutEngine { + fn default() -> Self { + Self::new() + } +} + +impl LayoutEngine { + /// Build an empty reusable engine. + #[must_use] + pub fn new() -> Self { + Self { + fonts: BTreeMap::new(), + composer: jlreq_core::Composer::new(), + unicode_buffer: Some(UnicodeBuffer::new()), + shape_features: Vec::new(), + shape_variations: Vec::new(), + } + } + + /// Shape, compose, reorder, and physically place plain UTF-8 text. + pub fn layout( + &mut self, + text: &str, + fonts: &FontLibrary, + options: LayoutOptions, + ) -> Result { + let document = DocumentBuilder::new(text).build()?; + self.layout_document(&document, fonts, options) + } + + /// Shape, compose, reorder, and physically place a typed document. + pub fn layout_document( + &mut self, + document: &Document, + fonts: &FontLibrary, + options: LayoutOptions, + ) -> Result { + // The public API intentionally takes an owned option set so callers can configure and + // submit it in one expression. Moving it through this single-element container makes + // that ownership boundary explicit while the pipeline borrows the stable value. + let options = [options]; + let options = &options[0]; + validate_call(document, fonts, options)?; + if document.text.is_empty() { + return Ok(TextLayout { + source: String::new(), + lines: Vec::new(), + fonts: Vec::new(), + diagnostics: Vec::new(), + writing_mode: options.writing_mode, + }); + } + + let segments = paragraph_segments(&document.text); + check_limit( + Resource::Paragraphs, + options.limits.paragraphs, + segments.len(), + )?; + let mut call = CallState::new(options); + let mut lines = Vec::new(); + let mut block_offset = 0_i32; + let mut next_construct = 0_usize; + + for segment in segments { + let content = &document.text[segment.content.clone()]; + if content.is_empty() { + let origin = match options.writing_mode { + WritingMode::HorizontalTb => Point::from_fixed(0, block_offset), + WritingMode::VerticalRl => Point::from_fixed(block_offset, 0), + }; + lines.push(TextLine { + range: segment.content.clone(), + origin, + inline_extent: 0, + block_extent: options.font_size, + writing_mode: options.writing_mode, + glyphs: Vec::new(), + hit_bounds: None, + }); + block_offset = advance_block( + block_offset, + options.font_size.saturating_add(options.line_gap), + options.writing_mode, + ); + continue; + } + + let prepared = self.prepare_text( + PrepareRequest { + source: content, + global_offset: segment.content.start, + spans: &document.spans, + fonts, + options, + diagnostic_range: None, + }, + &mut call, + )?; + let shaped = prepared.to_core(content, options.font_size)?; + let mut construct_paragraph = ConstructParagraph { + range: &segment.content, + next_construct: &mut next_construct, + }; + let (constructs, attachments) = self.lower_constructs( + document, + &mut construct_paragraph, + &prepared, + fonts, + options, + &mut call, + )?; + let breaks = + collect_breaks(document, &segment.content, content, &prepared, &constructs); + let tabs = collect_tab_stops(content, options)?; + let paragraph = jlreq_core::Paragraph::builder(shaped, options.line_extent) + .breaks(breaks) + .constructs(constructs) + .tab_stops(tabs) + .alignment(options.alignment.core()) + .writing_mode(options.writing_mode.core()) + .build()?; + + let core_limits = jlreq_core::CompositionLimits::default() + .with_max_clusters(options.limits.glyphs) + .with_max_break_candidates(options.limits.runs) + .with_max_constructs(options.limits.constructs) + .with_max_tab_stops(options.limits.constructs) + .with_max_search_transitions(options.limits.core_operations); + self.composer.set_limits(core_limits); + let core_layout = self + .composer + .compose(¶graph, &options.style) + .map_err(map_core_resource_error)?; + + for diagnostic in core_layout.diagnostics() { + call.diagnostics.push(Diagnostic { + code: diagnostic.code(), + // Keep every public core severity distinct at the facade boundary. + // The helper is independently tested even when a particular composer + // release does not currently emit every declared severity. + severity: diagnostic_severity(diagnostic.severity()), + range: diagnostic.range().map(|range| { + range.start.saturating_add(segment.content.start) + ..range.end.saturating_add(segment.content.start) + }), + message: "the core composer produced a recoverable layout diagnostic", + jlreq: Some(diagnostic.jlreq()), + }); + } + + let paragraph_lines = map_core_lines( + &core_layout, + &prepared, + &attachments, + segment.content.start, + block_offset, + options, + ); + let consumed = paragraph_lines.len().max(1); + lines.extend(paragraph_lines); + let line_count = i32::try_from(consumed).unwrap_or(i32::MAX); + let block_advance = line_count + .saturating_mul(options.font_size.saturating_add(options.line_gap).max(0)); + block_offset = advance_block(block_offset, block_advance, options.writing_mode); + } + + call.diagnostics.sort_by_key(|diagnostic| { + diagnostic + .range + .as_ref() + .map_or((usize::MAX, usize::MAX), |range| (range.start, range.end)) + }); + let retained_fonts = call + .used_fonts + .iter() + .filter_map(|id| fonts.get(*id).cloned()) + .collect(); + Ok(TextLayout { + source: document.text.clone(), + lines, + fonts: retained_fonts, + diagnostics: call.diagnostics, + writing_mode: options.writing_mode, + }) + } + + fn ensure_cache(&mut self, resource: &FontResource) -> Result<(), LayoutError> { + let needs_replacement = self.fonts.get(&resource.id()).is_none_or(|cached| { + cached.face_index != resource.face_index() + || !Arc::ptr_eq(&cached.bytes, &resource.bytes) + }); + if needs_replacement { + let _ = harfrust::FontRef::from_index(resource.bytes(), resource.face_index()) + .map_err(|_| LayoutError::invalid_font(resource.face_index()))?; + self.fonts.insert( + resource.id(), + FontCache { + bytes: resource.bytes.clone(), + face_index: resource.face_index(), + shaper_data: Arc::clone(&resource.shaper_data), + }, + ); + } + Ok(()) + } + + fn shape_font(&mut self, request: ShapeRequest<'_>) -> Result, LayoutError> { + let ShapeRequest { + source, + range, + resource, + size, + direction, + language, + features, + variations, + } = request; + self.ensure_cache(resource)?; + self.shape_variations.clear(); + self.shape_variations + .extend(variations.iter().map(|variation| Variation { + tag: harfrust::Tag::new(&variation.tag().bytes()), + value: variation.value(), + })); + self.shape_features.clear(); + self.shape_features.extend(features.iter().map(|feature| { + Feature::new( + harfrust::Tag::new(&feature.tag().bytes()), + feature.value(), + .., + ) + })); + let cached = self + .fonts + .get(&resource.id()) + .ok_or_else(|| LayoutError::invalid_font(resource.face_index()))?; + let font = harfrust::FontRef::from_index(&cached.bytes, cached.face_index) + .map_err(|_| LayoutError::invalid_font(cached.face_index))?; + let instance = ShaperInstance::from_variations(&font, &self.shape_variations); + let shaper = cached + .shaper_data + .shaper(&font) + .instance(Some(&instance)) + .build(); + let mut buffer = self.unicode_buffer.take().unwrap_or_default(); + buffer.clear(); + let _ = buffer.reserve(source[range.clone()].chars().count()); + for (relative, character) in source[range.clone()].char_indices() { + let cluster = u32::try_from(range.start.saturating_add(relative)).map_err(|_| { + LayoutError::resource(Resource::InputBytes, u32::MAX as usize, source.len()) + })?; + buffer.add(character, cluster); + } + buffer.set_direction(direction); + if let Some(language) = Language::new(language) { + buffer.set_language(language); + } + buffer.guess_segment_properties(); + let glyphs = shaper.shape( + buffer, + ShapeOptions::new() + .scale(Some(size)) + .features(&self.shape_features), + ); + let raw = glyphs + .glyph_infos() + .iter() + .zip(glyphs.glyph_positions()) + .map(|(info, position)| RawGlyph { + font_id: resource.id(), + glyph_id: info.glyph_id, + cluster: info.cluster as usize, + x_advance: position.x_advance, + y_advance: position.y_advance, + x_offset: position.x_offset, + y_offset: position.y_offset, + }) + .collect(); + self.unicode_buffer = Some(glyphs.clear()); + Ok(raw) + } + + fn prepare_text( + &mut self, + request: PrepareRequest<'_>, + call: &mut CallState, + ) -> Result { + let PrepareRequest { + source, + global_offset, + spans, + fonts, + options, + diagnostic_range, + } = request; + if source.is_empty() { + return Ok(PreparedText { + clusters: Vec::new(), + }); + } + let base_level = match options.base_direction { + BaseDirection::Auto => None, + BaseDirection::LeftToRight => Some(Level::ltr()), + BaseDirection::RightToLeft => Some(Level::rtl()), + }; + let bidi = ParagraphBidiInfo::new(source, base_level); + let boundaries: Vec<_> = GraphemeClusterSegmenter::new() + .segment_str(source) + .collect(); + let mut graphemes = Vec::with_capacity(boundaries.len().saturating_sub(1)); + let mut styles = StyleResolver::new(spans, options, global_offset); + for pair in boundaries.windows(2) { + let range = pair[0]..pair[1]; + let global = + range.start.saturating_add(global_offset)..range.end.saturating_add(global_offset); + let effective = styles.resolve(&global)?; + let level = bidi + .levels + .get(range.start) + .copied() + .unwrap_or(bidi.paragraph_level); + let script = script_class(&source[range.clone()]); + let direction = shape_direction(options.writing_mode, level, script); + let is_tab = &source[range.clone()] == "\t"; + let (font_id, missing) = if is_tab { + (fonts.primary().ok_or(LayoutError::NoFonts)?, false) + } else { + self.select_font(source, range.clone(), fonts, &effective, direction, call)? + }; + if missing { + call.diagnostics.push(Diagnostic { + code: "font.missing-glyph", + severity: DiagnosticSeverity::Warning, + range: Some(diagnostic_range.clone().unwrap_or(global)), + message: "no fallback face covers the complete grapheme; primary .notdef was retained", + jlreq: None, + }); + } + graphemes.push(GraphemeItem { + range, + level, + script, + direction, + font_id, + effective, + is_tab, + }); + } + + let mut clusters = Vec::new(); + let mut index = 0; + while index < graphemes.len() { + if graphemes[index].is_tab { + let item = &graphemes[index]; + clusters.push(PreparedCluster { + range: item.range.clone(), + advance: 0, + size: item.effective.size, + frame: jlreq_core::Frame::Proportional, + role: None, + bidi_level: item.level.number(), + glyphs: Vec::new(), + }); + index = index.saturating_add(1); + continue; + } + let start = index; + index = index.saturating_add(1); + while index < graphemes.len() + && !graphemes[index].is_tab + && graphemes[index].same_run(&graphemes[start]) + { + index = index.saturating_add(1); + } + call.charge_run()?; + let first = &graphemes[start]; + let run_range = first.range.start..graphemes[index.saturating_sub(1)].range.end; + let resource = fonts + .get(first.font_id) + .ok_or_else(|| LayoutError::invalid_document("font.unknown-id", None))?; + #[cfg(test)] + call.charge_shape(); + let raw = self.shape_font(ShapeRequest { + source, + range: run_range.clone(), + resource, + size: first.effective.size, + direction: first.direction, + language: &first.effective.language, + features: &first.effective.features, + variations: &first.effective.variations, + })?; + call.used_fonts.insert(first.font_id); + call.charge_glyphs(raw.len())?; + clusters.extend(aggregate_run( + source, + run_range, + raw, + first.effective.size, + first.effective.role, + first.level.number(), + first.direction, + )); + } + Ok(PreparedText { clusters }) + } + + fn select_font( + &mut self, + source: &str, + range: Range, + fonts: &FontLibrary, + style: &EffectiveStyle, + direction: Direction, + call: &mut CallState, + ) -> Result<(FontId, bool), LayoutError> { + let candidate_key = FontCandidateKey::new(style); + let selection_key = FontSelectionKey::new( + &source[range.clone()], + style, + direction, + candidate_key.clone(), + ); + if let Some(selection) = call.font_selections.get(&selection_key) { + return Ok(*selection); + } + let candidates = call + .font_candidates + .entry(candidate_key) + .or_insert_with(|| { + Arc::from( + fonts + .ordered_candidates(&style.families, style.font_style) + .into_boxed_slice(), + ) + }) + .clone(); + for id in candidates.iter().copied() { + let resource = fonts + .get(id) + .ok_or_else(|| LayoutError::invalid_document("font.unknown-id", None))?; + #[cfg(test)] + call.charge_shape(); + let glyphs = self.shape_font(ShapeRequest { + source, + range: range.clone(), + resource, + size: style.size, + direction, + language: &style.language, + features: &style.features, + variations: &style.variations, + })?; + if !glyphs.is_empty() && glyphs.iter().all(|glyph| glyph.glyph_id != 0) { + let selection = (id, false); + call.font_selections.insert(selection_key, selection); + return Ok(selection); + } + } + let selection = (fonts.primary().ok_or(LayoutError::NoFonts)?, true); + call.font_selections.insert(selection_key, selection); + Ok(selection) + } + + fn lower_constructs( + &mut self, + document: &Document, + paragraph: &mut ConstructParagraph<'_>, + prepared: &PreparedText, + fonts: &FontLibrary, + options: &LayoutOptions, + call: &mut CallState, + ) -> Result<(Vec, Vec>), LayoutError> { + let mut constructs = Vec::new(); + let mut attachments = Vec::new(); + while let Some(construct) = document.constructs.get(*paragraph.next_construct) { + let global_ordinal = *paragraph.next_construct; + let global_range = construct.range(); + if global_range.start >= paragraph.range.end { + break; + } + *paragraph.next_construct = paragraph.next_construct.saturating_add(1); + if !ranges_overlap(&global_range, paragraph.range) { + continue; + } + if global_range.start < paragraph.range.start || global_range.end > paragraph.range.end + { + return Err(LayoutError::invalid_document( + "document.construct-crosses-paragraph", + Some(global_range), + )); + } + let local_range = global_range.start.saturating_sub(paragraph.range.start) + ..global_range.end.saturating_sub(paragraph.range.start); + let local_ordinal = constructs.len(); + attachments.push(None); + match construct { + DocumentConstruct::Ruby { + kind, + annotation, + runs, + .. + } => { + let annotation_options = annotation_options(options); + let annotation_prepared = self.prepare_text( + PrepareRequest { + source: annotation, + global_offset: 0, + spans: &[], + fonts, + options: &annotation_options, + diagnostic_range: Some(global_range.clone()), + }, + call, + )?; + let shaped = + annotation_prepared.to_core(annotation, annotation_options.font_size)?; + let core_runs = ruby_runs( + *kind, + &local_range, + paragraph.range.start, + runs, + prepared, + &annotation_prepared, + annotation.len(), + )?; + let ruby = + jlreq_core::Ruby::new(kind.core(), local_range.clone(), shaped, core_runs)?; + constructs.push(jlreq_core::Construct::ruby(ruby)); + attachments[local_ordinal] = Some(AttachmentShape { + global_ordinal, + base: global_range, + prepared: annotation_prepared, + }); + }, + DocumentConstruct::TateChuYoko(_) => { + constructs.push(jlreq_core::Construct::tate_chu_yoko(local_range)); + }, + DocumentConstruct::Emphasis { mark, .. } => { + constructs.push(jlreq_core::Construct::emphasis_dots(local_range, *mark)); + let mark_text = mark.to_string(); + let annotation_options = annotation_options(options); + let mark_prepared = self.prepare_text( + PrepareRequest { + source: &mark_text, + global_offset: 0, + spans: &[], + fonts, + options: &annotation_options, + diagnostic_range: Some(global_range.clone()), + }, + call, + )?; + attachments[local_ordinal] = Some(AttachmentShape { + global_ordinal, + base: global_range, + prepared: mark_prepared, + }); + }, + DocumentConstruct::Warichu(_) => { + constructs.push(jlreq_core::Construct::warichu(local_range)); + }, + DocumentConstruct::Furawake { + columns, line_gap, .. + } => { + constructs.push(jlreq_core::Construct::furawake( + local_range, + *columns, + *line_gap, + )); + }, + DocumentConstruct::Jidori { cells, .. } => { + constructs.push(jlreq_core::Construct::jidori(local_range, *cells)); + }, + DocumentConstruct::ReferenceMark { mark, .. } => { + let annotation_options = annotation_options(options); + let annotation_prepared = self.prepare_text( + PrepareRequest { + source: mark, + global_offset: 0, + spans: &[], + fonts, + options: &annotation_options, + diagnostic_range: Some(global_range.clone()), + }, + call, + )?; + let shaped = annotation_prepared.to_core(mark, annotation_options.font_size)?; + constructs.push(jlreq_core::Construct::reference_mark(local_range, shaped)); + attachments[local_ordinal] = Some(AttachmentShape { + global_ordinal, + base: global_range, + prepared: annotation_prepared, + }); + }, + DocumentConstruct::Script { annotation, .. } => { + let annotation_options = annotation_options(options); + let annotation_prepared = self.prepare_text( + PrepareRequest { + source: annotation, + global_offset: 0, + spans: &[], + fonts, + options: &annotation_options, + diagnostic_range: Some(global_range.clone()), + }, + call, + )?; + let shaped = + annotation_prepared.to_core(annotation, annotation_options.font_size)?; + constructs.push(jlreq_core::Construct::script(local_range, shaped)); + attachments[local_ordinal] = Some(AttachmentShape { + global_ordinal, + base: global_range, + prepared: annotation_prepared, + }); + }, + DocumentConstruct::Formula(_) => { + constructs.push(jlreq_core::Construct::formula(local_range)); + }, + } + } + Ok((constructs, attachments)) + } +} diff --git a/crates/jlreq/src/engine/lowering.rs b/crates/jlreq/src/engine/lowering.rs new file mode 100644 index 0000000..faea0df --- /dev/null +++ b/crates/jlreq/src/engine/lowering.rs @@ -0,0 +1,187 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +fn paragraph_segments(text: &str) -> Vec { + let mut result = Vec::new(); + let mut start = 0; + let mut indices = text.char_indices().peekable(); + while let Some((offset, character)) = indices.next() { + let mut separator_end = offset.saturating_add(character.len_utf8()); + let separator = match character { + '\r' => { + if indices.peek().is_some_and(|(_, next)| *next == '\n') + && let Some((next_offset, next)) = indices.next() + { + separator_end = next_offset.saturating_add(next.len_utf8()); + } + true + }, + '\n' | '\u{2028}' | '\u{2029}' => true, + _ => false, + }; + if separator { + result.push(ParagraphSegment { + content: start..offset, + }); + start = separator_end; + } + } + result.push(ParagraphSegment { + content: start..text.len(), + }); + result +} + +fn collect_breaks( + document: &Document, + paragraph_range: &Range, + source: &str, + prepared: &PreparedText, + constructs: &[jlreq_core::Construct], +) -> Vec { + let prohibited = sorted_offsets_in_range(&document.prohibited_breaks, paragraph_range); + let mandatory = sorted_offsets_in_range(&document.mandatory_breaks, paragraph_range); + let mut breaks = BTreeMap::new(); + let mut construct_index = 0_usize; + let mut maximum_construct_end = 0_usize; + for offset in LineSegmenter::new_auto(LineBreakOptions::default()).segment_str(source) { + while let Some(construct) = constructs.get(construct_index) { + let range = construct.range(); + if range.start >= offset { + break; + } + maximum_construct_end = maximum_construct_end.max(range.end); + construct_index = construct_index.saturating_add(1); + } + if automatic_break_allowed( + offset, + source.len(), + prepared.is_boundary(offset, source.len()), + prohibited + .binary_search(&offset.saturating_add(paragraph_range.start)) + .is_ok(), + offset < maximum_construct_end, + ) { + breaks.insert(offset, false); + } + } + for offset in mandatory.iter().copied() { + let offset = offset.saturating_sub(paragraph_range.start); + if prepared.is_boundary(offset, source.len()) { + breaks.insert(offset, true); + } + } + breaks + .into_iter() + .map(|(offset, required)| { + if required { + jlreq_core::Break::mandatory(offset) + } else { + jlreq_core::Break::allowed(offset) + } + }) + .collect() +} + +fn sorted_offsets_in_range<'a>(offsets: &'a [usize], range: &Range) -> &'a [usize] { + let start = offsets.partition_point(|offset| *offset < range.start); + let end = offsets.partition_point(|offset| *offset < range.end); + &offsets[start..end] +} + +fn automatic_break_allowed( + offset: usize, + source_len: usize, + is_cluster_boundary: bool, + is_prohibited: bool, + is_inside_construct: bool, +) -> bool { + offset > 0 + && offset < source_len + && is_cluster_boundary + && !is_prohibited + && !is_inside_construct +} + +fn collect_tab_stops( + source: &str, + options: &LayoutOptions, +) -> Result, LayoutError> { + if !source.contains('\t') { + return Ok(Vec::new()); + } + let interval = options + .font_size + .saturating_mul(i32::from(options.tab_width)); + let mut position = interval; + let mut result = Vec::new(); + while position < options.line_extent && result.len() < options.limits.constructs { + result.push(jlreq_core::TabStop::new( + position, + jlreq_core::TabAlignment::Start, + )?); + position = position.saturating_add(interval); + } + Ok(result) +} + +fn annotation_options(options: &LayoutOptions) -> LayoutOptions { + let mut result = options.clone(); + result.font_size = (options.font_size / 2).max(1); + result.alignment = Alignment::Start; + result +} + +fn ruby_runs( + kind: crate::RubyKind, + local_base: &Range, + paragraph_offset: usize, + declared: &[crate::RubyRun], + base: &PreparedText, + annotation: &PreparedText, + annotation_len: usize, +) -> Result, LayoutError> { + if !declared.is_empty() { + return Ok(declared + .iter() + .map(|run| { + let base = run.base(); + jlreq_core::RubyRun::new( + base.start.saturating_sub(paragraph_offset) + ..base.end.saturating_sub(paragraph_offset), + run.annotation(), + ) + }) + .collect()); + } + if kind != crate::RubyKind::Mono { + return Ok(vec![jlreq_core::RubyRun::new( + local_base.clone(), + 0..annotation_len, + )]); + } + let base_clusters: Vec<_> = base + .clusters + .iter() + .filter(|cluster| { + local_base.start <= cluster.range.start && cluster.range.end <= local_base.end + }) + .collect(); + if base_clusters.len() != annotation.clusters.len() || base_clusters.is_empty() { + return Err(LayoutError::invalid_document( + "document.mono-ruby-cluster-count", + Some( + local_base.start.saturating_add(paragraph_offset) + ..local_base.end.saturating_add(paragraph_offset), + ), + )); + } + Ok(base_clusters + .iter() + .zip(&annotation.clusters) + .map(|(base, annotation)| { + jlreq_core::RubyRun::new(base.range.clone(), annotation.range.clone()) + }) + .collect()) +} + diff --git a/crates/jlreq/src/engine/preparation.rs b/crates/jlreq/src/engine/preparation.rs new file mode 100644 index 0000000..5bdf9c6 --- /dev/null +++ b/crates/jlreq/src/engine/preparation.rs @@ -0,0 +1,408 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#[derive(Debug)] +struct CallState { + runs: usize, + glyphs: usize, + max_runs: usize, + max_glyphs: usize, + used_fonts: BTreeSet, + diagnostics: Vec, + font_candidates: BTreeMap>, + font_selections: BTreeMap, + #[cfg(test)] + shape_calls: usize, +} + +impl CallState { + fn new(options: &LayoutOptions) -> Self { + Self { + runs: 0, + glyphs: 0, + max_runs: options.limits.runs, + max_glyphs: options.limits.glyphs, + used_fonts: BTreeSet::new(), + diagnostics: Vec::new(), + font_candidates: BTreeMap::new(), + font_selections: BTreeMap::new(), + #[cfg(test)] + shape_calls: 0, + } + } + + fn charge_run(&mut self) -> Result<(), LayoutError> { + self.runs = self.runs.saturating_add(1); + check_limit(Resource::Runs, self.max_runs, self.runs) + } + + fn charge_glyphs(&mut self, amount: usize) -> Result<(), LayoutError> { + self.glyphs = self.glyphs.saturating_add(amount); + check_limit(Resource::Glyphs, self.max_glyphs, self.glyphs) + } + + #[cfg(test)] + fn charge_shape(&mut self) { + self.shape_calls = self.shape_calls.saturating_add(1); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct FontCandidateKey { + families: Vec, + weight: u16, + width: u16, + slant: u8, +} + +impl FontCandidateKey { + fn new(style: &EffectiveStyle) -> Self { + let slant = match style.font_style.slant() { + FontSlant::Normal => 0, + FontSlant::Italic => 1, + FontSlant::Oblique => 2, + }; + Self { + families: style.families.clone(), + weight: style.font_style.weight(), + width: style.font_style.width(), + slant, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +struct FontSelectionKey { + text: String, + candidates: FontCandidateKey, + size: i32, + direction: u8, + language: String, + features: Vec<([u8; 4], u32)>, + variations: Vec<([u8; 4], u32)>, +} + +impl FontSelectionKey { + fn new( + text: &str, + style: &EffectiveStyle, + direction: Direction, + candidates: FontCandidateKey, + ) -> Self { + let direction = match direction { + Direction::Invalid => 0, + Direction::LeftToRight => 1, + Direction::RightToLeft => 2, + Direction::TopToBottom => 3, + Direction::BottomToTop => 4, + }; + Self { + text: text.to_owned(), + candidates, + size: style.size, + direction, + language: style.language.clone(), + features: style + .features + .iter() + .map(|feature| (feature.tag().bytes(), feature.value())) + .collect(), + variations: style + .variations + .iter() + .map(|variation| (variation.tag().bytes(), variation.value().to_bits())) + .collect(), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +struct EffectiveStyle { + families: Vec, + font_style: FontStyle, + size: i32, + language: String, + features: Vec, + variations: Vec, + role: TextRole, +} + +struct StyleResolver<'a> { + spans: &'a [(Range, SpanStyle)], + next: usize, + base: Arc, + selected: Option<(usize, Arc)>, +} + +impl<'a> StyleResolver<'a> { + fn new( + spans: &'a [(Range, SpanStyle)], + options: &LayoutOptions, + global_offset: usize, + ) -> Self { + Self { + spans, + next: spans.partition_point(|(range, _)| range.end <= global_offset), + base: Arc::new(base_effective_style(options)), + selected: None, + } + } + + fn resolve(&mut self, global: &Range) -> Result, LayoutError> { + while self + .spans + .get(self.next) + .is_some_and(|(range, _)| range.end <= global.start) + { + self.next = self.next.saturating_add(1); + self.selected = None; + } + let Some((range, style)) = self.spans.get(self.next) else { + return Ok(Arc::clone(&self.base)); + }; + if !ranges_overlap(range, global) { + return Ok(Arc::clone(&self.base)); + } + if range.start > global.start || range.end < global.end { + return Err(LayoutError::invalid_document( + "document.span-splits-grapheme", + Some(global.clone()), + )); + } + if let Some((index, selected)) = &self.selected + && *index == self.next + { + return Ok(Arc::clone(selected)); + } + let selected = Arc::new(span_effective_style(&self.base, style)); + self.selected = Some((self.next, Arc::clone(&selected))); + Ok(selected) + } +} + +#[derive(Debug)] +struct GraphemeItem { + range: Range, + level: Level, + script: ScriptClass, + direction: Direction, + font_id: FontId, + effective: Arc, + is_tab: bool, +} + +impl GraphemeItem { + fn same_run(&self, other: &Self) -> bool { + self.font_id == other.font_id + && self.level.is_rtl() == other.level.is_rtl() + && self.script == other.script + && self.direction == other.direction + && (Arc::ptr_eq(&self.effective, &other.effective) || self.effective == other.effective) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ScriptClass { + Japanese, + Latin, + Rtl, + Emoji, + Other, +} + +#[derive(Debug, Clone)] +struct RawGlyph { + font_id: FontId, + glyph_id: u32, + cluster: usize, + x_advance: i32, + y_advance: i32, + x_offset: i32, + y_offset: i32, +} + +impl RawGlyph { + fn inline_advance(&self, direction: Direction) -> i32 { + match direction { + Direction::TopToBottom | Direction::BottomToTop => self.y_advance.abs(), + _ => self.x_advance.abs(), + } + } +} + +#[derive(Debug, Clone)] +struct PreparedCluster { + range: Range, + advance: i32, + size: i32, + frame: jlreq_core::Frame, + role: Option, + bidi_level: u8, + glyphs: Vec, +} + +#[derive(Debug, Clone)] +struct PreparedText { + clusters: Vec, +} + +impl PreparedText { + fn to_core( + &self, + source: &str, + default_size: i32, + ) -> Result { + let size = jlreq_core::Size::square(default_size)?; + let mut clusters = Vec::with_capacity(self.clusters.len()); + for cluster in &self.clusters { + let local_size = jlreq_core::Size::square(cluster.size)?; + let mut core = jlreq_core::Cluster::new(cluster.range.clone(), cluster.advance) + .with_size(local_size) + .with_frame(cluster.frame); + if let Some(role) = cluster.role { + core = core.with_role(role); + } + clusters.push(core); + } + Ok(jlreq_core::ShapedText::new( + source, + size, + jlreq_core::Frame::FullEm, + clusters, + )?) + } + + fn is_boundary(&self, offset: usize, source_len: usize) -> bool { + offset == 0 + || offset == source_len + || self + .clusters + .binary_search_by_key(&offset, |cluster| cluster.range.start) + .is_ok() + } + + fn cluster_range(&self, range: &Range) -> Range { + let start = self + .clusters + .partition_point(|cluster| cluster.range.end <= range.start); + let end = self + .clusters + .partition_point(|cluster| cluster.range.start < range.end); + start..end + } +} + +#[derive(Debug, Clone)] +struct AttachmentShape { + global_ordinal: usize, + base: Range, + prepared: PreparedText, +} + +#[derive(Debug, Clone)] +struct ParagraphSegment { + content: Range, +} + +fn validate_call( + document: &Document, + fonts: &FontLibrary, + options: &LayoutOptions, +) -> Result<(), LayoutError> { + if fonts.is_empty() { + return Err(LayoutError::NoFonts); + } + check_limit( + Resource::InputBytes, + options.limits.input_bytes, + document.text.len(), + )?; + check_limit(Resource::Fonts, options.limits.fonts, fonts.len())?; + let font_bytes = fonts + .fonts() + .iter() + .fold(0_usize, |sum, font| sum.saturating_add(font.bytes().len())); + check_limit(Resource::FontBytes, options.limits.font_bytes, font_bytes)?; + check_limit( + Resource::Constructs, + options.limits.constructs, + document.constructs.len(), + ) +} + +fn check_limit(resource: Resource, limit: usize, observed: usize) -> Result<(), LayoutError> { + if observed > limit { + Err(LayoutError::resource(resource, limit, observed)) + } else { + Ok(()) + } +} + +fn map_core_resource_error(error: jlreq_core::ComposeError) -> LayoutError { + let Some(resource) = high_level_resource(error.resource()) else { + return LayoutError::CoreComposition(error); + }; + LayoutError::resource(resource, error.limit(), error.observed()) +} + +fn high_level_resource(resource: jlreq_core::CompositionResource) -> Option { + match resource { + jlreq_core::CompositionResource::Clusters => Some(Resource::Glyphs), + jlreq_core::CompositionResource::BreakCandidates => Some(Resource::Runs), + jlreq_core::CompositionResource::Constructs | jlreq_core::CompositionResource::TabStops => { + Some(Resource::Constructs) + }, + jlreq_core::CompositionResource::SearchTransitions => Some(Resource::CoreOperations), + _ => None, + } +} + +#[cfg(test)] +fn effective_style( + global: &Range, + spans: &[(Range, SpanStyle)], + options: &LayoutOptions, +) -> Result { + let mut selected = None; + for (range, style) in spans { + if ranges_overlap(range, global) { + if range.start > global.start || range.end < global.end { + return Err(LayoutError::invalid_document( + "document.span-splits-grapheme", + Some(global.clone()), + )); + } + selected = Some(style); + break; + } + } + let base = base_effective_style(options); + Ok(selected.map_or(base.clone(), |style| span_effective_style(&base, style))) +} + +fn base_effective_style(options: &LayoutOptions) -> EffectiveStyle { + EffectiveStyle { + families: Vec::new(), + font_style: FontStyle::default(), + size: options.font_size, + language: options.language.clone(), + features: options.features.clone(), + variations: options.variations.clone(), + role: TextRole::Text, + } +} + +fn span_effective_style(base: &EffectiveStyle, style: &SpanStyle) -> EffectiveStyle { + let mut result = base.clone(); + result.families.clone_from(&style.families); + result.font_style = style.font_style; + result.size = style.font_size.unwrap_or(base.size); + if let Some(language) = &style.language { + result.language.clone_from(language); + } + result.features.extend_from_slice(&style.features); + result.variations.extend_from_slice(&style.variations); + result.role = style.role; + result +} + diff --git a/crates/jlreq/src/engine/result.rs b/crates/jlreq/src/engine/result.rs new file mode 100644 index 0000000..965b907 --- /dev/null +++ b/crates/jlreq/src/engine/result.rs @@ -0,0 +1,327 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +fn map_core_lines( + layout: &jlreq_core::Layout, + prepared: &PreparedText, + attachments: &[Option], + global_offset: usize, + block_offset: i32, + options: &LayoutOptions, +) -> Vec { + let mut result = Vec::with_capacity(layout.lines().len()); + let mut used_clusters = vec![0_usize; prepared.clusters.len()]; + for (line_index, line) in layout.lines().iter().enumerate() { + let mut cells = Vec::new(); + let epoch = line_index.saturating_add(1); + for placement in line.clusters() { + let range = placement.range(); + let cluster_indices = placement_cluster_indices(placement.origin(), prepared, &range); + let cluster_indices: Vec<_> = cluster_indices + .into_iter() + .filter(|index| { + let Some(seen) = used_clusters.get_mut(*index) else { + return false; + }; + if *seen == epoch { + false + } else { + *seen = epoch; + true + } + }) + .collect(); + if cluster_indices.is_empty() { + continue; + } + let level = prepared.clusters[cluster_indices[0]].bidi_level; + cells.push(Cell { + clusters: cluster_indices, + block: placement.block(), + advance: placement.advance().max(0), + level, + transform: core_transform(placement.transform()), + }); + } + let levels: Vec<_> = cells + .iter() + .map(|cell| Level::new(cell.level).unwrap_or_else(|_| Level::ltr())) + .collect(); + let visual = BidiInfo::reorder_visual(&levels); + let mut cursor = line.inline_origin(); + let mut glyphs = Vec::new(); + for visual_index in visual { + let cell = &cells[visual_index]; + let mut cluster_cursor = 0_i32; + visit_logical_cluster_order(&cell.clusters, cell.level, |cluster_index| { + let cluster = &prepared.clusters[cluster_index]; + let mut glyph_cursor = 0_i32; + for raw in &cluster.glyphs { + glyphs.push(place_raw_glyph( + raw, + cluster, + PlacementContext { + source_range: global_offset.saturating_add(cluster.range.start) + ..global_offset.saturating_add(cluster.range.end), + annotation: None, + inline: cursor + .saturating_add(cluster_cursor) + .saturating_add(glyph_cursor), + block: adjusted_block(cell.block, line_index, block_offset, options), + transform: cell.transform, + writing_mode: options.writing_mode, + }, + )); + glyph_cursor = glyph_cursor.saturating_add(raw.inline_advance( + direction_from_geometry(raw, options.writing_mode, cell.transform), + )); + } + cluster_cursor = cluster_cursor.saturating_add(cluster.advance); + }); + cursor = cursor.saturating_add(cell.advance.max(cluster_cursor)); + } + append_attachments( + &mut glyphs, + line, + attachments, + line_index, + block_offset, + options, + ); + let physical_origin = match options.writing_mode { + WritingMode::HorizontalTb => Point::from_fixed( + line.inline_origin(), + adjusted_block(line.block_origin(), line_index, block_offset, options), + ), + WritingMode::VerticalRl => Point::from_fixed( + adjusted_block(line.block_origin(), line_index, block_offset, options), + line.inline_origin(), + ), + }; + let hit_bounds = TextLine::hit_bounds_for(&glyphs); + result.push(TextLine { + range: line.range().start.saturating_add(global_offset) + ..line.range().end.saturating_add(global_offset), + origin: physical_origin, + inline_extent: line.inline_extent(), + block_extent: line.block_extent(), + writing_mode: options.writing_mode, + glyphs, + hit_bounds, + }); + } + result +} + +fn placement_cluster_indices( + origin: jlreq_core::PlacementOrigin, + prepared: &PreparedText, + range: &Range, +) -> Range { + match origin { + jlreq_core::PlacementOrigin::Cluster(index) => { + index.min(prepared.clusters.len())..index.saturating_add(1).min(prepared.clusters.len()) + }, + jlreq_core::PlacementOrigin::Construct(_) => prepared.cluster_range(range), + _ => 0..0, + } +} + +fn visit_logical_cluster_order(indices: &[usize], level: u8, mut visit: impl FnMut(usize)) { + if level % 2 == 1 { + for index in indices.iter().rev().copied() { + visit(index); + } + } else { + for index in indices.iter().copied() { + visit(index); + } + } +} + +#[cfg(test)] +fn logical_cluster_order(indices: &[usize], level: u8) -> Vec { + let mut ordered = Vec::with_capacity(indices.len()); + visit_logical_cluster_order(indices, level, |index| ordered.push(index)); + ordered +} + +#[derive(Debug)] +struct Cell { + clusters: Vec, + block: i32, + advance: i32, + level: u8, + transform: GlyphTransform, +} + +struct PlacementContext { + source_range: Range, + annotation: Option, + inline: i32, + block: i32, + transform: GlyphTransform, + writing_mode: WritingMode, +} + +fn place_raw_glyph( + raw: &RawGlyph, + cluster: &PreparedCluster, + placement: PlacementContext, +) -> GlyphPlacement { + let PlacementContext { + source_range, + annotation, + inline, + block, + transform, + writing_mode, + } = placement; + let horizontal = + writing_mode == WritingMode::HorizontalTb || transform == GlyphTransform::TateChuYoko; + let (x, y, advance_x, advance_y, offset_x, offset_y) = if horizontal { + ( + inline, + block.saturating_add(cluster.size), + raw.x_advance.abs(), + 0, + raw.x_offset, + raw.y_offset.saturating_neg(), + ) + } else { + ( + block, + inline, + 0, + raw.y_advance.abs().max(raw.x_advance.abs()), + raw.x_offset, + raw.y_offset.saturating_neg(), + ) + }; + GlyphPlacement { + font_id: raw.font_id, + glyph_id: raw.glyph_id, + source_range, + annotation, + x, + y, + advance_x, + advance_y, + offset_x, + offset_y, + font_size: cluster.size, + transform, + bidi_level: cluster.bidi_level, + writing_mode, + } +} + +fn append_attachments( + glyphs: &mut Vec, + line: &jlreq_core::Line, + shapes: &[Option], + line_index: usize, + block_offset: i32, + options: &LayoutOptions, +) { + for attachment in line.attachments() { + let Some(shape) = shapes.get(attachment.construct()).and_then(Option::as_ref) else { + continue; + }; + let requested = attachment.range(); + let cluster_range = if requested.is_empty() { + 0..shape.prepared.clusters.len() + } else { + shape.prepared.cluster_range(&requested) + }; + let transform = core_transform(attachment.transform()); + let mut inline = attachment.inline(); + for cluster in &shape.prepared.clusters[cluster_range] { + let mut glyph_cursor = 0_i32; + for raw in &cluster.glyphs { + glyphs.push(place_raw_glyph( + raw, + cluster, + PlacementContext { + source_range: shape.base.clone(), + annotation: Some(AnnotationSource::new( + shape.global_ordinal, + cluster.range.clone(), + )), + inline: inline.saturating_add(glyph_cursor), + block: adjusted_block( + attachment.block(), + line_index, + block_offset, + options, + ), + transform, + writing_mode: options.writing_mode, + }, + )); + glyph_cursor = glyph_cursor.saturating_add(raw.inline_advance( + direction_from_geometry(raw, options.writing_mode, transform), + )); + } + inline = inline.saturating_add(cluster.advance); + } + } +} + +fn adjusted_block( + core_block: i32, + line_index: usize, + block_offset: i32, + options: &LayoutOptions, +) -> i32 { + let gap = i32::try_from(line_index) + .unwrap_or(i32::MAX) + .saturating_mul(options.line_gap); + match options.writing_mode { + WritingMode::HorizontalTb => block_offset.saturating_add(core_block).saturating_add(gap), + WritingMode::VerticalRl => block_offset.saturating_add(core_block).saturating_sub(gap), + } +} + +fn core_transform(value: jlreq_core::CoordinateTransform) -> GlyphTransform { + match value { + jlreq_core::CoordinateTransform::RotateClockwise => GlyphTransform::RotateClockwise, + jlreq_core::CoordinateTransform::TateChuYoko => GlyphTransform::TateChuYoko, + _ => GlyphTransform::Identity, + } +} + +fn direction_from_geometry( + raw: &RawGlyph, + mode: WritingMode, + transform: GlyphTransform, +) -> Direction { + if mode == WritingMode::VerticalRl + && transform != GlyphTransform::TateChuYoko + && raw.y_advance != 0 + { + Direction::TopToBottom + } else { + Direction::LeftToRight + } +} + +fn advance_block(value: i32, amount: i32, mode: WritingMode) -> i32 { + match mode { + WritingMode::HorizontalTb => value.saturating_add(amount), + WritingMode::VerticalRl => value.saturating_sub(amount), + } +} + +fn ranges_overlap(left: &Range, right: &Range) -> bool { + left.start < right.end && right.start < left.end +} + +fn diagnostic_severity(value: jlreq_core::Severity) -> DiagnosticSeverity { + match value { + jlreq_core::Severity::Info => DiagnosticSeverity::Info, + jlreq_core::Severity::Error => DiagnosticSeverity::Error, + _ => DiagnosticSeverity::Warning, + } +} + diff --git a/crates/jlreq/src/engine/shaping.rs b/crates/jlreq/src/engine/shaping.rs new file mode 100644 index 0000000..4f4f390 --- /dev/null +++ b/crates/jlreq/src/engine/shaping.rs @@ -0,0 +1,149 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +fn aggregate_run( + source: &str, + range: Range, + glyphs: Vec, + size: i32, + role: TextRole, + bidi_level: u8, + direction: Direction, +) -> Vec { + let mut starts: Vec<_> = glyphs + .iter() + .map(|glyph| glyph.cluster) + .filter(|start| range.contains(start)) + .collect(); + starts.push(range.start); + starts.sort_unstable(); + starts.dedup(); + let mut result = Vec::with_capacity(starts.len()); + for (ordinal, start) in starts.iter().copied().enumerate() { + let end = starts + .get(ordinal.saturating_add(1)) + .copied() + .unwrap_or(range.end); + let piece = &source[start..end]; + result.push(PreparedCluster { + range: start..end, + advance: 0, + size, + frame: frame_for(piece), + role: classify_role(source, start..end, role), + bidi_level, + glyphs: Vec::new(), + }); + } + for glyph in glyphs { + let Ok(bucket) = starts.binary_search(&glyph.cluster) else { + continue; + }; + if let Some(cluster) = result.get_mut(bucket) { + cluster.advance = cluster + .advance + .saturating_add(glyph.inline_advance(direction)); + cluster.glyphs.push(glyph); + } + } + result +} + +fn frame_for(piece: &str) -> jlreq_core::Frame { + if piece.chars().count() == 1 + && piece + .chars() + .next() + .is_some_and(|character| is_japanese(character) || is_emoji(character)) + { + jlreq_core::Frame::FullEm + } else { + jlreq_core::Frame::Proportional + } +} + +fn classify_role( + source: &str, + range: Range, + asserted: TextRole, +) -> Option { + if asserted != TextRole::Text { + return Some(asserted.core()); + } + let mut characters = source[range.clone()].chars(); + let character = characters.next()?; + if characters.next().is_some() { + return None; + } + let previous = source[..range.start].chars().next_back(); + let next = source[range.end..].chars().next(); + if matches!(character, '.' | '.' | '・') + && previous.is_some_and(char::is_numeric) + && next.is_some_and(char::is_numeric) + { + return Some(jlreq_core::ClusterRole::DecimalPoint); + } + if matches!(character, ',' | ',' | '、') + && previous.is_some_and(char::is_numeric) + && next.is_some_and(char::is_numeric) + { + return Some(jlreq_core::ClusterRole::DigitGroupSeparator); + } + if matches!(character, '!' | '?' | '!' | '?') { + return Some(if source[range.end..].trim().is_empty() { + jlreq_core::ClusterRole::SentenceTerminator + } else { + jlreq_core::ClusterRole::SentenceMedial + }); + } + None +} + +fn script_class(text: &str) -> ScriptClass { + for character in text.chars() { + let value = character as u32; + if is_japanese(character) { + return ScriptClass::Japanese; + } + if is_emoji(character) { + return ScriptClass::Emoji; + } + if matches!(value, 0x0590..=0x08ff | 0xfb1d..=0xfdff | 0xfe70..=0xfeff) { + return ScriptClass::Rtl; + } + if character.is_ascii_alphanumeric() || matches!(value, 0x00c0..=0x024f | 0x1e00..=0x1eff) { + return ScriptClass::Latin; + } + } + ScriptClass::Other +} + +fn is_japanese(character: char) -> bool { + matches!( + character as u32, + 0x2e80..=0x2fff + | 0x3000..=0x30ff + | 0x31f0..=0x31ff + | 0x3400..=0x4dbf + | 0x4e00..=0x9fff + | 0xf900..=0xfaff + | 0x20000..=0x323af + ) +} + +fn is_emoji(character: char) -> bool { + matches!(character as u32, 0x1f000..=0x1faff | 0x2600..=0x27bf) +} + +fn shape_direction(mode: WritingMode, level: Level, script: ScriptClass) -> Direction { + if mode == WritingMode::VerticalRl + && matches!(script, ScriptClass::Japanese | ScriptClass::Emoji) + { + Direction::TopToBottom + } else if level.is_rtl() { + Direction::RightToLeft + } else { + Direction::LeftToRight + } +} + diff --git a/crates/jlreq/src/font.rs b/crates/jlreq/src/font.rs index 997828a..b964d06 100644 --- a/crates/jlreq/src/font.rs +++ b/crates/jlreq/src/font.rs @@ -77,7 +77,7 @@ impl Default for FontStyle { } /// Font bytes retained by a completed layout for renderer use. -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone)] #[non_exhaustive] pub struct FontResource { pub(crate) id: FontId, @@ -85,8 +85,21 @@ pub struct FontResource { pub(crate) face_index: u32, pub(crate) family: String, pub(crate) style: FontStyle, + pub(crate) shaper_data: Arc, } +impl PartialEq for FontResource { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + && self.face_index == other.face_index + && self.bytes == other.bytes + && self.family == other.family + && self.style == other.style + } +} + +impl Eq for FontResource {} + impl fmt::Debug for FontResource { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter @@ -96,7 +109,7 @@ impl fmt::Debug for FontResource { .field("face_index", &self.face_index) .field("family", &self.family) .field("style", &self.style) - .finish() + .finish_non_exhaustive() } } @@ -192,9 +205,9 @@ impl FontLibrary { let bytes = bytes.into(); let font = harfrust::FontRef::from_index(&bytes, face_index) .map_err(|_| LayoutError::invalid_font(face_index))?; - // Constructing the cache touches the shaping-critical directory and metrics tables. - // It is intentionally discarded here; LayoutEngine owns reusable copies. - let _ = harfrust::ShaperData::new(&font); + // Parse shaping-critical tables exactly once. Layout engines retain this immutable + // cache through the resource instead of reconstructing it on first use. + let shaper_data = Arc::new(harfrust::ShaperData::new(&font)); let ordinal = u32::try_from(self.fonts.len()) .map_err(|_| LayoutError::invalid_document("font.too-many-ids", None))?; @@ -205,6 +218,7 @@ impl FontLibrary { face_index, family: family.into(), style, + shaper_data, }); self.fallback_order.push(id); if self.primary.is_none() { @@ -365,12 +379,16 @@ mod tests { use super::*; fn resource(id: u32, face_index: u32, family: &str, style: FontStyle) -> FontResource { + let bytes = Arc::<[u8]>::from(font_test_data::NOTO_SANS_JP_CFF); + let font = harfrust::FontRef::from_index(&bytes, 0).unwrap(); + let shaper_data = Arc::new(harfrust::ShaperData::new(&font)); FontResource { id: FontId(id), - bytes: Arc::from(vec![1_u8, 2, 3]), + bytes, face_index, family: family.to_owned(), style, + shaper_data, } } @@ -399,7 +417,7 @@ mod tests { ); assert_eq!(font.id().get(), 42); assert_eq!(font.face_index(), 7); - assert_eq!(font.bytes(), [1, 2, 3]); + assert_eq!(font.bytes(), font_test_data::NOTO_SANS_JP_CFF); assert_eq!(font.family(), "Distinct Family"); let rendered = format!("{font:?}"); assert!(rendered.contains("FontResource")); diff --git a/crates/jlreq/src/result.rs b/crates/jlreq/src/result.rs index e0b873f..85fd746 100644 --- a/crates/jlreq/src/result.rs +++ b/crates/jlreq/src/result.rs @@ -356,6 +356,7 @@ pub struct TextLine { pub(crate) block_extent: i32, pub(crate) writing_mode: WritingMode, pub(crate) glyphs: Vec, + pub(crate) hit_bounds: Option, } impl TextLine { @@ -394,6 +395,30 @@ impl TextLine { pub const fn writing_mode(&self) -> WritingMode { self.writing_mode } + + pub(crate) fn hit_bounds_for(glyphs: &[GlyphPlacement]) -> Option { + let mut bounds = glyphs + .iter() + .filter(|glyph| glyph.annotation.is_none()) + .map(GlyphPlacement::bounds); + let first = bounds.next()?; + let mut min_x = first.x; + let mut min_y = first.y; + let mut max_x = first.x.saturating_add(first.width); + let mut max_y = first.y.saturating_add(first.height); + for rect in bounds { + min_x = min_x.min(rect.x); + min_y = min_y.min(rect.y); + max_x = max_x.max(rect.x.saturating_add(rect.width)); + max_y = max_y.max(rect.y.saturating_add(rect.height)); + } + Some(Rect::from_fixed( + min_x, + min_y, + max_x.saturating_sub(min_x), + max_y.saturating_sub(min_y), + )) + } } /// Which logical side of a UTF-8 position was selected. @@ -479,15 +504,33 @@ impl TextLayout { #[must_use] pub fn hit_test(&self, point: Point) -> HitTest { let mut best: Option<(&GlyphPlacement, i64, bool)> = None; - for glyph in self.glyphs().filter(|glyph| glyph.annotation.is_none()) { - let bounds = glyph.bounds(); - let inside = bounds.contains(point); - let dx = axis_distance(point.x, bounds.x, bounds.width); - let dy = axis_distance(point.y, bounds.y, bounds.height); - let distance = dx.saturating_mul(dx).saturating_add(dy.saturating_mul(dy)); - if best.is_none_or(|(_, old, old_inside)| better_hit(inside, distance, old_inside, old)) + for line in &self.lines { + let Some(line_bounds) = line.hit_bounds else { + continue; + }; + let left_or_right = axis_distance(point.x, line_bounds.x, line_bounds.width); + let above_or_below = axis_distance(point.y, line_bounds.y, line_bounds.height); + let lower_bound = left_or_right + .saturating_mul(left_or_right) + .saturating_add(above_or_below.saturating_mul(above_or_below)); + if best.is_some_and(|(_, distance, inside)| inside || lower_bound >= distance) { + continue; + } + for glyph in line + .glyphs + .iter() + .filter(|glyph| glyph.annotation.is_none()) { - best = Some((glyph, distance, inside)); + let bounds = glyph.bounds(); + let inside = bounds.contains(point); + let dx = axis_distance(point.x, bounds.x, bounds.width); + let dy = axis_distance(point.y, bounds.y, bounds.height); + let distance = dx.saturating_mul(dx).saturating_add(dy.saturating_mul(dy)); + if best.is_none_or(|(_, old, old_inside)| { + better_hit(inside, distance, old_inside, old) + }) { + best = Some((glyph, distance, inside)); + } } } let Some((glyph, _, inside)) = best else { @@ -526,7 +569,13 @@ impl TextLayout { if !is_valid_caret_offset(&self.source, byte_offset) { return None; } - for line in &self.lines { + let first_line = self + .lines + .partition_point(|line| line.range.end < byte_offset); + for line in &self.lines[first_line..] { + if line.range.start > byte_offset { + break; + } let mut candidates = line .glyphs .iter() @@ -586,7 +635,13 @@ impl TextLayout { return Vec::new(); } let mut result = Vec::new(); - for line in &self.lines { + let first_line = self + .lines + .partition_point(|line| line.range.end <= range.start); + for line in &self.lines[first_line..] { + if line.range.start >= range.end { + break; + } let mut boxes = line .glyphs .iter() @@ -700,6 +755,141 @@ mod tests { } } + fn linear_hit_test(layout: &TextLayout, point: Point) -> HitTest { + let mut best: Option<(&GlyphPlacement, i64, bool)> = None; + for glyph in layout + .lines + .iter() + .flat_map(|line| &line.glyphs) + .filter(|glyph| glyph.annotation.is_none()) + { + let bounds = glyph.bounds(); + let inside = bounds.contains(point); + let dx = axis_distance(point.x, bounds.x, bounds.width); + let dy = axis_distance(point.y, bounds.y, bounds.height); + let distance = dx.saturating_mul(dx).saturating_add(dy.saturating_mul(dy)); + if best.is_none_or(|(_, old, old_inside)| better_hit(inside, distance, old_inside, old)) + { + best = Some((glyph, distance, inside)); + } + } + let Some((glyph, _, inside)) = best else { + return HitTest { + byte_offset: 0, + affinity: Affinity::Downstream, + inside: false, + }; + }; + let bounds = glyph.bounds(); + let after_visual_midpoint = is_after_visual_midpoint(point, bounds, glyph.writing_mode); + let logical_after = is_logically_after(after_visual_midpoint, glyph.bidi_level); + HitTest { + byte_offset: if logical_after { + glyph.source_range.end + } else { + glyph.source_range.start + }, + affinity: if logical_after { + Affinity::Downstream + } else { + Affinity::Upstream + }, + inside, + } + } + + fn linear_caret_rect(layout: &TextLayout, byte_offset: usize) -> Option { + if !is_valid_caret_offset(&layout.source, byte_offset) { + return None; + } + for line in &layout.lines { + let mut candidates = line + .glyphs + .iter() + .filter(|glyph| glyph.annotation.is_none()) + .filter(|glyph| glyph_touches_boundary(&glyph.source_range, byte_offset)); + if let Some(glyph) = candidates.next() { + let bounds = glyph.bounds(); + let at_start = glyph.source_range.start == byte_offset; + let visual_end = is_visual_end(at_start, glyph.bidi_level); + return Some(match glyph.writing_mode { + WritingMode::HorizontalTb => Rect::from_fixed( + if visual_end { + bounds.x.saturating_add(bounds.width) + } else { + bounds.x + }, + bounds.y, + 1, + bounds.height, + ), + WritingMode::VerticalRl => Rect::from_fixed( + bounds.x, + if visual_end { + bounds.y.saturating_add(bounds.height) + } else { + bounds.y + }, + bounds.width, + 1, + ), + }); + } + if line.range.start == byte_offset && line.glyphs.is_empty() { + return Some(match line.writing_mode { + WritingMode::HorizontalTb => Rect::from_fixed( + line.origin.x, + line.origin.y.saturating_sub(line.block_extent), + 1, + line.block_extent, + ), + WritingMode::VerticalRl => Rect::from_fixed( + line.origin.x.saturating_sub(line.block_extent), + line.origin.y, + line.block_extent, + 1, + ), + }); + } + } + None + } + + fn linear_selection_rects(layout: &TextLayout, range: Range) -> Vec { + if !is_valid_selection_range(&layout.source, &range) { + return Vec::new(); + } + let mut result = Vec::new(); + for line in &layout.lines { + let mut boxes = line + .glyphs + .iter() + .filter(|glyph| glyph.annotation.is_none()) + .filter(|glyph| ranges_overlap(&glyph.source_range, &range)) + .map(GlyphPlacement::bounds); + let Some(first) = boxes.next() else { + continue; + }; + let mut min_x = first.x; + let mut min_y = first.y; + let mut max_x = first.x.saturating_add(first.width); + let mut max_y = first.y.saturating_add(first.height); + for rect in boxes { + min_x = min_x.min(rect.x); + min_y = min_y.min(rect.y); + max_x = max_x.max(rect.x.saturating_add(rect.width)); + max_y = max_y.max(rect.y.saturating_add(rect.height)); + } + result.push(Rect::from_fixed( + min_x, + min_y, + max_x.saturating_sub(min_x), + max_y.saturating_sub(min_y), + )); + } + result + } + #[test] fn rectangle_accessors_preserve_all_signed_fixed_components() { let rect = Rect::from_fixed(128, -192, 256, 320); @@ -829,13 +1019,16 @@ mod tests { let mut rendered = glyph(WritingMode::HorizontalTb); rendered.annotation = None; + let glyphs = vec![rendered]; + let hit_bounds = TextLine::hit_bounds_for(&glyphs); let line = TextLine { range: 2..5, origin: Point::from_fixed(32, 64), inline_extent: 448, block_extent: 192, writing_mode: WritingMode::HorizontalTb, - glyphs: vec![rendered], + glyphs, + hit_bounds, }; assert_eq!(line.range(), 2..5); assert_eq!(line.origin().x_26_6(), 32); @@ -883,6 +1076,7 @@ mod tests { block_extent: 192, writing_mode: WritingMode::HorizontalTb, glyphs: Vec::new(), + hit_bounds: None, }], fonts: Vec::new(), diagnostics: Vec::new(), @@ -900,4 +1094,58 @@ mod tests { (128, 640, 192, 1) ); } + + #[test] + fn indexed_result_queries_match_full_linear_scans_across_lines() { + let mut lines = Vec::new(); + for (range, y) in [(0..2, 64), (2..4, 192), (4..6, 320)] { + let mut left = glyph(WritingMode::HorizontalTb); + left.annotation = None; + left.source_range = range.start..range.start.saturating_add(1); + left.x = 0; + left.y = y; + left.advance_x = 64; + left.font_size = 64; + let mut right = left.clone(); + right.source_range = range.start.saturating_add(1)..range.end; + right.x = 64; + right.bidi_level = u8::from(range.start == 2); + let glyphs = vec![left, right]; + lines.push(TextLine { + range, + origin: Point::from_fixed(0, y), + inline_extent: 128, + block_extent: 64, + writing_mode: WritingMode::HorizontalTb, + hit_bounds: TextLine::hit_bounds_for(&glyphs), + glyphs, + }); + } + let layout = TextLayout { + source: "abcdef".to_owned(), + lines, + fonts: Vec::new(), + diagnostics: Vec::new(), + writing_mode: WritingMode::HorizontalTb, + }; + + for x in [-32, 0, 63, 64, 96, 160] { + for y in [0, 64, 128, 192, 256, 320, 384] { + let point = Point::from_fixed(x, y); + assert_eq!(layout.hit_test(point), linear_hit_test(&layout, point)); + } + } + for offset in 0..=layout.source.len() { + assert_eq!( + layout.caret_rect(offset), + linear_caret_rect(&layout, offset) + ); + } + for range in [0..1, 0..2, 1..3, 2..4, 3..6, 5..6] { + assert_eq!( + layout.selection_rects(range.clone()), + linear_selection_rects(&layout, range) + ); + } + } } diff --git a/crates/jlreq/tests/high_level.rs b/crates/jlreq/tests/high_level.rs index 36a6c47..9e28f99 100644 --- a/crates/jlreq/tests/high_level.rs +++ b/crates/jlreq/tests/high_level.rs @@ -578,6 +578,18 @@ fn cross_paragraph_grapheme_and_mono_ruby_fail_atomically() -> Result<(), Box io::Result> { let source = fs::read_to_string(root.join("crates/jlreq/src").join(name))?; code.extend(quoted_error_codes(&source, '"')); } + for directory in ["crates/jlreq-core/src/pipeline", "crates/jlreq/src/engine"] { + for path in shared::rust_sources(&root.join(directory))? { + let source = fs::read_to_string(path)?; + code.extend(quoted_error_codes(&source, '"')); + } + } let reference = fs::read_to_string(root.join("docs/error-codes.md"))?; let documented = quoted_error_codes(&reference, '`'); let mut violations = Vec::new(); @@ -421,8 +427,8 @@ fn unresolved_link(root: &Path, document: &Path, target: &str) -> Option #[cfg(test)] mod tests { use super::{ - Link, fuzz_target_violations, is_initial_release_heading, local_links, - mutation_shard_violations, release_ready_state_violations, unresolved_link, + Link, error_code_violations, fuzz_target_violations, is_initial_release_heading, + local_links, mutation_shard_violations, release_ready_state_violations, unresolved_link, yaml_integer_values, }; use std::path::Path; @@ -474,6 +480,7 @@ mod tests { assert_eq!(release_ready_state_violations(root)?, Vec::::new()); assert_eq!(mutation_shard_violations(root)?, Vec::::new()); assert_eq!(fuzz_target_violations(root)?, Vec::::new()); + assert_eq!(error_code_violations(root)?, Vec::::new()); Ok(()) } diff --git a/xtask/src/spacing.rs b/xtask/src/spacing.rs index 932dacb..e196cbd 100644 --- a/xtask/src/spacing.rs +++ b/xtask/src/spacing.rs @@ -211,6 +211,116 @@ fn axis(label: &str) -> Result { .ok_or_else(|| format!("`{label}` is not cl-01 through cl-30")) } +/// Render the row/column maps that turn JLReq class ordinals into one matrix offset. +/// +/// The captured tables are required to be complete row-major matrices. Keeping that proof +/// in the generator lets the runtime cell omit an 841-entry linear search without making +/// the handwritten evaluator duplicate the transcription's axis order. +fn render_matrix_accessor(rows: &[Row<'_>], cell: &str) -> Result { + let mut before_labels = Vec::new(); + let mut after_labels = Vec::new(); + for row in rows { + if !before_labels.contains(&row.before) { + before_labels.push(row.before); + } + if !after_labels.contains(&row.after) { + after_labels.push(row.after); + } + } + if before_labels.is_empty() || after_labels.is_empty() { + return Err("a generated matrix has no axes".to_owned()); + } + let expected = before_labels.len().saturating_mul(after_labels.len()); + if rows.len() != expected { + return Err(format!( + "a generated matrix has {} rows, expected {expected}", + rows.len() + )); + } + for (position, row) in rows.iter().enumerate() { + let before_index = position + .checked_div(after_labels.len()) + .ok_or_else(|| "a generated matrix has no columns".to_owned())?; + let after_index = position + .checked_rem(after_labels.len()) + .ok_or_else(|| "a generated matrix has no columns".to_owned())?; + let before = before_labels + .get(before_index) + .copied() + .ok_or_else(|| "a generated matrix row is outside its axis".to_owned())?; + let after = after_labels + .get(after_index) + .copied() + .ok_or_else(|| "a generated matrix column is outside its axis".to_owned())?; + if row.before != before || row.after != after { + return Err(format!( + "line {} is not in complete row-major matrix order", + row.line + )); + } + } + + let index = |labels: &[&str]| -> Result<[u8; 31], String> { + let mut result = [u8::MAX; 31]; + for (position, label) in labels.iter().enumerate() { + let class = usize::from(axis(label)?); + let ordinal = u8::try_from(position) + .map_err(|_| "a generated matrix axis is too large".to_owned())?; + if result[class] != u8::MAX { + return Err(format!("matrix axis {label:?} is repeated")); + } + result[class] = ordinal; + } + Ok(result) + }; + let rows_index = index(&before_labels)?; + let columns_index = index(&after_labels)?; + let render_index = |values: &[u8; 31]| { + let mut rendered = "[\n ".to_owned(); + let mut column = 4_usize; + for value in values { + let token = format!("{value},"); + let separator = usize::from(column > 4); + if column.saturating_add(separator).saturating_add(token.len()) >= 100 { + rendered.push_str("\n "); + column = 4; + } else if separator != 0 { + rendered.push(' '); + column = column.saturating_add(1); + } + rendered.push_str(&token); + column = column.saturating_add(token.len()); + } + rendered.push_str("\n]"); + rendered + }; + let rows_index = render_index(&rows_index); + let columns_index = render_index(&columns_index); + Ok(format!( + concat!( + "\nconst ROW_INDEX: [u8; 31] = {rows_index};\n", + "const COLUMN_INDEX: [u8; 31] = {columns_index};\n", + "const COLUMN_COUNT: usize = {column_count};\n\n", + "/// Look up one cell directly from its class or line-edge coordinates.\n", + "pub(crate) fn cell(before: u8, after: u8) -> Option<&'static {cell}> {{\n", + " let row = *ROW_INDEX.get(usize::from(before))?;\n", + " let column = *COLUMN_INDEX.get(usize::from(after))?;\n", + " if row == u8::MAX || column == u8::MAX {{\n", + " return None;\n", + " }}\n", + " let index = usize::from(row)\n", + " .checked_mul(COLUMN_COUNT)?\n", + " .checked_add(usize::from(column))?;\n", + " CELLS.get(index)\n", + "}}\n", + ), + rows_index = rows_index, + columns_index = columns_index, + column_count = after_labels.len(), + cell = cell, + )) +} + /// Read a fraction (`1/4`, `1/2`, `1`, `0`) into units of 1/720 em (ADR-0007), exactly — /// `xtask attest`'s `amounts-are-multiples-of-the-unit` invariant is what already proves /// every committed amount survives this without rounding. @@ -342,21 +452,20 @@ fn emit_table1(table: &Table) -> Result { ); let mut entries = 0usize; for row in &rows { - let before = axis(row.before)?; - let after = axis(row.after)?; + let _ = (axis(row.before)?, axis(row.after)?); let (prohibited, hang, terms) = spacing_cell(row) .map_err(|reason| format!("{}:{}: {reason}", table.source, row.line))?; let rule = rule_constant(row.note, "SPACING_BETWEEN_CHARACTERS")?; let _ = write!( items, - " RawSpacingCell {{\n before: {before},\n after: {after},\n \ - prohibited: {prohibited},\n hang: {hang},\n rule: {rule},\n \ + " RawSpacingCell {{\n prohibited: {prohibited},\n hang: {hang},\n rule: {rule},\n \ {terms_field} }},\n", terms_field = render_terms(&terms), ); entries = entries.saturating_add(1); } items.push_str("];\n"); + items.push_str(&render_matrix_accessor(&rows, "RawSpacingCell")?); Ok(Emission { items, entries }) } @@ -372,21 +481,20 @@ fn emit_unified_table1(table: &Table) -> Result { ); let mut entries = 0usize; for row in &rows { - let before = axis(row.before)?; - let after = axis(row.after)?; + let _ = (axis(row.before)?, axis(row.after)?); let (prohibited, hang, terms) = spacing_cell(row) .map_err(|reason| format!("{}:{}: {reason}", table.source, row.line))?; let rule = if row.note.is_empty() { "B.1" } else { row.note }; let _ = write!( items, - " RawSpacingCell {{\n before: {before},\n after: {after},\n \ - prohibited: {prohibited},\n hang: {hang},\n rule: {rule:?},\n \ + " RawSpacingCell {{\n prohibited: {prohibited},\n hang: {hang},\n rule: {rule:?},\n \ {terms_field} }},\n", terms_field = render_terms(&terms), ); entries = entries.saturating_add(1); } items.push_str("];\n"); + items.push_str(&render_matrix_accessor(&rows, "RawSpacingCell")?); Ok(Emission { items, entries }) } @@ -438,8 +546,7 @@ fn emit_table2(table: &Table) -> Result { ); let mut entries = 0usize; for row in &rows { - let before = axis(row.before)?; - let after = axis(row.after)?; + let _ = (axis(row.before)?, axis(row.after)?); let (prohibited, levels) = break_cell(row).map_err(|reason| format!("{}:{}: {reason}", table.source, row.line))?; let rule = rule_constant( @@ -448,12 +555,12 @@ fn emit_table2(table: &Table) -> Result { )?; let _ = write!( items, - " RawBreakCell {{\n before: {before},\n after: {after},\n \ - prohibited: {prohibited},\n levels: 0b{levels:04b},\n rule: {rule},\n }},\n" + " RawBreakCell {{\n prohibited: {prohibited},\n levels: 0b{levels:04b},\n rule: {rule},\n }},\n" ); entries = entries.saturating_add(1); } items.push_str("];\n"); + items.push_str(&render_matrix_accessor(&rows, "RawBreakCell")?); Ok(Emission { items, entries }) } @@ -469,19 +576,18 @@ fn emit_unified_table2(table: &Table) -> Result { ); let mut entries = 0usize; for row in &rows { - let before = axis(row.before)?; - let after = axis(row.after)?; + let _ = (axis(row.before)?, axis(row.after)?); let (prohibited, levels) = break_cell(row).map_err(|reason| format!("{}:{}: {reason}", table.source, row.line))?; let rule = if row.note.is_empty() { "C" } else { row.note }; let _ = write!( items, - " RawBreakCell {{\n before: {before},\n after: {after},\n \ - prohibited: {prohibited},\n levels: 0b{levels:04b},\n rule: {rule:?},\n }},\n" + " RawBreakCell {{\n prohibited: {prohibited},\n levels: 0b{levels:04b},\n rule: {rule:?},\n }},\n" ); entries = entries.saturating_add(1); } items.push_str("];\n"); + items.push_str(&render_matrix_accessor(&rows, "RawBreakCell")?); Ok(Emission { items, entries }) } @@ -583,8 +689,7 @@ fn emit_ranged(table: &Table, which: RangedTable, fallback: &str) -> Result Result Result Date: Sun, 30 Aug 2026 14:21:19 +0900 Subject: [PATCH 10/12] feat: complete the 0.1.0 rendering contract --- .github/workflows/ci.yml | 18 +- ARCHITECTURE.md | 24 +- CHANGELOG.md | 17 +- CONTRIBUTING.md | 5 +- Justfile | 12 +- README.md | 54 +- crates/jlreq/README.md | 126 ++++- crates/jlreq/examples/benchmark.rs | 108 +++- crates/jlreq/src/document.rs | 17 +- crates/jlreq/src/engine.rs | 39 +- crates/jlreq/src/engine/layout.rs | 25 +- crates/jlreq/src/engine/preparation.rs | 52 +- crates/jlreq/src/engine/result.rs | 33 +- crates/jlreq/src/engine/shaping.rs | 10 +- crates/jlreq/src/font.rs | 208 +++++++- crates/jlreq/src/lib.rs | 2 +- crates/jlreq/src/options.rs | 73 ++- crates/jlreq/src/result.rs | 671 ++++++++++++++++++------- crates/jlreq/tests/high_level.rs | 153 +++++- data/manifest.toml | 2 +- docs/design/api-spine.md | 34 +- docs/error-codes.md | 6 + docs/guide.ja.md | 135 +++++ docs/public-api.toml | 1 + scripts/run-mutation-smoke.sh | 13 +- scripts/verify-mutation-ledger.sh | 175 ------- xtask/src/main.rs | 2 + xtask/src/mutation.rs | 436 ++++++++++++++++ 28 files changed, 1994 insertions(+), 457 deletions(-) create mode 100644 docs/guide.ja.md delete mode 100644 scripts/verify-mutation-ledger.sh create mode 100644 xtask/src/mutation.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4266642..c81f8a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -199,10 +199,22 @@ jobs: - run: just coverage mutation-smoke: - name: mutation smoke (changed Rust product lines) + name: mutation smoke (changed Rust product lines, shard ${{ matrix.shard.display }}/4) if: github.event_name == 'pull_request' runs-on: ubuntu-latest timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + shard: + - index: 0 + display: 1 + - index: 1 + display: 2 + - index: 2 + display: 3 + - index: 3 + display: 4 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -212,11 +224,11 @@ jobs: with: tool: just@1.50.0,cargo-mutants@27.1.0,nextest@0.9.140 - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 - - run: just mutants-smoke '${{ github.event.pull_request.base.sha }}' + - run: just mutants-smoke '${{ github.event.pull_request.base.sha }}' '${{ matrix.shard.index }}/4' - if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: mutants-smoke + name: mutants-smoke-${{ matrix.shard.display }}-of-4 path: mutants.out/ if-no-files-found: ignore retention-days: 14 diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 610664d..ccd5c65 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -78,10 +78,16 @@ range remain present, a primary `.notdef` is shaped, and a positioned Fontique-backed OS discovery is behind the disabled-by-default `system-fonts` feature. Layout is cross-platform deterministic when the same explicit bytes, face indices, text, and options are supplied. An OS collection may change or choose a different face and is -therefore outside that guarantee. +therefore outside that guarantee. System queries receive requested weight, width, and +slant. The selected face's variable-axis settings and required synthetic emboldening/skew +are copied into `FontResource`, so the completed layout remains sufficient to reproduce +that selection without consulting Fontique again. OpenType feature tags and variation coordinates are facade values. No upstream crate type -crosses the public API. +crosses the public API. Variation coordinates are quantized 26.6 values with `Eq` and +`Hash`. Global settings, system-selected defaults, and span settings are overlaid by tag in +that order, with later values winning; the same resolved shared slice drives HarfRust and +is exposed on each glyph. ## Documents and results @@ -92,9 +98,17 @@ are shaped by the same font, fallback, and bidi machinery as body text. `TextLayout` owns its source, lines, diagnostics, and every `FontResource` used by its glyphs. Each `GlyphPlacement` has visual draw order, font and glyph IDs, original UTF-8 -range, physical origin, advance, offset, transform, and bidi level. `hit_test`, -`caret_rect`, and `selection_rects` use that same geometry for horizontal, vertical, -and mixed-direction text. +range, physical draw origin, advance, offset, resolved size and variations, cell bounds, +transform, and bidi level. `TextLayout::font` resolves retained resources by ID even when +the retained ID set is sparse. Glyph, line, and layout bounds are physical cell bounds, +including whitespace and annotations, not rasterized ink bounds. + +`hit_test`, affinity-required `caret_rect`, and `selection_rects` use that same geometry +for horizontal, vertical, and mixed-direction text. Hit testing chooses the nearest line +region before the nearest glyph and maps empty lines to their own source position. +Selections are emitted per visually contiguous selected run, so a bidi selection never +fills an intervening unselected run. Paragraph block progression is derived from actual +line cells plus one configured line gap rather than from the global font size. The facade does not rasterize, draw, allocate GPU resources, or serialize a document. diff --git a/CHANGELOG.md b/CHANGELOG.md index c9ffba1..b421581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,9 +23,14 @@ unset. No crate upload, tag, or GitHub Release has occurred. - `DocumentBuilder` spans, explicit break controls, mono/group/jukugo ruby, emphasis, warichu, furawake, jidori, reference marks, scripts, and formulae; annotation strings are shaped automatically. -- `TextLayout`, `TextLine`, and `GlyphPlacement` with owned font resources, original - UTF-8 ranges, 26.6 geometry, transforms and bidi levels, plus hit testing, caret - rectangles, and selection rectangles. +- `TextLayout`, `TextLine`, and `GlyphPlacement` with sparse-ID-safe font lookup, original + UTF-8 ranges, draw origins, resolved sizes and variations, 26.6 geometry, transforms, + bidi levels, and whitespace/annotation-preserving cell bounds. +- Affinity-exact hit testing and carets at wraps, paragraph breaks, bidi boundaries, and + empty lines, plus visually contiguous selection rectangles that do not bridge + unselected bidi runs. +- `FontSynthesis` and renderer-visible system-font default axes, synthetic emboldening, + and skew state. - Typed high-level errors, positioned missing-glyph diagnostics, configurable limits for input/font bytes, fonts, paragraphs, runs, glyphs, constructs, and core operations, and atomic failure with engine reuse. @@ -42,6 +47,12 @@ unset. No crate upload, tag, or GitHub Release has occurred. - Public-library responsibilities are separated from the conformance process contract. Fontique 0.11.1, HarfRust 0.13.3, ICU4X segmenter 2.3.0, and unicode-bidi 0.3.18 are facade implementation dependencies and do not enter the core. +- Variation settings now use deterministic 26.6 equality/hashing and merge global, + system-selected, and span values by tag with the last layer winning. Paragraphs advance + by actual line/annotation cells plus one line gap, including large styled spans. +- Mutation-ledger validation now runs through the cross-platform Rust `xtask`; pull-request + mutation smoke is split into four deterministic shards. Benchmarks now cover variable + font state, multi-paragraph annotations, and bidi editing queries. - Release packaging produces and offline-verifies three crate archives. Future publication order is `jlreq-core`, registry visibility, then `jlreq` and `jlreq-conformance`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c87c3e..b51b7ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,10 +60,11 @@ cargo build --release -p jlreq-conformance target/release/jlreq-conformance run target/release/jlreq-sample-engine ``` -Full mutation runs cover both handwritten products. Only generated table files and exact +Full mutation runs cover all three handwritten Rust products. Only generated table files and exact mutants proven equivalent in [docs/mutation-ledger.toml](docs/mutation-ledger.toml) may be excluded. `just mutation-ledger` binds every such entry to its source SHA-256 and rejects an -undocumented or broad cargo-mutants exclusion. +undocumented or broad cargo-mutants exclusion. The ledger gate is implemented in Rust so the +same `just check` path runs on Windows without a POSIX hashing toolchain. ## Rules that are not negotiable diff --git a/Justfile b/Justfile index 37e9987..3cd83cc 100644 --- a/Justfile +++ b/Justfile @@ -282,7 +282,7 @@ actionlint: # Validate every repository-owned POSIX shell entry point, including release packaging and # the three-engine census driver. shellcheck: - shellcheck engines/census-all.sh scripts/check-semver.sh scripts/finalize-release.sh scripts/package-binaries.sh scripts/run-mutation-smoke.sh scripts/verify-crates.sh scripts/verify-mutation-ledger.sh scripts/verify-release-state.sh + shellcheck engines/census-all.sh scripts/check-semver.sh scripts/finalize-release.sh scripts/package-binaries.sh scripts/run-mutation-smoke.sh scripts/verify-crates.sh scripts/verify-release-state.sh # Reject high-severity GitHub Actions and Dependabot security findings without # granting the auditor network or repository credentials. @@ -311,19 +311,19 @@ msrv: # `::std::iter::empty()`) that do not type-check against this crate's domain types or its # `no_std` boundary — see the milestone report for the per-crate rate. mutants crate="" shard="": - sh scripts/verify-mutation-ledger.sh + cargo run --quiet -p xtask -- mutation-ledger cargo mutants {{ if crate == "" { mutant_crates } else { "-p " + crate } }} {{ if shard == "" { "" } else { "--shard " + shard } }} --all-features --test-tool cargo --minimum-test-timeout 120 --no-times --colors=never -j 4 # Pull requests exercise only mutations in the changed Rust surface; weekly and release # workflows run the complete sharded gate above. -mutants-smoke base: - sh scripts/verify-mutation-ledger.sh - sh scripts/run-mutation-smoke.sh {{ quote(base) }} +mutants-smoke base shard="": + cargo run --quiet -p xtask -- mutation-ledger + sh scripts/run-mutation-smoke.sh {{ quote(base) }} {{ if shard == "" { "" } else { quote(shard) } }} # Hold generated and equivalent-mutant exclusions to their individual source hashes and # require every cargo-mutants regex to have one proof in the reviewable ledger. mutation-ledger: - sh scripts/verify-mutation-ledger.sh + cargo run --quiet -p xtask -- mutation-ledger # Build the independent OCaml reference engine (engines/ocaml/README.md). The engines are # outside the Cargo workspace and no Rust gate reads them, so the recipes below are the diff --git a/README.md b/README.md index b1fdf3e..efdef1f 100644 --- a/README.md +++ b/README.md @@ -33,27 +33,44 @@ let options = LayoutOptions::try_new(240.0, 16.0)?; let layout = jlreq::layout("日本語組版 — draw-ready glyphs", &fonts, options)?; for glyph in layout.glyphs() { - let font = &layout.fonts()[glyph.font_id().get() as usize]; - renderer_draw( - font.bytes(), - font.face_index(), - glyph.glyph_id(), - glyph.origin(), - glyph.transform(), - ); + if let Some(font) = layout.font(glyph.font_id()) { + renderer_draw( + font.bytes(), + font.face_index(), + glyph.glyph_id(), + glyph.draw_origin(), + glyph.font_size_26_6(), + glyph.variations(), + font.synthesis(), + glyph.transform(), + ); + } } # fn renderer_draw( -# _: &[u8], _: u32, _: u32, _: jlreq::Point, _: jlreq::GlyphTransform, +# _: &[u8], +# _: u32, +# _: u32, +# _: jlreq::Point, +# _: i32, +# _: &[jlreq::FontVariation], +# _: jlreq::FontSynthesis, +# _: jlreq::GlyphTransform, # ) {} # Ok(()) # } ``` -`TextLayout` owns every `FontResource` referenced by its glyphs. A renderer therefore needs -only the returned layout: use `font_id` to select bytes and TTC face, draw `glyph_id` at the -physical origin plus offsets, and apply `GlyphTransform`. Rasterization, GPU upload, PDF +`TextLayout` owns every `FontResource` referenced by its glyphs. Retained IDs can be sparse, +so renderers resolve them with `TextLayout::font`, not slice indexing. Draw `glyph_id` at +`draw_origin`, instantiate it with the returned size and effective variations, apply the +font's synthetic emboldening/skew and then `GlyphTransform`. Rasterization, GPU upload, PDF encoding, and drawing are intentionally outside the crate. +`GlyphPlacement::cell_bounds`, `TextLine::bounds`, and `TextLayout::bounds` are physical +layout-cell boundaries. They deliberately retain whitespace and annotation cells. They are +not glyph ink bounds; a rasterizer must derive ink bounds from the selected outline and +variation instance when clipping or painting decorations. + Use `LayoutEngine` instead of `jlreq::layout` for batches. It reuses parsed fonts, shaping data, Unicode services, and core-composer scratch space, remains reusable after an error, and produces bit-identical results to the one-shot call. @@ -89,8 +106,12 @@ Horizontal and vertical results use physical coordinates. Latin rotation, uprigh tate-chu-yoko, per-line UAX #9 visual reordering, hit testing, caret rectangles, and selection rectangles therefore require no logical-to-physical conversion in the caller. OpenType features and variable-font axes can be set globally or per span. The optional -`system-fonts` feature adds Fontique-backed OS discovery; only explicit bytes carry the -cross-platform determinism guarantee. +`system-fonts` feature adds Fontique-backed OS discovery, passes weight/width/slant to the +matcher, and records its default axes and synthetic styling in `FontResource`. Global, +system-selected, and span variation values are merged by tag in that order, with the last +value winning. Only explicit bytes carry the cross-platform determinism guarantee; once a +system face is registered, its copied bytes and recorded rendering state are stable for that +layout, but repeating discovery against a changed OS collection may select another face. ## Typed documents and all nine inline constructs @@ -114,7 +135,7 @@ reference marks, and script annotations are ordinary strings and are shaped auto # fn document(font_bytes: Vec) -> Result<(), jlreq::LayoutError> { use jlreq::{DocumentBuilder, FontLibrary, LayoutOptions, ScriptPosition}; -let text = "漢字12 注記 振分 字取 * H2O x+y"; +let text = "漢字12 注記 割注 振分 字取 * H2O x+y"; let mut doc = DocumentBuilder::new(text); doc.group_ruby(0..6, "かんじ")?; doc.tate_chu_yoko(6..8)?; @@ -174,7 +195,8 @@ The exact 0.1.x public API is frozen in [`docs/public-api.toml`](docs/public-api.toml); stable failures and diagnostics are in [`docs/error-codes.md`](docs/error-codes.md). Architecture, resource bounds, and the release handoff are documented in [ARCHITECTURE.md](ARCHITECTURE.md), [SECURITY.md](SECURITY.md), -and [docs/RELEASING.md](docs/RELEASING.md). +and [docs/RELEASING.md](docs/RELEASING.md). A Japanese usage guide is available at +[`docs/guide.ja.md`](docs/guide.ja.md). ## License diff --git a/crates/jlreq/README.md b/crates/jlreq/README.md index 8c384e4..342a603 100644 --- a/crates/jlreq/README.md +++ b/crates/jlreq/README.md @@ -16,33 +16,135 @@ let options = LayoutOptions::try_new(240.0, 16.0)?; let layout = jlreq::layout("日本語組版 — draw-ready glyphs", &fonts, options)?; for glyph in layout.glyphs() { - let font = &layout.fonts()[glyph.font_id().get() as usize]; - renderer_draw( - font.bytes(), - font.face_index(), - glyph.glyph_id(), - glyph.origin(), - glyph.transform(), - ); + if let Some(font) = layout.font(glyph.font_id()) { + renderer_draw( + font.bytes(), + font.face_index(), + glyph.glyph_id(), + glyph.draw_origin(), + glyph.font_size_26_6(), + glyph.variations(), + font.synthesis(), + glyph.transform(), + ); + } } # fn renderer_draw( -# _: &[u8], _: u32, _: u32, _: jlreq::Point, _: jlreq::GlyphTransform, +# _: &[u8], +# _: u32, +# _: u32, +# _: jlreq::Point, +# _: i32, +# _: &[jlreq::FontVariation], +# _: jlreq::FontSynthesis, +# _: jlreq::GlyphTransform, # ) {} # Ok(()) # } ``` `TextLayout` owns every font resource referenced by its glyphs, so it can be passed to a -renderer without retaining `FontLibrary`. Glyphs preserve the original UTF-8 byte range, -physical origin, advances and offsets, transform, bidi level, and visual draw order. +renderer without retaining `FontLibrary`. Retained font IDs can be sparse; resolve each +`GlyphPlacement::font_id` with `TextLayout::font`. Glyphs preserve the original UTF-8 byte +range, draw origin, size, effective variation axes, advances, transform, bidi level, cell +bounds, and visual draw order. `FontResource` also preserves system-selected default axes +and synthetic bold/skew state. Rasterization, drawing, PDF serialization, and GPU integration remain renderer concerns. +Glyph, line, and layout `bounds` are physical layout-cell bounds rather than ink bounds. +They include whitespace and annotation cells. Outline ink bounds remain a renderer concern. + Use `LayoutEngine` for repeated work, `DocumentBuilder` for spans, explicit break control, ruby and the other eight typed inline constructs, and `jlreq::core` when text is already shaped and the caller wants exact control over clusters and break opportunities. +## Typed vertical layout + +```rust,no_run +# fn vertical(font_bytes: Vec) -> Result<(), jlreq::LayoutError> { +use jlreq::{DocumentBuilder, FontLibrary, LayoutOptions, ScriptPosition, WritingMode}; + +let text = "漢字12 注記 割注 振分 字取 * H2O x+y"; +let mut document = DocumentBuilder::new(text); +document.group_ruby(0..6, "かんじ")?; +document.tate_chu_yoko(6..8)?; +document.emphasis_dots(9..15, '・')?; +document.warichu(16..22)?; +document.furawake(23..29, 2, 1.0)?; +document.jidori(30..36, 4)?; +document.reference_mark(37..38, "※")?; +document.script(39..42, "2", ScriptPosition::Subscript)?; +document.formula(43..46)?; + +let mut fonts = FontLibrary::new(); +fonts.register_font(font_bytes)?; +let layout = jlreq::layout_document( + &document.build()?, + &fonts, + LayoutOptions::try_new(240.0, 16.0)? + .writing_mode(WritingMode::VerticalRl), +)?; +assert!(layout.lines().iter().all(|line| { + line.writing_mode() == WritingMode::VerticalRl +})); +# Ok(()) +# } +``` + +## Editing geometry + +`caret_rect` requires the affinity returned by hit testing. This preserves the selected +visual edge at wraps, paragraph breaks, and bidi boundaries. + +```rust,no_run +# fn editing(font_bytes: Vec) -> Result<(), jlreq::LayoutError> { +use jlreq::{FontLibrary, LayoutOptions, Point}; + +let mut fonts = FontLibrary::new(); +fonts.register_font(font_bytes)?; +let layout = jlreq::layout( + "日本語 abc العربية", + &fonts, + LayoutOptions::try_new(160.0, 16.0)?, +)?; +let hit = layout.hit_test(Point::try_new(24.0, 12.0)?); +if let Some(caret) = layout.caret_rect(hit.byte_offset(), hit.affinity()) { + let _physical_caret = caret.as_26_6(); +} +let _visual_runs = layout.selection_rects(0.."日本語".len()); +# Ok(()) +# } +``` + +## Diagnostics + +Recoverable conditions accompany a complete layout; invalid input and exhausted limits +return `LayoutError` without a partial result. + +```rust,no_run +# fn diagnostics(font_bytes: Vec) -> Result<(), jlreq::LayoutError> { +use jlreq::{FontLibrary, LayoutOptions}; + +let mut fonts = FontLibrary::new(); +fonts.register_font(font_bytes)?; +let layout = jlreq::layout("missing: \u{10ffff}", &fonts, LayoutOptions::try_new(240.0, 16.0)?)?; +for diagnostic in layout.diagnostics() { + eprintln!( + "{} {:?} {:?}: {}", + diagnostic.code(), + diagnostic.severity(), + diagnostic.range(), + diagnostic.message(), + ); +} +# Ok(()) +# } +``` + The optional `system-fonts` feature exposes Fontique-backed OS font discovery. Layout from explicit font bytes is deterministic across supported operating systems; selection from a -changing system collection is intentionally outside that guarantee. +changing system collection is intentionally outside that guarantee. Global values, +system-selected defaults, and span values are merged per variation tag in that order, with +the last value winning. Licensed under MIT or Apache-2.0, at your option. diff --git a/crates/jlreq/examples/benchmark.rs b/crates/jlreq/examples/benchmark.rs index 62497df..4717220 100644 --- a/crates/jlreq/examples/benchmark.rs +++ b/crates/jlreq/examples/benchmark.rs @@ -16,8 +16,8 @@ use std::{ }; use jlreq::{ - BaseDirection, Document, DocumentBuilder, FontLibrary, FontStyle, LayoutEngine, LayoutOptions, - ScriptPosition, SpanStyle, WritingMode, + Affinity, BaseDirection, Document, DocumentBuilder, FontLibrary, FontStyle, FontVariation, + LayoutEngine, LayoutOptions, OpenTypeTag, ScriptPosition, SpanStyle, WritingMode, core::{ Break, Cluster, Composer, CompositionLimits, Construct, Frame, Paragraph, ShapedText, Size, Style, @@ -63,7 +63,36 @@ fn main() -> BenchResult<()> { Ok(layout.glyphs().count()) })?; + let variable_fonts = variable_fonts()?; + let variable_text = "variable ".repeat(128); + let variable_document = variable_span_document(&variable_text)?; + let variable_options = LayoutOptions::try_new(640.0, 18.0)?.variation(FontVariation::try_new( + OpenTypeTag::try_new("wght")?, + 500.0, + )?); + measure("layout.reused-engine.variable-runs", samples, || { + let layout = engine.layout_document( + black_box(&variable_document), + &variable_fonts, + variable_options.clone(), + )?; + Ok(layout.glyphs().map(|glyph| glyph.variations().len()).sum()) + })?; + let complex_document = complex_document(32)?; + let horizontal_options = LayoutOptions::try_new(320.0, 18.0)?.line_gap(3.0)?; + measure( + "layout.reused-engine.horizontal-multiparagraph", + samples, + || { + let layout = engine.layout_document( + black_box(&complex_document), + &fonts, + horizontal_options.clone(), + )?; + Ok(layout.lines().len()) + }, + )?; let vertical_options = LayoutOptions::try_new(640.0, 18.0)? .writing_mode(WritingMode::VerticalRl) .tab_width(4)?; @@ -94,7 +123,9 @@ fn main() -> BenchResult<()> { observed = observed.saturating_add(hit.byte_offset()); let boundary = cyclic_value(&boundaries, ordinal)?; observed = observed.saturating_add(usize::from( - query_layout.caret_rect(black_box(boundary)).is_some(), + query_layout + .caret_rect(black_box(boundary), Affinity::Downstream) + .is_some(), )); let next = cyclic_value(&boundaries, ordinal.saturating_add(1))?; let range = boundary.min(next)..boundary.max(next); @@ -104,6 +135,38 @@ fn main() -> BenchResult<()> { Ok(observed) })?; + let bidi_query_layout = engine.layout(&bidi, &fonts, bidi_options)?; + let bidi_points: Vec<_> = bidi_query_layout + .glyphs() + .take(256) + .map(jlreq::GlyphPlacement::origin) + .collect(); + let bidi_boundaries: Vec<_> = bidi + .char_indices() + .map(|(offset, _)| offset) + .chain([bidi.len()]) + .collect(); + measure("result.bidi-hit-affinity-caret-selection", samples, || { + let mut observed = 0_usize; + for (ordinal, point) in bidi_points.iter().enumerate() { + let hit = bidi_query_layout.hit_test(black_box(*point)); + observed = observed.saturating_add(hit.byte_offset()); + observed = observed.saturating_add(usize::from( + bidi_query_layout + .caret_rect(hit.byte_offset(), hit.affinity()) + .is_some(), + )); + let start = cyclic_value(&bidi_boundaries, ordinal)?; + let end = cyclic_value(&bidi_boundaries, ordinal.saturating_add(8))?; + observed = observed.saturating_add( + bidi_query_layout + .selection_rects(black_box(start.min(end)..start.max(end))) + .len(), + ); + } + Ok(observed) + })?; + let regular = core_paragraph(10_000, false)?; let core_style = Style::default(); measure("core.compose-fresh.10k", samples, || { @@ -203,6 +266,17 @@ fn fixture_fonts() -> BenchResult { Ok(fonts) } +fn variable_fonts() -> BenchResult { + let mut fonts = FontLibrary::new(); + let _ = fonts.register_face( + Arc::<[u8]>::from(font_test_data::VAZIRMATN_VAR), + 0, + "Vazirmatn", + FontStyle::default(), + )?; + Ok(fonts) +} + fn many_span_document(text: &str) -> BenchResult { let mut boundaries: Vec<_> = text.char_indices().map(|(offset, _)| offset).collect(); boundaries.push(text.len()); @@ -221,11 +295,39 @@ fn many_span_document(text: &str) -> BenchResult { Ok(builder.build()?) } +fn variable_span_document(text: &str) -> BenchResult { + const SEGMENT_LEN: usize = "variable ".len(); + let weight = OpenTypeTag::try_new("wght")?; + let light = FontVariation::try_new(weight, 350.0)?; + let bold = FontVariation::try_new(weight, 725.0)?; + let mut builder = DocumentBuilder::new(text); + for (ordinal, start) in (0..text.len()).step_by(SEGMENT_LEN).enumerate() { + let variation = if ordinal.is_multiple_of(2) { + light + } else { + bold + }; + let _ = builder.span( + start..start.saturating_add(SEGMENT_LEN), + SpanStyle::new().variation(variation), + )?; + } + Ok(builder.build()?) +} + fn complex_document(repetitions: usize) -> BenchResult { const SEGMENT: &str = "漢12強注式\t\n"; let mut builder = DocumentBuilder::new(SEGMENT.repeat(repetitions)); for repetition in 0..repetitions { let base = repetition.saturating_mul(SEGMENT.len()); + let _ = builder.span( + base..base.saturating_add(11), + SpanStyle::new().font_size(if repetition.is_multiple_of(2) { + 30.0 + } else { + 22.0 + })?, + )?; let _ = builder.group_ruby(base..base.saturating_add(3), "かん")?; let _ = builder.tate_chu_yoko(base.saturating_add(3)..base.saturating_add(5))?; let _ = builder.emphasis_dots(base.saturating_add(5)..base.saturating_add(8), '・')?; diff --git a/crates/jlreq/src/document.rs b/crates/jlreq/src/document.rs index b7ea4b1..f20b61a 100644 --- a/crates/jlreq/src/document.rs +++ b/crates/jlreq/src/document.rs @@ -297,9 +297,7 @@ impl DocumentBuilder { style: SpanStyle, ) -> Result<&mut Self, LayoutError> { self.validate_non_empty_range(&range, "document.invalid-span-range")?; - let insertion = self - .spans - .partition_point(|(other, _)| other.start < range.start); + let insertion = span_insertion_point(&self.spans, range.start); let overlaps_previous = insertion .checked_sub(1) .and_then(|index| self.spans.get(index)) @@ -566,6 +564,10 @@ impl DocumentBuilder { } } +fn span_insertion_point(spans: &[(Range, SpanStyle)], start: usize) -> usize { + spans.partition_point(|(other, _)| other.start < start) +} + fn validate_ruby_runs( text: &str, kind: RubyKind, @@ -723,4 +725,13 @@ mod tests { assert!(!ranges_overlap(&(0..1), &(1..2))); assert!(!ranges_overlap(&(1..2), &(0..1))); } + + #[test] + fn span_insertion_uses_the_lower_bound_for_every_ordering_case() { + let spans = [(1..2, SpanStyle::default()), (3..4, SpanStyle::default())]; + assert_eq!(span_insertion_point(&spans, 0), 0); + assert_eq!(span_insertion_point(&spans, 2), 1); + assert_eq!(span_insertion_point(&spans, 3), 1); + assert_eq!(span_insertion_point(&spans, 5), 2); + } } diff --git a/crates/jlreq/src/engine.rs b/crates/jlreq/src/engine.rs index 120d350..df48c0f 100644 --- a/crates/jlreq/src/engine.rs +++ b/crates/jlreq/src/engine.rs @@ -72,6 +72,7 @@ mod tests { frame: jlreq_core::Frame::Proportional, role: None, bidi_level: level, + variations: Arc::from([]), glyphs: vec![raw(font_id)], } } @@ -83,7 +84,8 @@ mod tests { size, language: "und".into(), features: Vec::new(), - variations: Vec::new(), + global_variations: Vec::new(), + span_variations: Vec::new(), role: TextRole::Text, } } @@ -386,10 +388,43 @@ mod tests { assert_eq!(merged.size, 18 * 64); assert_eq!(merged.language, "ja"); assert_eq!(merged.features, [feature, span_feature]); - assert_eq!(merged.variations, [variation, span_variation]); + assert_eq!(merged.global_variations, [variation]); + assert_eq!(merged.span_variations, [span_variation]); assert_eq!(merged.role, TextRole::Formula); } + #[test] + fn variation_layers_merge_by_tag_in_global_system_span_order() { + let variation = |tag, value| { + FontVariation::try_new(crate::OpenTypeTag::try_new(tag).unwrap(), value).unwrap() + }; + let options = LayoutOptions::try_new(100.0, 16.0) + .unwrap() + .variation(variation("wght", 400.0)) + .variation(variation("wdth", 90.0)) + .variation(variation("wght", 500.0)); + let span = SpanStyle::new() + .variation(variation("wdth", 80.0)) + .variation(variation("wght", 700.0)); + let style = span_effective_style(&base_effective_style(&options), &span); + let (fonts, first, _) = fixture_fonts(); + let mut resource = fonts.get(first).unwrap().clone(); + resource.default_variations = vec![variation("wght", 600.0), variation("opsz", 12.0)]; + + let resolved = resolved_variations(&style, &resource); + assert_eq!( + resolved + .iter() + .map(|value| (value.tag().bytes(), value.value_26_6())) + .collect::>(), + [ + (*b"opsz", 12 * 64), + (*b"wdth", 80 * 64), + (*b"wght", 700 * 64), + ] + ); + } + #[test] fn frames_roles_scripts_and_shape_directions_cover_the_closed_tables() { assert_eq!(frame_for("日"), jlreq_core::Frame::FullEm); diff --git a/crates/jlreq/src/engine/layout.rs b/crates/jlreq/src/engine/layout.rs index 0ec6844..c291483 100644 --- a/crates/jlreq/src/engine/layout.rs +++ b/crates/jlreq/src/engine/layout.rs @@ -212,12 +212,13 @@ impl LayoutEngine { block_offset, options, ); - let consumed = paragraph_lines.len().max(1); + let next_block_offset = next_paragraph_block_offset( + ¶graph_lines, + block_offset, + options, + ); lines.extend(paragraph_lines); - let line_count = i32::try_from(consumed).unwrap_or(i32::MAX); - let block_advance = line_count - .saturating_mul(options.font_size.saturating_add(options.line_gap).max(0)); - block_offset = advance_block(block_offset, block_advance, options.writing_mode); + block_offset = next_block_offset; } call.diagnostics.sort_by_key(|diagnostic| { @@ -408,6 +409,9 @@ impl LayoutEngine { while index < graphemes.len() { if graphemes[index].is_tab { let item = &graphemes[index]; + let resource = fonts + .get(item.font_id) + .ok_or_else(|| LayoutError::invalid_document("font.unknown-id", None))?; clusters.push(PreparedCluster { range: item.range.clone(), advance: 0, @@ -415,6 +419,7 @@ impl LayoutEngine { frame: jlreq_core::Frame::Proportional, role: None, bidi_level: item.level.number(), + variations: resolved_variations(&item.effective, resource), glyphs: Vec::new(), }); index = index.saturating_add(1); @@ -434,6 +439,7 @@ impl LayoutEngine { let resource = fonts .get(first.font_id) .ok_or_else(|| LayoutError::invalid_document("font.unknown-id", None))?; + let variations = resolved_variations(&first.effective, resource); #[cfg(test)] call.charge_shape(); let raw = self.shape_font(ShapeRequest { @@ -444,7 +450,7 @@ impl LayoutEngine { direction: first.direction, language: &first.effective.language, features: &first.effective.features, - variations: &first.effective.variations, + variations: &variations, })?; call.used_fonts.insert(first.font_id); call.charge_glyphs(raw.len())?; @@ -452,10 +458,10 @@ impl LayoutEngine { source, run_range, raw, - first.effective.size, - first.effective.role, + &first.effective, first.level.number(), first.direction, + &variations, )); } Ok(PreparedText { clusters }) @@ -495,6 +501,7 @@ impl LayoutEngine { let resource = fonts .get(id) .ok_or_else(|| LayoutError::invalid_document("font.unknown-id", None))?; + let variations = resolved_variations(style, resource); #[cfg(test)] call.charge_shape(); let glyphs = self.shape_font(ShapeRequest { @@ -505,7 +512,7 @@ impl LayoutEngine { direction, language: &style.language, features: &style.features, - variations: &style.variations, + variations: &variations, })?; if !glyphs.is_empty() && glyphs.iter().all(|glyph| glyph.glyph_id != 0) { let selection = (id, false); diff --git a/crates/jlreq/src/engine/preparation.rs b/crates/jlreq/src/engine/preparation.rs index 5bdf9c6..90f7121 100644 --- a/crates/jlreq/src/engine/preparation.rs +++ b/crates/jlreq/src/engine/preparation.rs @@ -79,7 +79,8 @@ struct FontSelectionKey { direction: u8, language: String, features: Vec<([u8; 4], u32)>, - variations: Vec<([u8; 4], u32)>, + global_variations: Vec<([u8; 4], i32)>, + span_variations: Vec<([u8; 4], i32)>, } impl FontSelectionKey { @@ -107,23 +108,29 @@ impl FontSelectionKey { .iter() .map(|feature| (feature.tag().bytes(), feature.value())) .collect(), - variations: style - .variations + global_variations: style + .global_variations .iter() - .map(|variation| (variation.tag().bytes(), variation.value().to_bits())) + .map(|variation| (variation.tag().bytes(), variation.value_26_6())) + .collect(), + span_variations: style + .span_variations + .iter() + .map(|variation| (variation.tag().bytes(), variation.value_26_6())) .collect(), } } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] struct EffectiveStyle { families: Vec, font_style: FontStyle, size: i32, language: String, features: Vec, - variations: Vec, + global_variations: Vec, + span_variations: Vec, role: TextRole, } @@ -238,6 +245,7 @@ struct PreparedCluster { frame: jlreq_core::Frame, role: Option, bidi_level: u8, + variations: Arc<[FontVariation]>, glyphs: Vec, } @@ -387,7 +395,8 @@ fn base_effective_style(options: &LayoutOptions) -> EffectiveStyle { size: options.font_size, language: options.language.clone(), features: options.features.clone(), - variations: options.variations.clone(), + global_variations: options.variations.clone(), + span_variations: Vec::new(), role: TextRole::Text, } } @@ -401,8 +410,35 @@ fn span_effective_style(base: &EffectiveStyle, style: &SpanStyle) -> EffectiveSt result.language.clone_from(language); } result.features.extend_from_slice(&style.features); - result.variations.extend_from_slice(&style.variations); + result.span_variations.clone_from(&style.variations); result.role = style.role; result } +fn resolved_variations( + style: &EffectiveStyle, + resource: &FontResource, +) -> Arc<[FontVariation]> { + let capacity = style + .global_variations + .len() + .saturating_add(resource.default_variations().len()) + .saturating_add(style.span_variations.len()); + let mut merged = Vec::with_capacity(capacity); + overlay_variations(&mut merged, &style.global_variations); + overlay_variations(&mut merged, resource.default_variations()); + overlay_variations(&mut merged, &style.span_variations); + Arc::from(merged) +} + +fn overlay_variations(merged: &mut Vec, layer: &[FontVariation]) { + for variation in layer { + if let Some(index) = merged + .iter() + .position(|existing| existing.tag() == variation.tag()) + { + merged.remove(index); + } + merged.push(*variation); + } +} diff --git a/crates/jlreq/src/engine/result.rs b/crates/jlreq/src/engine/result.rs index 965b907..0f696b2 100644 --- a/crates/jlreq/src/engine/result.rs +++ b/crates/jlreq/src/engine/result.rs @@ -210,6 +210,7 @@ fn place_raw_glyph( offset_x, offset_y, font_size: cluster.size, + variations: Arc::clone(&cluster.variations), transform, bidi_level: cluster.bidi_level, writing_mode, @@ -313,6 +314,37 @@ fn advance_block(value: i32, amount: i32, mode: WritingMode) -> i32 { } } +fn next_paragraph_block_offset( + lines: &[TextLine], + current: i32, + options: &LayoutOptions, +) -> i32 { + if lines.is_empty() { + return advance_block( + current, + options.font_size.saturating_add(options.line_gap), + options.writing_mode, + ); + } + match options.writing_mode { + WritingMode::HorizontalTb => lines + .iter() + .map(|line| { + let (_, y, _, height) = line.bounds().as_26_6(); + y.saturating_add(height) + }) + .max() + .unwrap_or(current) + .saturating_add(options.line_gap), + WritingMode::VerticalRl => lines + .iter() + .map(|line| line.bounds().as_26_6().0) + .min() + .unwrap_or(current) + .saturating_sub(options.line_gap), + } +} + fn ranges_overlap(left: &Range, right: &Range) -> bool { left.start < right.end && right.start < left.end } @@ -324,4 +356,3 @@ fn diagnostic_severity(value: jlreq_core::Severity) -> DiagnosticSeverity { _ => DiagnosticSeverity::Warning, } } - diff --git a/crates/jlreq/src/engine/shaping.rs b/crates/jlreq/src/engine/shaping.rs index 4f4f390..20998f1 100644 --- a/crates/jlreq/src/engine/shaping.rs +++ b/crates/jlreq/src/engine/shaping.rs @@ -5,10 +5,10 @@ fn aggregate_run( source: &str, range: Range, glyphs: Vec, - size: i32, - role: TextRole, + style: &EffectiveStyle, bidi_level: u8, direction: Direction, + variations: &Arc<[FontVariation]>, ) -> Vec { let mut starts: Vec<_> = glyphs .iter() @@ -28,10 +28,11 @@ fn aggregate_run( result.push(PreparedCluster { range: start..end, advance: 0, - size, + size: style.size, frame: frame_for(piece), - role: classify_role(source, start..end, role), + role: classify_role(source, start..end, style.role), bidi_level, + variations: Arc::clone(variations), glyphs: Vec::new(), }); } @@ -146,4 +147,3 @@ fn shape_direction(mode: WritingMode, level: Level, script: ScriptClass) -> Dire Direction::LeftToRight } } - diff --git a/crates/jlreq/src/font.rs b/crates/jlreq/src/font.rs index b964d06..f419039 100644 --- a/crates/jlreq/src/font.rs +++ b/crates/jlreq/src/font.rs @@ -4,7 +4,12 @@ use std::fmt; use std::sync::Arc; -use crate::LayoutError; +#[cfg(feature = "system-fonts")] +use crate::OpenTypeTag; +#[cfg(feature = "system-fonts")] +use crate::units::quantize; +use crate::units::to_f32; +use crate::{FontVariation, LayoutError}; /// Stable identifier assigned by a [`FontLibrary`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -76,6 +81,70 @@ impl Default for FontStyle { } } +#[cfg(feature = "system-fonts")] +fn system_attributes(style: FontStyle) -> fontique::Attributes { + let slant = match style.slant() { + FontSlant::Normal => fontique::FontStyle::Normal, + FontSlant::Italic => fontique::FontStyle::Italic, + FontSlant::Oblique => fontique::FontStyle::Oblique(None), + }; + fontique::Attributes::new( + fontique::FontWidth::from_percentage(f32::from(style.width())), + slant, + fontique::FontWeight::new(f32::from(style.weight())), + ) +} + +/// Renderer-side synthetic styling requested for a selected font face. +/// +/// Variable-axis synthesis is exposed separately through +/// [`FontResource::default_variations`]. This value carries only operations a +/// renderer must apply outside normal variable-font instancing. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +#[non_exhaustive] +pub struct FontSynthesis { + embolden: bool, + skew: i32, +} + +impl FontSynthesis { + #[cfg(feature = "system-fonts")] + pub(crate) fn new(embolden: bool, skew: Option) -> Self { + Self { + embolden, + skew: skew.map_or(0, quantize), + } + } + + /// Whether the renderer should apply synthetic emboldening. + #[must_use] + pub const fn embolden(self) -> bool { + self.embolden + } + + /// Synthetic skew angle in degrees, when one is required. + #[must_use] + pub fn skew(self) -> Option { + (self.skew != 0).then(|| to_f32(self.skew)) + } + + /// Synthetic skew angle in signed 26.6 degrees. + #[must_use] + pub const fn skew_26_6(self) -> Option { + if self.skew == 0 { + None + } else { + Some(self.skew) + } + } + + /// Whether neither emboldening nor skew is required. + #[must_use] + pub const fn is_empty(self) -> bool { + !self.embolden && self.skew == 0 + } +} + /// Font bytes retained by a completed layout for renderer use. #[derive(Clone)] #[non_exhaustive] @@ -85,6 +154,8 @@ pub struct FontResource { pub(crate) face_index: u32, pub(crate) family: String, pub(crate) style: FontStyle, + pub(crate) default_variations: Vec, + pub(crate) synthesis: FontSynthesis, pub(crate) shaper_data: Arc, } @@ -95,6 +166,8 @@ impl PartialEq for FontResource { && self.bytes == other.bytes && self.family == other.family && self.style == other.style + && self.default_variations == other.default_variations + && self.synthesis == other.synthesis } } @@ -109,6 +182,8 @@ impl fmt::Debug for FontResource { .field("face_index", &self.face_index) .field("family", &self.family) .field("style", &self.style) + .field("default_variations", &self.default_variations) + .field("synthesis", &self.synthesis) .finish_non_exhaustive() } } @@ -143,6 +218,22 @@ impl FontResource { pub const fn style(&self) -> FontStyle { self.style } + + /// Variable-axis defaults selected with this face by the system matcher. + /// + /// Explicitly registered faces return an empty slice. During layout these + /// values override global settings with the same tag and are in turn + /// overridden by span-specific settings. + #[must_use] + pub fn default_variations(&self) -> &[FontVariation] { + &self.default_variations + } + + /// Synthetic emboldening or skew required to reproduce the selected face. + #[must_use] + pub const fn synthesis(&self) -> FontSynthesis { + self.synthesis + } } /// Ordered, in-memory font faces used for primary selection and fallback. @@ -203,12 +294,28 @@ impl FontLibrary { B: Into>, { let bytes = bytes.into(); + self.register_face_with_rendering( + bytes, + face_index, + family.into(), + style, + Vec::new(), + FontSynthesis::default(), + ) + } + + fn register_face_with_rendering( + &mut self, + bytes: Arc<[u8]>, + face_index: u32, + family: String, + style: FontStyle, + default_variations: Vec, + synthesis: FontSynthesis, + ) -> Result { let font = harfrust::FontRef::from_index(&bytes, face_index) .map_err(|_| LayoutError::invalid_font(face_index))?; - // Parse shaping-critical tables exactly once. Layout engines retain this immutable - // cache through the resource instead of reconstructing it on first use. let shaper_data = Arc::new(harfrust::ShaperData::new(&font)); - let ordinal = u32::try_from(self.fonts.len()) .map_err(|_| LayoutError::invalid_document("font.too-many-ids", None))?; let id = FontId(ordinal); @@ -216,8 +323,10 @@ impl FontLibrary { id, bytes, face_index, - family: family.into(), + family, style, + default_variations, + synthesis, shaper_data, }); self.fallback_order.push(id); @@ -326,14 +435,35 @@ impl FontLibrary { { let mut query = collection.query(&mut cache); query.set_families([family]); + query.set_attributes(system_attributes(style)); query.matches_with(|font| { - selected = Some((font.blob.as_ref().to_vec(), font.index)); + selected = Some(( + Arc::<[u8]>::from(font.blob.as_ref()), + font.index, + font.synthesis, + )); QueryStatus::Stop }); } - let (bytes, index) = selected + let (bytes, index, selected_synthesis) = selected .ok_or_else(|| LayoutError::invalid_document("font.system-family-not-found", None))?; - self.register_face(bytes, index, family, style) + let default_variations = selected_synthesis + .variation_settings() + .iter() + .map(|(tag, value)| { + FontVariation::try_new(OpenTypeTag::from_bytes(tag.to_be_bytes()), *value) + }) + .collect::, _>>()?; + let synthesis = + FontSynthesis::new(selected_synthesis.embolden(), selected_synthesis.skew()); + self.register_face_with_rendering( + bytes, + index, + family.to_owned(), + style, + default_variations, + synthesis, + ) } pub(crate) fn ordered_candidates( @@ -388,6 +518,8 @@ mod tests { face_index, family: family.to_owned(), style, + default_variations: Vec::new(), + synthesis: FontSynthesis::default(), shaper_data, } } @@ -456,4 +588,64 @@ mod tests { [FontId(0), FontId(1), FontId(2)] ); } + + #[test] + fn resource_equality_includes_every_rendering_field() { + let base = resource(0, 0, "Family", FontStyle::default()); + let mut changed = base.clone(); + assert_eq!(base, changed); + + changed.id = FontId(1); + assert_ne!(base, changed); + changed = base.clone(); + changed.face_index = 1; + assert_ne!(base, changed); + changed = base.clone(); + changed.bytes = Arc::from(font_test_data::TINOS_SUBSET); + assert_ne!(base, changed); + changed = base.clone(); + changed.family = "Other".into(); + assert_ne!(base, changed); + changed = base.clone(); + changed.style = FontStyle::new(700, 80, FontSlant::Italic); + assert_ne!(base, changed); + changed = base.clone(); + changed.default_variations.push( + FontVariation::try_new(crate::OpenTypeTag::try_new("wght").unwrap(), 650.0).unwrap(), + ); + assert_ne!(base, changed); + changed = base.clone(); + changed.synthesis = FontSynthesis { + embolden: true, + skew: 14 * 64, + }; + assert_ne!(base, changed); + } + + #[test] + fn synthesis_accessors_preserve_fixed_renderer_state() { + let synthesis = FontSynthesis { + embolden: true, + skew: 14 * 64, + }; + assert!(synthesis.embolden()); + assert_eq!(synthesis.skew(), Some(14.0)); + assert_eq!(synthesis.skew_26_6(), Some(14 * 64)); + assert!(!synthesis.is_empty()); + assert!(FontSynthesis::default().is_empty()); + } + + #[cfg(feature = "system-fonts")] + #[test] + fn fontique_attributes_receive_weight_width_and_slant_without_loss() { + let attributes = system_attributes(FontStyle::new(625, 87, FontSlant::Oblique)); + assert_eq!(attributes.weight.value().to_bits(), 625.0_f32.to_bits()); + assert_eq!(attributes.width.percentage().to_bits(), 87.0_f32.to_bits()); + assert_eq!(attributes.style, fontique::FontStyle::Oblique(None)); + + let italic = system_attributes(FontStyle::new(400, 100, FontSlant::Italic)); + assert_eq!(italic.style, fontique::FontStyle::Italic); + let normal = system_attributes(FontStyle::default()); + assert_eq!(normal.style, fontique::FontStyle::Normal); + } } diff --git a/crates/jlreq/src/lib.rs b/crates/jlreq/src/lib.rs index c6bfc9e..d6a5b1c 100644 --- a/crates/jlreq/src/lib.rs +++ b/crates/jlreq/src/lib.rs @@ -19,7 +19,7 @@ pub use document::{ }; pub use engine::LayoutEngine; pub use error::{LayoutError, OptionKind, Resource}; -pub use font::{FontId, FontLibrary, FontResource, FontSlant, FontStyle}; +pub use font::{FontId, FontLibrary, FontResource, FontSlant, FontStyle, FontSynthesis}; pub use options::{ Alignment, BaseDirection, FontVariation, LayoutOptions, OpenTypeFeature, OpenTypeTag, ResourceLimits, WritingMode, diff --git a/crates/jlreq/src/options.rs b/crates/jlreq/src/options.rs index 865ff7b..027349f 100644 --- a/crates/jlreq/src/options.rs +++ b/crates/jlreq/src/options.rs @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: 2026 jlreq contributors // SPDX-License-Identifier: MIT OR Apache-2.0 -use crate::units::{finite, non_negative, positive}; +use crate::units::{finite, non_negative, positive, quantize, to_f32}; use crate::{LayoutError, OptionKind}; /// Physical writing mode requested from the high-level pipeline. @@ -68,18 +68,34 @@ pub enum BaseDirection { pub struct OpenTypeTag([u8; 4]); impl OpenTypeTag { - /// Validate an ASCII OpenType tag of exactly four bytes. + /// Validate an OpenType tag of exactly four printable ASCII bytes. + /// + /// Space padding is accepted only at the end, as required by the OpenType + /// specification for tags shorter than four visible characters. pub fn try_new(tag: &str) -> Result { let bytes = tag.as_bytes(); - if bytes.len() != 4 || !bytes.iter().all(u8::is_ascii_graphic) { + let printable = bytes.iter().all(|byte| matches!(byte, 0x20..=0x7e)); + let trailing_spaces_only = + bytes + .iter() + .position(|byte| *byte == b' ') + .is_none_or(|first_space| { + first_space > 0 && bytes[first_space..].iter().all(|byte| *byte == b' ') + }); + if bytes.len() != 4 || !printable || !trailing_spaces_only { return Err(LayoutError::invalid_option( OptionKind::Feature, - "an OpenType tag must contain exactly four printable ASCII bytes", + "an OpenType tag must contain four printable ASCII bytes with spaces only as trailing padding", )); } Ok(Self([bytes[0], bytes[1], bytes[2], bytes[3]])) } + #[cfg(feature = "system-fonts")] + pub(crate) const fn from_bytes(bytes: [u8; 4]) -> Self { + Self(bytes) + } + /// Tag bytes in network order. #[must_use] pub const fn bytes(self) -> [u8; 4] { @@ -115,18 +131,19 @@ impl OpenTypeFeature { } /// One variable-font axis value, quantized at the public boundary. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct FontVariation { tag: OpenTypeTag, - value: f32, + value: i32, } impl FontVariation { /// Validate and quantize a variation setting. pub fn try_new(tag: OpenTypeTag, value: f32) -> Result { + let value = finite(value, OptionKind::Variation)?; Ok(Self { tag, - value: finite(value, OptionKind::Variation)?, + value: quantize(value), }) } @@ -138,7 +155,13 @@ impl FontVariation { /// Quantized user-space axis value. #[must_use] - pub const fn value(self) -> f32 { + pub fn value(self) -> f32 { + to_f32(self.value) + } + + /// Quantized axis value in signed 26.6 fixed point. + #[must_use] + pub const fn value_26_6(self) -> i32 { self.value } } @@ -466,4 +489,38 @@ mod tests { .is_err() ); } + + #[test] + fn opentype_tags_accept_only_printable_bytes_and_trailing_padding() { + for valid in ["wght", "abc ", "a ", "~~~~"] { + assert_eq!( + OpenTypeTag::try_new(valid).unwrap().bytes(), + valid.as_bytes() + ); + } + for invalid in ["abc", "abcde", " abc", "a bc", " ", "ab\n ", "éab"] { + assert!(OpenTypeTag::try_new(invalid).is_err(), "{invalid:?}"); + } + } + + #[test] + fn font_variations_are_fixed_point_equal_and_hashable() { + use std::hash::{Hash, Hasher}; + + let tag = OpenTypeTag::try_new("wght").unwrap(); + let first = FontVariation::try_new(tag, 650.124).unwrap(); + let same_cell = FontVariation::try_new(tag, 650.125).unwrap(); + let different = FontVariation::try_new(tag, 650.14).unwrap(); + assert_eq!(first.value_26_6(), 41_608); + assert_eq!(first.value().to_bits(), 650.125_f32.to_bits()); + assert_eq!(first, same_cell); + assert_ne!(first, different); + let hash = |value: FontVariation| { + let mut state = std::collections::hash_map::DefaultHasher::new(); + value.hash(&mut state); + state.finish() + }; + assert_eq!(hash(first), hash(same_cell)); + assert_ne!(hash(first), hash(different)); + } } diff --git a/crates/jlreq/src/result.rs b/crates/jlreq/src/result.rs index 85fd746..3ac7841 100644 --- a/crates/jlreq/src/result.rs +++ b/crates/jlreq/src/result.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use std::ops::Range; +use std::sync::Arc; use crate::units::{finite, quantize, to_f32}; use crate::{FontId, FontResource, LayoutError, OptionKind, WritingMode}; @@ -108,6 +109,25 @@ impl Rect { && point.x <= self.x.saturating_add(self.width) && point.y <= self.y.saturating_add(self.height) } + + fn union(self, other: Self) -> Self { + let min_x = self.x.min(other.x); + let min_y = self.y.min(other.y); + let max_x = self + .x + .saturating_add(self.width) + .max(other.x.saturating_add(other.width)); + let max_y = self + .y + .saturating_add(self.height) + .max(other.y.saturating_add(other.height)); + Self::from_fixed( + min_x, + min_y, + max_x.saturating_sub(min_x), + max_y.saturating_sub(min_y), + ) + } } /// Local transform a renderer applies around a glyph origin. @@ -164,6 +184,7 @@ pub struct GlyphPlacement { pub(crate) offset_x: i32, pub(crate) offset_y: i32, pub(crate) font_size: i32, + pub(crate) variations: Arc<[crate::FontVariation]>, pub(crate) transform: GlyphTransform, pub(crate) bidi_level: u8, pub(crate) writing_mode: WritingMode, @@ -206,6 +227,19 @@ impl GlyphPlacement { Point::from_fixed(self.x, self.y) } + /// Physical draw origin after applying the shaper's glyph offset. + /// + /// Renderers should place the glyph outline at this point, then apply + /// [`Self::transform`]. [`Self::origin`] remains the unoffset advance-cell + /// origin. + #[must_use] + pub const fn draw_origin(&self) -> Point { + Point::from_fixed( + self.x.saturating_add(self.offset_x), + self.y.saturating_add(self.offset_y), + ) + } + /// Physical horizontal origin. #[must_use] pub fn x(&self) -> f32 { @@ -242,6 +276,26 @@ impl GlyphPlacement { to_f32(self.offset_y) } + /// Resolved font size used to shape this glyph. + #[must_use] + pub fn font_size(&self) -> f32 { + to_f32(self.font_size) + } + + /// Resolved font size in signed 26.6 fixed point. + #[must_use] + pub const fn font_size_26_6(&self) -> i32 { + self.font_size + } + + /// Effective variable-font settings used for shaping. + /// + /// The backing slice is shared by glyphs with the same resolved style. + #[must_use] + pub fn variations(&self) -> &[crate::FontVariation] { + &self.variations + } + /// `(x, y, advance_x, advance_y, offset_x, offset_y)` in raw 26.6 units. #[must_use] pub const fn geometry_26_6(&self) -> (i32, i32, i32, i32, i32, i32) { @@ -267,9 +321,13 @@ impl GlyphPlacement { self.bidi_level } - fn bounds(&self) -> Rect { - match self.writing_mode { - WritingMode::HorizontalTb => { + /// Physical layout-cell bounds, including advance space but not glyph ink. + /// + /// Rasterizer outline bounds may be smaller or may overhang this rectangle. + #[must_use] + pub fn cell_bounds(&self) -> Rect { + match (self.writing_mode, self.transform) { + (WritingMode::HorizontalTb, _) | (_, GlyphTransform::TateChuYoko) => { let width = self.advance_x.abs().max(1); Rect::from_fixed( self.x, @@ -278,7 +336,7 @@ impl GlyphPlacement { self.font_size, ) }, - WritingMode::VerticalRl => { + (WritingMode::VerticalRl, _) => { let height = self.advance_y.abs().max(1); Rect::from_fixed( self.x.saturating_sub(self.font_size), @@ -396,11 +454,38 @@ impl TextLine { self.writing_mode } + /// Physical layout-cell bounds for the line. + /// + /// These are not ink bounds. They retain whitespace and include the cell + /// bounds of ruby and other automatically positioned annotations. + #[must_use] + pub fn bounds(&self) -> Rect { + let base = match self.writing_mode { + WritingMode::HorizontalTb => Rect::from_fixed( + self.origin.x, + self.origin.y, + self.inline_extent, + self.block_extent, + ), + WritingMode::VerticalRl => Rect::from_fixed( + self.origin.x.saturating_sub(self.block_extent), + self.origin.y, + self.block_extent, + self.inline_extent, + ), + }; + let base = self.hit_bounds.map_or(base, |bounds| base.union(bounds)); + self.glyphs + .iter() + .map(GlyphPlacement::cell_bounds) + .fold(base, Rect::union) + } + pub(crate) fn hit_bounds_for(glyphs: &[GlyphPlacement]) -> Option { let mut bounds = glyphs .iter() .filter(|glyph| glyph.annotation.is_none()) - .map(GlyphPlacement::bounds); + .map(GlyphPlacement::cell_bounds); let first = bounds.next()?; let mut min_x = first.x; let mut min_y = first.y; @@ -489,6 +574,29 @@ impl TextLayout { &self.fonts } + /// Look up a retained font by its library identifier. + /// + /// Retained resources can have non-contiguous identifiers because a + /// layout owns only the faces used by its glyphs. + #[must_use] + pub fn font(&self, id: FontId) -> Option<&FontResource> { + self.fonts + .binary_search_by_key(&id, FontResource::id) + .ok() + .and_then(|index| self.fonts.get(index)) + } + + /// Union of all physical line-cell bounds, or `None` for an empty layout. + /// + /// This is a layout-cell boundary rather than an ink boundary: whitespace + /// and annotation cells are included. + #[must_use] + pub fn bounds(&self) -> Option { + let mut lines = self.lines.iter().map(TextLine::bounds); + let first = lines.next()?; + Some(lines.fold(first, Rect::union)) + } + /// Recoverable issues in source order. #[must_use] pub fn diagnostics(&self) -> &[Diagnostic] { @@ -503,44 +611,44 @@ impl TextLayout { /// Map a physical point to the nearest logical UTF-8 boundary. #[must_use] pub fn hit_test(&self, point: Point) -> HitTest { - let mut best: Option<(&GlyphPlacement, i64, bool)> = None; + let mut nearest_line: Option<(&TextLine, i64)> = None; for line in &self.lines { - let Some(line_bounds) = line.hit_bounds else { - continue; - }; - let left_or_right = axis_distance(point.x, line_bounds.x, line_bounds.width); - let above_or_below = axis_distance(point.y, line_bounds.y, line_bounds.height); - let lower_bound = left_or_right - .saturating_mul(left_or_right) - .saturating_add(above_or_below.saturating_mul(above_or_below)); - if best.is_some_and(|(_, distance, inside)| inside || lower_bound >= distance) { - continue; + let bounds = line.bounds(); + let distance = rect_distance(point, bounds); + if nearest_line.is_none_or(|(_, old)| distance < old) { + nearest_line = Some((line, distance)); } - for glyph in line - .glyphs - .iter() - .filter(|glyph| glyph.annotation.is_none()) + } + let Some((line, _)) = nearest_line else { + return HitTest { + byte_offset: 0, + affinity: Affinity::Downstream, + inside: false, + }; + }; + + let mut best: Option<(&GlyphPlacement, i64, bool)> = None; + for glyph in line + .glyphs + .iter() + .filter(|glyph| glyph.annotation.is_none()) + { + let bounds = glyph.cell_bounds(); + let inside = bounds.contains(point); + let distance = rect_distance(point, bounds); + if best.is_none_or(|(_, old, old_inside)| better_hit(inside, distance, old_inside, old)) { - let bounds = glyph.bounds(); - let inside = bounds.contains(point); - let dx = axis_distance(point.x, bounds.x, bounds.width); - let dy = axis_distance(point.y, bounds.y, bounds.height); - let distance = dx.saturating_mul(dx).saturating_add(dy.saturating_mul(dy)); - if best.is_none_or(|(_, old, old_inside)| { - better_hit(inside, distance, old_inside, old) - }) { - best = Some((glyph, distance, inside)); - } + best = Some((glyph, distance, inside)); } } let Some((glyph, _, inside)) = best else { return HitTest { - byte_offset: 0, + byte_offset: line.range.start, affinity: Affinity::Downstream, - inside: false, + inside: line.bounds().contains(point), }; }; - let bounds = glyph.bounds(); + let bounds = glyph.cell_bounds(); let after_visual_midpoint = is_after_visual_midpoint(point, bounds, glyph.writing_mode); let logical_after = is_logically_after(after_visual_midpoint, glyph.bidi_level); HitTest { @@ -563,72 +671,41 @@ impl TextLayout { Ok(self.hit_test(Point::try_new(x, y)?)) } - /// Return a one-subpixel caret at the closest rendered edge of a UTF-8 boundary. + /// Return a one-subpixel caret for one logical side of a UTF-8 boundary. + /// + /// Affinity disambiguates wrapping, paragraph breaks, and bidirectional + /// boundaries. Passing back both fields of [`HitTest`] reproduces the same + /// visual edge. #[must_use] - pub fn caret_rect(&self, byte_offset: usize) -> Option { + pub fn caret_rect(&self, byte_offset: usize, affinity: Affinity) -> Option { if !is_valid_caret_offset(&self.source, byte_offset) { return None; } - let first_line = self - .lines - .partition_point(|line| line.range.end < byte_offset); - for line in &self.lines[first_line..] { - if line.range.start > byte_offset { - break; - } + for line in &self.lines { let mut candidates = line .glyphs .iter() .filter(|glyph| glyph.annotation.is_none()) - .filter(|glyph| glyph_touches_boundary(&glyph.source_range, byte_offset)); - if let Some(glyph) = candidates.next() { - let bounds = glyph.bounds(); - let at_start = glyph.source_range.start == byte_offset; - let visual_end = is_visual_end(at_start, glyph.bidi_level); - return Some(match glyph.writing_mode { - WritingMode::HorizontalTb => Rect::from_fixed( - if visual_end { - bounds.x.saturating_add(bounds.width) - } else { - bounds.x - }, - bounds.y, - 1, - bounds.height, - ), - WritingMode::VerticalRl => Rect::from_fixed( - bounds.x, - if visual_end { - bounds.y.saturating_add(bounds.height) - } else { - bounds.y - }, - bounds.width, - 1, - ), + .filter(|glyph| match affinity { + Affinity::Upstream => glyph.source_range.start == byte_offset, + Affinity::Downstream => glyph.source_range.end == byte_offset, }); - } - if line.range.start == byte_offset && line.glyphs.is_empty() { - return Some(match line.writing_mode { - WritingMode::HorizontalTb => Rect::from_fixed( - line.origin.x, - line.origin.y.saturating_sub(line.block_extent), - 1, - line.block_extent, - ), - WritingMode::VerticalRl => Rect::from_fixed( - line.origin.x.saturating_sub(line.block_extent), - line.origin.y, - line.block_extent, - 1, - ), + if let Some(first) = candidates.next() { + let bounds = candidates.fold(first.cell_bounds(), |bounds, glyph| { + bounds.union(glyph.cell_bounds()) }); + let visual_end = is_visual_end(affinity == Affinity::Upstream, first.bidi_level); + return Some(caret_for_bounds(bounds, visual_end, first.writing_mode)); + } + let has_main_glyph = line.glyphs.iter().any(|glyph| glyph.annotation.is_none()); + if line.range.start == byte_offset && !has_main_glyph { + return Some(empty_line_caret(line)); } } None } - /// Return one merged selection rectangle per touched line. + /// Return one rectangle per visually contiguous selected run on each line. #[must_use] pub fn selection_rects(&self, range: Range) -> Vec { if !is_valid_selection_range(&self.source, &range) { @@ -642,31 +719,22 @@ impl TextLayout { if line.range.start >= range.end { break; } - let mut boxes = line + let mut current = None; + for glyph in line .glyphs .iter() .filter(|glyph| glyph.annotation.is_none()) - .filter(|glyph| ranges_overlap(&glyph.source_range, &range)) - .map(GlyphPlacement::bounds); - let Some(first) = boxes.next() else { - continue; - }; - let mut min_x = first.x; - let mut min_y = first.y; - let mut max_x = first.x.saturating_add(first.width); - let mut max_y = first.y.saturating_add(first.height); - for rect in boxes { - min_x = min_x.min(rect.x); - min_y = min_y.min(rect.y); - max_x = max_x.max(rect.x.saturating_add(rect.width)); - max_y = max_y.max(rect.y.saturating_add(rect.height)); + { + if ranges_overlap(&glyph.source_range, &range) { + let bounds = glyph.cell_bounds(); + current = Some(current.map_or(bounds, |rect: Rect| rect.union(bounds))); + } else if let Some(bounds) = current.take() { + result.push(bounds); + } + } + if let Some(bounds) = current { + result.push(bounds); } - result.push(Rect::from_fixed( - min_x, - min_y, - max_x.saturating_sub(min_x), - max_y.saturating_sub(min_y), - )); } result } @@ -679,6 +747,12 @@ fn axis_distance(value: i32, start: i32, extent: i32) -> i64 { i64::from(before.max(after)) } +fn rect_distance(point: Point, bounds: Rect) -> i64 { + let dx = axis_distance(point.x, bounds.x, bounds.width); + let dy = axis_distance(point.y, bounds.y, bounds.height); + dx.saturating_mul(dx).saturating_add(dy.saturating_mul(dy)) +} + fn better_hit(inside: bool, distance: i64, old_inside: bool, old_distance: i64) -> bool { (inside && !old_inside) || (inside == old_inside && distance < old_distance) } @@ -698,14 +772,43 @@ fn is_valid_caret_offset(source: &str, offset: usize) -> bool { offset <= source.len() && source.is_char_boundary(offset) } -fn glyph_touches_boundary(range: &Range, offset: usize) -> bool { - range.start == offset || range.end == offset -} - fn is_visual_end(at_start: bool, bidi_level: u8) -> bool { at_start == (bidi_level % 2 == 1) } +fn caret_for_bounds(bounds: Rect, visual_end: bool, mode: WritingMode) -> Rect { + match mode { + WritingMode::HorizontalTb => Rect::from_fixed( + if visual_end { + bounds.x.saturating_add(bounds.width) + } else { + bounds.x + }, + bounds.y, + 1, + bounds.height, + ), + WritingMode::VerticalRl => Rect::from_fixed( + bounds.x, + if visual_end { + bounds.y.saturating_add(bounds.height) + } else { + bounds.y + }, + bounds.width, + 1, + ), + } +} + +fn empty_line_caret(line: &TextLine) -> Rect { + let bounds = line.bounds(); + match line.writing_mode { + WritingMode::HorizontalTb => Rect::from_fixed(line.origin.x, bounds.y, 1, bounds.height), + WritingMode::VerticalRl => Rect::from_fixed(bounds.x, line.origin.y, bounds.width, 1), + } +} + fn is_valid_selection_range(source: &str, range: &Range) -> bool { if range.start >= range.end { return false; @@ -749,6 +852,11 @@ mod tests { offset_x: 96, offset_y: -128, font_size: 192, + variations: Arc::from([crate::FontVariation::try_new( + crate::OpenTypeTag::try_new("wght").unwrap(), + 650.0, + ) + .unwrap()]), transform: GlyphTransform::RotateClockwise, bidi_level: 2, writing_mode: mode, @@ -756,14 +864,24 @@ mod tests { } fn linear_hit_test(layout: &TextLayout, point: Point) -> HitTest { - let mut best: Option<(&GlyphPlacement, i64, bool)> = None; - for glyph in layout + let Some(line) = layout .lines .iter() - .flat_map(|line| &line.glyphs) + .min_by_key(|line| rect_distance(point, line.bounds())) + else { + return HitTest { + byte_offset: 0, + affinity: Affinity::Downstream, + inside: false, + }; + }; + let mut best: Option<(&GlyphPlacement, i64, bool)> = None; + for glyph in line + .glyphs + .iter() .filter(|glyph| glyph.annotation.is_none()) { - let bounds = glyph.bounds(); + let bounds = glyph.cell_bounds(); let inside = bounds.contains(point); let dx = axis_distance(point.x, bounds.x, bounds.width); let dy = axis_distance(point.y, bounds.y, bounds.height); @@ -775,12 +893,12 @@ mod tests { } let Some((glyph, _, inside)) = best else { return HitTest { - byte_offset: 0, + byte_offset: line.range.start, affinity: Affinity::Downstream, - inside: false, + inside: line.bounds().contains(point), }; }; - let bounds = glyph.bounds(); + let bounds = glyph.cell_bounds(); let after_visual_midpoint = is_after_visual_midpoint(point, bounds, glyph.writing_mode); let logical_after = is_logically_after(after_visual_midpoint, glyph.bidi_level); HitTest { @@ -798,7 +916,11 @@ mod tests { } } - fn linear_caret_rect(layout: &TextLayout, byte_offset: usize) -> Option { + fn linear_caret_rect( + layout: &TextLayout, + byte_offset: usize, + affinity: Affinity, + ) -> Option { if !is_valid_caret_offset(&layout.source, byte_offset) { return None; } @@ -807,10 +929,13 @@ mod tests { .glyphs .iter() .filter(|glyph| glyph.annotation.is_none()) - .filter(|glyph| glyph_touches_boundary(&glyph.source_range, byte_offset)); + .filter(|glyph| match affinity { + Affinity::Upstream => glyph.source_range.start == byte_offset, + Affinity::Downstream => glyph.source_range.end == byte_offset, + }); if let Some(glyph) = candidates.next() { - let bounds = glyph.bounds(); - let at_start = glyph.source_range.start == byte_offset; + let bounds = glyph.cell_bounds(); + let at_start = affinity == Affinity::Upstream; let visual_end = is_visual_end(at_start, glyph.bidi_level); return Some(match glyph.writing_mode { WritingMode::HorizontalTb => Rect::from_fixed( @@ -835,21 +960,10 @@ mod tests { ), }); } - if line.range.start == byte_offset && line.glyphs.is_empty() { - return Some(match line.writing_mode { - WritingMode::HorizontalTb => Rect::from_fixed( - line.origin.x, - line.origin.y.saturating_sub(line.block_extent), - 1, - line.block_extent, - ), - WritingMode::VerticalRl => Rect::from_fixed( - line.origin.x.saturating_sub(line.block_extent), - line.origin.y, - line.block_extent, - 1, - ), - }); + if line.range.start == byte_offset + && !line.glyphs.iter().any(|glyph| glyph.annotation.is_none()) + { + return Some(empty_line_caret(line)); } } None @@ -861,31 +975,22 @@ mod tests { } let mut result = Vec::new(); for line in &layout.lines { - let mut boxes = line + let mut current = None; + for glyph in line .glyphs .iter() .filter(|glyph| glyph.annotation.is_none()) - .filter(|glyph| ranges_overlap(&glyph.source_range, &range)) - .map(GlyphPlacement::bounds); - let Some(first) = boxes.next() else { - continue; - }; - let mut min_x = first.x; - let mut min_y = first.y; - let mut max_x = first.x.saturating_add(first.width); - let mut max_y = first.y.saturating_add(first.height); - for rect in boxes { - min_x = min_x.min(rect.x); - min_y = min_y.min(rect.y); - max_x = max_x.max(rect.x.saturating_add(rect.width)); - max_y = max_y.max(rect.y.saturating_add(rect.height)); + { + if ranges_overlap(&glyph.source_range, &range) { + let bounds = glyph.cell_bounds(); + current = Some(current.map_or(bounds, |rect: Rect| rect.union(bounds))); + } else if let Some(bounds) = current.take() { + result.push(bounds); + } + } + if let Some(bounds) = current { + result.push(bounds); } - result.push(Rect::from_fixed( - min_x, - min_y, - max_x.saturating_sub(min_x), - max_y.saturating_sub(min_y), - )); } result } @@ -957,9 +1062,6 @@ mod tests { assert!(is_valid_caret_offset(source, 2)); assert!(is_valid_caret_offset(source, source.len())); assert!(!is_valid_caret_offset(source, source.len() + 1)); - assert!(glyph_touches_boundary(&(2..5), 2)); - assert!(glyph_touches_boundary(&(2..5), 5)); - assert!(!glyph_touches_boundary(&(2..5), 3)); assert!(!is_visual_end(true, 0)); assert!(is_visual_end(false, 0)); assert!(is_visual_end(true, 1)); @@ -987,19 +1089,27 @@ mod tests { assert_eq!(annotation.range(), 7..11); assert_eq!(horizontal.origin().x_26_6(), 128); assert_eq!(horizontal.origin().y_26_6(), 320); + assert_eq!(horizontal.draw_origin().x_26_6(), 224); + assert_eq!(horizontal.draw_origin().y_26_6(), 192); assert_float(horizontal.x(), 2.0); assert_float(horizontal.y(), 5.0); assert_float(horizontal.advance_x(), 4.0); assert_float(horizontal.advance_y(), -6.0); assert_float(horizontal.offset_x(), 1.5); assert_float(horizontal.offset_y(), -2.0); + assert_float(horizontal.font_size(), 3.0); + assert_eq!(horizontal.font_size_26_6(), 192); + assert_eq!(horizontal.variations()[0].value_26_6(), 650 * 64); assert_eq!(horizontal.geometry_26_6(), (128, 320, 256, -384, 96, -128)); assert_eq!(horizontal.transform(), GlyphTransform::RotateClockwise); assert_eq!(horizontal.bidi_level(), 2); - assert_eq!(horizontal.bounds().as_26_6(), (128, 128, 256, 192)); + assert_eq!(horizontal.cell_bounds().as_26_6(), (128, 128, 256, 192)); let vertical = glyph(WritingMode::VerticalRl); - assert_eq!(vertical.bounds().as_26_6(), (-64, 320, 192, 384)); + assert_eq!(vertical.cell_bounds().as_26_6(), (-64, 320, 192, 384)); + let mut tate_chu_yoko = vertical; + tate_chu_yoko.transform = GlyphTransform::TateChuYoko; + assert_eq!(tate_chu_yoko.cell_bounds().as_26_6(), (128, 128, 256, 192)); } #[test] @@ -1037,6 +1147,7 @@ mod tests { assert_float(line.block_extent(), 3.0); assert_eq!(line.writing_mode(), WritingMode::HorizontalTb); assert_eq!(line.glyphs().len(), 1); + assert_eq!(line.bounds().as_26_6(), (32, 64, 448, 256)); let layout = TextLayout { source: "abXYZq".to_owned(), @@ -1045,6 +1156,7 @@ mod tests { diagnostics: vec![diagnostic], writing_mode: WritingMode::HorizontalTb, }; + assert_eq!(layout.bounds().unwrap().as_26_6(), (32, 64, 448, 256)); let before = layout.hit_test(Point::from_fixed(160, 200)); assert_eq!(before.byte_offset(), 2); assert_eq!(before.affinity(), Affinity::Upstream); @@ -1053,8 +1165,17 @@ mod tests { assert_eq!(after.byte_offset(), 5); assert_eq!(after.affinity(), Affinity::Downstream); assert!(after.is_inside()); - assert_eq!(layout.caret_rect(2).unwrap().as_26_6(), (128, 128, 1, 192)); - assert_eq!(layout.caret_rect(5).unwrap().as_26_6(), (384, 128, 1, 192)); + assert_eq!( + layout.caret_rect(2, Affinity::Upstream).unwrap().as_26_6(), + (128, 128, 1, 192) + ); + assert_eq!( + layout + .caret_rect(5, Affinity::Downstream) + .unwrap() + .as_26_6(), + (384, 128, 1, 192) + ); assert_eq!( layout.selection_rects(2..5)[0].as_26_6(), (128, 128, 256, 192) @@ -1064,8 +1185,14 @@ mod tests { let mut rtl = layout.clone(); rtl.lines[0].glyphs[0].bidi_level = 1; - assert_eq!(rtl.caret_rect(2).unwrap().as_26_6(), (384, 128, 1, 192)); - assert_eq!(rtl.caret_rect(5).unwrap().as_26_6(), (128, 128, 1, 192)); + assert_eq!( + rtl.caret_rect(2, Affinity::Upstream).unwrap().as_26_6(), + (384, 128, 1, 192) + ); + assert_eq!( + rtl.caret_rect(5, Affinity::Downstream).unwrap().as_26_6(), + (128, 128, 1, 192) + ); let empty_horizontal = TextLayout { source: "A".into(), @@ -1083,18 +1210,214 @@ mod tests { writing_mode: WritingMode::HorizontalTb, }; assert_eq!( - empty_horizontal.caret_rect(0).unwrap().as_26_6(), - (320, 448, 1, 192) + empty_horizontal + .caret_rect(0, Affinity::Downstream) + .unwrap() + .as_26_6(), + (320, 640, 1, 192) + ); + assert!( + empty_horizontal + .caret_rect(1, Affinity::Downstream) + .is_none() ); - assert!(empty_horizontal.caret_rect(1).is_none()); let mut empty_vertical = empty_horizontal; empty_vertical.lines[0].writing_mode = WritingMode::VerticalRl; assert_eq!( - empty_vertical.caret_rect(0).unwrap().as_26_6(), + empty_vertical + .caret_rect(0, Affinity::Downstream) + .unwrap() + .as_26_6(), (128, 640, 192, 1) ); } + #[test] + fn affinity_round_trips_wrapped_and_bidi_visual_edges_and_empty_lines() { + let mut top = glyph(WritingMode::HorizontalTb); + top.annotation = None; + top.source_range = 0..1; + top.x = 0; + top.y = 64; + top.advance_x = 64; + top.font_size = 64; + top.bidi_level = 0; + let mut bottom = top.clone(); + bottom.source_range = 1..2; + bottom.y = 192; + let top_glyphs = vec![top]; + let bottom_glyphs = vec![bottom]; + let layout = TextLayout { + source: "ab\n".into(), + lines: vec![ + TextLine { + range: 0..1, + origin: Point::from_fixed(0, 0), + inline_extent: 64, + block_extent: 64, + writing_mode: WritingMode::HorizontalTb, + hit_bounds: TextLine::hit_bounds_for(&top_glyphs), + glyphs: top_glyphs, + }, + TextLine { + range: 1..2, + origin: Point::from_fixed(0, 128), + inline_extent: 64, + block_extent: 64, + writing_mode: WritingMode::HorizontalTb, + hit_bounds: TextLine::hit_bounds_for(&bottom_glyphs), + glyphs: bottom_glyphs, + }, + TextLine { + range: 3..3, + origin: Point::from_fixed(0, 256), + inline_extent: 0, + block_extent: 64, + writing_mode: WritingMode::HorizontalTb, + hit_bounds: None, + glyphs: Vec::new(), + }, + ], + fonts: Vec::new(), + diagnostics: Vec::new(), + writing_mode: WritingMode::HorizontalTb, + }; + + let wrapped_end = layout.hit_test(Point::from_fixed(48, 32)); + assert_eq!( + (wrapped_end.byte_offset(), wrapped_end.affinity()), + (1, Affinity::Downstream) + ); + assert_eq!( + layout + .caret_rect(wrapped_end.byte_offset(), wrapped_end.affinity()) + .unwrap() + .as_26_6(), + (64, 0, 1, 64) + ); + let wrapped_start = layout.hit_test(Point::from_fixed(16, 160)); + assert_eq!( + (wrapped_start.byte_offset(), wrapped_start.affinity()), + (1, Affinity::Upstream) + ); + assert_eq!( + layout + .caret_rect(wrapped_start.byte_offset(), wrapped_start.affinity()) + .unwrap() + .as_26_6(), + (0, 128, 1, 64) + ); + let empty = layout.hit_test(Point::from_fixed(0, 280)); + assert_eq!(empty.byte_offset(), 3); + assert_eq!(empty.affinity(), Affinity::Downstream); + assert_eq!( + layout + .caret_rect(empty.byte_offset(), empty.affinity()) + .unwrap() + .as_26_6(), + (0, 256, 1, 64) + ); + + let mut visual = Vec::new(); + for (position, (range, level)) in [(0..1, 0), (3..4, 1), (2..3, 1), (1..2, 1), (4..5, 0)] + .into_iter() + .enumerate() + { + let mut placed = glyph(WritingMode::HorizontalTb); + placed.annotation = None; + placed.source_range = range; + placed.x = i32::try_from(position).unwrap().saturating_mul(64); + placed.y = 64; + placed.advance_x = 64; + placed.font_size = 64; + placed.bidi_level = level; + visual.push(placed); + } + let bidi_line = TextLine { + range: 0..5, + origin: Point::from_fixed(0, 0), + inline_extent: 320, + block_extent: 64, + writing_mode: WritingMode::HorizontalTb, + hit_bounds: TextLine::hit_bounds_for(&visual), + glyphs: visual, + }; + let bidi = TextLayout { + source: "abcde".into(), + lines: vec![bidi_line], + fonts: Vec::new(), + diagnostics: Vec::new(), + writing_mode: WritingMode::HorizontalTb, + }; + for glyph in bidi.glyphs() { + let bounds = glyph.cell_bounds(); + for (x, expected) in [ + (bounds.x.saturating_add(16), bounds.x), + ( + bounds.x.saturating_add(48), + bounds.x.saturating_add(bounds.width), + ), + ] { + let hit = bidi.hit_test(Point::from_fixed(x, 32)); + assert_eq!( + bidi.caret_rect(hit.byte_offset(), hit.affinity()) + .unwrap() + .as_26_6() + .0, + expected + ); + } + } + } + + #[test] + fn bidi_selection_returns_only_visually_contiguous_runs() { + let mut visual = Vec::new(); + for (position, range) in [0..1, 3..4, 2..3, 1..2, 4..5].into_iter().enumerate() { + let mut placed = glyph(WritingMode::HorizontalTb); + placed.annotation = None; + placed.source_range = range; + placed.x = i32::try_from(position).unwrap().saturating_mul(64); + placed.y = 64; + placed.advance_x = 64; + placed.font_size = 64; + visual.push(placed); + } + let line = TextLine { + range: 0..5, + origin: Point::from_fixed(0, 0), + inline_extent: 320, + block_extent: 64, + writing_mode: WritingMode::HorizontalTb, + hit_bounds: TextLine::hit_bounds_for(&visual), + glyphs: visual, + }; + let layout = TextLayout { + source: "abcde".into(), + lines: vec![line], + fonts: Vec::new(), + diagnostics: Vec::new(), + writing_mode: WritingMode::HorizontalTb, + }; + + assert_eq!( + layout + .selection_rects(0..2) + .iter() + .map(|rect| rect.as_26_6()) + .collect::>(), + [(0, 0, 64, 64), (192, 0, 64, 64)] + ); + assert_eq!( + layout.selection_rects(1..4), + [Rect::from_fixed(64, 0, 192, 64)] + ); + assert_eq!( + layout.selection_rects(0..5), + [Rect::from_fixed(0, 0, 320, 64)] + ); + } + #[test] fn indexed_result_queries_match_full_linear_scans_across_lines() { let mut lines = Vec::new(); @@ -1136,10 +1459,12 @@ mod tests { } } for offset in 0..=layout.source.len() { - assert_eq!( - layout.caret_rect(offset), - linear_caret_rect(&layout, offset) - ); + for affinity in [Affinity::Upstream, Affinity::Downstream] { + assert_eq!( + layout.caret_rect(offset, affinity), + linear_caret_rect(&layout, offset, affinity) + ); + } } for range in [0..1, 0..2, 1..3, 2..4, 3..6, 5..6] { assert_eq!( diff --git a/crates/jlreq/tests/high_level.rs b/crates/jlreq/tests/high_level.rs index 9e28f99..47e9031 100644 --- a/crates/jlreq/tests/high_level.rs +++ b/crates/jlreq/tests/high_level.rs @@ -122,6 +122,24 @@ fn bidi_fallback_and_missing_glyphs_preserve_source_ranges() -> Result<(), Box Result<(), Box> { + let (fonts, arabic, emoji) = fixture_fonts()?; + let layout = jlreq::layout("مرحبا", &fonts, LayoutOptions::try_new(240.0, 18.0)?)?; + assert_eq!(layout.fonts().len(), 1); + assert_eq!(layout.fonts()[0].id(), arabic); + assert!(layout.font(emoji).is_none()); + for glyph in layout.glyphs() { + let resource = layout + .font(glyph.font_id()) + .ok_or_else(|| std::io::Error::other("glyph font was not retained"))?; + assert_eq!(resource.id(), arabic); + assert_eq!(resource.bytes(), rwml_fonts::noto_sans_arabic_subset()); + assert_eq!(resource.face_index(), 0); + } + Ok(()) +} + #[test] fn paragraph_breaks_tabs_and_empty_input_are_defined() -> Result<(), Box> { let (fonts, _, _) = fixture_fonts()?; @@ -176,6 +194,116 @@ fn ttc_variations_and_features_reach_the_shaper() -> Result<(), Box> Ok(()) } +#[test] +fn resolved_size_variations_and_draw_cells_match_shaping_runs() -> Result<(), Box> { + let mut fonts = FontLibrary::new(); + let id = fonts.register_face( + bytes(font_test_data::VAZIRMATN_VAR), + 0, + "Vazirmatn", + FontStyle::default(), + )?; + let global = FontVariation::try_new(OpenTypeTag::try_new("wght")?, 450.0)?; + let span_value = FontVariation::try_new(OpenTypeTag::try_new("wght")?, 725.0)?; + let mut builder = DocumentBuilder::new("abc"); + builder.span( + 0..3, + SpanStyle::new().font_size(20.0)?.variation(span_value), + )?; + let layout = jlreq::layout_document( + &builder.build()?, + &fonts, + LayoutOptions::try_new(240.0, 16.0)?.variation(global), + )?; + let glyphs = layout.glyphs().collect::>(); + assert!(glyphs.len() >= 2); + let shared_axes = glyphs[0].variations().as_ptr(); + for glyph in glyphs { + assert_eq!(glyph.font_id(), id); + assert_eq!(glyph.font_size().to_bits(), 20.0_f32.to_bits()); + assert_eq!(glyph.font_size_26_6(), 20 * 64); + assert_eq!(glyph.variations(), [span_value]); + assert_eq!(glyph.variations().as_ptr(), shared_axes); + assert_eq!( + glyph.draw_origin().x_26_6(), + glyph + .origin() + .x_26_6() + .saturating_add(glyph.geometry_26_6().4) + ); + assert_eq!( + glyph.draw_origin().y_26_6(), + glyph + .origin() + .y_26_6() + .saturating_add(glyph.geometry_26_6().5) + ); + let (_, _, width, height) = glyph.cell_bounds().as_26_6(); + assert!(width > 0 && height > 0); + } + let resource = layout + .font(id) + .ok_or_else(|| std::io::Error::other("variable font was not retained"))?; + assert!(resource.default_variations().is_empty()); + assert!(resource.synthesis().is_empty()); + assert!(layout.bounds().is_some()); + Ok(()) +} + +#[test] +fn paragraph_progress_uses_actual_line_cells_and_applies_gap_once() -> Result<(), Box> { + let (fonts, _, _) = fixture_fonts()?; + let gap = 3 * 64; + for mode in [WritingMode::HorizontalTb, WritingMode::VerticalRl] { + let plain = jlreq::layout( + "A\nB", + &fonts, + LayoutOptions::try_new(240.0, 16.0)? + .writing_mode(mode) + .line_gap(3.0)?, + )?; + assert_eq!(plain.lines().len(), 2); + if mode == WritingMode::HorizontalTb { + assert_eq!( + plain.lines()[1] + .origin() + .y_26_6() + .saturating_sub(plain.lines()[0].origin().y_26_6()), + 19 * 64 + ); + } else { + assert_eq!( + plain.lines()[0] + .origin() + .x_26_6() + .saturating_sub(plain.lines()[1].origin().x_26_6()), + 19 * 64 + ); + } + + let mut builder = DocumentBuilder::new("漢注\n字"); + builder.span(0..6, SpanStyle::new().font_size(40.0)?)?; + builder.group_ruby(0..3, "かん")?; + builder.warichu(3..6)?; + let layout = jlreq::layout_document( + &builder.build()?, + &fonts, + LayoutOptions::try_new(240.0, 16.0)? + .writing_mode(mode) + .line_gap(3.0)?, + )?; + assert_eq!(layout.lines().len(), 2); + let first = layout.lines()[0].bounds().as_26_6(); + let second = layout.lines()[1].bounds().as_26_6(); + if mode == WritingMode::HorizontalTb { + assert!(second.1 >= first.1.saturating_add(first.3).saturating_add(gap)); + } else { + assert!(second.0.saturating_add(second.2) <= first.0.saturating_sub(gap)); + } + } + Ok(()) +} + #[test] fn all_typed_constructs_and_ruby_modes_are_automatically_shaped() -> Result<(), Box> { let (fonts, _, _) = fixture_fonts()?; @@ -274,7 +402,11 @@ fn hit_caret_and_selection_round_trip_in_both_writing_modes() -> Result<(), Box< .ok_or_else(|| std::io::Error::other("fixture produced no glyph"))?; let hit = layout.hit_test(first.origin()); assert!(hit.byte_offset() <= layout.source().len()); - assert!(layout.caret_rect(hit.byte_offset()).is_some()); + assert!( + layout + .caret_rect(hit.byte_offset(), hit.affinity()) + .is_some() + ); assert!(!layout.selection_rects(0..layout.source().len()).is_empty()); } Ok(()) @@ -363,7 +495,10 @@ fn public_configuration_and_font_metadata_are_observable() -> Result<(), Box Result<(), Box> { let tag = OpenTypeTag::try_new("kern")?; assert_eq!(tag.bytes(), *b"kern"); + assert_eq!(OpenTypeTag::try_new("abc ")?.bytes(), *b"abc "); assert!(OpenTypeTag::try_new("bad").is_err()); + assert!(OpenTypeTag::try_new("a bc").is_err()); + assert!(OpenTypeTag::try_new(" abc").is_err()); assert!(OpenTypeTag::try_new("a\ncd").is_err()); let feature = OpenTypeFeature::new(tag, 7); assert_eq!((feature.tag(), feature.value()), (tag, 7)); @@ -760,6 +895,7 @@ fn renderer_geometry_and_interaction_accessors_are_complete() -> Result<(), Box< line.origin(), line.inline_extent(), line.block_extent(), + line.bounds(), line.writing_mode(), line.glyphs().len(), ); @@ -774,6 +910,11 @@ fn renderer_geometry_and_interaction_accessors_are_complete() -> Result<(), Box< glyph.advance_y(), glyph.offset_x(), glyph.offset_y(), + glyph.draw_origin(), + glyph.font_size(), + glyph.font_size_26_6(), + glyph.variations().len(), + glyph.cell_bounds(), glyph.geometry_26_6(), ); if let Some(annotation) = glyph.annotation() { @@ -784,6 +925,7 @@ fn renderer_geometry_and_interaction_accessors_are_complete() -> Result<(), Box< assert_eq!(diagnostic.severity(), DiagnosticSeverity::Warning); let _ = (diagnostic.range(), diagnostic.message(), diagnostic.jlreq()); } + let _ = layout.bounds(); for test_point in [ Point::try_new(-10_000.0, -10_000.0)?, @@ -802,10 +944,13 @@ fn renderer_geometry_and_interaction_accessors_are_complete() -> Result<(), Box< assert!(layout.hit_test_xy(f32::NAN, 0.0).is_err()); assert!( layout - .caret_rect(layout.source().len().saturating_add(1)) + .caret_rect( + layout.source().len().saturating_add(1), + Affinity::Downstream, + ) .is_none() ); - assert!(layout.caret_rect(1).is_none()); + assert!(layout.caret_rect(1, Affinity::Downstream).is_none()); assert!(layout.selection_rects(0..0).is_empty()); assert!( layout @@ -823,7 +968,7 @@ fn renderer_geometry_and_interaction_accessors_are_complete() -> Result<(), Box< } let blank_line = jlreq::layout("\n", &fonts, LayoutOptions::try_new(100.0, 16.0)?)?; - assert!(blank_line.caret_rect(0).is_some()); + assert!(blank_line.caret_rect(0, Affinity::Downstream).is_some()); let empty = jlreq::layout("", &fonts, LayoutOptions::try_new(100.0, 16.0)?)?; let empty_hit = empty.hit_test(Point::default()); assert_eq!(empty_hit.byte_offset(), 0); diff --git a/data/manifest.toml b/data/manifest.toml index 6023ecd..5ed7951 100644 --- a/data/manifest.toml +++ b/data/manifest.toml @@ -67,7 +67,7 @@ sha256 = "e75e3651a359f3886308b0a01867d18538d4e62423498f405c9b5e1e8c204221" [[file]] path = "docs/public-api.toml" -sha256 = "543682c99a71b5e2c90bb02f655c8b47dbbde700040f28a01aee783f5451b5fa" +sha256 = "ee6fde8f360f03405ad3e84bf505d70e26290d20582600fec6663a9b185d7301" [[file]] path = "spec/PROVENANCE.toml" diff --git a/docs/design/api-spine.md b/docs/design/api-spine.md index fbbdfb8..d8cb298 100644 --- a/docs/design/api-spine.md +++ b/docs/design/api-spine.md @@ -16,8 +16,18 @@ let options = jlreq::LayoutOptions::try_new(240.0, 16.0)?; let layout = jlreq::layout("日本語組版", &fonts, options)?; for glyph in layout.glyphs() { - let font = &layout.fonts()[glyph.font_id().get() as usize]; - let _draw = (font.bytes(), glyph.glyph_id(), glyph.origin()); + if let Some(font) = layout.font(glyph.font_id()) { + let _draw = ( + font.bytes(), + font.face_index(), + glyph.glyph_id(), + glyph.draw_origin(), + glyph.font_size_26_6(), + glyph.variations(), + font.synthesis(), + glyph.transform(), + ); + } } # Ok(()) # } @@ -31,7 +41,9 @@ internals. `FontLibrary` registers owned memory fonts and TTC indices, family/style metadata, a primary face, and ordered fallback. The optional `system-fonts` feature is the only OS -discovery surface. HarfRust, Fontique, ICU4X, and unicode-bidi types are private. +discovery surface. It matches weight, width, and slant and records selected default axes +plus synthetic bold/skew in `FontResource`. HarfRust, Fontique, ICU4X, and unicode-bidi +types are private. ## Typed authored content @@ -55,12 +67,20 @@ immutable `Document`. `layout_document` then returns a complete result or a type - positioned, stable-code diagnostics. A glyph exposes font ID, glyph ID, source byte range, optional annotation attribution, -origin, advances, offsets, 26.6 geometry, transform, and bidi level. A line exposes source -range, physical origin and extents, writing mode, and its visual glyph slice. +draw origin, advances, offsets, resolved size and variation axes, cell bounds, 26.6 +geometry, transform, and bidi level. A line exposes source range, physical origin and +extents, writing mode, cell bounds, and its visual glyph slice. `TextLayout::font` is the +required ID lookup because retained font identifiers may be sparse. `hit_test`, `caret_rect`, and `selection_rects` are defined over the same physical geometry and support both writing modes and bidi. They never require the renderer to infer -logical ordering from glyph order. +logical ordering from glyph order. `caret_rect` requires `Affinity`; hit-test results can +therefore round-trip exactly at wraps, paragraph breaks, and bidi boundaries. Selection +rectangles are split at visually unselected runs. + +`cell_bounds` and line/layout `bounds` are layout-cell boundaries, not outline ink +boundaries. They preserve whitespace and annotation cells. Renderers derive ink bounds +from font outlines when needed. ## Errors, diagnostics, and atomicity @@ -75,6 +95,8 @@ Failures do not poison `LayoutEngine`; the next call is independent. All public float values are checked for finiteness and range and quantized to 26.6 fixed point before processing. Equivalent quantized inputs are intentionally equivalent. +`FontVariation` consequently implements `Eq` and `Hash`; global, system, and span values +are overlaid per tag with the span layer last. ## Core: pre-shaped exact composition diff --git a/docs/error-codes.md b/docs/error-codes.md index 2bf560b..5b815ad 100644 --- a/docs/error-codes.md +++ b/docs/error-codes.md @@ -23,6 +23,12 @@ handwritten public-library sources in both directions. | `font.system-family-not-found` | Opt-in system discovery found no face for the requested family. | | `layout.invalid-option` | A numeric, tag, language, tab, feature, or variation option violates its invariant. | +OpenType tags use this single option code when they are not exactly four bytes in +`0x20..=0x7e`, contain a leading/interior space, or use non-space data after trailing +padding. Invalid hit-test coordinates are also option failures; an invalid UTF-8 caret or +selection boundary instead returns `None` or an empty rectangle list because result +queries do not mutate layout state. + ## High-level document errors | Code | Meaning | diff --git a/docs/guide.ja.md b/docs/guide.ja.md new file mode 100644 index 0000000..7a75bde --- /dev/null +++ b/docs/guide.ja.md @@ -0,0 +1,135 @@ + + +# `jlreq` 0.1.0 日本語利用ガイド + +`jlreq` は、UTF-8本文とメモリ上のTTF/OTF/TTCを受け取り、描画順のグリフ、物理座標、 +元本文のバイト範囲、使用フォント、診断をまとめた `TextLayout` を返します。字形の +ラスタライズ、PDF/GPUへの出力、描画自体はレンダラーの責任です。 + +## 最短の利用手順 + +```rust,no_run +# fn example(font_bytes: Vec) -> Result<(), jlreq::LayoutError> { +use jlreq::{FontLibrary, LayoutOptions}; + +let mut fonts = FontLibrary::new(); +fonts.register_font(font_bytes)?; +let layout = jlreq::layout( + "日本語組版", + &fonts, + LayoutOptions::try_new(240.0, 16.0)?, +)?; + +for glyph in layout.glyphs() { + if let Some(font) = layout.font(glyph.font_id()) { + let draw_data = ( + font.bytes(), + font.face_index(), + glyph.glyph_id(), + glyph.draw_origin(), + glyph.font_size_26_6(), + glyph.variations(), + font.synthesis(), + glyph.transform(), + ); + let _ = draw_data; + } +} +# Ok(()) +# } +``` + +完成したレイアウトは、参照する `FontResource` をすべて所有します。ただし、未使用の +登録フォントは保持しないため、IDは `0, 1, 2, ...` と連続するとは限りません。 +`layout.font(glyph.font_id())` で検索し、`layout.fonts()[id]` のような添字アクセスは +行わないでください。 + +## 描画契約 + +レンダラーは、各グリフについて次の情報をそのまま利用できます。 + +- `draw_origin`: シェーパーのオフセットを反映済みの物理描画原点 +- `font_size` / `font_size_26_6`: シェーピングに用いた実効文字サイズ +- `variations`: グローバル値、システムフォント既定値、span値をタグ単位で統合した軸 +- `FontResource::synthesis`: 可変軸では表せない合成太字と傾斜 +- `transform`: 縦書き回転または縦中横の局所変換 + +同じrunのグリフは実効軸の配列を共有します。軸値は公開境界で26.6固定小数へ量子化 +され、同じ固定小数値は `Eq` と `Hash` でも同一です。OpenTypeタグは4バイトの +`0x20..=0x7e` で、空白は末尾のパディングにだけ使えます。 + +## セル境界とインク境界 + +`GlyphPlacement::cell_bounds`、`TextLine::bounds`、`TextLayout::bounds` は、組版セルの +物理境界です。空白のadvance、ルビなどの注釈セル、空行のblock extentを含みます。 +これは字形アウトラインのインク境界ではありません。クリッピング、衝突判定、装飾線 +をインクに合わせる場合は、選択したface、サイズ、軸、合成状態からレンダラー側で +アウトライン境界を取得してください。 + +## span、縦書き、注釈 + +`DocumentBuilder` はUTF-8バイト範囲に `SpanStyle` を設定し、明示改行、ルビ、縦中横、 +圏点、割注、振分け、字取り、合印、添字、数式を型付きで受け付けます。大きなspanや +注釈がある行の後続段落は、実際の行セルと `line_gap` から配置されるため、横書き・ +縦書きのどちらでも重なりません。 + +```rust,no_run +# fn vertical(font_bytes: Vec) -> Result<(), jlreq::LayoutError> { +use jlreq::{DocumentBuilder, FontLibrary, LayoutOptions, SpanStyle, WritingMode}; + +let mut document = DocumentBuilder::new("漢字12\n次段落"); +document.span(0..6, SpanStyle::new().font_size(24.0)?)?; +document.group_ruby(0..6, "かんじ")?; +document.tate_chu_yoko(6..8)?; + +let mut fonts = FontLibrary::new(); +fonts.register_font(font_bytes)?; +let _layout = jlreq::layout_document( + &document.build()?, + &fonts, + LayoutOptions::try_new(240.0, 16.0)? + .writing_mode(WritingMode::VerticalRl) + .line_gap(2.0)?, +)?; +# Ok(()) +# } +``` + +## 編集UI + +`hit_test` は `(byte_offset, affinity)` を返します。caretを戻すときは両方を +`caret_rect` に渡してください。`Affinity` を省くと、折返し位置、改行、bidi境界の +どちら側かを一意に決められません。空行へのヒットは、その空行自身のUTF-8位置へ +写像されます。`selection_rects` は行全体を一つに塗らず、視覚順で連続する選択run +ごとの矩形を返すため、bidi本文の未選択領域を覆いません。 + +```rust,no_run +# fn editing(layout: &jlreq::TextLayout) -> Result<(), jlreq::LayoutError> { +let hit = layout.hit_test_xy(20.0, 30.0)?; +let caret = layout.caret_rect(hit.byte_offset(), hit.affinity()); +let selected = layout.selection_rects(0..layout.source().len()); +let _ = (caret, selected); +# Ok(()) +# } +``` + +## 決定論の境界 + +明示的なフォントbytes、face index、本文、オプションが同じなら、対応OS間で26.6結果は +bit単位で同一です。`system-fonts` による探索はOSのフォント集合とFontiqueの結果に +依存するため、この保証の外です。ただし登録時に選ばれたbytes、face index、既定軸、 +合成状態は `FontResource` にコピーされ、その完成レイアウトだけで同じ外観を再現 +できます。キャッシュの有無、`layout` と再利用可能な `LayoutEngine` の違いは結果に +影響しません。 + +`jlreq-core` は引き続き依存なしの `no_std + alloc` 固定小数組版層です。既に +シェーピング済みのclusterを持つ実装だけが直接利用し、通常のアプリケーションは +高水準の `jlreq` APIを利用してください。 + +エラーコードは [`error-codes.md`](error-codes.md)、公開名は +[`public-api.toml`](public-api.toml)、設計上の境界は +[`design/api-spine.md`](design/api-spine.md) に固定されています。 diff --git a/docs/public-api.toml b/docs/public-api.toml index 94a3d27..90e3397 100644 --- a/docs/public-api.toml +++ b/docs/public-api.toml @@ -27,6 +27,7 @@ items = [ "FontResource", "FontSlant", "FontStyle", + "FontSynthesis", "FontVariation", "GlyphPlacement", "GlyphTransform", diff --git a/scripts/run-mutation-smoke.sh b/scripts/run-mutation-smoke.sh index 1d68d67..487ca96 100644 --- a/scripts/run-mutation-smoke.sh +++ b/scripts/run-mutation-smoke.sh @@ -5,12 +5,13 @@ set -eu -if [ "$#" -ne 1 ]; then - echo "usage: $0 BASE_COMMIT" >&2 +if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then + echo "usage: $0 BASE_COMMIT [SHARD/TOTAL]" >&2 exit 2 fi base=$1 +shard=${2:-} if ! base_commit=$(git rev-parse --verify --end-of-options "${base}^{commit}"); then echo "mutation smoke: base is not a commit: $base" >&2 exit 2 @@ -26,10 +27,16 @@ if [ ! -s "$diff_file" ]; then exit 0 fi -cargo mutants -p jlreq -p jlreq-core -p jlreq-conformance --in-diff "$diff_file" \ +set -- cargo mutants -p jlreq -p jlreq-core -p jlreq-conformance --in-diff "$diff_file" \ --all-features \ --test-tool cargo \ --minimum-test-timeout 120 \ --no-times \ --colors=never \ -j 4 + +if [ -n "$shard" ]; then + set -- "$@" --shard "$shard" +fi + +"$@" diff --git a/scripts/verify-mutation-ledger.sh b/scripts/verify-mutation-ledger.sh deleted file mode 100644 index acc1e19..0000000 --- a/scripts/verify-mutation-ledger.sh +++ /dev/null @@ -1,175 +0,0 @@ -#!/bin/sh -# SPDX-FileCopyrightText: 2026 jlreq contributors -# SPDX-License-Identifier: MIT OR Apache-2.0 - -set -eu - -root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) -cd "$root" - -ledger=docs/mutation-ledger.toml -config=.cargo/mutants.toml -scratch=$(mktemp -d "${TMPDIR:-/tmp}/jlreq-mutation-ledger.XXXXXX") - -cleanup() { - rm -r "$scratch" -} -trap cleanup EXIT HUP INT TERM - -fail() { - echo "mutation-ledger: $*" >&2 - exit 1 -} - -digest() { - if command -v sha256sum >/dev/null 2>&1; then - sha256sum -- "$1" | awk '{ print $1 }' - elif command -v shasum >/dev/null 2>&1; then - shasum -a 256 -- "$1" | awk '{ print $1 }' - else - fail "neither sha256sum nor shasum is available" - fi -} - -# The ledger deliberately uses only flat, quoted fields inside these array tables. Parse -# that closed shape instead of adding a package dependency merely to validate repository -# metadata. -awk ' -function clear() { - path = sha = kind = provenance = reason = "" -} -function value(line) { - sub(/^[^=]+=[[:space:]]*/, "", line) - return substr(line, 2, length(line) - 2) -} -function emit() { - if (section != "exclusion") return - if (path == "" || sha == "" || kind == "" || provenance == "" || reason == "") { - print "docs/mutation-ledger.toml: incomplete [[exclusion]]" > "/dev/stderr" - failed = 1 - return - } - print path "\t" sha "\t" kind "\t" provenance "\t" reason -} -$0 == "[[exclusion]]" { - emit() - section = "exclusion" - clear() - next -} -/^\[\[/ { - emit() - section = "" - clear() - next -} -section == "exclusion" && /^path = / { path = value($0); next } -section == "exclusion" && /^sha256 = / { sha = value($0); next } -section == "exclusion" && /^kind = / { kind = value($0); next } -section == "exclusion" && /^provenance = / { provenance = value($0); next } -section == "exclusion" && /^reason = / { reason = value($0); next } -END { - emit() - if (failed) exit 1 -} -' "$ledger" >"$scratch/exclusions.tsv" - -awk ' -function clear() { - mutant = pattern = sha = proof = "" -} -function value(line) { - sub(/^[^=]+=[[:space:]]*/, "", line) - return substr(line, 2, length(line) - 2) -} -function emit() { - if (section != "equivalent") return - if (mutant == "" || pattern == "" || sha == "" || proof == "") { - print "docs/mutation-ledger.toml: incomplete [[equivalent]]" > "/dev/stderr" - failed = 1 - return - } - print mutant "\t" pattern "\t" sha "\t" proof -} -$0 == "[[equivalent]]" { - emit() - section = "equivalent" - clear() - next -} -/^\[\[/ { - emit() - section = "" - clear() - next -} -section == "equivalent" && /^mutant = / { mutant = value($0); next } -section == "equivalent" && /^exclude_re = / { pattern = value($0); next } -section == "equivalent" && /^source_sha256 = / { sha = value($0); next } -section == "equivalent" && /^proof = / { proof = value($0); next } -END { - emit() - if (failed) exit 1 -} -' "$ledger" >"$scratch/equivalent.tsv" - -if grep -Eq '^glob = ' "$ledger"; then - fail "generated exclusions must name individual paths, not a broad glob" -fi - -find crates/jlreq-core/src/generated -maxdepth 1 -type f -name '*.rs' -print | - LC_ALL=C sort >"$scratch/generated-files.txt" -cut -f 1 "$scratch/exclusions.tsv" | LC_ALL=C sort >"$scratch/excluded-files.txt" -if ! cmp -s "$scratch/generated-files.txt" "$scratch/excluded-files.txt"; then - diff -u "$scratch/generated-files.txt" "$scratch/excluded-files.txt" >&2 || true - fail "the ledger must exclude every generated table, and only those tables" -fi - -duplicates=$(cut -f 1 "$scratch/exclusions.tsv" | LC_ALL=C sort | uniq -d) -test -z "$duplicates" || fail "duplicate generated exclusion: $duplicates" - -tab=$(printf '\t') -while IFS="$tab" read -r path expected kind provenance reason; do - test "$kind" = generated || fail "$path has non-generated exclusion kind $kind" - test "$provenance" = data/manifest.toml || fail "$path is not anchored to data/manifest.toml" - test -n "$reason" || fail "$path has no exclusion reason" - test -f "$path" || fail "$path does not exist" - test "${#expected}" -eq 64 || fail "$path has a malformed SHA-256" - case "$expected" in - *[!0-9a-f]*) fail "$path has a non-hexadecimal SHA-256" ;; - esac - actual=$(digest "$path") - test "$actual" = "$expected" || fail "$path changed: expected $expected, observed $actual" -done <"$scratch/exclusions.tsv" - -if ! grep -Fqx 'exclude_globs = ["crates/jlreq-core/src/generated/**"]' "$config"; then - fail "$config must exclude the generated table directory exactly" -fi -if grep -Fq '"crates/jlreq-core/src/generated.rs"' "$config"; then - fail "$config excludes the handwritten generated.rs integrity checks" -fi - -duplicates=$(cut -f 1 "$scratch/equivalent.tsv" | LC_ALL=C sort | uniq -d) -test -z "$duplicates" || fail "duplicate equivalent mutant: $duplicates" -duplicates=$(cut -f 2 "$scratch/equivalent.tsv" | LC_ALL=C sort | uniq -d) -test -z "$duplicates" || fail "duplicate equivalent-mutant regex: $duplicates" - -while IFS="$tab" read -r mutant pattern expected proof; do - case "$mutant" in - *.rs:*) source="${mutant%%.rs:*}.rs" ;; - *) fail "equivalent mutant does not start with a Rust source path: $mutant" ;; - esac - test -n "$proof" || fail "$mutant has no equivalence proof" - test -f "$source" || fail "$source does not exist" - actual=$(digest "$source") - test "$actual" = "$expected" || fail "$mutant is stale: expected $expected, observed $actual" - grep -Fqx " '$pattern'," "$config" || fail "$mutant has no exact regex in $config" -done <"$scratch/equivalent.tsv" - -documented=$(wc -l <"$scratch/equivalent.tsv" | tr -d ' ') -configured=$(grep -c "^ '" "$config" || true) -test "$configured" -eq "$documented" || - fail "$config has $configured equivalent regex(es), but the ledger documents $documented" - -generated=$(wc -l <"$scratch/exclusions.tsv" | tr -d ' ') -echo "mutation-ledger: verified $generated generated file(s) and $documented equivalent mutant(s) against source SHA-256" diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9a9f05a..e37c2f8 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -26,6 +26,7 @@ mod derive; mod direction; mod generate; mod inventory; +mod mutation; mod placeholder; mod policy; mod purity; @@ -47,6 +48,7 @@ const GATES: &[Gate] = &[ generate::GATE, attest::GATE, conform::GATE, + mutation::GATE, repository::GATE, ]; diff --git a/xtask/src/mutation.rs b/xtask/src/mutation.rs new file mode 100644 index 0000000..6077fb0 --- /dev/null +++ b/xtask/src/mutation.rs @@ -0,0 +1,436 @@ +// SPDX-FileCopyrightText: 2026 jlreq contributors +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Cross-platform validation of generated-source and equivalent-mutant exclusions. + +use std::collections::BTreeSet; +use std::fs; +use std::io; +use std::path::Path; + +use crate::generate::sha256; +use crate::shared::{Gate, array_header, before_comment, workspace_root}; + +const LEDGER: &str = "docs/mutation-ledger.toml"; +const CONFIG: &str = ".cargo/mutants.toml"; +const GENERATED: &str = "crates/jlreq-core/src/generated"; + +pub(crate) const GATE: Gate = Gate { + name: "mutation-ledger", + purpose: "every generated exclusion and equivalent mutant is individually provenance- and SHA-256-bound", + reference: LEDGER, + run, +}; + +#[derive(Debug, Default)] +struct Exclusion { + path: String, + sha256: String, + kind: String, + provenance: String, + reason: String, +} + +impl Exclusion { + fn complete(&self) -> bool { + !self.path.is_empty() + && !self.sha256.is_empty() + && !self.kind.is_empty() + && !self.provenance.is_empty() + && !self.reason.is_empty() + } +} + +#[derive(Debug, Default)] +struct Equivalent { + mutant: String, + exclude_re: String, + source_sha256: String, + proof: String, +} + +impl Equivalent { + fn complete(&self) -> bool { + !self.mutant.is_empty() + && !self.exclude_re.is_empty() + && !self.source_sha256.is_empty() + && !self.proof.is_empty() + } +} + +#[derive(Debug)] +enum OpenTable { + Exclusion(Exclusion), + Equivalent(Equivalent), + Other, +} + +#[derive(Debug, Default)] +struct Ledger { + exclusions: Vec, + equivalents: Vec, + violations: Vec, +} + +fn run(arguments: &[String]) -> io::Result> { + if let Some(first) = arguments.first() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("mutation-ledger takes no arguments, observed `{first}`"), + )); + } + let root = workspace_root()?; + let ledger_source = fs::read_to_string(root.join(LEDGER))?; + let config = fs::read_to_string(root.join(CONFIG))?; + let mut ledger = parse_ledger(&ledger_source); + validate_exclusions(&root, &ledger.exclusions, &mut ledger.violations)?; + validate_config(&root, &config, &ledger.equivalents, &mut ledger.violations)?; + Ok(ledger.violations) +} + +fn parse_ledger(source: &str) -> Ledger { + let mut ledger = Ledger::default(); + let mut open = OpenTable::Other; + for (index, raw) in source.lines().enumerate() { + let line_number = index.saturating_add(1); + let line = before_comment(raw).trim(); + if line.is_empty() { + continue; + } + if let Some(header) = array_header(line) { + finish_table(&mut open, &mut ledger, line_number); + open = match header { + "exclusion" => OpenTable::Exclusion(Exclusion::default()), + "equivalent" => OpenTable::Equivalent(Equivalent::default()), + _ => OpenTable::Other, + }; + continue; + } + let Some((key, raw_value)) = line.split_once('=') else { + continue; + }; + let key = key.trim(); + match &mut open { + OpenTable::Exclusion(value) => { + let Some(parsed) = quoted_value(raw_value) else { + ledger.violations.push(format!( + "{LEDGER}:{line_number}: `{key}` is not a one-line quoted string" + )); + continue; + }; + match key { + "path" => value.path = parsed, + "sha256" => value.sha256 = parsed, + "kind" => value.kind = parsed, + "provenance" => value.provenance = parsed, + "reason" => value.reason = parsed, + "glob" => ledger.violations.push(format!( + "{LEDGER}:{line_number}: generated exclusions must name individual paths, not a broad glob" + )), + _ => ledger.violations.push(format!( + "{LEDGER}:{line_number}: unknown exclusion field `{key}`" + )), + } + }, + OpenTable::Equivalent(value) => { + let Some(parsed) = quoted_value(raw_value) else { + ledger.violations.push(format!( + "{LEDGER}:{line_number}: `{key}` is not a one-line quoted string" + )); + continue; + }; + match key { + "mutant" => value.mutant = parsed, + "exclude_re" => value.exclude_re = parsed, + "source_sha256" => value.source_sha256 = parsed, + "proof" => value.proof = parsed, + _ => ledger.violations.push(format!( + "{LEDGER}:{line_number}: unknown equivalent field `{key}`" + )), + } + }, + OpenTable::Other => {}, + } + } + finish_table( + &mut open, + &mut ledger, + source.lines().count().saturating_add(1), + ); + ledger +} + +fn finish_table(open: &mut OpenTable, ledger: &mut Ledger, line_number: usize) { + match std::mem::replace(open, OpenTable::Other) { + OpenTable::Exclusion(value) if value.complete() => ledger.exclusions.push(value), + OpenTable::Exclusion(_) => ledger + .violations + .push(format!("{LEDGER}:{line_number}: incomplete [[exclusion]]")), + OpenTable::Equivalent(value) if value.complete() => ledger.equivalents.push(value), + OpenTable::Equivalent(_) => ledger + .violations + .push(format!("{LEDGER}:{line_number}: incomplete [[equivalent]]")), + OpenTable::Other => {}, + } +} + +fn quoted_value(raw: &str) -> Option { + let value = raw.trim(); + if let Some(inner) = value + .strip_prefix('"') + .and_then(|rest| rest.strip_suffix('"')) + { + return Some(inner.to_owned()); + } + value + .strip_prefix('\'') + .and_then(|rest| rest.strip_suffix('\'')) + .map(str::to_owned) +} + +fn validate_exclusions( + root: &Path, + exclusions: &[Exclusion], + violations: &mut Vec, +) -> io::Result<()> { + let generated = generated_sources(root)?; + let documented: BTreeSet<_> = exclusions.iter().map(|item| item.path.clone()).collect(); + for path in generated.difference(&documented) { + violations.push(format!( + "{LEDGER}: generated source `{path}` is not excluded" + )); + } + for path in documented.difference(&generated) { + violations.push(format!( + "{LEDGER}: exclusion `{path}` is not a generated table source" + )); + } + report_duplicates( + exclusions.iter().map(|item| item.path.as_str()), + "generated exclusion", + violations, + ); + + for exclusion in exclusions { + if exclusion.kind != "generated" { + violations.push(format!( + "{} has non-generated exclusion kind `{}`", + exclusion.path, exclusion.kind + )); + } + if exclusion.provenance != "data/manifest.toml" { + violations.push(format!( + "{} is not anchored to data/manifest.toml", + exclusion.path + )); + } + validate_digest( + root, + &exclusion.path, + &exclusion.sha256, + "generated exclusion", + violations, + )?; + } + Ok(()) +} + +fn generated_sources(root: &Path) -> io::Result> { + let directory = root.join(GENERATED); + let mut paths = BTreeSet::new(); + for entry in fs::read_dir(directory)? { + let path = entry?.path(); + if path.is_file() && path.extension().is_some_and(|extension| extension == "rs") { + paths.insert(repository_path(root, &path)); + } + } + Ok(paths) +} + +fn validate_config( + root: &Path, + config: &str, + equivalents: &[Equivalent], + violations: &mut Vec, +) -> io::Result<()> { + let exact_glob = "exclude_globs = [\"crates/jlreq-core/src/generated/**\"]"; + if !config.lines().any(|line| line == exact_glob) { + violations.push(format!( + "{CONFIG} must exclude the generated table directory exactly" + )); + } + if config.contains("\"crates/jlreq-core/src/generated.rs\"") { + violations.push(format!( + "{CONFIG} excludes the handwritten generated.rs integrity checks" + )); + } + report_duplicates( + equivalents.iter().map(|item| item.mutant.as_str()), + "equivalent mutant", + violations, + ); + report_duplicates( + equivalents.iter().map(|item| item.exclude_re.as_str()), + "equivalent-mutant regex", + violations, + ); + + for equivalent in equivalents { + let Some(source) = mutant_source(&equivalent.mutant) else { + violations.push(format!( + "equivalent mutant does not start with a Rust source path: {}", + equivalent.mutant + )); + continue; + }; + validate_digest( + root, + &source, + &equivalent.source_sha256, + "equivalent mutant", + violations, + )?; + let configured = format!(" '{}',", equivalent.exclude_re); + if !config.lines().any(|line| line == configured) { + violations.push(format!( + "{} has no exact regex in {CONFIG}", + equivalent.mutant + )); + } + } + let configured = config + .lines() + .filter(|line| line.starts_with(" '") && line.ends_with("',")) + .count(); + if configured != equivalents.len() { + violations.push(format!( + "{CONFIG} has {configured} equivalent regex(es), but {LEDGER} documents {}", + equivalents.len() + )); + } + Ok(()) +} + +fn mutant_source(mutant: &str) -> Option { + let (prefix, _) = mutant.split_once(".rs:")?; + Some(format!("{prefix}.rs")) +} + +fn validate_digest( + root: &Path, + relative: &str, + expected: &str, + label: &str, + violations: &mut Vec, +) -> io::Result<()> { + if expected.len() != 64 || !expected.bytes().all(|byte| byte.is_ascii_hexdigit()) { + violations.push(format!("{relative} has a malformed SHA-256 for {label}")); + return Ok(()); + } + if expected.bytes().any(|byte| byte.is_ascii_uppercase()) { + violations.push(format!( + "{relative} has a non-lowercase SHA-256 for {label}" + )); + return Ok(()); + } + let path = root.join(relative); + if !path.is_file() { + violations.push(format!("{relative} does not exist")); + return Ok(()); + } + let actual = sha256(&fs::read(path)?); + if actual != expected { + violations.push(format!( + "{relative} changed: expected {expected}, observed {actual}" + )); + } + Ok(()) +} + +fn report_duplicates<'a>( + values: impl Iterator, + label: &str, + violations: &mut Vec, +) { + let mut seen = BTreeSet::new(); + let mut reported = BTreeSet::new(); + for value in values { + if !seen.insert(value) && reported.insert(value) { + violations.push(format!("duplicate {label}: {value}")); + } + } +} + +fn repository_path(root: &Path, path: &Path) -> String { + path.strip_prefix(root) + .unwrap_or(path) + .components() + .map(|component| component.as_os_str().to_string_lossy()) + .collect::>() + .join("/") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn closed_ledger_tables_parse_both_toml_string_styles() { + let parsed = parse_ledger( + "[[exclusion]]\npath = \"a.rs\"\nsha256 = \"00\"\nkind = \"generated\"\nprovenance = \"data/manifest.toml\"\nreason = \"why\"\n\n[[equivalent]]\nmutant = \"a.rs:1\"\nexclude_re = '^a$'\nsource_sha256 = \"11\"\nproof = \"same\"\n", + ); + assert!(parsed.violations.is_empty()); + assert_eq!(parsed.exclusions.len(), 1); + assert_eq!(parsed.equivalents.len(), 1); + assert_eq!(parsed.equivalents[0].exclude_re, "^a$"); + } + + #[test] + fn incomplete_unknown_and_glob_fields_are_findings() { + let parsed = parse_ledger( + "[[exclusion]]\nglob = \"generated/**\"\nunknown = \"x\"\n\n[[equivalent]]\nmutant = \"a.rs:1\"\n", + ); + assert!( + parsed + .violations + .iter() + .any(|item| item.contains("broad glob")) + ); + assert!( + parsed + .violations + .iter() + .any(|item| item.contains("unknown exclusion")) + ); + assert_eq!( + parsed + .violations + .iter() + .filter(|item| item.contains("incomplete")) + .count(), + 2 + ); + } + + #[test] + fn mutant_paths_and_repository_paths_are_platform_independent() { + assert_eq!( + mutant_source("crates/a/src/lib.rs:1: replace"), + Some("crates/a/src/lib.rs".into()) + ); + let root = std::path::PathBuf::from("workspace"); + assert_eq!( + repository_path(&root, &root.join("crates").join("a").join("lib.rs")), + "crates/a/lib.rs" + ); + } + + #[test] + fn the_repository_ledger_holds_without_posix_tools() { + assert!( + run(&[]) + .expect("the mutation ledger is readable") + .is_empty() + ); + } +} From 74e062538f00c13ea9295f7d76771e40c6423a2b Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Sun, 30 Aug 2026 14:45:00 +0900 Subject: [PATCH 11/12] test: close remaining mutation gaps --- crates/jlreq-core/src/paragraph.rs | 18 ++++++++++++++---- crates/jlreq/src/font.rs | 16 ++++++++++++++++ crates/jlreq/src/result.rs | 11 ++--------- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/crates/jlreq-core/src/paragraph.rs b/crates/jlreq-core/src/paragraph.rs index e86272b..0bcd84b 100644 --- a/crates/jlreq-core/src/paragraph.rs +++ b/crates/jlreq-core/src/paragraph.rs @@ -305,10 +305,8 @@ impl Paragraph { .flatten(); let mut selected = None; while let Some(ordinal) = current { - if self.constructs.get(ordinal).is_some_and(&mut matches) - && selected.is_none_or(|previous| ordinal < previous) - { - selected = Some(ordinal); + if self.constructs.get(ordinal).is_some_and(&mut matches) { + selected = Some(selected.map_or(ordinal, |previous: usize| previous.min(ordinal))); } current = self.construct_index.parents.get(ordinal).copied().flatten(); } @@ -1086,6 +1084,18 @@ mod tests { assert_eq!(ordinals, [0, 2]); indexed.collect_constructs_overlapping(0, 1, &mut ordinals); assert!(ordinals.is_empty()); + + let (first, _) = indexed + .find_construct_containing(2, |_| true) + .expect("both nested constructs contain the cluster"); + assert_eq!(first, 0); + let (inner, _) = indexed + .find_construct_containing(2, |construct| { + matches!(construct.kind(), ConstructKind::ReferenceMark { .. }) + }) + .expect("the inner reference mark contains the cluster"); + assert_eq!(inner, 1); + assert!(indexed.find_construct_containing(0, |_| true).is_none()); } #[test] diff --git a/crates/jlreq/src/font.rs b/crates/jlreq/src/font.rs index f419039..fb00eb3 100644 --- a/crates/jlreq/src/font.rs +++ b/crates/jlreq/src/font.rs @@ -633,6 +633,22 @@ mod tests { assert_eq!(synthesis.skew_26_6(), Some(14 * 64)); assert!(!synthesis.is_empty()); assert!(FontSynthesis::default().is_empty()); + + let embolden_only = FontSynthesis { + embolden: true, + skew: 0, + }; + assert!(embolden_only.embolden()); + assert!(!embolden_only.is_empty()); + assert_eq!(embolden_only.skew_26_6(), None); + + let skew_only = FontSynthesis { + embolden: false, + skew: 14 * 64, + }; + assert!(!skew_only.embolden()); + assert!(!skew_only.is_empty()); + assert_eq!(skew_only.skew_26_6(), Some(14 * 64)); } #[cfg(feature = "system-fonts")] diff --git a/crates/jlreq/src/result.rs b/crates/jlreq/src/result.rs index 3ac7841..366f1f0 100644 --- a/crates/jlreq/src/result.rs +++ b/crates/jlreq/src/result.rs @@ -474,18 +474,11 @@ impl TextLine { self.inline_extent, ), }; - let base = self.hit_bounds.map_or(base, |bounds| base.union(bounds)); - self.glyphs - .iter() - .map(GlyphPlacement::cell_bounds) - .fold(base, Rect::union) + self.hit_bounds.map_or(base, |bounds| base.union(bounds)) } pub(crate) fn hit_bounds_for(glyphs: &[GlyphPlacement]) -> Option { - let mut bounds = glyphs - .iter() - .filter(|glyph| glyph.annotation.is_none()) - .map(GlyphPlacement::cell_bounds); + let mut bounds = glyphs.iter().map(GlyphPlacement::cell_bounds); let first = bounds.next()?; let mut min_x = first.x; let mut min_y = first.y; From 8d4fd94f50fe7cc7f3e60bdbd3fc7db365953029 Mon Sep 17 00:00:00 2001 From: Yasunobu <42543015+P4suta@users.noreply.github.com> Date: Sun, 30 Aug 2026 15:23:36 +0900 Subject: [PATCH 12/12] fix: address release review findings --- .github/workflows/release.yml | 11 + Justfile | 18 +- LICENSE-MIT | 2 +- crates/jlreq-core/LICENSE-MIT | 2 +- crates/jlreq-core/src/pipeline.rs | 14 + crates/jlreq-core/src/pipeline/special.rs | 6 +- crates/jlreq/README.md | 1 + crates/jlreq/src/engine.rs | 9 + crates/jlreq/src/engine/preparation.rs | 2 +- crates/jlreq/src/engine/shaping.rs | 1 + data/manifest.toml | 2 +- .../adr/0024-independent-reference-engines.md | 19 +- engines/ocaml/README.md | 2 +- engines/racket/README.md | 2 +- fuzz/fuzz_targets/high_level_layout.rs | 2 +- scripts/package-binaries.sh | 4 +- scripts/verify-release-state.sh | 12 +- xtask/src/api.rs | 58 +-- xtask/src/mutation.rs | 378 ++++++++++++++++-- 19 files changed, 471 insertions(+), 74 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 351aacb..3d9e5ff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -134,6 +134,17 @@ jobs: env: CARGO_REGISTRY_TOKEN: ${{ steps.crates_io_auth.outputs.token }} run: cargo publish --locked -p jlreq + - name: wait for jlreq in the crates.io index + run: |- + for attempt in $(seq 1 30); do + if cargo info "jlreq@$VERSION" >/dev/null 2>&1; then + exit 0 + fi + echo "jlreq $VERSION is not visible yet (attempt $attempt/30)" + sleep 10 + done + echo "jlreq $VERSION was uploaded but did not become visible; inspect crates.io before retrying" >&2 + exit 1 - name: publish jlreq-conformance with first-release token if: inputs.authentication == 'initial-token' env: diff --git a/Justfile b/Justfile index 3cd83cc..cf3bebd 100644 --- a/Justfile +++ b/Justfile @@ -101,16 +101,16 @@ lint: # which nextest currently executes. test: cargo nextest run --workspace --all-features - cargo test -p jlreq-core --lib pipeline::tests::ten_thousand_cluster_standard_paragraph_stays_below_the_search_budget -- --ignored --exact - cargo test --release -p jlreq-core --lib pipeline::tests::zero_width_pathological_paragraph_stops_at_the_default_search_budget -- --ignored --exact + cargo nextest run -p jlreq-core --lib -- --ignored --exact pipeline::tests::ten_thousand_cluster_standard_paragraph_stays_below_the_search_budget + cargo nextest run --release -p jlreq-core --lib -- --ignored --exact pipeline::tests::zero_width_pathological_paragraph_stops_at_the_default_search_budget cargo test -p jlreq-conformance --test transport --all-features cargo test --workspace --doc --all-features # Run the complete test suite with the non-fail-fast CI profile. test-ci: cargo nextest run --profile ci --workspace --all-features - cargo test -p jlreq-core --lib pipeline::tests::ten_thousand_cluster_standard_paragraph_stays_below_the_search_budget -- --ignored --exact - cargo test --release -p jlreq-core --lib pipeline::tests::zero_width_pathological_paragraph_stops_at_the_default_search_budget -- --ignored --exact + cargo nextest run -p jlreq-core --lib -- --ignored --exact pipeline::tests::ten_thousand_cluster_standard_paragraph_stays_below_the_search_budget + cargo nextest run --release -p jlreq-core --lib -- --ignored --exact pipeline::tests::zero_width_pathological_paragraph_stops_at_the_default_search_budget cargo test -p jlreq-conformance --test transport --all-features cargo test --workspace --doc --all-features @@ -128,8 +128,8 @@ package: sh scripts/verify-crates.sh # Ask Cargo to execute its complete crates.io publication preflight while retaining the -# upload locally. The patch validates jlreq-conformance before the first jlreq release has -# appeared in the index; published metadata still carries only version 0.1.0. +# upload locally. The patch validates jlreq and jlreq-conformance before their first +# dependencies have appeared in the index; published metadata still carries only version 0.1.0. publish-dry-run: cargo publish --dry-run --locked {{package_dirty}} -p jlreq-core cargo publish --dry-run --locked {{package_dirty}} -p jlreq --config .cargo/initial-release-patch.toml @@ -179,9 +179,7 @@ _fuzz-target target seconds: [private] _fuzz-target-linux target seconds: - mkdir -p target/fuzz-corpus/{{target}} - cp fuzz/seeds/{{target}}/* target/fuzz-corpus/{{target}}/ - cargo +nightly fuzz run {{target}} target/fuzz-corpus/{{target}} --fuzz-dir fuzz --target x86_64-unknown-linux-gnu -- -max_total_time={{seconds}} -timeout=10 + just _fuzz-target {{target}} {{seconds}} # Scheduled sanitizer budget: fifteen minutes for each independent failure domain. fuzz-scheduled: @@ -282,7 +280,7 @@ actionlint: # Validate every repository-owned POSIX shell entry point, including release packaging and # the three-engine census driver. shellcheck: - shellcheck engines/census-all.sh scripts/check-semver.sh scripts/finalize-release.sh scripts/package-binaries.sh scripts/run-mutation-smoke.sh scripts/verify-crates.sh scripts/verify-release-state.sh + shellcheck engines/census-all.sh scripts/*.sh # Reject high-severity GitHub Actions and Dependabot security findings without # granting the auditor network or repository credentials. diff --git a/LICENSE-MIT b/LICENSE-MIT index d817195..10d4b60 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) 2026 jlreq contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including diff --git a/crates/jlreq-core/LICENSE-MIT b/crates/jlreq-core/LICENSE-MIT index d817195..10d4b60 100644 --- a/crates/jlreq-core/LICENSE-MIT +++ b/crates/jlreq-core/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) +Copyright (c) 2026 jlreq contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including diff --git a/crates/jlreq-core/src/pipeline.rs b/crates/jlreq-core/src/pipeline.rs index f152a96..7180827 100644 --- a/crates/jlreq-core/src/pipeline.rs +++ b/crates/jlreq-core/src/pipeline.rs @@ -2265,6 +2265,20 @@ mod tests { .expect("valid automatic warichu split"); assert_eq!(super::choose_warichu_split(&automatic, 1..5), 4); + let nested_tate_chu_yoko = Paragraph::builder(text("abcd"), 10_000) + .writing_mode(WritingMode::VerticalRl) + .constructs([Construct::warichu(0..4), Construct::tate_chu_yoko(1..3)]) + .build() + .expect("valid tate-chu-yoko nested in warichu"); + assert_eq!( + super::choose_warichu_split(&nested_tate_chu_yoko, 0..4), + 3, + "a boundary inside tate-chu-yoko is not a legal lane split" + ); + let nested_segment = super::warichu_segment(&nested_tate_chu_yoko, 0..4, 0, 4); + assert_eq!(nested_segment.first_lane, 0..3); + assert_eq!(nested_segment.second_lane, 3..4); + let tied_text = mapped_text("abc", Frame::Proportional, |ordinal, cluster| { if ordinal == 1 { Cluster::new(cluster.range(), 0) diff --git a/crates/jlreq-core/src/pipeline/special.rs b/crates/jlreq-core/src/pipeline/special.rs index b65f6d8..7f9cf7a 100644 --- a/crates/jlreq-core/src/pipeline/special.rs +++ b/crates/jlreq-core/src/pipeline/special.rs @@ -217,6 +217,11 @@ fn choose_warichu_split(paragraph: &Paragraph, interior: Range) -> usize let mut best = None; for require_declared in [true, false] { for split in interior.start.saturating_add(1)..interior.end { + if tate_chu_yoko_cluster_range(paragraph, split) + .is_some_and(|group| group.start < split) + { + continue; + } let offset = paragraph.text.clusters()[split].range().start; let declared = paragraph .breaks @@ -482,4 +487,3 @@ fn is_comma(character: char) -> bool { fn is_middle_dot(character: char) -> bool { crate::spec::single_has_class(character, crate::spec::MIDDLE_DOT) } - diff --git a/crates/jlreq/README.md b/crates/jlreq/README.md index 342a603..97e21be 100644 --- a/crates/jlreq/README.md +++ b/crates/jlreq/README.md @@ -71,6 +71,7 @@ document.tate_chu_yoko(6..8)?; document.emphasis_dots(9..15, '・')?; document.warichu(16..22)?; document.furawake(23..29, 2, 1.0)?; +document.mandatory_break(26)?; document.jidori(30..36, 4)?; document.reference_mark(37..38, "※")?; document.script(39..42, "2", ScriptPosition::Subscript)?; diff --git a/crates/jlreq/src/engine.rs b/crates/jlreq/src/engine.rs index df48c0f..9d98694 100644 --- a/crates/jlreq/src/engine.rs +++ b/crates/jlreq/src/engine.rs @@ -274,6 +274,9 @@ mod tests { changed.level = Level::rtl(); assert!(!base.same_run(&changed)); changed = grapheme(first); + changed.level = Level::new(2).expect("valid nested LTR level"); + assert!(!base.same_run(&changed)); + changed = grapheme(first); changed.script = ScriptClass::Japanese; assert!(!base.same_run(&changed)); changed = grapheme(first); @@ -429,6 +432,9 @@ mod tests { fn frames_roles_scripts_and_shape_directions_cover_the_closed_tables() { assert_eq!(frame_for("日"), jlreq_core::Frame::FullEm); assert_eq!(frame_for("😀"), jlreq_core::Frame::FullEm); + assert_eq!(frame_for("!"), jlreq_core::Frame::FullEm); + assert_eq!(frame_for("⦆"), jlreq_core::Frame::FullEm); + assert_eq!(frame_for("。"), jlreq_core::Frame::Proportional); assert_eq!(frame_for("A"), jlreq_core::Frame::Proportional); assert_eq!(frame_for("日本"), jlreq_core::Frame::Proportional); @@ -474,6 +480,9 @@ mod tests { assert_eq!(script_class("é"), ScriptClass::Latin); assert_eq!(script_class("\u{0301}"), ScriptClass::Other); assert!(is_japanese('日')); + assert!(is_japanese('!')); + assert!(is_japanese('⦆')); + assert!(!is_japanese('。')); assert!(!is_japanese('A')); assert!(is_emoji('😀')); assert!(!is_emoji('A')); diff --git a/crates/jlreq/src/engine/preparation.rs b/crates/jlreq/src/engine/preparation.rs index 90f7121..da558d7 100644 --- a/crates/jlreq/src/engine/preparation.rs +++ b/crates/jlreq/src/engine/preparation.rs @@ -201,7 +201,7 @@ struct GraphemeItem { impl GraphemeItem { fn same_run(&self, other: &Self) -> bool { self.font_id == other.font_id - && self.level.is_rtl() == other.level.is_rtl() + && self.level == other.level && self.script == other.script && self.direction == other.direction && (Arc::ptr_eq(&self.effective, &other.effective) || self.effective == other.effective) diff --git a/crates/jlreq/src/engine/shaping.rs b/crates/jlreq/src/engine/shaping.rs index 20998f1..5803583 100644 --- a/crates/jlreq/src/engine/shaping.rs +++ b/crates/jlreq/src/engine/shaping.rs @@ -128,6 +128,7 @@ fn is_japanese(character: char) -> bool { | 0x3400..=0x4dbf | 0x4e00..=0x9fff | 0xf900..=0xfaff + | 0xff01..=0xff60 | 0x20000..=0x323af ) } diff --git a/data/manifest.toml b/data/manifest.toml index 5ed7951..9ab2fe7 100644 --- a/data/manifest.toml +++ b/data/manifest.toml @@ -183,7 +183,7 @@ sha256 = "2e1efc1dcb59c575eedf5ccae60f95229f706ee6d031835247d843c11d96470c" [[file]] path = "xtask/src/api.rs" -sha256 = "0b0abd4208a79e9c41d1812bc84205919627af023d6d5aa01a126dc614d4b939" +sha256 = "cb1d30b9241ef6a93b953d36299b48d061e15bf905b0415efbabcc87578b5afe" [[file]] path = "xtask/src/attest.rs" diff --git a/docs/adr/0024-independent-reference-engines.md b/docs/adr/0024-independent-reference-engines.md index df74c87..b81c67c 100644 --- a/docs/adr/0024-independent-reference-engines.md +++ b/docs/adr/0024-independent-reference-engines.md @@ -47,7 +47,7 @@ restated per engine. Neither engine is a Cargo workspace member. `engines/ocaml/` builds with `dune` from a `dune-project` at the repository root (dune cannot depend on a file above its project -root, and the engine reads `spec/*.tsv` directly); `engines/racket/` will build with `raco`. +root, and the engine reads `spec/*.tsv` directly); `engines/racket/` builds with `raco`. This keeps every gate whose scope is the Cargo graph — `purity`, `api`, `direction`, `derive`, `generate`, `deny`, `shear`, `msrv`, `mutants`, `release-plz` — unaffected by their presence, exactly as `fuzz/` already sits outside the workspace as its own @@ -59,14 +59,20 @@ Each engine reaches the full eighty-nine-case built-in suite through nine cumula disjoint milestones rather than in one step. A `milestones/CURRENT` file names the milestone the tree currently claims; the engine's CI job runs the cumulative suite through that milestone, so `CURRENT` — not the engine's completeness in the abstract — is what a -pull request is actually held to. `conform-ocaml` and (once it exists) `conform-racket` join -`ci-required`, gated at whatever milestone is current, from the PR that adds the job. At +pull request is actually held to. `conform-ocaml` and `conform-racket` join `ci-required`, +gated at whatever milestone is current, from the PR that adds the job. At `CURRENT = 9` the cumulative suite is the full suite and the milestone gate becomes identical to running all eighty-nine cases. `just conform-engines`, which `just ci` and the pre-push hook run, wraps both engine gates with a toolchain check: a developer without an OCaml or Racket toolchain installed gets a loud `SKIPPED` line and a green local run, while CI has the toolchain and enforces the gate for real. +Both engines have now reached `CURRENT = 9` and pass all eighty-nine built-in cases. Their +generated ten-kind census additionally exercises 122,199 requests and records zero +differences among the Rust, OCaml, and Racket implementations; the committed +[conformance summary](../generated/conformance-summary.md) is the mechanically checked +record of that completed milestone. + Where the hand-keyed matrices are transcribed a second time, each reference engine reads the opposite locale from the Rust sample engine: the sample engine reads `spec/captured/table*.en.tsv`; `engines/ocaml/` and `engines/racket/` read @@ -86,12 +92,11 @@ happen to be running. Every one of these disagreements found this way is exactly have. Building and gating two more language toolchains is real, ongoing cost: `engines/` adds -OCaml and (later) Racket to the CI matrix and to what a contributor working on those trees +OCaml and Racket to the CI matrix and to what a contributor working on those trees needs installed locally, tracked in `CONTRIBUTING.md`. The milestone mechanism is what keeps that cost bounded during development — a reference engine is gated on the portion of the -suite it actually implements, not on all eighty-nine cases from its first commit — at the -price of the built-in suite being only partially cross-checked until each engine's `CURRENT` -reaches `9`. +suite it actually implements, not on all eighty-nine cases from its first commit. Both +engines are now at `CURRENT = 9`, so the built-in suite is fully cross-checked. `engines/` is deliberately not a path to a released product. Neither engine gains a stable CLI contract, a version number, or a publish target; both stay tooling in service of the diff --git a/engines/ocaml/README.md b/engines/ocaml/README.md index 5c3b53c..ac54cf2 100644 --- a/engines/ocaml/README.md +++ b/engines/ocaml/README.md @@ -234,7 +234,7 @@ This is the point of the exercise, so it is worth stating precisely. **May not be read into this tree** — the Rust implementation: -- the layout logic in `crates/jlreq/src/` (`pipeline.rs` above all); +- the layout logic in `crates/jlreq-core/src/` (`pipeline.rs` above all); - `crates/jlreq-core/src/generated/`, and the xtask code generators that write it. Where the two engines disagree, the disagreement is settled by returning to JLReq diff --git a/engines/racket/README.md b/engines/racket/README.md index acf3fe0..a071558 100644 --- a/engines/racket/README.md +++ b/engines/racket/README.md @@ -208,7 +208,7 @@ OCaml one is already finished and green on all eighty-nine cases. **May not be read into this tree:** -- the layout logic under `crates/jlreq/src/` (`pipeline.rs` above all), and +- the layout logic under `crates/jlreq-core/src/` (`pipeline.rs` above all), and `crates/jlreq-core/src/generated/`; - **the bodies of `engines/ocaml/lib/`**, `pipeline.ml` and `spec.ml` above all. A line-by-line translation of a finished engine is not an independent diff --git a/fuzz/fuzz_targets/high_level_layout.rs b/fuzz/fuzz_targets/high_level_layout.rs index 41b4a3d..a8ed540 100644 --- a/fuzz/fuzz_targets/high_level_layout.rs +++ b/fuzz/fuzz_targets/high_level_layout.rs @@ -46,7 +46,7 @@ fuzz_target!(|data: &[u8]| { }) .limits( ResourceLimits::default() - .with_max_input_bytes(MAX_TEXT_BYTES) + .with_max_input_bytes(text.len().max(MAX_TEXT_BYTES)) .with_max_fonts(4) .with_max_font_bytes(2 * 1024 * 1024) .with_max_paragraphs(512) diff --git a/scripts/package-binaries.sh b/scripts/package-binaries.sh index c6f4450..acf2dbe 100755 --- a/scripts/package-binaries.sh +++ b/scripts/package-binaries.sh @@ -63,9 +63,9 @@ case $candidate in *.zip) # Native 7-Zip prints backslash-separated entries on Windows. Normalize its # listing so the archive contract is identical on every runner shell. - listing=$(7z l -ba "$candidate" | tr '\134' '/') + listing=$(7z l -slt "$candidate" | sed -n 's/^Path = //p' | tr '\134' '/') for required in "jlreq-conformance$suffix" "jlreq-sample-engine$suffix" README.md LICENSE-MIT LICENSE-APACHE; do - printf '%s\n' "$listing" | grep -F "$name/$required" >/dev/null || { + printf '%s\n' "$listing" | grep -Fx "$name/$required" >/dev/null || { echo "$candidate is missing $name/$required" >&2 exit 1 } diff --git a/scripts/verify-release-state.sh b/scripts/verify-release-state.sh index 3e8377c..ef9f4ed 100755 --- a/scripts/verify-release-state.sh +++ b/scripts/verify-release-state.sh @@ -37,7 +37,17 @@ for package in jlreq-core jlreq jlreq-conformance; do done ( cd target/dist - sha256sum --check --strict jlreq-0.1.0-crates.sha256 + manifest=jlreq-0.1.0-crates.sha256 + expected=$(printf '%s\n' \ + jlreq-core-0.1.0.crate \ + jlreq-0.1.0.crate \ + jlreq-conformance-0.1.0.crate | sort) + actual=$(awk 'NF { name = $2; sub(/^\*/, "", name); print name }' "$manifest" | sort) + if [ "$actual" != "$expected" ]; then + echo "$manifest must contain exactly one entry for each release crate and no others" >&2 + exit 1 + fi + sha256sum --check --strict "$manifest" ) test -z "$(git tag --list v0.1.0)" || { echo "v0.1.0 tag must not exist during prerelease preparation" >&2 diff --git a/xtask/src/api.rs b/xtask/src/api.rs index 238f1fe..2fbaecc 100644 --- a/xtask/src/api.rs +++ b/xtask/src/api.rs @@ -320,28 +320,11 @@ fn check_allowed_items(allowed: &AllowedModule, source: &str) -> Vec { fn check_zero_one_zero_allowlist(root: &Path, violations: &mut Vec) -> io::Result<()> { let modules = allowed_modules(root)?; for module in &modules { - let (crate_directory, relative) = match module.path.as_str() { - "jlreq" => ("jlreq", "lib.rs".to_owned()), - path if path.starts_with("jlreq::") => ( - "jlreq", - format!( - "{}.rs", - path.trim_start_matches("jlreq::").replace("::", "/") - ), - ), - "jlreq_core" => ("jlreq-core", "lib.rs".to_owned()), - path if path.starts_with("jlreq_core::") => ( - "jlreq-core", - format!( - "{}.rs", - path.trim_start_matches("jlreq_core::").replace("::", "/") - ), - ), - path => { - return Err(malformed(format!( - "docs/public-api.toml names `{path}`; public Rust crates are `jlreq` and `jlreq_core`" - ))); - }, + let Some((crate_directory, relative)) = public_module_source(&module.path) else { + return Err(malformed(format!( + "docs/public-api.toml names `{path}`; public Rust crates are `jlreq` and `jlreq_core`", + path = module.path + ))); }; let source_path = root .join("crates") @@ -362,6 +345,17 @@ fn check_zero_one_zero_allowlist(root: &Path, violations: &mut Vec) -> i Ok(()) } +fn public_module_source(path: &str) -> Option<(&'static str, String)> { + const CRATES: [(&str, &str); 2] = [("jlreq", "jlreq"), ("jlreq_core", "jlreq-core")]; + CRATES.iter().find_map(|(rust_path, directory)| { + if path == *rust_path { + return Some((*directory, "lib.rs".to_owned())); + } + let relative = path.strip_prefix(*rust_path)?.strip_prefix("::")?; + (!relative.is_empty()).then(|| (*directory, format!("{}.rs", relative.replace("::", "/")))) + }) +} + // ------------------------------------------------------------------------------------- // The control file // ------------------------------------------------------------------------------------- @@ -2781,7 +2775,8 @@ mod tests { answers_a_closed_set, check_allowed_items, check_closed_choices, check_construction, check_exhaustiveness, check_forbidden_names, check_forbidden_signatures, check_projections, check_readability, check_style_choice_mappings, code_only, declarations_of, entries_of, - obtainable_types, parse_signature_spec, reexported_names, results_in, tokenize, + obtainable_types, parse_signature_spec, public_module_source, reexported_names, results_in, + tokenize, }; use std::collections::BTreeSet; @@ -2896,6 +2891,23 @@ mod tests { assert!(super::run(&["--check".to_owned()]).is_err()); } + #[test] + fn public_module_paths_remove_exactly_one_crate_prefix() { + assert_eq!( + public_module_source("jlreq"), + Some(("jlreq", "lib.rs".to_owned())) + ); + assert_eq!( + public_module_source("jlreq_core::style"), + Some(("jlreq-core", "style.rs".to_owned())) + ); + assert_eq!( + public_module_source("jlreq::jlreq::style"), + Some(("jlreq", "jlreq/style.rs".to_owned())) + ); + assert_eq!(public_module_source("other::style"), None); + } + #[test] fn the_jlreq_release_surface_is_checked_for_compatibility() { let surface = Surface { diff --git a/xtask/src/mutation.rs b/xtask/src/mutation.rs index 6077fb0..1557301 100644 --- a/xtask/src/mutation.rs +++ b/xtask/src/mutation.rs @@ -29,6 +29,7 @@ struct Exclusion { kind: String, provenance: String, reason: String, + assigned: BTreeSet, } impl Exclusion { @@ -47,6 +48,7 @@ struct Equivalent { exclude_re: String, source_sha256: String, proof: String, + assigned: BTreeSet, } impl Equivalent { @@ -110,6 +112,7 @@ fn parse_ledger(source: &str) -> Ledger { continue; }; let key = key.trim(); + let mut findings = Vec::new(); match &mut open { OpenTable::Exclusion(value) => { let Some(parsed) = quoted_value(raw_value) else { @@ -119,15 +122,54 @@ fn parse_ledger(source: &str) -> Ledger { continue; }; match key { - "path" => value.path = parsed, - "sha256" => value.sha256 = parsed, - "kind" => value.kind = parsed, - "provenance" => value.provenance = parsed, - "reason" => value.reason = parsed, - "glob" => ledger.violations.push(format!( + "path" => assign_once( + &mut value.path, + &mut value.assigned, + &parsed, + key, + line_number, + &mut findings, + ), + "sha256" => { + assign_once( + &mut value.sha256, + &mut value.assigned, + &parsed, + key, + line_number, + &mut findings, + ); + }, + "kind" => assign_once( + &mut value.kind, + &mut value.assigned, + &parsed, + key, + line_number, + &mut findings, + ), + "provenance" => assign_once( + &mut value.provenance, + &mut value.assigned, + &parsed, + key, + line_number, + &mut findings, + ), + "reason" => { + assign_once( + &mut value.reason, + &mut value.assigned, + &parsed, + key, + line_number, + &mut findings, + ); + }, + "glob" => findings.push(format!( "{LEDGER}:{line_number}: generated exclusions must name individual paths, not a broad glob" )), - _ => ledger.violations.push(format!( + _ => findings.push(format!( "{LEDGER}:{line_number}: unknown exclusion field `{key}`" )), } @@ -140,17 +182,50 @@ fn parse_ledger(source: &str) -> Ledger { continue; }; match key { - "mutant" => value.mutant = parsed, - "exclude_re" => value.exclude_re = parsed, - "source_sha256" => value.source_sha256 = parsed, - "proof" => value.proof = parsed, - _ => ledger.violations.push(format!( + "mutant" => { + assign_once( + &mut value.mutant, + &mut value.assigned, + &parsed, + key, + line_number, + &mut findings, + ); + }, + "exclude_re" => assign_once( + &mut value.exclude_re, + &mut value.assigned, + &parsed, + key, + line_number, + &mut findings, + ), + "source_sha256" => assign_once( + &mut value.source_sha256, + &mut value.assigned, + &parsed, + key, + line_number, + &mut findings, + ), + "proof" => { + assign_once( + &mut value.proof, + &mut value.assigned, + &parsed, + key, + line_number, + &mut findings, + ); + }, + _ => findings.push(format!( "{LEDGER}:{line_number}: unknown equivalent field `{key}`" )), } }, OpenTable::Other => {}, } + ledger.violations.extend(findings); } finish_table( &mut open, @@ -188,6 +263,23 @@ fn quoted_value(raw: &str) -> Option { .map(str::to_owned) } +fn assign_once( + field: &mut String, + assigned: &mut BTreeSet, + parsed: &str, + key: &str, + line_number: usize, + findings: &mut Vec, +) { + if assigned.insert(key.to_owned()) { + field.push_str(parsed); + } else { + findings.push(format!( + "{LEDGER}:{line_number}: `{key}` is stated more than once in one table" + )); + } +} + fn validate_exclusions( root: &Path, exclusions: &[Exclusion], @@ -253,17 +345,34 @@ fn validate_config( equivalents: &[Equivalent], violations: &mut Vec, ) -> io::Result<()> { - let exact_glob = "exclude_globs = [\"crates/jlreq-core/src/generated/**\"]"; - if !config.lines().any(|line| line == exact_glob) { + let generated_glob = "crates/jlreq-core/src/generated/**"; + let configured_globs = match config_string_array(config, "exclude_globs") { + Ok(values) => values, + Err(finding) => { + violations.push(format!("{CONFIG}: {finding}")); + Vec::new() + }, + }; + if configured_globs != [generated_glob] { violations.push(format!( "{CONFIG} must exclude the generated table directory exactly" )); } - if config.contains("\"crates/jlreq-core/src/generated.rs\"") { + if configured_globs + .iter() + .any(|glob| glob == "crates/jlreq-core/src/generated.rs") + { violations.push(format!( "{CONFIG} excludes the handwritten generated.rs integrity checks" )); } + let configured_regexes = match config_string_array(config, "exclude_re") { + Ok(values) => values, + Err(finding) => { + violations.push(format!("{CONFIG}: {finding}")); + Vec::new() + }, + }; report_duplicates( equivalents.iter().map(|item| item.mutant.as_str()), "equivalent mutant", @@ -274,6 +383,11 @@ fn validate_config( "equivalent-mutant regex", violations, ); + report_duplicates( + configured_regexes.iter().map(String::as_str), + "configured equivalent-mutant regex", + violations, + ); for equivalent in equivalents { let Some(source) = mutant_source(&equivalent.mutant) else { @@ -290,27 +404,136 @@ fn validate_config( "equivalent mutant", violations, )?; - let configured = format!(" '{}',", equivalent.exclude_re); - if !config.lines().any(|line| line == configured) { + if !configured_regexes.contains(&equivalent.exclude_re) { violations.push(format!( "{} has no exact regex in {CONFIG}", equivalent.mutant )); } } - let configured = config - .lines() - .filter(|line| line.starts_with(" '") && line.ends_with("',")) - .count(); - if configured != equivalents.len() { + let documented: BTreeSet<_> = equivalents + .iter() + .map(|item| item.exclude_re.as_str()) + .collect(); + for unexpected in configured_regexes + .iter() + .map(String::as_str) + .filter(|regex| !documented.contains(regex)) + { violations.push(format!( - "{CONFIG} has {configured} equivalent regex(es), but {LEDGER} documents {}", + "{CONFIG} has undocumented equivalent-mutant regex `{unexpected}`" + )); + } + if configured_regexes.len() != equivalents.len() { + violations.push(format!( + "{CONFIG} has {} equivalent regex(es), but {LEDGER} documents {}", + configured_regexes.len(), equivalents.len() )); } Ok(()) } +fn config_string_array(config: &str, key: &str) -> Result, String> { + let mut offsets = Vec::new(); + let mut line_start = 0_usize; + for line in config.split_inclusive('\n') { + let content = line.strip_suffix('\n').unwrap_or(line); + let visible = before_comment(content); + match (visible.split_once('='), content.find('=')) { + (Some((candidate, _)), Some(equals)) if candidate.trim() == key => { + offsets.push(line_start.saturating_add(equals).saturating_add(1)); + }, + _ => {}, + } + line_start = line_start.saturating_add(line.len()); + } + match offsets.as_slice() { + [] => Err(format!("missing `{key}` string array")), + [offset] => parse_toml_string_array(&config[*offset..]) + .map_err(|finding| format!("invalid `{key}` string array: {finding}")), + _ => Err(format!("`{key}` is assigned more than once")), + } +} + +fn parse_toml_string_array(source: &str) -> Result, String> { + let characters: Vec = source.chars().collect(); + let mut cursor = 0_usize; + skip_toml_trivia(&characters, &mut cursor); + if characters.get(cursor) != Some(&'[') { + return Err("value is not an array".to_owned()); + } + cursor = cursor.saturating_add(1); + let mut values = Vec::new(); + loop { + skip_toml_trivia(&characters, &mut cursor); + if characters.get(cursor) == Some(&']') { + return Ok(values); + } + let quote = characters + .get(cursor) + .copied() + .filter(|quote| matches!(quote, '\'' | '"')) + .ok_or_else(|| "array element is not a quoted string".to_owned())?; + cursor = cursor.saturating_add(1); + let mut value = String::new(); + loop { + let character = characters + .get(cursor) + .copied() + .ok_or_else(|| "quoted string is not terminated".to_owned())?; + cursor = cursor.saturating_add(1); + if character == quote { + break; + } + if quote == '"' && character == '\\' { + let escaped = characters + .get(cursor) + .copied() + .ok_or_else(|| "basic string escape is not terminated".to_owned())?; + cursor = cursor.saturating_add(1); + value.push(match escaped { + 'b' => '\u{0008}', + 't' => '\t', + 'n' => '\n', + 'f' => '\u{000c}', + 'r' => '\r', + '"' => '"', + '\\' => '\\', + _ => return Err(format!("unsupported basic string escape `\\{escaped}`")), + }); + } else { + value.push(character); + } + } + values.push(value); + skip_toml_trivia(&characters, &mut cursor); + match characters.get(cursor) { + Some(',') => cursor = cursor.saturating_add(1), + Some(']') => return Ok(values), + Some(_) => return Err("array elements are not comma-separated".to_owned()), + None => return Err("array is not terminated".to_owned()), + } + } +} + +fn skip_toml_trivia(characters: &[char], cursor: &mut usize) { + loop { + while characters + .get(*cursor) + .is_some_and(|value| value.is_whitespace()) + { + *cursor = cursor.saturating_add(1); + } + if characters.get(*cursor) != Some(&'#') { + return; + } + while characters.get(*cursor).is_some_and(|value| *value != '\n') { + *cursor = cursor.saturating_add(1); + } + } +} + fn mutant_source(mutant: &str) -> Option { let (prefix, _) = mutant.split_once(".rs:")?; Some(format!("{prefix}.rs")) @@ -412,6 +635,115 @@ mod tests { ); } + #[test] + fn repeated_ledger_fields_are_findings_and_keep_the_first_value() { + let parsed = parse_ledger( + "[[exclusion]]\npath = \"first.rs\"\npath = \"second.rs\"\nsha256 = \"00\"\nkind = \"generated\"\nprovenance = \"data/manifest.toml\"\nreason = \"why\"\n\n[[equivalent]]\nmutant = \"first.rs:1\"\nexclude_re = '^first$'\nexclude_re = '^second$'\nsource_sha256 = \"11\"\nproof = \"same\"\n", + ); + assert_eq!( + parsed + .violations + .iter() + .filter(|finding| finding.contains("stated more than once")) + .count(), + 2 + ); + assert_eq!(parsed.exclusions[0].path, "first.rs"); + assert_eq!(parsed.equivalents[0].exclude_re, "^first$"); + } + + #[test] + fn repeated_ledger_fields_are_findings_after_an_empty_first_value() { + let parsed = parse_ledger( + "[[exclusion]]\npath = \"\"\npath = \"replacement.rs\"\nsha256 = \"00\"\nkind = \"generated\"\nprovenance = \"data/manifest.toml\"\nreason = \"why\"\n", + ); + assert!( + parsed + .violations + .iter() + .any(|finding| finding.contains("`path` is stated more than once")) + ); + assert!( + parsed + .violations + .iter() + .any(|finding| finding.contains("incomplete [[exclusion]]")) + ); + assert!(parsed.exclusions.is_empty()); + } + + #[test] + fn config_arrays_ignore_formatting_and_reject_undocumented_regexes() { + let expanded = "exclude_globs = [\n \"crates/jlreq-core/src/generated/**\", # generated tables\n]\nexclude_re = [\n]\n"; + let mut valid = Vec::new(); + validate_config(Path::new("."), expanded, &[], &mut valid).unwrap(); + assert!(valid.is_empty(), "{valid:?}"); + + let mismatched = + "exclude_globs = [\"crates/jlreq-core/src/generated/**\"]\nexclude_re = ['^extra$']\n"; + let mut invalid = Vec::new(); + validate_config(Path::new("."), mismatched, &[], &mut invalid).unwrap(); + assert!( + invalid + .iter() + .any(|finding| finding.contains("undocumented equivalent-mutant regex")) + ); + assert!( + invalid + .iter() + .any(|finding| finding.contains("has 1 equivalent regex")) + ); + } + + #[test] + fn digest_validation_rejects_every_unbound_state() { + let root = + std::env::temp_dir().join(format!("jlreq-xtask-mutation-{}", std::process::id())); + fs::create_dir(&root).expect("create isolated digest fixture"); + + let cases = [ + ("bad".to_owned(), "malformed SHA-256"), + ("A".repeat(64), "non-lowercase SHA-256"), + ]; + for (digest, expected) in cases { + let mut findings = Vec::new(); + validate_digest(&root, "fixture.rs", &digest, "fixture", &mut findings).unwrap(); + assert!( + findings.iter().any(|finding| finding.contains(expected)), + "{findings:?}" + ); + } + + let mut missing = Vec::new(); + validate_digest( + &root, + "missing.rs", + &"0".repeat(64), + "fixture", + &mut missing, + ) + .unwrap(); + assert!( + missing + .iter() + .any(|finding| finding.contains("does not exist")) + ); + + fs::write(root.join("fixture.rs"), b"actual").expect("write digest fixture"); + let mut mismatch = Vec::new(); + validate_digest( + &root, + "fixture.rs", + &"0".repeat(64), + "fixture", + &mut mismatch, + ) + .unwrap(); + assert!(mismatch.iter().any(|finding| finding.contains("changed"))); + + fs::remove_dir_all(root).expect("remove isolated digest fixture"); + } + #[test] fn mutant_paths_and_repository_paths_are_platform_independent() { assert_eq!(