Compare commits

...
Author SHA1 Message Date
Dylan M. Taylor 1c00ae3492 security: write the release from the release job only (SonarCloud S8233)
The workflow-level contents: write granted every job the right to push
tags and releases; the reusable release workflow is the only job that
needs it. Workflow level drops to read.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-10-23 17:16:13 -04:00
Dylan M. Taylor c1dd60ffd6 Merge pull request #2018 from dylanmtaylor/ci/sonar-main-not-cancelled
ci(sonar): stop cancelling main-branch runs so coverage is published
2026-10-23 16:58:56 -04:00
Dylan M. Taylor bec28d6b87 ci(sonar): don't cancel superseded runs on main, so coverage is published 2026-10-23 16:50:42 -04:00
aiongg d3e6111cf1 Xrefs and data links: resolve stored paths with craft-docpath's resolve_absolute (#1750)
* Xrefs and data links: use resolve_absolute for stored paths

* CI: check that git dependencies come from storytold

* CI: release only with git dependencies from storytold

* Parse Cargo.lock as TOML when checking git sources

* Use the shared git-sources check in CI

* Parse cargo config, legacy .cargo/config included, in the git-sources check
2026-10-23 16:42:01 -04:00
aiongg dd1f0531ac i18n: missing translations never fail; cargo xtask i18n reports them (#2013) 2026-10-23 15:40:12 -04:00
aiongg 37150774c6 CI: run the gate through craft-common-actions' shared gate.yml; FreeBSD nightly and on release (#1987)
ci.yml is new: it calls the shared workflow (cached apt packages, nextest, main-only cache saves, Cargo.lock check). FreeBSD and Windows ARM64 run nightly and on pushes to release instead of on pull requests. Depends on storytold/craft-common-actions#69.
2026-10-23 13:56:16 -04:00
aiongg 9cf115e808 CI: one test binary per crate, nextest, line tables only (#1989)
* Build: line tables only in dev, no debug info for dependencies

* Tests: one integration-test binary per crate

Each crate's tests/*.rs becomes a module of tests/it/main.rs. Files that
install process-wide state (the file io hooks, user aliases, SAVETIME and
SAVEFILEPATH, user linetype and pattern files, registered fonts) stay their
own test binaries.

* xtask ci: nextest, tests without examples, one wasm check call
2026-10-23 13:34:26 -04:00
aiongg 9d8f16c825 Merge pull request #1991 from marcoarnulfo/fix/harfrust-0.14
Build main again after the #1957 dependency bump
2026-10-23 13:00:16 -04:00
Dylan M. Taylor f043540d13 packaging: use [[ ]] tests, named locals and explicit returns in shell scripts (SonarCloud S7688/S7679/S7682) (#1972)
All flagged scripts run under bash (shebang or '# shellcheck shell=bash'), so
[[ ]] is safe. Positional parameters in helper functions are copied into named
locals, and helper functions end with a bare 'return', which keeps the exit
status of the last command (no behaviour change, notably for sha256 under
pipefail). packaging/linux/postinst.sh is POSIX sh and is left alone.
2026-10-23 11:16:40 -04:00
Dylan M. Taylor 78decf80f7 web: allow browser zoom in the viewport meta (SonarCloud Web:S7926) (#1974)
user-scalable=no stops people with low vision from zooming the page. The
canvas keeps its own pinch/pan handling: eframe already prevent_default()s
touch and gesture events on the canvas, so dropping the flag only restores
browser zoom (and accessibility zoom) outside of the app's gestures.
2026-10-23 11:16:34 -04:00
Marco Arnulfo 8ad9a3ce15 dwg: require acadrust 0.6.5, the first version that inverts dimension bit 0 itself 2026-10-23 14:54:19 +02:00
Marco Arnulfo 251fddb658 fonts: harfrust comment names both versions; the advance check skips fonts at 1000 per em 2026-10-23 09:49:38 +02:00
Marco Arnulfo 51db2d52e4 dependabot: leave the egui crates at 0.36 until craft-ui moves to 0.37 (craft-libs#199) 2026-10-23 09:49:38 +02:00
Marco Arnulfo 9621dcbd94 dwg: drop the dimension bit-0 inversion that acadrust 0.6.5 now does itself 2026-10-23 09:30:10 +02:00
Marco Arnulfo 5648a06ac4 Hold the egui crates at 0.36 until craft-ui moves to 0.37 (craft-libs#199) 2026-10-23 09:14:51 +02:00
Marco Arnulfo f344cf4460 engine: pass the query envelope by value to rstar 0.13 2026-10-23 08:50:32 +02:00
Marco Arnulfo e61e328edb io: read SVG element and attribute names with quick-xml 0.42 2026-10-23 08:50:32 +02:00
Marco Arnulfo 611eb9f5a9 fonts: port complex-text shaping to harfrust 0.14 2026-10-23 08:44:31 +02:00
dependabot[bot]anddependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 1977a493c7 chore(deps): bump the cargo-dependencies group across 1 directory with 17 updates (#1957)
Bumps the cargo-dependencies group with 17 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [serde_json](https://github.com/serde-rs/json) | `1.0.151` | `1.0.154` |
| [tiny-skia](https://github.com/linebender/tiny-skia) | `0.11.4` | `0.12.0` |
| [skrifa](https://github.com/googlefonts/fontations) | `0.47.0` | `0.48.0` |
| [harfrust](https://github.com/harfbuzz/harfrust) | `0.12.0` | `0.14.0` |
| [quick-xml](https://github.com/tafia/quick-xml) | `0.41.0` | `0.42.0` |
| [egui](https://github.com/emilk/egui) | `0.36.2` | `0.37.0` |
| [egui_kittest](https://github.com/emilk/egui) | `0.36.2` | `0.37.0` |
| [eframe](https://github.com/emilk/egui) | `0.36.2` | `0.37.0` |
| [egui-wgpu](https://github.com/emilk/egui) | `0.36.2` | `0.37.0` |
| [arboard](https://github.com/1Password/arboard) | `3.6.1` | `3.7.0` |
| [acadrust](https://github.com/hakanaktt/acadrust) | `0.6.4` | `0.6.5` |
| [rstar](https://github.com/georust/rstar) | `0.12.2` | `0.13.0` |
| [libc](https://github.com/rust-lang/libc) | `0.2.190` | `0.2.191` |
| [miniz_oxide](https://github.com/Frommi/miniz_oxide) | `0.8.9` | `0.9.1` |
| [getrandom](https://github.com/rust-random/getrandom) | `0.3.4` | `0.4.3` |
| [muda](https://github.com/tauri-apps/muda) | `0.21.0` | `0.21.2` |
| [winresource](https://github.com/BenjaminRi/winresource) | `0.1.31` | `0.1.34` |



Updates `serde_json` from 1.0.151 to 1.0.154
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.151...v1.0.154)

Updates `tiny-skia` from 0.11.4 to 0.12.0
- [Changelog](https://github.com/linebender/tiny-skia/blob/main/CHANGELOG.md)
- [Commits](https://github.com/linebender/tiny-skia/compare/v0.11.4...v0.12.0)

Updates `skrifa` from 0.47.0 to 0.48.0
- [Release notes](https://github.com/googlefonts/fontations/releases)
- [Changelog](https://github.com/googlefonts/fontations/blob/main/docs/RELEASE.md)
- [Commits](https://github.com/googlefonts/fontations/compare/skrifa-v0.47.0...skrifa-v0.48.0)

Updates `harfrust` from 0.12.0 to 0.14.0
- [Release notes](https://github.com/harfbuzz/harfrust/releases)
- [Changelog](https://github.com/harfbuzz/harfrust/blob/main/CHANGELOG.md)
- [Commits](https://github.com/harfbuzz/harfrust/compare/0.12.0...0.14.0)

Updates `quick-xml` from 0.41.0 to 0.42.0
- [Release notes](https://github.com/tafia/quick-xml/releases)
- [Changelog](https://github.com/tafia/quick-xml/blob/master/Changelog.md)
- [Commits](https://github.com/tafia/quick-xml/compare/v0.41.0...v0.42.0)

Updates `egui` from 0.36.2 to 0.37.0
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.36.2...0.37.0)

Updates `egui_kittest` from 0.36.2 to 0.37.0
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.36.2...0.37.0)

Updates `eframe` from 0.36.2 to 0.37.0
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.36.2...0.37.0)

Updates `egui-wgpu` from 0.36.2 to 0.37.0
- [Release notes](https://github.com/emilk/egui/releases)
- [Changelog](https://github.com/emilk/egui/blob/main/CHANGELOG.md)
- [Commits](https://github.com/emilk/egui/compare/0.36.2...0.37.0)

Updates `arboard` from 3.6.1 to 3.7.0
- [Release notes](https://github.com/1Password/arboard/releases)
- [Commits](https://github.com/1Password/arboard/compare/v3.6.1...v3.7.0)

Updates `acadrust` from 0.6.4 to 0.6.5
- [Release notes](https://github.com/hakanaktt/acadrust/releases)
- [Changelog](https://github.com/hakanaktt/acadrust/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hakanaktt/acadrust/compare/v0.6.4...v0.6.5)

Updates `rstar` from 0.12.2 to 0.13.0
- [Commits](https://github.com/georust/rstar/compare/v0.12.2...v0.13.0)

Updates `libc` from 0.2.190 to 0.2.191
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.191/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.190...0.2.191)

Updates `miniz_oxide` from 0.8.9 to 0.9.1
- [Changelog](https://github.com/Frommi/miniz_oxide/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Frommi/miniz_oxide/commits)

Updates `getrandom` from 0.3.4 to 0.4.3
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.3.4...v0.4.3)

Updates `muda` from 0.21.0 to 0.21.2
- [Release notes](https://github.com/tauri-apps/muda/releases)
- [Changelog](https://github.com/tauri-apps/muda/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/tauri-apps/muda/compare/muda-v0.21...muda-v0.21.2)

Updates `winresource` from 0.1.31 to 0.1.34
- [Commits](https://github.com/BenjaminRi/winresource/commits)

---
updated-dependencies:
- dependency-name: acadrust
  dependency-version: 0.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: arboard
  dependency-version: 3.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: eframe
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: egui
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: egui-wgpu
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: egui_kittest
  dependency-version: 0.37.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: getrandom
  dependency-version: 0.4.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: harfrust
  dependency-version: 0.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: libc
  dependency-version: 0.2.191
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: miniz_oxide
  dependency-version: 0.9.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: muda
  dependency-version: 0.21.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: quick-xml
  dependency-version: 0.42.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: rstar
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.154
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
- dependency-name: skrifa
  dependency-version: 0.48.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: tiny-skia
  dependency-version: 0.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-dependencies
- dependency-name: winresource
  dependency-version: 0.1.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-22 23:54:32 -04:00
skylar taylor-barrick a08fd7c1d1 Merge pull request #1963 from storytold/docs/security-review-docstrings-20261022
docs: document security limits and regression guarantees
2026-10-22 18:36:19 -06:00
skylartaylor fa6bb0351b docs: clarify security regression guarantees 2026-10-23 02:14:11 +02:00
skylartaylor 48b4165591 docs: document security limits and regression guarantees 2026-10-23 01:59:55 +02:00
330 changed files with 982 additions and 1224 deletions
+11
View File
@@ -14,6 +14,17 @@ updates:
- "*" - "*"
commit-message: commit-message:
prefix: "chore(deps)" prefix: "chore(deps)"
# craft-ui is still on egui 0.36, and two eguis in the graph don't build (#1990). Remove this
# once craft-ui moves to egui 0.37 (storytold/craft-libs#199).
ignore:
- dependency-name: "egui"
versions: [">=0.37"]
- dependency-name: "eframe"
versions: [">=0.37"]
- dependency-name: "egui-wgpu"
versions: [">=0.37"]
- dependency-name: "egui_kittest"
versions: [">=0.37"]
# Maintain GitHub Actions workflows # Maintain GitHub Actions workflows
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
+30
View File
@@ -0,0 +1,30 @@
name: CI
# Quality gates for every push to main and every pull request (`cargo xtask ci`), through the
# suite's shared gate workflow in craft-common-actions.
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
# A newer push to a PR branch cancels its older run; every push to main runs to the end.
concurrency:
group: ci-${{ github.event_name == 'pull_request' && github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
gate:
# Tracks @main so suite-wide CI fixes reach every app without a PR in each repository.
uses: storytold/craft-common-actions/.github/workflows/gate.yml@main
with:
# Same craft-fonts commit as release.yml.
craft-fonts-ref: 3d9ab3a132ed0eff457e424226a435a901da30d3
timeout-minutes: 90
packages: >-
pkg-config libxkbcommon-dev libwayland-dev libx11-dev libxcb1-dev libxcursor-dev
libxrandr-dev libxi-dev libfontconfig1-dev libfreetype-dev mesa-vulkan-drivers
libgl1-mesa-dev
+3
View File
@@ -2,6 +2,9 @@
# (GitHub has no FreeBSD runners). GPU tests skip themselves when the VM has no adapter. # (GitHub has no FreeBSD runners). GPU tests skip themselves when the VM has no adapter.
name: FreeBSD name: FreeBSD
# The VM build is slow, so it runs nightly on main and on pushes to `release`, not on every pull
# request: a drifted dependency still shows up within a day, and always before a release ships.
# PRs that change the FreeBSD workflow or packaging itself still run it.
on: on:
push: push:
branches: [release] branches: [release]
+38
View File
@@ -21,3 +21,41 @@ jobs:
nix: nix:
# Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos. # Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos.
uses: storytold/craft-common-actions/.github/workflows/nix.yml@main uses: storytold/craft-common-actions/.github/workflows/nix.yml@main
git-sources:
name: Git dependencies come from storytold
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Git dependencies come from storytold
run: |
set -euo pipefail
test -f Cargo.lock || { echo "Cargo.lock is missing"; exit 1; }
# Source replacement, patches and path overrides in cargo config never show up
# in Cargo.lock. Parsed rather than grepped: TOML has many spellings of one key.
# Cargo still reads the legacy .cargo/config.
python3 - <<'PY'
import os, sys, tomllib
for path in (".cargo/config", ".cargo/config.toml"):
if not os.path.exists(path):
continue
config = tomllib.load(open(path, "rb"))
replaced = [name for name, source in config.get("source", {}).items()
if isinstance(source, dict) and "replace-with" in source]
if replaced or "patch" in config or "paths" in config:
print(f"{path} replaces, patches or overrides a source")
sys.exit(1)
PY
# Fails when Cargo.lock is out of date with the manifests.
cargo metadata --locked --format-version 1 > /dev/null
python3 - <<'PY'
import sys, tomllib
lock = tomllib.load(open("Cargo.lock", "rb"))
bad = sorted({p["source"] for p in lock.get("package", [])
if p.get("source", "").startswith("git+")
and not p["source"].startswith("git+https://github.com/storytold/")})
if bad:
print("Git dependencies outside github.com/storytold:", *bad, sep="\n")
sys.exit(1)
PY
+44 -1
View File
@@ -11,14 +11,57 @@ on:
default: "" default: ""
permissions: permissions:
contents: write contents: read
concurrency: concurrency:
group: release-${{ github.ref }} group: release-${{ github.ref }}
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
git-sources:
name: Git dependencies come from storytold
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Git dependencies come from storytold
run: |
set -euo pipefail
test -f Cargo.lock || { echo "Cargo.lock is missing"; exit 1; }
# Source replacement, patches and path overrides in cargo config never show up
# in Cargo.lock. Parsed rather than grepped: TOML has many spellings of one key.
# Cargo still reads the legacy .cargo/config.
python3 - <<'PY'
import os, sys, tomllib
for path in (".cargo/config", ".cargo/config.toml"):
if not os.path.exists(path):
continue
config = tomllib.load(open(path, "rb"))
replaced = [name for name, source in config.get("source", {}).items()
if isinstance(source, dict) and "replace-with" in source]
if replaced or "patch" in config or "paths" in config:
print(f"{path} replaces, patches or overrides a source")
sys.exit(1)
PY
# Fails when Cargo.lock is out of date with the manifests.
cargo metadata --locked --format-version 1 > /dev/null
python3 - <<'PY'
import sys, tomllib
lock = tomllib.load(open("Cargo.lock", "rb"))
bad = sorted({p["source"] for p in lock.get("package", [])
if p.get("source", "").startswith("git+")
and not p["source"].startswith("git+https://github.com/storytold/")})
if bad:
print("Git dependencies outside github.com/storytold:", *bad, sep="\n")
sys.exit(1)
PY
release: release:
needs: git-sources
permissions:
# The reusable release workflow tags and uploads the draft release.
contents: write
# Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos. # Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos.
uses: storytold/craft-common-actions/.github/workflows/release.yml@main uses: storytold/craft-common-actions/.github/workflows/release.yml@main
with: with:
+2 -1
View File
@@ -11,7 +11,8 @@ permissions:
concurrency: concurrency:
group: sonarcloud-${{ github.ref }} group: sonarcloud-${{ github.ref }}
cancel-in-progress: true # A push to main is the run that publishes coverage; superseded runs there would never finish.
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs: jobs:
sonarcloud: sonarcloud:
+4 -2
View File
@@ -3,15 +3,17 @@
# binaries are ARM64, run the CLI natively, and uninstall. # binaries are ARM64, run the CLI natively, and uninstall.
name: Windows ARM64 name: Windows ARM64
# Nightly on main and on pushes to `release` (dependency and runner-image drift shows up within a
# day, and always before a release ships), and on PRs that change the Windows packaging itself.
on: on:
schedule:
- cron: "3 5 * * *"
push: push:
branches: [release] branches: [release]
pull_request: pull_request:
paths: paths:
- .github/workflows/windows-arm64.yml - .github/workflows/windows-arm64.yml
- packaging/windows/** - packaging/windows/**
schedule:
- cron: "41 3 * * 1"
workflow_dispatch: workflow_dispatch:
concurrency: concurrency:
Generated
+69 -134
View File
@@ -4,9 +4,9 @@ version = 4
[[package]] [[package]]
name = "acadrust" name = "acadrust"
version = "0.6.4" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94862189cab6e282ed0433a801a216cd1a712953813284a48fa81c67223e38f6" checksum = "5eaa72520f70eb6f7f09b06f44e1ed0d8fad5fa743364f2c3715c30df962ec7c"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.13.2", "bitflags 2.13.2",
@@ -209,9 +209,9 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]] [[package]]
name = "arboard" name = "arboard"
version = "3.6.1" version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" checksum = "aeb9553e62312c8756a5979254fc6d7da8bbeb5986a1e17037fab932f7037917"
dependencies = [ dependencies = [
"clipboard-win", "clipboard-win",
"image", "image",
@@ -223,7 +223,7 @@ dependencies = [
"objc2-foundation 0.3.2", "objc2-foundation 0.3.2",
"parking_lot", "parking_lot",
"percent-encoding", "percent-encoding",
"windows-sys 0.60.2", "windows-sys 0.61.2",
"x11rb", "x11rb",
] ]
@@ -650,8 +650,8 @@ name = "cadcraft-fonts"
version = "0.12.0" version = "0.12.0"
dependencies = [ dependencies = [
"cadcraft-geom", "cadcraft-geom",
"harfrust", "harfrust 0.14.0",
"skrifa 0.47.0", "skrifa 0.48.0",
"unicode-bidi", "unicode-bidi",
] ]
@@ -674,8 +674,8 @@ dependencies = [
"cadcraft-fonts", "cadcraft-fonts",
"cadcraft-geom", "cadcraft-geom",
"cadcraft-render", "cadcraft-render",
"miniz_oxide 0.8.9", "miniz_oxide 0.9.1",
"quick-xml", "quick-xml 0.42.0",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 2.0.21", "thiserror 2.0.21",
@@ -689,7 +689,7 @@ dependencies = [
"cadcraft-engine", "cadcraft-engine",
"cadcraft-io", "cadcraft-io",
"cadcraft-render", "cadcraft-render",
"getrandom 0.3.4", "getrandom 0.4.3",
"log", "log",
"serde_json", "serde_json",
] ]
@@ -1312,7 +1312,7 @@ dependencies = [
"emath", "emath",
"epaint_default_fonts", "epaint_default_fonts",
"font-types", "font-types",
"harfrust", "harfrust 0.12.0",
"log", "log",
"nohash-hasher", "nohash-hasher",
"parking_lot", "parking_lot",
@@ -1441,9 +1441,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]] [[package]]
name = "font-types" name = "font-types"
version = "0.12.6" version = "0.12.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274" checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"serde", "serde",
@@ -1734,6 +1734,18 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "harfrust"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86aa776d6d32b072119e1f1650f4db843ff4382a4aaa6a2c9c4e35703cd377cb"
dependencies = [
"bitflags 2.13.2",
"bytemuck",
"read-fonts 0.45.1",
"smallvec",
]
[[package]] [[package]]
name = "hash32" name = "hash32"
version = "0.3.1" version = "0.3.1"
@@ -1914,7 +1926,7 @@ dependencies = [
"byteorder-lite", "byteorder-lite",
"moxcms", "moxcms",
"num-traits", "num-traits",
"png 0.18.1", "png",
"tiff", "tiff",
"zune-core", "zune-core",
"zune-jpeg", "zune-jpeg",
@@ -2100,9 +2112,9 @@ checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.190" version = "0.2.191"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
[[package]] [[package]]
name = "libloading" name = "libloading"
@@ -2271,9 +2283,9 @@ dependencies = [
[[package]] [[package]]
name = "muda" name = "muda"
version = "0.21.0" version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6744ab645ebe79f9c428455c7cb9dfa4275fa3b50a25408e08f927da8dc618c" checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dpi", "dpi",
@@ -2282,8 +2294,7 @@ dependencies = [
"objc2-app-kit 0.3.2", "objc2-app-kit 0.3.2",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation 0.3.2", "objc2-foundation 0.3.2",
"once_cell", "png",
"png 0.18.1",
"thiserror 2.0.21", "thiserror 2.0.21",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@@ -2945,19 +2956,6 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide 0.8.9",
]
[[package]] [[package]]
name = "png" name = "png"
version = "0.18.1" version = "0.18.1"
@@ -3081,6 +3079,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "quick-xml"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.47" version = "1.0.47"
@@ -3159,13 +3166,15 @@ dependencies = [
[[package]] [[package]]
name = "read-fonts" name = "read-fonts"
version = "0.44.0" version = "0.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5a0cf4bb6cbce4c29756270d189a5e400ac05e91192161d83a0bcb6e68492e8" checksum = "4edc8db0d972de88e53a085b1f01fb3fbdae7bb0cb7a0ce2b2f8285a412fe3ef"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"foldhash",
"font-types", "font-types",
"once_cell", "once_cell",
"yoke",
] ]
[[package]] [[package]]
@@ -3244,9 +3253,9 @@ dependencies = [
[[package]] [[package]]
name = "rstar" name = "rstar"
version = "0.12.2" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" checksum = "5912b862fa5ffb462607bfd1e35036c458c537921f508c8235a83d5f3987edfe"
dependencies = [ dependencies = [
"heapless", "heapless",
"num-traits", "num-traits",
@@ -3377,9 +3386,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.151" version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -3464,12 +3473,12 @@ dependencies = [
[[package]] [[package]]
name = "skrifa" name = "skrifa"
version = "0.47.0" version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "526e654b303ff4ad73a5a40b25de2b3ce3d039c5e3b6da5f89ae6e57004cb40f" checksum = "a5213b4825bac3587046330052d61bfac7bd3bd0d203c758d7c2ef726fcea034"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"read-fonts 0.44.0", "read-fonts 0.45.1",
] ]
[[package]] [[package]]
@@ -3715,24 +3724,24 @@ dependencies = [
[[package]] [[package]]
name = "tiny-skia" name = "tiny-skia"
version = "0.11.4" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea"
dependencies = [ dependencies = [
"arrayref", "arrayref",
"arrayvec", "arrayvec",
"bytemuck", "bytemuck",
"cfg-if", "cfg-if",
"log", "log",
"png 0.17.16", "png",
"tiny-skia-path", "tiny-skia-path",
] ]
[[package]] [[package]]
name = "tiny-skia-path" name = "tiny-skia-path"
version = "0.11.4" version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9"
dependencies = [ dependencies = [
"arrayref", "arrayref",
"bytemuck", "bytemuck",
@@ -4153,7 +4162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quick-xml", "quick-xml 0.41.0",
"quote", "quote",
] ]
@@ -4510,7 +4519,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets 0.52.6", "windows-targets",
] ]
[[package]] [[package]]
@@ -4519,16 +4528,7 @@ version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [ dependencies = [
"windows-targets 0.52.6", "windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
] ]
[[package]] [[package]]
@@ -4546,31 +4546,14 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.52.6", "windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.52.6", "windows_aarch64_msvc",
"windows_i686_gnu 0.52.6", "windows_i686_gnu",
"windows_i686_gnullvm 0.52.6", "windows_i686_gnullvm",
"windows_i686_msvc 0.52.6", "windows_i686_msvc",
"windows_x86_64_gnu 0.52.6", "windows_x86_64_gnu",
"windows_x86_64_gnullvm 0.52.6", "windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.52.6", "windows_x86_64_msvc",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
] ]
[[package]] [[package]]
@@ -4588,96 +4571,48 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]] [[package]]
name = "winit" name = "winit"
version = "0.30.13" version = "0.30.13"
@@ -4740,9 +4675,9 @@ dependencies = [
[[package]] [[package]]
name = "winresource" name = "winresource"
version = "0.1.31" version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" checksum = "a2bc6d36480b829272a5bdabf08e6dca8004761c1da7e35efef800540c66221b"
dependencies = [ dependencies = [
"toml", "toml",
"version_check", "version_check",
+8 -6
View File
@@ -37,17 +37,17 @@ chrono = { version = "0.4.45", default-features = false, features = ["std"] }
rayon = "1" rayon = "1"
# rendering / formats # rendering / formats
tiny-skia = { version = "0.11", default-features = false, features = ["std", "simd", "png-format"] } tiny-skia = { version = "0.12", default-features = false, features = ["std", "simd", "png-format"] }
lyon_tessellation = "1.0.22" lyon_tessellation = "1.0.22"
skrifa = "0.47" skrifa = "0.48"
# Text shaping (OpenType GSUB/GPOS, HarfBuzz port on read-fonts; the version egui's epaint uses) # Text shaping (OpenType GSUB/GPOS, HarfBuzz port on read-fonts; egui's epaint is still on 0.12,
# and the Unicode Bidirectional Algorithm for right-to-left drawing text. # so the graph has both) and the Unicode Bidirectional Algorithm for right-to-left drawing text.
harfrust = "0.12" harfrust = "0.14"
unicode-bidi = "0.3.18" unicode-bidi = "0.3.18"
image = { version = "0.25", default-features = false, features = ["png", "jpeg"] } image = { version = "0.25", default-features = false, features = ["png", "jpeg"] }
encoding_rs = "0.8.35" encoding_rs = "0.8.35"
# XML for the SVG reader (streaming parser, no entity expansion beyond the five predefined ones) # XML for the SVG reader (streaming parser, no entity expansion beyond the five predefined ones)
quick-xml = "0.41" quick-xml = "0.42"
# ui # ui
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] } craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] }
@@ -69,9 +69,11 @@ type_complexity = "allow"
[profile.dev.package."*"] [profile.dev.package."*"]
opt-level = 2 opt-level = 2
debug = false
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1
debug = "line-tables-only"
[profile.release] [profile.release]
opt-level = 3 opt-level = 3
+6
View File
@@ -0,0 +1,6 @@
mod cli;
mod drafting_spacing;
mod help;
mod mcp_connect;
mod skipped_records;
mod undrawn_entities;
+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>CADCraft</title> <title>CADCraft</title>
<link data-trunk rel="rust" data-bin="cadcraft-web" data-wasm-opt="z" data-weak-refs /> <link data-trunk rel="rust" data-bin="cadcraft-web" data-wasm-opt="z" data-weak-refs />
<style> <style>
+10
View File
@@ -48,6 +48,8 @@ impl Default for Limits {
} }
} }
/// Start the loopback server with a fresh session token and a bounded request queue.
/// Binding or token-issuance failure returns a disconnected receiver.
pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> { pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
match TcpListener::bind(("127.0.0.1", port)) { match TcpListener::bind(("127.0.0.1", port)) {
Ok(l) => { Ok(l) => {
@@ -101,6 +103,7 @@ impl Drop for Slot {
} }
} }
/// Accept clients within the connection limit and give each worker the same session token.
fn accept_loop(listener: TcpListener, tx: SyncSender<ControlRequest>, ctx: egui::Context, limits: Limits, token: Arc<String>) { fn accept_loop(listener: TcpListener, tx: SyncSender<ControlRequest>, ctx: egui::Context, limits: Limits, token: Arc<String>) {
let open = Arc::new(AtomicUsize::new(0)); let open = Arc::new(AtomicUsize::new(0));
for mut stream in accepted(listener.incoming(), || std::thread::sleep(ACCEPT_RETRY)) { for mut stream in accepted(listener.incoming(), || std::thread::sleep(ACCEPT_RETRY)) {
@@ -162,6 +165,7 @@ enum Line {
Reject(String), Reject(String),
} }
/// Parse a UTF-8 JSON request and its optional token, distinguishing blank lines from rejected input.
fn parse_line(bytes: &[u8]) -> Line { fn parse_line(bytes: &[u8]) -> Line {
let Ok(text) = std::str::from_utf8(bytes) else { let Ok(text) = std::str::from_utf8(bytes) else {
return Line::Reject("not a control request (invalid UTF-8); closing the connection".into()); return Line::Reject("not a control request (invalid UTF-8); closing the connection".into());
@@ -198,6 +202,8 @@ fn is_http_request_line(text: &str) -> bool {
|| text.rsplit_once(' ').is_some_and(|(_, version)| version.starts_with("HTTP/")) || text.rsplit_once(' ').is_some_and(|(_, version)| version.starts_with("HTTP/"))
} }
/// Serve bounded JSON lines, authenticating each request before enqueueing it for the UI.
/// Invalid credentials close the connection; queue and reply limits report whether a request ran.
fn serve(stream: TcpStream, tx: &SyncSender<ControlRequest>, ctx: &egui::Context, limits: Limits, token: &str) { fn serve(stream: TcpStream, tx: &SyncSender<ControlRequest>, ctx: &egui::Context, limits: Limits, token: &str) {
if stream.set_read_timeout(Some(limits.idle_timeout)).is_err() || stream.set_write_timeout(Some(limits.write_timeout)).is_err() { if stream.set_read_timeout(Some(limits.idle_timeout)).is_err() || stream.set_write_timeout(Some(limits.write_timeout)).is_err() {
return; return;
@@ -385,6 +391,7 @@ mod tests {
assert!(t.elapsed() < Duration::from_secs(4)); assert!(t.elapsed() < Duration::from_secs(4));
} }
/// Authenticated requests rejected by a full queue retain their IDs and report that they did not run.
#[test] #[test]
fn a_full_queue_answers_busy() { fn a_full_queue_answers_busy() {
let (addr, rx) = serve_with(short()); let (addr, rx) = serve_with(short());
@@ -407,6 +414,7 @@ mod tests {
drop(rx); drop(rx);
} }
/// Replies on one authenticated connection preserve the ID of each request.
#[test] #[test]
fn requests_are_answered_with_their_ids() { fn requests_are_answered_with_their_ids() {
let addr = serve_echo(); let addr = serve_echo();
@@ -423,6 +431,7 @@ mod tests {
} }
} }
/// Missing and incorrect session tokens are rejected before dispatch.
#[test] #[test]
fn a_request_without_the_token_is_unauthorized() { fn a_request_without_the_token_is_unauthorized() {
// A local peer that does not hold the session token cannot drive the app (CWE-306). // A local peer that does not hold the session token cannot drive the app (CWE-306).
@@ -437,6 +446,7 @@ mod tests {
assert!(wrong[0]["error"].as_str().unwrap().contains("unauthorized"), "{wrong:?}"); assert!(wrong[0]["error"].as_str().unwrap().contains("unauthorized"), "{wrong:?}");
} }
/// A request carrying the session token reaches the control dispatcher.
#[test] #[test]
fn a_request_with_the_token_is_dispatched() { fn a_request_with_the_token_is_dispatched() {
let addr = serve_echo(); let addr = serve_echo();
+3
View File
@@ -89,6 +89,8 @@ struct CompDof {
objects: Vec<Option<usize>>, objects: Vec<Option<usize>>,
} }
/// Compute component and object freedom only when rank and residual-evaluation work fit their budgets.
/// Return unknown freedom before building the Jacobian when either work limit is exceeded.
fn analyse(comp: &Comp) -> CompDof { fn analyse(comp: &Comp) -> CompDof {
let free = comp.free(None); let free = comp.free(None);
let rows = comp.rows(None); let rows = comp.rows(None);
@@ -213,6 +215,7 @@ mod budget_tests {
use cadcraft_doc::{Common, ConstraintKind, EntityKind, GeomRef, Space}; use cadcraft_doc::{Common, ConstraintKind, EntityKind, GeomRef, Space};
use cadcraft_geom::{Spline, Vec2}; use cadcraft_geom::{Spline, Vec2};
/// An expensive fit-spline endpoint residual produces unknown freedom before numerical differentiation.
#[test] #[test]
fn endpoint_analysis_rejects_cubic_fit_work_before_differentiation() { fn endpoint_analysis_rejects_cubic_fit_work_before_differentiation() {
let mut d = Drawing::default(); let mut d = Drawing::default();
+2
View File
@@ -328,6 +328,8 @@ pub(crate) fn build(d: &Drawing, cons: &[&Constraint], opts: &SolveOptions) -> R
build_budgeted(d, cons, opts, &mut members_left) build_budgeted(d, cons, opts, &mut members_left)
} }
/// Build constraint components with one caller-owned rigid-member sampling budget shared by every model.
/// Return constraints that cannot be interpreted as skipped entries.
pub(crate) fn build_budgeted( pub(crate) fn build_budgeted(
d: &Drawing, d: &Drawing,
cons: &[&Constraint], cons: &[&Constraint],
+5
View File
@@ -117,6 +117,8 @@ pub fn rigid_points(d: &Drawing, ins: &Insert) -> Vec<Vec2> {
rigid_points_budgeted(d, ins, &mut members_left) rigid_points_budgeted(d, ins, &mut members_left)
} }
/// Sample finite block points within per-insert and caller-shared member limits.
/// Members that contribute no points still consume traversal work; nested blocks are not entered.
fn rigid_points_budgeted(d: &Drawing, ins: &Insert, members_left: &mut usize) -> Vec<Vec2> { fn rigid_points_budgeted(d: &Drawing, ins: &Insert, members_left: &mut usize) -> Vec<Vec2> {
let Some(block) = d.block(&ins.block) else { return Vec::new() }; let Some(block) = d.block(&ins.block) else { return Vec::new() };
let m = ins.transform(block.base.xy()); let m = ins.transform(block.base.xy());
@@ -187,6 +189,7 @@ impl Model {
Self::build_budgeted(d, handles, &mut members_left) Self::build_budgeted(d, handles, &mut members_left)
} }
/// Build solver parameters for supported entities while sharing rigid-member sampling work with other models.
pub(crate) fn build_budgeted(d: &Drawing, handles: &[Handle], members_left: &mut usize) -> Model { pub(crate) fn build_budgeted(d: &Drawing, handles: &[Handle], members_left: &mut usize) -> Model {
let mut m = Model::default(); let mut m = Model::default();
for &h in handles { for &h in handles {
@@ -620,6 +623,7 @@ mod budget_tests {
use cadcraft_geom::Vec3; use cadcraft_geom::Vec3;
use std::sync::Arc; use std::sync::Arc;
/// Writing an unchanged model back leaves partial rational weights and the original spline geometry intact.
#[test] #[test]
fn write_back_keeps_partial_rational_weights() { fn write_back_keeps_partial_rational_weights() {
let mut drawing = Drawing::default(); let mut drawing = Drawing::default();
@@ -635,6 +639,7 @@ mod budget_tests {
} }
} }
/// A second model receives no sampled block points after the first model spends the shared member budget.
#[test] #[test]
fn rigid_sampling_budget_is_shared_between_models() { fn rigid_sampling_budget_is_shared_between_models() {
let mut d = Drawing::default(); let mut d = Drawing::default();
+1
View File
@@ -116,6 +116,7 @@ fn r_at(c: &Constraint, i: usize) -> Option<&GeomRef> {
} }
impl Built { impl Built {
/// Estimate spline reconstruction work for endpoint and join residuals; other residual forms add no such cost.
pub(crate) fn spline_reconstruct_cost(&self, m: &Model) -> f64 { pub(crate) fn spline_reconstruct_cost(&self, m: &Model) -> f64 {
let cost = |i: usize| self.refs.get(i).map_or(0.0, |r| m.spline_reconstruct_cost(r.handle)); let cost = |i: usize| self.refs.get(i).map_or(0.0, |r| m.spline_reconstruct_cost(r.handle));
match self.form { match self.form {
+1
View File
@@ -364,6 +364,7 @@ fn hostile_new_geometry_never_panics() {
let _ = dof(&d, &[Handle(0), Handle(u64::MAX)]); let _ = dof(&d, &[Handle(0), Handle(u64::MAX)]);
} }
/// Fit-point vertex constraints retain a known freedom count because they do not reconstruct the spline.
#[test] #[test]
fn vertex_constraints_on_large_fit_spline_do_not_charge_reconstruction() { fn vertex_constraints_on_large_fit_spline_do_not_charge_reconstruction() {
let mut d = Drawing::default(); let mut d = Drawing::default();
+2
View File
@@ -0,0 +1,2 @@
mod lin_pat_parse;
mod sub_segment_alias;
+1 -1
View File
@@ -10,4 +10,4 @@ workspace = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# MPL-2.0: used as an unmodified dependency only (see plan/adr/0001 and NOTICE). # MPL-2.0: used as an unmodified dependency only (see plan/adr/0001 and NOTICE).
acadrust = "0.6.4" acadrust = "0.6.5"
+12 -19
View File
@@ -107,7 +107,6 @@ mod native {
current_style_names(&mut doc); current_style_names(&mut doc);
table_merges(&mut doc); table_merges(&mut doc);
table_style_rows(&mut doc, false); table_style_rows(&mut doc, false);
dimension_flags(&mut doc, false);
let orphans = drop_orphaned_dgn_components(&mut doc); let orphans = drop_orphaned_dgn_components(&mut doc);
if orphans > 0 { if orphans > 0 {
let (s, were) = if orphans == 1 { ("", "was") } else { ("s", "were") }; let (s, were) = if orphans == 1 { ("", "was") } else { ("s", "were") };
@@ -291,24 +290,14 @@ mod native {
} }
} }
/// The DWG dimension flags byte. Its bit 0 is the opposite of DXF group 70's bit 128 (set: the /// The DWG dimension flags byte. Its bit 1 is DXF group 70's bit 32 (ODA DWG specification,
/// text is at its default position) and its bit 1 is group 70's bit 32 (ODA DWG /// common dimension data), which acadrust keeps no copy of from DXF: it is set here as
/// specification, common dimension data), while acadrust 0.6.4 reads and writes bit 0 as /// CADCraft's DXF writer always sets it (#1094). Bit 0, the opposite of group 70's bit 128,
/// `text_user_positioned` itself and keeps no bit 32 from DXF. Without this, every dimension /// acadrust 0.6.5 reads and writes itself.
/// of a DWG CADCraft writes is user-positioned for AutoCAD (#1094), and every dimension of an fn dimension_flags(doc: &mut acadrust::CadDocument) {
/// AutoCAD DWG is read as user-positioned. `to_dwg` sets the fields up for the writer:
/// bit 32 as CADCraft's DXF writer always sets it, and bit 0 through the inverted field;
/// otherwise the field is read from bit 0 after a DWG is read.
fn dimension_flags(doc: &mut acadrust::CadDocument, to_dwg: bool) {
for e in doc.entities_mut() { for e in doc.entities_mut() {
let acadrust::entities::EntityType::Dimension(d) = e else { continue }; let acadrust::entities::EntityType::Dimension(d) = e else { continue };
let b = d.base_mut(); d.base_mut().dwg_flags_byte |= 0x02;
if to_dwg {
b.dwg_flags_byte |= 0x02;
b.text_user_positioned = !b.text_user_positioned;
} else {
b.text_user_positioned = b.dwg_flags_byte & 0x01 == 0;
}
} }
} }
@@ -529,7 +518,11 @@ mod native {
// interrupted once it starts (CWE-789). This bounds the large-input variant; the decoder // interrupted once it starts (CWE-789). This bounds the large-input variant; the decoder
// runs inside `catch_unwind` so a decode failure is reported rather than crashing. // runs inside `catch_unwind` so a decode failure is reported rather than crashing.
if dxf.len() > Limits::DEFAULT.max_dxf_bytes { if dxf.len() > Limits::DEFAULT.max_dxf_bytes {
return Err(format!("DXF: the input is {}, larger than the {} CADCraft converts to DWG", mb(dxf.len() as u64), mb(Limits::DEFAULT.max_dxf_bytes as u64))); return Err(format!(
"DXF: the input is {}, larger than the {} CADCraft converts to DWG",
mb(dxf.len() as u64),
mb(Limits::DEFAULT.max_dxf_bytes as u64)
));
} }
let data = dxf.to_vec(); let data = dxf.to_vec();
let mut doc = std::panic::catch_unwind(move || acadrust::DxfReader::from_reader(Cursor::new(data)).and_then(|r| r.read())) let mut doc = std::panic::catch_unwind(move || acadrust::DxfReader::from_reader(Cursor::new(data)).and_then(|r| r.read()))
@@ -545,7 +538,7 @@ mod native {
} }
other_layout_owners(&mut doc); other_layout_owners(&mut doc);
table_style_rows(&mut doc, true); table_style_rows(&mut doc, true);
dimension_flags(&mut doc, true); dimension_flags(&mut doc);
drop_unwritable(&mut doc); drop_unwritable(&mut doc);
current_dimstyle(&mut doc); current_dimstyle(&mut doc);
current_text_style(&mut doc); current_text_style(&mut doc);
+1 -1
View File
@@ -22,7 +22,7 @@ serde_json = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
log = { workspace = true } log = { workspace = true }
# R-tree spatial index for picking, window selection and object snaps (MIT OR Apache-2.0). # R-tree spatial index for picking, window selection and object snaps (MIT OR Apache-2.0).
rstar = { version = "0.12", default-features = false } rstar = { version = "0.13", default-features = false }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" libc = "0.2"
+3
View File
@@ -44,6 +44,7 @@ pub fn read_capped(path: &Path, max: usize) -> io::Result<Vec<u8>> {
mod tests { mod tests {
use super::*; use super::*;
/// Regular-file reads accept the byte boundary and reject any larger payload.
#[test] #[test]
fn reads_within_limit_and_rejects_oversize() { fn reads_within_limit_and_rejects_oversize() {
let dir = std::env::temp_dir().join(format!("cadcraft-cr-{}", std::process::id())); let dir = std::env::temp_dir().join(format!("cadcraft-cr-{}", std::process::id()));
@@ -57,6 +58,7 @@ mod tests {
std::fs::remove_dir_all(&dir).ok(); std::fs::remove_dir_all(&dir).ok();
} }
/// An endless device is rejected before its contents can be buffered.
#[cfg(unix)] #[cfg(unix)]
#[test] #[test]
fn rejects_non_regular_file() { fn rejects_non_regular_file() {
@@ -66,6 +68,7 @@ mod tests {
assert!(read_capped(p, 8 << 20).is_err()); assert!(read_capped(p, 8 << 20).is_err());
} }
} }
/// Opening a FIFO without a writer returns an error without waiting for a peer.
#[cfg(unix)] #[cfg(unix)]
#[test] #[test]
fn rejects_fifo_without_a_writer() { fn rejects_fifo_without_a_writer() {
+1
View File
@@ -337,6 +337,7 @@ fn opened(s: &Session, i: usize, entities: bool) -> Value {
} }
} }
/// Open a drawing from bounded regular-file bytes or base64 data and resolve its external references.
fn run_open(s: &mut Session, p: &Value) -> Result<Value> { fn run_open(s: &mut Session, p: &Value) -> Result<Value> {
if let Some(_path) = str_param(p, "path") { if let Some(_path) = str_param(p, "path") {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
+1 -2
View File
@@ -18,8 +18,7 @@ pub use craft_docpath::Refused;
/// on other platforms. /// on other platforms.
pub fn resolve(stored: &str, document: Option<&Path>) -> Result<PathBuf, Refused> { pub fn resolve(stored: &str, document: Option<&Path>) -> Result<PathBuf, Refused> {
let document = document.filter(|d| !d.as_os_str().is_empty()).map(|d| std::path::absolute(d).unwrap_or_else(|_| d.to_path_buf())); let document = document.filter(|d| !d.as_os_str().is_empty()).map(|d| std::path::absolute(d).unwrap_or_else(|_| d.to_path_buf()));
let path = craft_docpath::resolve(stored.trim(), document.as_deref())?; craft_docpath::resolve_absolute(stored.trim(), document.as_deref())
if path.is_absolute() { Ok(path) } else { Err(Refused::Invalid) }
} }
#[cfg(test)] #[cfg(test)]
+9
View File
@@ -698,11 +698,14 @@ fn wcmatch(a: &[Value]) -> R {
/// cover), but finite so truly exponential `*`-heavy patterns are rejected rather than hanging. /// cover), but finite so truly exponential `*`-heavy patterns are rejected rather than hanging.
const WCMATCH_BUDGET: u64 = 200_000_000; const WCMATCH_BUDGET: u64 = 200_000_000;
/// Spend one wildcard work unit, returning an error when the shared budget is exhausted.
fn charge_match(budget: &mut u64) -> Result<(), ()> { fn charge_match(budget: &mut u64) -> Result<(), ()> {
*budget = budget.checked_sub(1).ok_or(())?; *budget = budget.checked_sub(1).ok_or(())?;
Ok(()) Ok(())
} }
/// Split comma-separated wildcard alternatives while preserving escapes and bracket sets.
/// Every consumed character spends from the same budget used for matching.
fn split_patterns(pat: &str, budget: &mut u64) -> Result<Vec<Vec<char>>, ()> { fn split_patterns(pat: &str, budget: &mut u64) -> Result<Vec<Vec<char>>, ()> {
let mut out = vec![Vec::new()]; let mut out = vec![Vec::new()];
let mut chars = pat.chars().peekable(); let mut chars = pat.chars().peekable();
@@ -742,6 +745,7 @@ fn split_patterns(pat: &str, budget: &mut u64) -> Result<Vec<Vec<char>>, ()> {
Ok(out) Ok(out)
} }
/// Match one wildcard alternative, including leading `~` negation, within the shared budget.
fn match_pattern(text: &[char], pat: &[char], budget: &mut u64) -> Result<bool, ()> { fn match_pattern(text: &[char], pat: &[char], budget: &mut u64) -> Result<bool, ()> {
if let Some(('~', rest)) = pat.split_first().map(|(c, r)| (*c, r)) if let Some(('~', rest)) = pat.split_first().map(|(c, r)| (*c, r))
&& !rest.is_empty() && !rest.is_empty()
@@ -757,6 +761,7 @@ fn glob(text: &[char], pat: &[char], depth: usize, budget: &mut u64) -> Result<b
glob_cached(text, pat, depth, budget, &mut std::collections::HashSet::new()) glob_cached(text, pat, depth, budget, &mut std::collections::HashSet::new())
} }
/// Match suffixes while memoizing failures; both matching work and retained failure entries are bounded.
fn glob_cached( fn glob_cached(
text: &[char], text: &[char],
pat: &[char], pat: &[char],
@@ -778,6 +783,7 @@ fn glob_cached(
Ok(matched) Ok(matched)
} }
/// Evaluate wildcard tokens and backtracking branches under shared work and recursion limits.
fn glob_inner( fn glob_inner(
text: &[char], text: &[char],
pat: &[char], pat: &[char],
@@ -1060,6 +1066,7 @@ fn findfile(a: &[Value]) -> R {
Ok(if file_exists(&p) { Value::Str(p) } else { Value::Nil }) Ok(if file_exists(&p) { Value::Str(p) } else { Value::Nil })
} }
/// Read a regular text file up to 64 MiB, replacing invalid UTF-8 and returning `None` on read failure.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub fn read_text_file(path: &str) -> Option<String> { pub fn read_text_file(path: &str) -> Option<String> {
// Read through a byte-limited, regular-file-only reader so a path naming an endless device // Read through a byte-limited, regular-file-only reader so a path naming an endless device
@@ -1068,6 +1075,7 @@ pub fn read_text_file(path: &str) -> Option<String> {
let bytes = crate::capped_read::read_capped(std::path::Path::new(path), MAX).ok()?; let bytes = crate::capped_read::read_capped(std::path::Path::new(path), MAX).ok()?;
Some(String::from_utf8_lossy(&bytes).into_owned()) Some(String::from_utf8_lossy(&bytes).into_owned())
} }
/// Return `None` on the web, where native filesystem paths are unavailable.
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub fn read_text_file(_path: &str) -> Option<String> { pub fn read_text_file(_path: &str) -> Option<String> {
None None
@@ -1588,6 +1596,7 @@ pub fn wild_match(text: &str, pattern: &str) -> bool {
mod wildcard_budget_tests { mod wildcard_budget_tests {
use super::*; use super::*;
/// Pattern splitting, literal matching, and bracket scans all stop when their shared fuel runs out.
#[test] #[test]
fn all_native_scans_consume_fuel() { fn all_native_scans_consume_fuel() {
assert!(split_patterns("abcdefgh", &mut 4).is_err()); assert!(split_patterns("abcdefgh", &mut 4).is_err());
+3
View File
@@ -158,6 +158,8 @@ pub fn from_json(v: &serde_json::Value) -> Value {
const TO_JSON_BUDGET: u64 = 2_000_000; const TO_JSON_BUDGET: u64 = 2_000_000;
const TO_JSON_MAX_DEPTH: usize = 256; const TO_JSON_MAX_DEPTH: usize = 256;
/// Convert a LISP value to owned JSON within element and nesting limits.
/// Return `None` before a shared cons structure can expand beyond the remaining budget.
fn to_json_bounded(v: &Value, depth: usize, budget: &mut u64) -> Option<serde_json::Value> { fn to_json_bounded(v: &Value, depth: usize, budget: &mut u64) -> Option<serde_json::Value> {
if depth > TO_JSON_MAX_DEPTH || *budget == 0 { if depth > TO_JSON_MAX_DEPTH || *budget == 0 {
return None; return None;
@@ -185,6 +187,7 @@ pub fn getvar(ctx: &mut Ctx<'_>, a: Vec<Value>) -> Result<Flow, LispError> {
Ok(Flow::Value(crate::sysvars::get(ctx.s, &name).map(|v| from_json(&v)).unwrap_or_default())) Ok(Flow::Value(crate::sysvars::get(ctx.s, &name).map(|v| from_json(&v)).unwrap_or_default()))
} }
/// Set a system variable after bounded JSON conversion, reporting invalid or oversized values as LISP errors.
pub fn setvar(ctx: &mut Ctx<'_>, a: Vec<Value>) -> Result<Flow, LispError> { pub fn setvar(ctx: &mut Ctx<'_>, a: Vec<Value>) -> Result<Flow, LispError> {
arity(&a, 2, 2)?; arity(&a, 2, 2)?;
let name = string(&a[0])?; let name = string(&a[0])?;
+1
View File
@@ -131,6 +131,7 @@ pub(crate) struct Limits {
} }
impl Default for Limits { impl Default for Limits {
/// Provide finite cons-cell, string-byte, and synchronous-callback limits for a LISP run.
fn default() -> Self { fn default() -> Self {
Limits { cells: value::MAX_CELLS, string_bytes: value::STRING_BUDGET, callback_steps: 500_000_000 } Limits { cells: value::MAX_CELLS, string_bytes: value::STRING_BUDGET, callback_steps: 500_000_000 }
} }
+5
View File
@@ -221,6 +221,7 @@ fn runaway_routines_are_errors_not_crashes() {
assert_eq!(r, Ok("3000000".into())); assert_eq!(r, Ok("3000000".into()));
} }
/// An infinite comparator stops at its callback cap, and a later finite comparator still succeeds.
#[test] #[test]
fn synchronous_callback_loop_is_an_error_not_a_hang() { fn synchronous_callback_loop_is_an_error_not_a_hang() {
// A comparator with an infinite loop used to run with u64::MAX fuel and hang the thread; it // A comparator with an infinite loop used to run with u64::MAX fuel and hang the thread; it
@@ -232,6 +233,7 @@ fn synchronous_callback_loop_is_an_error_not_a_hang() {
assert_eq!(ok(&mut s, "(vl-sort '(3 1 2) '<)"), "(1 2 3)"); assert_eq!(ok(&mut s, "(vl-sort '(3 1 2) '<)"), "(1 2 3)");
} }
/// A zero-step callback limit prevents even the first callback side effect.
#[test] #[test]
fn a_zero_callback_budget_does_not_run_the_callback() { fn a_zero_callback_budget_does_not_run_the_callback() {
let mut s = session(); let mut s = session();
@@ -242,6 +244,7 @@ fn a_zero_callback_budget_does_not_run_the_callback() {
assert_eq!(ok(&mut s, "callback-ran"), "nil"); assert_eq!(ok(&mut s, "callback-ran"), "nil");
} }
/// A callback stops at a cap smaller than one scheduling slice without completing all its assignments.
#[test] #[test]
fn a_callback_must_fit_within_a_partial_slice() { fn a_callback_must_fit_within_a_partial_slice() {
let mut s = session(); let mut s = session();
@@ -252,6 +255,7 @@ fn a_callback_must_fit_within_a_partial_slice() {
assert_eq!(ok(&mut s, "(and (> callback-count 0) (< callback-count 1000))"), "T"); assert_eq!(ok(&mut s, "(and (> callback-count 0) (< callback-count 1000))"), "T");
} }
/// Crossing a scheduling boundary grants only the additional configured step, rather than another slice.
#[test] #[test]
fn reaching_a_callback_slice_boundary_does_not_grant_another_slice() { fn reaching_a_callback_slice_boundary_does_not_grant_another_slice() {
let counts: Vec<u64> = [399_999, 400_000] let counts: Vec<u64> = [399_999, 400_000]
@@ -270,6 +274,7 @@ fn reaching_a_callback_slice_boundary_does_not_grant_another_slice() {
assert!(counts[1] >= counts[0] && counts[1] - counts[0] <= 1, "{counts:?}"); assert!(counts[1] >= counts[0] && counts[1] - counts[0] <= 1, "{counts:?}");
} }
/// System-variable conversion rejects a shared list whose owned JSON expansion would exceed its budget.
#[test] #[test]
fn setvar_rejects_an_oversized_shared_structure() { fn setvar_rejects_an_oversized_shared_structure() {
// A list repeatedly sharing its own contents expands exponentially; setvar's bounded JSON // A list repeatedly sharing its own contents expands exponentially; setvar's bounded JSON
+1
View File
@@ -353,6 +353,7 @@ pub fn equal(a: &Value, b: &Value, fuzz: f64) -> bool {
/// Largest number of node comparisons one `equal` call may perform. /// Largest number of node comparisons one `equal` call may perform.
const EQUAL_BUDGET: u64 = 50_000_000; const EQUAL_BUDGET: u64 = 50_000_000;
/// Compare nested values within depth and node-work limits, returning false when either limit is reached.
fn equal_depth(a: &Value, b: &Value, fuzz: f64, depth: usize, budget: &mut u64) -> bool { fn equal_depth(a: &Value, b: &Value, fuzz: f64, depth: usize, budget: &mut u64) -> bool {
if depth > MAX_DEPTH || *budget == 0 { if depth > MAX_DEPTH || *budget == 0 {
return false; return false;
+1 -1
View File
@@ -208,7 +208,7 @@ impl SpatialIndex {
let pad = (b.width() + b.height()) * 1e-9 + (b.min.x.abs() + b.min.y.abs() + b.max.x.abs() + b.max.y.abs()) * 1e-12 + 1e-300; let pad = (b.width() + b.height()) * 1e-9 + (b.min.x.abs() + b.min.y.abs() + b.max.x.abs() + b.max.y.abs()) * 1e-12 + 1e-300;
let env = AABB::from_corners([b.min.x - pad, b.min.y - pad], [b.max.x + pad, b.max.y + pad]); let env = AABB::from_corners([b.min.x - pad, b.min.y - pad], [b.max.x + pad, b.max.y + pad]);
let mut out: Vec<u32> = let mut out: Vec<u32> =
self.tree.locate_in_envelope_intersecting(&env).filter_map(|it| self.offsets.get(&it.data.0).map(|off| off + it.data.1)).collect(); self.tree.locate_in_envelope_intersecting(env).filter_map(|it| self.offsets.get(&it.data.0).map(|off| off + it.data.1)).collect();
if with_infinite { if with_infinite {
out.extend_from_slice(&self.infinite); out.extend_from_slice(&self.infinite);
} }
@@ -29,7 +29,7 @@ fn session() -> (Session, Handle) {
/// As [`session`], with the fixture's text changed by `edit` first. /// As [`session`], with the fixture's text changed by `edit` first.
fn session_with(edit: impl Fn(String) -> String) -> (Session, Handle) { fn session_with(edit: impl Fn(String) -> String) -> (Session, Handle) {
let grip = "0 BLOCKLINEARGRIP\n5 68\n330 61\n100 AcDbEvalExpr\n90 9\n98 33\n99 232\n100 AcDbBlockElement\n300 End Grip\n98 33\n99 232\n1071 0\n"; let grip = "0 BLOCKLINEARGRIP\n5 68\n330 61\n100 AcDbEvalExpr\n90 9\n98 33\n99 232\n100 AcDbBlockElement\n300 End Grip\n98 33\n99 232\n1071 0\n";
let src = include_str!("../../io/tests/data/dynblock_door.txt") let src = include_str!("../../../io/tests/data/dynblock_door.txt")
.replace("\r\n", "\n") .replace("\r\n", "\n")
.replace("170 4\n91 0\n91 0\n", "170 4\n91 8\n91 9\n") .replace("170 4\n91 0\n91 0\n", "170 4\n91 8\n91 9\n")
.replace("1030 0\n93 0\n170 0\n171 0\n100 AcDbBlockVisibilityParameter", "1030 0\n93 10\n170 0\n171 0\n100 AcDbBlockVisibilityParameter") .replace("1030 0\n93 0\n170 0\n171 0\n100 AcDbBlockVisibilityParameter", "1030 0\n93 10\n170 0\n171 0\n100 AcDbBlockVisibilityParameter")
@@ -2,7 +2,6 @@
//! leave exactly the expected entities, layers and printed text. The corpus lives in its own //! leave exactly the expected entities, layers and printed text. The corpus lives in its own
//! repository; point `CADCRAFT_LISP_CORPUS` at its `cases` directory (or the repository) to run //! repository; point `CADCRAFT_LISP_CORPUS` at its `cases` directory (or the repository) to run
//! it. Without it the test only says so. //! it. Without it the test only says so.
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};

Some files were not shown because too many files have changed in this diff Show More