mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-24 09:57:08 -06:00
* Use chrono for UTC/calendar conversion instead of hand-rolled copies Replaces the civil_from_days copies behind the app's log timestamp, the Measurement Log's date/time, and the perf report date with chrono as a workspace dependency (reusing the version already in the tree). Output formats are unchanged and pinned by the existing tests. AGENTS.md now says time goes through chrono. Follow-up to storytold/cadcraft#1785 * Never panic on a clock outside chrono's range; keep 5-digit years unprefixed chrono's From<SystemTime> unwraps internally, so a clock past year 262143 panicked. logging::timestamp and analysis_cmds::now_iso now read the duration since the epoch and go through the fallible DateTime::from_timestamp, reading an out-of-range or pre-epoch clock as the epoch as the old saturating code did. All three ISO helpers (timestamp, now_iso, perf::iso_utc) write the year with {:04} rather than %Y, which prefixes a '+' past year 9999. Tests cover the far-future and year-10000 cases for timestamp and iso_utc. * Don't build an unrepresentable SystemTime in the far-future test UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows' SystemTime and panicked before timestamp() ran, failing the Windows test job. Build it with checked_add and only assert where it exists. * time: preserve old out-of-range date conversion (no epoch fallback) `DateTime::from_timestamp(...).unwrap_or_default()` silently reset any instant past chrono's year range to 1970-01-01, changing output the old hand-rolled Howard-Hinnant code converted exactly (e.g. 10_000_000_000_000 s was 318857-05-20 17:46:40, not the epoch). Restore the old conversion domain with a cycle-based helper that routes the calendar through chrono (the calendar repeats every 400 years = 146_097 days, so month/day/time come from chrono and only the year is carried in i128, keeping every i64 exact). - apps/photocraft/src/logging.rs: `timestamp` via `utc_parts`; keep the pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch. - crates/engine/src/analysis_cmds.rs: extract pure `iso_from_unix` (cycle based) from `now_iso` so the conversion is tested directly. - xtask/src/perf.rs: `iso_utc` cycle-based, accepts the full u64 range the old code did. Pinned old outputs in tests: logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch), 10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z iso_from_unix 10_000_000_000_000 -> 318857-05-20T17:46:40Z, -10_000_000_000_000 -> -314918-08-13T06:13:20Z, i64 extremes iso_utc u64::MAX -> 584554051223-11-09T07:00:15Z (was epoch), 10_000_000_000_000 -> 318857-05-20T17:46:40Z * Don't build a year-318857 SystemTime unchecked in the logging test Windows' SystemTime only reaches about year 30828, so UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before timestamp() ran and failed the Windows test job. Build it with checked_add and assert where the platform can hold it. The year-10000 case fits everywhere and stays unconditional. * Name the 400-year range extension as the one exception to the time rule The rule said never to hand-roll calendar math, but the range fix (shifting by whole 400-year Gregorian cycles so dates past chrono's year range keep their old output) is cycle arithmetic, so the rule and the code disagreed. The rule now names that exception: the library still computes month, day and time, and only whole cycles are carried in the year. * Say the library computes month and day; time-of-day is plain arithmetic Narrows the range-extension wording: the date library computes the calendar fields, and splitting seconds into hours, minutes and seconds is ordinary arithmetic, which is what the code does.
172 lines
6.9 KiB
TOML
172 lines
6.9 KiB
TOML
[workspace]
|
||
resolver = "3"
|
||
members = ["crates/*", "apps/*", "xtask"]
|
||
default-members = ["crates/*", "apps/photocraft", "apps/photocraft-cli", "xtask"]
|
||
|
||
[workspace.package]
|
||
version = "0.12.0"
|
||
edition = "2024"
|
||
license = "MIT OR Apache-2.0"
|
||
rust-version = "1.95"
|
||
|
||
[workspace.dependencies]
|
||
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] }
|
||
# internal
|
||
photocraft-geom = { path = "crates/geom" }
|
||
photocraft-color = { path = "crates/color" }
|
||
photocraft-cms = { path = "crates/cms" }
|
||
photocraft-raster = { path = "crates/raster" }
|
||
photocraft-doc = { path = "crates/doc" }
|
||
photocraft-ops = { path = "crates/ops" }
|
||
photocraft-psd = { path = "crates/psd" }
|
||
photocraft-codecs = { path = "crates/codecs" }
|
||
photocraft-raw = { path = "crates/raw" }
|
||
photocraft-affinity = { path = "crates/affinity" }
|
||
photocraft-compose = { path = "crates/compose" }
|
||
photocraft-gpu = { path = "crates/gpu" }
|
||
photocraft-paint = { path = "crates/paint" }
|
||
photocraft-algo = { path = "crates/algo" }
|
||
photocraft-io = { path = "crates/io" }
|
||
photocraft-text = { path = "crates/text" }
|
||
photocraft-vector = { path = "crates/vector" }
|
||
photocraft-format = { path = "crates/format" }
|
||
photocraft-automation = { path = "crates/automation" }
|
||
photocraft-plugins = { path = "crates/plugins" }
|
||
photocraft-engine = { path = "crates/engine" }
|
||
photocraft-ui-egui = { path = "crates/ui-egui" }
|
||
photocraft-testkit = { path = "crates/testkit" }
|
||
photocraft-tablet = { path = "crates/tablet" }
|
||
photocraft-ipp = { path = "crates/ipp" }
|
||
|
||
# external
|
||
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "3d37da389a3618ab9b76109da11416e9ca4ea719" }
|
||
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "82a24b006eefa036b43c09a67fb9a3fbd17ffe63" }
|
||
thiserror = "2"
|
||
serde = { version = "1", features = ["derive", "rc"] }
|
||
serde_json = "1"
|
||
arc-swap = "1"
|
||
bytemuck = { version = "1", features = ["derive"] }
|
||
flate2 = { version = "1", default-features = false, features = ["rust_backend"] }
|
||
image = { version = "0.25", default-features = false }
|
||
# Baseline-JPEG decode of lossy DNG tiles (compression 34892), shared with zune-jpeg.
|
||
zune-core = "0.5"
|
||
zune-jpeg = "0.5"
|
||
proptest = "1"
|
||
eframe = { version = "0.36", default-features = false }
|
||
# Without `default_fonts`, which embeds egui's fonts uncompressed: ui-egui ships them deflated
|
||
# (egui_fonts.rs; the web build's size gate).
|
||
egui = { version = "0.36", default-features = false }
|
||
egui_kittest = "0.36"
|
||
rfd = "0.17"
|
||
log = "0.4"
|
||
# All date/time conversion goes through chrono (no hand-rolled calendar code beyond the 400-year range extension; AGENTS.md).
|
||
chrono = { version = "0.4.45", default-features = false, features = ["std"] }
|
||
rayon = "1"
|
||
criterion = { version = "0.5", default-features = false }
|
||
getrandom = "0.3"
|
||
|
||
[workspace.lints.rust]
|
||
unsafe_code = "forbid"
|
||
|
||
[workspace.lints.clippy]
|
||
dbg_macro = "warn"
|
||
todo = "warn"
|
||
|
||
[profile.dev.package."*"]
|
||
opt-level = 2
|
||
|
||
[profile.dev]
|
||
opt-level = 1
|
||
# Line tables keep file:line in panic backtraces but drop variable info, which roughly halves link
|
||
# time and PDB size for the ~260 test binaries (#2204). CI sets the same through
|
||
# CARGO_PROFILE_DEV_DEBUG (and no debuginfo at all for the Windows tests). To inspect locals in a
|
||
# debugger, build with CARGO_PROFILE_DEV_DEBUG=true.
|
||
debug = "line-tables-only"
|
||
|
||
# Test builds compile ui-egui without optimization: its library and unit tests were the biggest
|
||
# test-build units after engine's (12.6 of ~63 compile minutes on the Windows CI runner), and its
|
||
# tests run no heavier pixel code of their own (that lives in the crates that stay optimized).
|
||
# Measured on CI: Windows build 18.9 -> 15.4 min, test run 10.0 -> 10.5. `cargo run` and
|
||
# `cargo build` use the dev profile and are unaffected.
|
||
[profile.test.package.photocraft-ui-egui]
|
||
opt-level = 0
|
||
|
||
[profile.release]
|
||
lto = "thin"
|
||
codegen-units = 1
|
||
|
||
# The browser build (apps/photocraft-web, `data-cargo-profile` in its index.html). Hosts cap the
|
||
# size of a single file (Cloudflare Pages/Workers: 25 MiB), and the wasm is downloaded on every
|
||
# visit, so this profile optimizes for size: fat LTO, opt-level "z" for PhotoCraft's own crates,
|
||
# "s" for dependencies and for the crates that decode files, and 3 for pixel processing (filters
|
||
# and compositing). Dependencies stay at "s": at "z" the whole wasm grows (by 0.4 MB, measured).
|
||
# panic stays the target default (wasm32-unknown-unknown aborts; AGENTS.md's `panic = "unwind"`
|
||
# is for the native app shell). packaging/web/package.sh fails the build above 24 MiB.
|
||
[profile.wasm-release]
|
||
inherits = "release"
|
||
opt-level = "z"
|
||
lto = "fat"
|
||
codegen-units = 1
|
||
strip = "debuginfo"
|
||
|
||
[profile.wasm-release.package."*"]
|
||
opt-level = "s"
|
||
|
||
# Decoding and rasterizing keep their speed.
|
||
[profile.wasm-release.package.photocraft-codecs]
|
||
opt-level = "s"
|
||
[profile.wasm-release.package.photocraft-raw]
|
||
opt-level = "s"
|
||
[profile.wasm-release.package.photocraft-heif]
|
||
opt-level = "s"
|
||
[profile.wasm-release.package.photocraft-vector]
|
||
opt-level = "s"
|
||
|
||
# Large crates off the per-frame and per-pixel paths: the document model, file parsers run once
|
||
# per open/save, JSON (settings, presets, control channel), SVG parsing, and shader translation
|
||
# (runs when a pipeline is created). "z" keeps the wasm well under the size gate.
|
||
[profile.wasm-release.package.photocraft-doc]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.photocraft-psd]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.photocraft-format]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.photocraft-affinity]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.serde_json]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.usvg]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.naga]
|
||
opt-level = "z"
|
||
|
||
# The browser UI toolkit favors size to stay within the WebAssembly upload budget.
|
||
[profile.wasm-release.package.egui]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.epaint]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.eframe]
|
||
opt-level = "z"
|
||
|
||
# Pixel processing keeps its speed-oriented optimization level, with two exceptions.
|
||
# Colour-management transforms are built once and cached (`transform::cached`), so "z" costs
|
||
# little runtime. The browser app composites per frame on the GPU (`crates/gpu`, WebGPU/WebGL2),
|
||
# so the CPU compositor is not the browser's hot path there; "z" for compose keeps the wasm
|
||
# under the 24 MiB gate (measured −122 KB vs opt-level 3). algo's filters run per pixel on
|
||
# every interactive filter application; "z" there is the size lever of last resort (−331 KB),
|
||
# taken because the gate is enforced in CI: if browser filters feel too slow, restore 3 here
|
||
# and reclaim the bytes from a feature-gated decoder instead.
|
||
[profile.wasm-release.package.photocraft-raster]
|
||
opt-level = 3
|
||
[profile.wasm-release.package.photocraft-color]
|
||
opt-level = 3
|
||
[profile.wasm-release.package.photocraft-cms]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.photocraft-compose]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.photocraft-algo]
|
||
opt-level = "z"
|
||
[profile.wasm-release.package.photocraft-paint]
|
||
opt-level = 3
|
||
|