Commit Graph
155 Commits
Author SHA1 Message Date
aiongg eb12fc6ee4 Integration: cargo fmt 2026-10-22 01:59:03 +00:00
aiongg 64f98d824a Merge PR #2280: fix(formats): support xs:time, string-value, and case-insensitive cell types in spreadsheets 2026-10-22 01:54:24 +00:00
aiongg 04b3881e3e Integration: track t06
docs/gaps.md row 4: one row (t06 had kept a second, older copy of it); t05's #1840 and t06's #2964 clauses both in, main's #2963 clause kept. docs/recovery.md: t06's retry sentence with t01's base64 size text. Test files: both tracks' tests kept whole.
2026-10-22 01:47:34 +00:00
aiongg 16d1c8a933 Integration: track t05
crates/pdf/src/lib.rs: t01's vector_text kept before t05's watermark (box area signature). Test files and canvas.rs tests: both tracks' tests kept whole. Docs: status lines combined, rows unioned or word-merged.
2026-10-22 01:46:47 +00:00
aiongg 0d3b8eb0cb Integration: track t04
crates/formats/src/odt.rs: t01's Body::new/take_blocks kept; take_blocks finishes tables with t04's TableB::finish (column widths). Test files: both tracks' tests kept whole. Docs: status lines combined, rows unioned or word-merged.
2026-10-22 01:46:17 +00:00
aiongg 86df1e70ce Integration: track t03
# Conflicts:
#	ROADMAP.md
#	crates/formats/src/html.rs
#	docs/gaps.md
#	docs/ui-parity.md
2026-10-22 01:44:54 +00:00
aiongg 245b5bd8d0 Integration: track t02
Docs: status lines combined (t02's entries first); log rows unioned; file-format-parity .rtf row from t02 (#1653, duplicate .rtf row dropped as t02 did), .odt row word-merged.
2026-10-22 01:44:01 +00:00
aiongg 520f54cb81 Integration: cargo fmt 2026-10-22 01:34:26 +00:00
aiongg 5c260a16fd Integration: cargo fmt 2026-10-22 01:31:48 +00:00
aiongg d015988e93 Integration: #2334 test a formula string result continued in a CONTINUE record 2026-10-22 01:25:01 +00:00
aiongg 92b36ceae0 Merge PR #2557: #1681: HTML export and import keep furigana (ruby)
docs/file-format-parity.md: main's status line with this change added; both progress-log rows, this one first.
docs/localization-parity.md: main's status line with this change added.
2026-10-22 01:23:40 +00:00
aiongg 9cd24539bd Merge PR #2549: #1653: RTF import decodes the East Asian multibyte code pages (932/936/949/950)
docs/file-format-parity.md: main's status line with this change added.
2026-10-22 01:23:23 +00:00
aiongg a05c650a16 Merge PR #3391: ODT: table column widths are read, so a resized column survives a save
crates/formats/src/odt.rs: StyleInfo keeps both cell_valign (#2416) and column_width.
ROADMAP.md, docs/file-format-parity.md, docs/gaps.md: main's status lines kept; the PR's log rows added.
2026-10-22 01:23:19 +00:00
aiongg 5b19acb4ec Merge PR #3193: ODT: read an embedded equation's own MathML instead of its undecodable metafile preview
ROADMAP.md: progress log carries both sides.
docs/file-format-parity.md: ODT row keeps #873's headers and footers and adds #3193's formula objects.
2026-10-22 01:23:17 +00:00
aiongg a7b8e32fde Merge PR #2331: fix(mathml): support linethickness=none and menclose notations
crates/formats/src/mathml.rs: menclose keeps main's box-with-strikes reading (the writer emits "box horizontalstrike") and adds the PR's radical, actuarial, cross and case-insensitive notations.
2026-10-22 01:22:19 +00:00
aiongg 601af83f2c Integration: #2416 collapse the nested vertical-align if (clippy::collapsible_if) 2026-10-22 01:22:13 +00:00
aiongg 544647318d Merge PR #873: ODT: read headers and footers from the master page (#863)
crates/formats/src/odt.rs: Body::new from #873 with main's xml_space_end; parse_tracked returns main's document font as 4th element and #873's page parts as 5th (the reviewed integration fix for #873).
docs/file-format-parity.md: status line, ODT row and change log carry both sides.
2026-10-22 01:22:10 +00:00
aiongg c8b657ff9b Merge PR #2416: fix(formats): support table cell vertical alignment in rtf, html, and odt
crates/formats/src/html.rs: both sides' appended tests kept.
2026-10-22 01:22:05 +00:00
aiongg 96dd01bb30 Integration: #2273 Latin weight/style and line styles take precedence over Asian, complex and line-type attributes 2026-10-22 01:21:51 +00:00
aiongg 82b9795c62 Merge PR #2273: fix(formats): support asian/complex scripts and underline/strike types in ODT
crates/formats/src/odt.rs: kept main's tests and added the PR's test after them.
2026-10-22 01:21:33 +00:00
aiongg 415320056b Integration: #2269 LaTeX lengths keep macro names case-sensitive 2026-10-22 01:21:17 +00:00
aiongg eae3d8496e Merge PR #2269: fix(formats): parse leading plus and case-insensitive units in LaTeX lengths 2026-10-22 01:21:08 +00:00
aiongg f14a0ce13f Integration: #2268 ODT lengths accept only the standard pc unit for picas 2026-10-22 01:20:59 +00:00
aiongg 7d8f31547e Merge PR #3320: Require hex digits in percent_decode escapes 2026-10-22 01:20:54 +00:00
aiongg 61fbb080a9 Merge PR #2232: fix(formats): support borderless and bordered tables in LaTeX import and export
crates/formats/src/latex.rs: kept both sides' new tests
2026-10-22 01:20:23 +00:00
aiongg d15c57a713 Integration: #2153 drop trailing blank line and use assert! for bool checks in tab_separated_list_markers 2026-10-22 01:20:12 +00:00
aiongg 0c583fe58c Merge PR #2153: fix(formats): support tab-separated list markers in markdown
crates/formats/src/markdown.rs: kept both sides' new tests
2026-10-22 01:20:08 +00:00
aiongg 5b9b1f552b Merge PR #2144: fix(formats): support distributed paragraph alignment in RTF 2026-10-22 01:20:00 +00:00
aiongg d8e5b1823d Merge PR #2140: fix(formats): support left alignment in markdown tables
crates/formats/src/markdown.rs: kept both sides' new tests
2026-10-22 01:19:56 +00:00
aiongg 0c039708db Integration: #3267 leave footnote definitions out of link definitions 2026-10-22 01:19:54 +00:00
aiongg c88b2740d8 Merge PR #2268: fix(formats): parse case-insensitive and pica units in ODT lengths 2026-10-22 01:19:50 +00:00
aiongg f2e441862d Merge PR #2249: fix(formats): support 4/8-digit hex, space-separated rgb and hsl in parse_color 2026-10-22 01:19:47 +00:00
aiongg 05c34486f5 Merge PR #2086: fix(export): pictures marked decorative are skipped by screen readers 2026-10-22 01:19:47 +00:00
aiongg 81fbf9fb77 Merge PR #3322: wasm: use the browser clock only on wasm32-unknown-unknown, not on WASI 2026-10-22 01:19:46 +00:00
aiongg 57387979f1 Merge PR #3356: Keep '&' in LaTeX \href and \url addresses on import 2026-10-22 01:19:45 +00:00
aiongg 8b9c85ded8 Merge PR #2292: fix(formats): support bullet, top, bot, hslash, accents, bm, and mod in LaTeX math 2026-10-22 01:19:45 +00:00
aiongg d97e32fd06 Merge PR #2258: fix(formats): support case-insensitive and spaced !important in HTML CSS 2026-10-22 01:19:45 +00:00
aiongg a7568eb86c Merge PR #3267: Markdown import: reference-style links 2026-10-22 01:19:44 +00:00
aiongg a6c31d8efd Merge PR #2172: fix(formats): support small and big tags and relative/large font-size keywords in HTML 2026-10-22 01:19:44 +00:00
aiongg 44d569cf74 Merge PR #3249: Reject non-digit characters in numeric HTML entities 2026-10-22 01:19:44 +00:00
aiongg fd7152826e Merge PR #2171: fix(formats): support column break in RTF import and export 2026-10-22 01:19:44 +00:00
aiongg ac334d65f3 Merge PR #1833: test(formats): verify txt export preserves list item labels 2026-10-22 01:19:43 +00:00
aiongg 80b19a90c0 Merge PR #2334: fix(sheet/xls): track substream depth and support CONTINUE records on formula strings 2026-10-22 01:19:43 +00:00
aiongg 00b2bfc329 Merge PR #1788: formats: the Markdown code fence outgrows any backtick run in its content, so a round trip keeps it 2026-10-22 01:19:43 +00:00
Dylan M. Taylor 7f38f41d8c Use chrono instead of hand-rolled UTC conversion (#3427)
* Use chrono for UTC/calendar conversion instead of hand-rolled copies

Replaces the hand-rolled civil_from_days / days_from_civil copies and the
Excel serial-date, FILETIME and logging/PDF date conversions with chrono as
a workspace dependency (default-features = false, std only, so wasm is
unaffected). Output formats are unchanged and pinned by the existing (and one
new) test. Excel's 1900 leap-year bug and the 1900/1904 systems keep their
explicit special cases; a small proleptic-Gregorian fallback covers years
outside chrono's NaiveDate range so no input changes behaviour or panics.
AGENTS.md now says time goes through chrono.

Follow-up to storytold/cadcraft#1785

* Never panic or reshape date output for clocks outside chrono's range

chrono's From<SystemTime> unwraps, so a clock past year 262143 panicked
inside the logger. logging::timestamp now uses the fallible
DateTime::from_timestamp and reads such a clock as the epoch, as the old
saturating code did.

iso_from_unix_secs fell back to the Unix epoch for timestamps chrono
rejects; it now keeps the proleptic Gregorian date civil_from_days gives,
restoring the pre-chrono result for supported out-of-range u64 inputs.

Years past 9999 are written with {:04} rather than %Y (which prepends a
'+') in logging::timestamp, iso_from_unix_secs, docbin::filetime_iso and
odt::now_iso, so byte-for-byte output is preserved.

filetime_iso gained a docstring; AGENTS.md's chrono bullet now notes the
narrow out-of-range fallback. Tests cover each boundary.

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

* Route out-of-range dates through chrono's 400-year cycle, not hand-rolled copies

The PR kept hand-rolled Hinnant `civil_fallback`/`days_from_civil_fallback`
copies for dates outside chrono's ±262143-year range, and AGENTS.md carved a
"narrow fallback" exception for them. That left calendar arithmetic outside
chrono after all.

Replace every copy with the Gregorian 400-year cycle (146_097 days): chrono
converts the day/year within one cycle and whole cycles are added back via i128
(so i64::MIN/MAX can't overflow). This keeps chrono as the only calendar logic
and reproduces the pre-chrono proleptic Gregorian result for every value:

- engine cmd/mod.rs `civil_from_days` (reached by the system clock and by
  `iso_from_unix_secs` for a u64 seconds count past chrono's range).
- engine cmd/controls.rs `days_from_civil` and `days_in_month` (`days_in_month`
  now uses `rem_euclid(400)` since month lengths repeat every 400 years).
- formats sheet.rs `civil` and docx chart.rs `civil` (Excel serials).

Outputs are unchanged: verified the cycle result equals the old formula over a
wide range and at the extremes with a scratch chrono program. New tests pin
accepted inputs past chrono's range on both sides of the epoch, through the
public/lib entry points and the private converters, with values from the old
formula. Removed the AGENTS.md "narrow fallback" exception.

pdf/lib.rs `civil` is left as-is: its only caller is the live system clock and
its year is clamped to 0..9999 by the PDF DateTime, so the epoch fallback past
year 262143 is the accepted live-clock behavior, not a hand-rolled copy.

Tested: cargo test -p wordcraft-engine -p wordcraft-formats -p wordcraft-docx;
fmt --check; clippy --all-targets -D warnings.

* logging/docbin/odt/pdf: keep extended-year dates, no epoch fallback

FredApps (CHANGES_REQUESTED) showed `DateTime::from_timestamp(..).unwrap_or_default()`
changes output: a timestamp the old code converted past chrono's year 262143 range now
collapsed to 1970-01-01 instead of its real proleptic-Gregorian date. The same regression
exists wherever the PR swapped a hand-rolled converter for the epoch fallback. Fix every
such site to keep chrono as the only calendar logic via the 400-year Gregorian cycle
(146_097 days): chrono converts the day within one cycle, whole cycles carry in the year
(i128, saturated into i64). Verified the cycle result equals the old formula across a wide
range and at the extremes.

- apps/wordcraft/src/logging.rs `timestamp`: added `civil_from_days(u64)` (cycle), kept the
  pre-1970 epoch clamp (`duration_since`) and the milliseconds/time-of-day format exactly.
  Pins: UNIX_EPOCH + 10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z (FredApps' example,
  was 1970-...); the checked_add u64::MAX/2 s fixture now asserts 292277026596-12-04T15:30:07.000Z
  instead of the epoch.
- crates/docbin/src/metadata.rs `filetime_iso`: restored a cycle-based `civil(i64)` instead
  of `from_timestamp(..)?` (which dropped the field past chrono's range). A u64 FILETIME caps
  at ~1.83e12 unix s (year 60056), inside chrono's range, so no reachable value changes; pinned
  the largest FILETIME (u64::MAX) -> 60056-05-28T05:36:10Z to lock the extended-year path.
- crates/formats/src/odt.rs `now_iso`: reuse the crate's existing cycle converter
  `sheet::civil` (made pub(crate)) rather than the epoch fallback.
- crates/pdf/src/lib.rs `civil`: cycle converter; year returned unclamped and the caller's
  existing PDF DateTime 0..=9999 year clamp is kept exactly, so month/day/time of a far-future
  clock survive. Pins: civil(10_000_000_000_000)=(318857,5,20,17,46,40),
  civil(-10_000_000_000_000)=(-314918,8,13,6,13,20), civil(253_402_300_800)=(10000,1,1,0,0,0).

engine `cmd/mod.rs::iso_from_unix_secs` already uses the cycle fallback (last round); unchanged.

* 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.
2026-10-21 19:36:41 -04:00
Divyanshu Gupta bfb81b72eb fix(sheet/xls): track substream depth and support CONTINUE records on formula strings 2026-10-21 20:00:37 +05:30
Midasdf 1678d93f54 #1681: ruby review follow-ups — tracked ODT ruby, HTML start space, docs
- ODT export reads the tracked insertion/deletion from a Ruby inline's formatting
  too (Item::marks and the paragraph's tracked check): an inserted or deleted
  phonetic guide keeps its change range around the base text instead of being
  written as untracked text.
- HTML export computes the paragraph-start state before the ruby's formatting tags,
  so a leading space in the base text becomes &nbsp; instead of a space HTML can
  collapse.
- docs/file-format-parity.md: restore main's format rows (the PR had replaced them
  with stale copies that lost ODT keep-with-next #2813, TXT #2853 and the HTML
  blockquote/MathML details) and add the ruby sentence to the retained .htm/.html
  row; restore the rolled-back header change list.
- docs/localization-parity.md: restore the header change list; date the new
  revision-history rows 2026-10-21.
- Tests: html_ruby_at_the_paragraph_start_keeps_a_leading_space and
  an_inserted_ruby_keeps_its_tracked_insertion.

Validation: cargo fmt --all -- --check; cargo clippy -p wordcraft-formats
--all-targets -- -D warnings; cargo test -p wordcraft-formats -> 204 passed,
0 failed (doctests 0).
2026-10-21 22:41:05 +09:00
Midasdf cc724045dd #1653: review follow-ups — BOM, streamed runs, consolidated docs
- Create the East Asian decoder without BOM handling: the code page is named by
  \ansicpg, \cpg or \fcharset, so a GBK/Big5/UHC run that starts EF BB BF is text,
  not a UTF-8 BOM that switches the rest of the document to UTF-8 (#2549 review).
- Decode raw 8-bit runs through the streaming decoder in one pass instead of one
  byte at a time: ASCII body text in a Japanese/Chinese/Korean document and picture
  hex skip the per-byte path, and a single \'hh byte decodes into a stack buffer
  (no String allocation per byte). Picture hex no longer goes through the code page
  decoder at all.
- Decode loop: decode_to_string writes only into spare capacity, so drain the stack
  buffer and keep decoding until the run is consumed (the old one-call version could
  truncate a run longer than the reserved capacity).
- docs/file-format-parity.md: keep main's format rows (the PR had replaced them with
  stale copies that lost ODT keep-with-next #2813, TXT #2853 and the HTML blockquote
  and MathML details), merge the East Asian sentence into the one RTF row — which also
  removes the duplicate RTF row main carries and restores "export writes every
  section" and the tracked paragraph-mark wording — restore the rolled-back header
  change list, and date the new revision row 2026-10-21.

Validation: cargo fmt --all -- --check; cargo clippy -p wordcraft-formats -p wordcraft-doc
--all-targets -- -D warnings; cargo test -p wordcraft-formats -p wordcraft-doc
→ 200 + 174 passed, 0 failed (doctests 0).
2026-10-21 22:38:07 +09:00
Divyanshu Gupta b333885228 fix(formats): support 4/8-digit hex, space-separated rgb and hsl in parse_color 2026-10-21 18:52:08 +05:30
Divyanshu Gupta c60d9df0c3 fix(formats): support table cell vertical alignment in rtf, html, and odt 2026-10-21 18:35:08 +05:30