mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-25 10:57:14 -06:00
sonar-fix/enforce-https
26
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2054f8fd88 |
fix(web): fall back after asynchronous save picker rejection (#2145)
Preserve AbortError cancellation and report download fallback failures without marking saves successful. Fixes #1246 Assisted-by: OpenAI Codex Co-authored-by: Codex <codex@openai.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
28d6260a81 |
#1789: Add Font imports every selected font file (#2404)
* Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1789: cap font bytes waiting in the web inbox Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
8701f4e28d |
Theme: default to system appearance (#718)
* Theme: default to system appearance Sets Appearance::System as the default variant for ThemePreference, so fresh installs and unconfigured preference files follow the host OS appearance dynamically. * contributors: add the missing blank line Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * contributors: drop the duplicate entry (already listed on main) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Web prefs test: junk storage keeps the new default (System) appearance Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a30a1866e2 |
Web: keep preferences such as dark mode between visits (#2132) (#2205)
The web shell never loaded or saved the UiState the desktop app keeps in ui.json, so a reload reset the theme and every other preference. It now restores it from localStorage at startup and writes it back whenever it changes (a reload runs no exit code). Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
60b9500347 |
Web: add option to load system installed fonts in dropdown (#1390)
* feat(web): add option to load system installed fonts in dropdown
Add explicit 'Load System Installed Fonts…' button in font dropdowns for web (Wasm).
- Dispatches font.loadSystem via transient user activation on click
- Calls window.queryLocalFonts() to enumerate and load installed fonts into FontDb
- Invalidates font family cache in FontPreviews upon new font installation
- Complete localization across all 18 languages
- Credit GhostMitra in contributors/people.toml
* fix(ui, engine): canvas focus after chrome click and checked file access in sniff
* Revert "fix(ui, engine): canvas focus after chrome click and checked file access in sniff"
This reverts commit
|
||
|
|
eb54d88ec0 |
Mail merge: .xls recipients, legacy encodings, Filter and Sort (#778) (#885)
* #778: .xls and legacy-encoding recipient lists; filter and sort recipients - Recipient lists: legacy Excel 97-2003 .xls (BIFF8 reader on the cfb crate already in the tree); Excel 5.0/95 and password-protected .xls refused with a clear message; a CSV named .xls reads as text, an HTML one gets an error. .xls added to the picker filters (desktop, web, built-in browser) and drop handling. - Text lists (and plain-text import) detect legacy encodings when not UTF-8/UTF-16: GBK/GB18030, Big5, Shift_JIS, EUC-KR, Windows code pages (encoding_rs + chardetng, MIT/Apache); an `encoding` param overrides. - mailings.filterRecipients / mailings.sortRecipients: field, comparison (equal, notEqual, less, greater, lessOrEqual, greaterOrEqual, blank, notBlank, contains, notContains), value, And/Or joins; sort keys. Preview, Finish & Merge and labels use only the matching records in sort order. Edit Recipient List edits every record and keeps the filter. - Recipient List dialog: Filter and Sort section and a merge-order column; catalog rows; strings in every catalog; docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n: Hungarian and French strings for this change Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Review fixes for #885: total order for recipient sort, Bulgarian strings - compare_cells now ranks numbers (compared numerically) before text (compared case-insensitively), so it is a total order; a column mixing 9, 10 and 1a could make slice::sort_by panic. Regression test sorts mixed columns. - Bulgarian catalog: the 24 Filter and Sort strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * vi: mail merge Filter and Sort strings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
03ba9e483f |
Web: New/Open waits for Save before discarding the document (#1315) (#1510)
* #1445: Save in the close prompt that can't write the file says so instead of looking like Cancel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1445: a failed save from the close prompt brings the prompt back with the reason, Save As included Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1315: web Save before New/Open waits for the save picker; cancel or failure keeps the document Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1315: overlapping saves hold the replacement; cancel and waiting messages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
53b7debea5 |
Web: image paste goes to the active edit target (#1349) (#1513)
* #1349: WIP * Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1349: pastes onto the page work again; the guard uses page focus Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1349: pastes onto the page work again; the guard uses page focus (clippy) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1349: a focused text field outside dialogs ignores picture pastes (test) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
9d1e207c78 |
#802: Insert › Picture takes Windows Metafiles and Enhanced Metafiles (#869)
WMF and EMF pictures already drew (crates/metafile) when they came inside a
document, but Insert › Picture refused them ("not a supported image"), and the
file dialog, drag-and-drop and paste lists left them out. A metafile is now
placed at its own frame size (points for placeable WMF and EMF; very large
frames scaled to 22 inches on the longer side) and stored as .wmf or .emf, so a
save writes it as such. render gains metafile_size().
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
8c67ef7c28 |
Start on File › Home when launched with nothing to open (#766) (#853)
* #766: start on File › Home (Start screen) when launched with nothing to open Desktop and web start on Backstage Home (Blank document, templates, Recent) when no file, --sample or --control is given; Esc/back leaves the empty blank document. File › Options › General › Start-up checkbox (ui.startScreen, kept between runs, default on). WordApp::new is unchanged; hosts opt in via WordApp::start_screen. ui_shot: {"empty":true,"startScreen":true}. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n: Hungarian and French strings for this change Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #766: Bulgarian for the Start screen setting Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
00ed50515e |
New documents follow the system region: A4, centimetres and decimal comma outside the US (#838) (#857)
* #838: new documents follow the system region (A4/cm/decimal comma outside the US) - geom: Region (from a locale tag; CLDR Letter and US-measurement territories, decimal separator), Unit::format_with; parse_measure takes either separator and a Unicode minus. - engine: Session.region (plain value, en-US headless), Session::units() (explicit Measurement units choice wins, else the region's), blank_document(); file.new blank uses the region's paper; file.options takes "region" and units "auto" and reports unitsFromRegion/region. Prefs.units is now Option and saved as measurementUnits (old saved values were the inch default, not a choice). - ui: region from WORDCRAFT_REGION, LC_PAPER/LC_MEASUREMENT/LC_NUMERIC, the platform locale (browser on the web) and Windows' home location; length fields show and read the region's decimal separator (MeasureExt); Size menu lists sizes in the measurement unit. - Tests: engine new_documents_follow_the_region, geom region/format/parse, ui length_fields_follow_the_region. Docs: localization parity, roadmap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Review fixes for #857: keep a measurement unit chosen before the rename Settings saved before #838 kept the unit under `editing.units`, defaulting to inches. On load, a value there other than inches was the user's own choice, so it now carries over to `measurementUnits` (unless that key is already present); inches, junk and non-strings follow the region. Test: `a_unit_chosen_before_regions_survives_the_rename`. Also merged main (ROADMAP.md, engine lib.rs re-exports: `blank_document` and main's `set_system_locale`). The Windows region code (`GlobalizationPreferences::HomeGeographicRegion`) was cross-checked with clippy -D warnings for x86_64-pc-windows-gnu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #838 review: saved region choice, Unix conventions apart, the browser's locale - File › Options › Advanced › Region: Automatic (the system's) or a chosen region, saved with the preferences (`Prefs::region`; `file.options {"region": "pl-PL" | "auto"}`). The session keeps the platform's region as `system_region`; `Session::region()` is the one in effect. Measurement units gains an Automatic entry (follows the region). - Unix: numbers follow LC_NUMERIC (else LANG), paper LC_PAPER and measurement LC_MEASUREMENT, each on its own (`Region::with_paper_and_measurement`); WORDCRAFT_REGION and LC_ALL still set all three. - Web: sys-locale's `js` feature, so the browser's language reaches the region (and the interface language, as its comment already said). - Runs without preferences (WORDCRAFT_NO_PREFS) stay en-US unless WORDCRAFT_REGION is set. - docs/localization-parity.md: where the region comes from, the Windows home location vs. Word's regional format, and where the decimal separator comes from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #838 review: rustfmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #838: rustfmt after merging main Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n: Hungarian and French strings for this change Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
59b04d7644 |
web: use File System Access API showSaveFilePicker with download fallback (#728)
* web: use File System Access API showSaveFilePicker with download fallback * Review fixes for #728: mark the document saved only after the picker writes the file A cancelled or failed save in the browser's save picker marks the document unsaved again, and the beforeunload guard keeps warning while a picker save is in flight. The picker's file types use our own neutral format names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c08d9f8598 |
Accessibility Options: Animation effects setting that follows the system (#791)
* Options: Animation effects setting that follows the system The app eased wheel scrolling, Ctrl+wheel zoom and its fades whatever the system's animation setting was: with Windows' Animation effects off, the page still smooth-scrolled. File > Options > Accessibility > Animation effects: Use system setting (the default), On or Off; `ui.animations` for agents; kept in ui.json. - Off: a wheel notch scrolls the page its whole distance at once, egui's own scroll areas get the notch whole instead of eased in, Ctrl+wheel zoom steps at once, and egui's fades and scroll-to glides are off. Touchpad momentum and the caret blink are unchanged. - Use system setting asks the host (`Services::animations_enabled`) at start-up and when the window regains focus. Windows: the Windows Runtime's UISettings, a safe API of the `windows` crate already in the build. Web: `prefers-reduced-motion`. macOS and Linux/BSD aren't read yet (docs/gaps.md); there it means on. - The eight new strings are in all eleven interface catalogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Animation effects off: zoom steps land finished in one frame Follow-up to the Animation effects setting: zooming could still look animated with the setting off. - Ctrl+wheel zooms in whole steps, each at once. A mouse-wheel notch is two steps in its frame, the same zoom as before. Turns that arrive in small pieces (a touchpad pinch that Windows reports as Ctrl+wheel, a high-resolution or free-spinning wheel) add up to a step of about 10% first, where each piece used to zoom a little and the zoom glided. - A page re-rendered for a zoom step is drawn on the pixel grid in that frame. It used to be drawn off the grid, slightly soft, and snap sharp a frame later. - The frame a zoom changes in asks for the next one at once, so the status bar's percentage and slider follow in the next frame. They could lag until the caret's next blink, up to half a second. The last two apply whatever the setting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Animation effects: follow the system's setting on macOS, Linux and the BSDs "Use system setting" only read Windows and the browser; elsewhere it meant on. - macOS: System Settings > Accessibility > Display > Reduce motion, through NSWorkspace (a safe call in objc2-app-kit 0.3, already in the build). - Linux and the BSDs: the XDG settings portal over D-Bus (zbus, already in the build through AccessKit). Three settings say whether animations play, and any one saying no turns them off: the cross-desktop reduced-motion preference, GNOME's enable-animations and KDE Plasma's animation speed. A thread reads them at start-up and follows the portal's change signal. Where no portal answers, or it knows none of them, KDE's kdeglobals and GTK's settings.ini are read instead. - The UI now asks the host every frame instead of at start-up and on focus, so a change in the system's settings is followed while the app runs. Hosts answer from memory: Windows and macOS are asked again at most once a second, the web keeps its media query, and Linux/BSD is kept current by its thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Animation setting on Linux: ask every setting again when one changes KDE's portal announces a new animation speed only as the cross-desktop reduced-motion preference, not under its own kdeglobals name. Keeping each setting's last announced value left KDE's own one stale: after going from instant back to normal speed, animations stayed off. Every setting is now read afresh on any relevant change, and the settings files' answer stands whenever the portal has none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Caret glide follows Animation effects; its canvas test turns the setting off, then on --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
9a58b5ba33 |
Web: add wordcraft.ico as favicon via Trunk (#717)
Registers assets/app-icon/wordcraft.ico in apps/wordcraft-web/index.html using Trunk's rel=icon asset pipeline. Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
1b96e04b2c |
Add fonts at run time: Add Font…, drop a font file, font.add (#582) (#668)
* Add fonts at run time: Add Font…, drop a font file, font.add (#582) Rebased onto main as one commit (the branch had merge commits from main). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #582: font test no longer depends on the system's fallback font The FreeBSD runner has no system fonts, so a missing family fell back to Source Serif 4, the font the test compared against. The test now adds a renamed JetBrains Mono under a name without Serif/Sans/Mono (which picks a fallback of that kind), with narrow-letter sample text: monospaced, it measures unlike any proportional fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
baeb8d9c6c |
Open and save Word XML Document (Flat OPC); open Word 2003 XML (#548) (#560)
- docx: Flat OPC (`pkg:package`) read by unpacking the parts into an in-memory package for the DOCX reader; written from the DOCX writer's parts (XML inline, binary parts in base64). Part count, part size, total size and base64 length are capped. - docx: Word 2003 XML (`w:wordDocument`) imported by rewriting it as the corresponding package (hyphenated names, w:hlink, w:listPr, w:lists, inline headers/footers, notes, aml annotations, w:binData pictures, o:DocumentProperties) and reading that with the DOCX reader. - `.xml` told apart by root element; other XML is refused with "not a Word XML document". File > Export gets "Word XML Document (*.xml)" (all 9 catalogs); .xml saves keep the document like .docx. - Tests: Flat OPC round trip equals DOCX round trip, hand-written Flat OPC package, Word 2003 import, hostile inputs, engine io. Fixes #548 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
589e82e050 |
Mail merge recipients from Excel and OpenDocument spreadsheets (#334) (#341)
Select Recipients › Use an Existing List… now reads .xlsx, .xlsm and .ods workbooks besides CSV. A new read-only SpreadsheetML / ODF spreadsheet reader in wordcraft-formats lists the sheets and turns one sheet into rows of display strings: shared, inline and rich strings, numbers, booleans, cached formula results, dates and times as ISO 8601 from the cell's number format, sparse cells, ODS value types, repeated rows/columns and covered cells. Zip entries, rows, columns, cells, string lengths and repeat counts are capped, and nothing recurses. mailings.recipients takes "data" (base64) and "sheet" and "headers"; a workbook with several sheets and no sheet answers with the sheet names, and the app shows a Select Table dialog with "First row of data contains column headers". The desktop picker, dropped files and the web inbox route spreadsheets to the recipient list. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
11a6ad60a5 |
Paste pictures from the system clipboard (screenshots, Copy Image, copied files) (#45) (#272)
egui's paste event only carries text, so a clipboard holding just a picture
sent no paste at all on Ctrl+V, and the ribbon/context-menu Paste only knew
WordCraft's own copy ("the clipboard is empty").
Desktop: the app reads the clipboard's picture through a new
Services::clipboard_picture (arboard, already in the build via egui-winit):
copied files first, else image pixels. Mod+V whose release arrives without a
paste event, and Paste from the ribbon or menus, insert that picture through
insert.picture (one undo step); text naming copied picture files inserts the
pictures. Pixel sizes are hostile: at most 16,000 px a side and 50 Mpx, the
buffer must match, files at most 200 MB.
Web: a document paste listener reads image files from the paste event when
there's no text and delivers them through the inbox like dropped pictures.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
3684289699 |
Select Recipients lets you type a list or pick a CSV, instead of a parameters error (#247)
* Tests: shapes and pictures can be clicked, resized, dragged and nudged Fixed by #46 but only text boxes were covered. These walk the editor's path for shapes (rectangle, ellipse, star, arrow) and pictures: hit test, select, resize with arrange.bounds, drag to float with Square wrap, nudge, undo. Covers #142 and #82. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Font list offers every weight a family installs, named as Word names them (#219) Fonts were listed and matched only by their typographic family name (name ID 16, e.g. "Roboto"), so a face other than Regular, Bold or Italic (Light, Medium, SemiBold, Black, Condensed...) never appeared in the font list, and a document asking for "Roboto Light" fell back to a substitute. Each face now also carries its legacy family name, the one Word lists and stores in documents: name ID 1 for static fonts ("Source Sans 3 Semibold"), or the family plus the style words other than Regular, Bold and Italic for a variable font's named instances ("Inter Light"). The font list includes these names, has_family and face() resolve them (bold and italic still choose or synthesise within the faces the name covers), and asking for a legacy name loads the whole installed family. The system font scan now also reads fvar, so a variable font's instances are listed before it is loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Absolutely positioned VML text boxes float instead of sitting inline (#149) A legacy VML shape (w:pict > v:shape / v:rect) with position:absolute in its style was read as an inline object, so a tall text box in a header made the header as tall as the box and pushed the body down the page. The reader now maps the VML style to the same float model DrawingML anchors use: left/margin-left and top/margin-top offsets, mso-position-*-relative reference areas, mso-position-horizontal/vertical alignment, mso-wrap-distance-* and w10:wrap (square, tight, through, topAndBottom; none or absent is in front of the text, or behind it at a negative z-index). Shapes without position:absolute stay inline. Saving writes the boxes as DrawingML anchors with the same placement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Reject All removes paragraph breaks inserted with Track Changes on (#229) Enter under Track Changes already recorded the new paragraph mark as an insertion, but accepting or rejecting only cleared that flag, so Reject All left the paragraphs split. Rejecting an inserted mark (or accepting a deleted one) now joins the paragraph with the next one again; Accept All, Reject All, Accept/Reject at the caret or over a selection, Next/Previous Change and the Reviewing Pane all see paragraph marks. Backspace at a paragraph start and Delete at a paragraph end now record a tracked deletion of the mark (or remove a mark the same author inserted) instead of only moving the caret; tracked deletions across paragraphs mark the paragraph marks too. DOCX reads and writes paragraph-mark revisions (w:ins / w:del in the mark's w:rPr), and a tracked mark is drawn in its author's colour when formatting marks are shown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Table Layout: Text Direction turns cell text (#226) The Text Direction button only flipped a flag that layout and rendering ignored (and docx always wrote it as btLr), so pressing it did nothing visible. Cells now carry a text direction (horizontal, top-to-bottom tbRl, bottom-to-top btLr). The button cycles through them like Word's, for every selected cell. Layout runs turned text along the cell's height: an exact row height sets the line length, otherwise the row grows to fit the unwrapped text. Turned lines are drawn in a turned frame on screen and in PDF, and clicks, caret, and selection follow the turn. docx reads and writes w:textDirection with the matching value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * File dialogs no longer freeze the window on Linux (#94) Open, Save As, Export and Insert/Change Picture used rfd's blocking FileDialog on the UI thread with no parent window. On Linux the xdg-desktop-portal call blocked the event loop until the dialog closed, and the unparented dialog could open behind WordCraft, so the app looked frozen. The desktop app now shows rfd::AsyncFileDialog parented to the window (set_parent with eframe's Frame, no unsafe) and waits for the answer on a background thread, which hands it back through a channel and wakes the UI. ui-egui gains a backend-agnostic Services::file_dialog hook: the app remembers what the dialog was for and finishes it when the answer arrives (open the file, save, export, insert or change the picture, turn AutoSave on). "Save changes?" -> Save on a new document now runs the held-back New/Open/Close only once the later Save As has saved; a cancelled Save As cancels it. Only one file dialog is open at a time; further clicks are ignored until it is answered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Select Recipients opens a recipient list instead of a parameters error (#240) Clicking Mailings › Select Recipients ran mailings.recipients with no data, so the status bar showed "`csv`, `path` or `rows` required" and nothing opened. The button is now a menu: Type a New List… opens a Recipient List dialog (an editable table of fields and entries), and Use an Existing List… opens the file picker for a CSV/TSV/text file (desktop: loaded by path; web: the picked file comes through the inbox and loads as recipients instead of opening as a document). User-run commands that lack their input open a dialog instead of failing: Select Recipients and Edit Recipient List open the Recipient List, Insert Merge Field and Find Recipient open small dialogs of their own. Scripts and agents still get the command's error, never a dialog. mailings.recipients and mailings.editRecipients take an optional `fields` list, so a typed list keeps its column order and rows can be arrays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chinese interface: embed the Chinese UI font in releases, fall back to an installed one (#241) The release workflow pinned storytold/craft-fonts at abb8331, the commit that added the Japanese fonts. Noto Sans CJK SC came one commit later (8dcdacd), so release builds embedded only the Japanese faces and the Simplified Chinese interface fell back to BIZ UDPGothic, which lacks about 157 simplified-only hanzi (页, 选, 删 …) and drew them as boxes. Document text was unaffected because it uses the system font fallback. - Bump the craft-fonts pin in the release and FreeBSD workflows to 8dcdacd. - With CRAFT_FONTS_REQUIRED, a native build now fails when the craft-fonts manifest has no Jpan or Hans font, so an outdated pin can't ship silently again. - When no embedded face covers the interface language, the UI font families get an installed CJK font (desktop only): a short per-platform list of files in the system font folders (Windows Deng/msyh/simhei/simsun, macOS PingFang/STHeiti, Linux Noto Sans CJK/WenQuanYi), header-checked, size-capped, and the face chosen by coverage (a SC face in a pan-CJK collection), so egui never sees a file it can't parse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add a System interface theme that follows the OS appearance (#115) The interface theme is now a three-way choice: Use system setting, Light or Dark, in File > Options > General and in a new Interface Theme menu in View > Dark Mode. System follows the light/dark appearance egui reports (winit on macOS, Windows and Linux; prefers-color-scheme on the web) at launch and live, and falls back to light when the OS reports none. egui's own theme is now pinned to ours, so an OS change no longer swaps in egui's unstyled style. Saved preferences migrate: the old "dark" switch becomes Dark or Light and is never written again. Light stays the default for new installs. New ui.theme command (light/dark/system); ui.dark still sets Light/Dark. The dark page (View > Switch Modes) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #248: read the installed CJK UI font only when CJK text is shown A build without craft-fonts read a large system CJK font on the first frame even for an English interface. Now it is loaded only for a CJK interface language, or once File > Options shows the language list (names in their own scripts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #247 on #246: Use an Existing List… goes through the non-blocking file dialog #246 removed the desktop's blocking pick_open; the recipient list picker now asks the host's file dialog hook (AfterPick::Recipients → load_recipients), and the desktop dialog offers a Recipient lists filter (csv/tsv/txt). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n: translate the recipient list and file dialog strings (#247, #246) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
85da7e40e0 |
web: print straight to the system print dialog, not just export (#209)
* web: print straight to the system print dialog, not just export The web build's Print backstage page only let you save a copy in another format — there was no way to actually send the document to a printer short of exporting a PDF and opening it yourself. Add a dedicated Print button that exports to PDF in memory and opens it in a hidden iframe, then calls the iframe's own print() once it has loaded, which is the standard way to drive the browser's native print dialog on a PDF without a download or a popup window. - ui-egui: new Services::print hook (web: hand PDF bytes to the browser's print flow directly, no download). - ui-egui backstage: "Print" button on the Print page when the host implements the hook; goes through wordcraft_engine::io::save_bytes directly rather than the existing save/download command path, since we want raw bytes in memory, not a file write. - wordcraft-web: implement the hook (Blob -> object URL -> hidden iframe -> iframe.contentWindow.print() on load). Desktop builds are unaffected (no Services::print set there, so the button doesn't appear - file.print still opens the Print page as before). * web print: make printing the ui.print command, dispatched by id The backstage Print button now runs `ui.print` instead of exporting and calling the host hook inline, so the control channel, MCP bridge and command search reach the same path. Like ui.openFileDialog it is the one explicit call that opens system UI; without a print hook it is an error. Status strings get rows in all six catalogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * web print: drop the previous print's iframe and object URL Each Print appended a hidden iframe holding a fresh blob URL of the whole document's PDF and never released it, so copies piled up in the page. The frame now has a fixed id; the next print revokes its URL and removes it first, and a failure part-way through cleans up after itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * web print: test that Print needs the host's print hook ui.print errors without Services::print and hands the hook a PDF with it; a failing hook reports why. The Print page shows the Print button only when the hook is set, and the button prints through the command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
663904a2e2 |
Import Word 97-2003 .doc files (#28) (#145)
* D0+D1: open Word 97-2003 .doc — container, FIB, piece table text (#28) New wordcraft-docbin crate (L2) reads the OLE2 container via the cfb crate (MIT) and parses the FIB and piece table from the [MS-DOC] specification: text pieces decode as UTF-16LE or Windows-1252 (table moved to wordcraft-doc::encoding), subdocument CP ranges, and Word's special characters map to the model's (tabs, breaks, hyphens). .doc/.dot registered in OPEN_EXTS and the I/O dispatch. Encrypted and Word 6/95 files give clear errors; every length is checked and proptest truncation/flips never panic. * D2: .doc formatting — stylesheet, fonts, CHPX/PAPX FKPs, sprms (#28) Sprm decoder with operand-size rules (incl. sprmTDefTable/sprmPChgTabs exceptions and the Prm0 isprm table), bin tables + ChpxFkp/PapxFkp page lookup, STSH styles (StdfBase layout validated against real Word files: bchUpe == cbStd, sti 0 = Normal) and the SttbfFfn font table in both extended and self-delimiting flavours. The CP walk now splits runs on formatting changes and resolves paragraph styles: bold/italic/strike/ underline/caps/hidden, colours (Ico palette + COLORREF), half-point sizes, super/subscript, fonts, highlights; alignment, indents, spacing, keep options, outline levels. Unknown sprms are skipped leniently; an env-gated corpus test opens real files (styles + text asserted). * D3: .doc structure — sections, headers/footers, tables, lists (#28) - Sections: PlcfSed/Sepx with 16-bit measurement sprms (sprmSXaPage & co), explicit sprmSBOrientation with a width/height fallback; even/odd/first header and footer stories from Plcfhdd become document parts. - Tables: sprmTDefTable grids (operand length fixed to cb+1 per the spec), horizontal merges (sprmTMerge), vertical merges (sprmTVertMerge), cell shading (sprmTSetShd / sprmTDefTableShd80), cell widths, valign, row height/header/cant-split, and table borders (sprmTTableBorders80 Brc80s). - Lists: PlfLst (LSTF + appended LVLs) and PlfLfo (LFO + LFOLVL overrides) resolved into the numbering model; sprmPIlvl/sprmPIlfo mark paragraphs. - Combined fixture (intro + table + list + 2 sections + header) round-trips through Document JSON; hostile proptests now mutate the structured fixture. * D4: .doc notes, fields, bookmarks and inline pictures (#28) - Footnotes/endnotes: PlcffndRef/PlcfEndRef reference CPs paired with the PlcffndTxt/PlcfEndTxt story ranges; each story walks into a Footnote or Endnote part and the 0x02 anchor becomes a NoteRef object. - Fields: 0x13/0x14/0x15 state machine in the walker — instruction text is hidden, the cached result kept; HYPERLINK fields keep their result as linked runs, everything else becomes one Field object. - Bookmarks: SttbfBkmk names + Plcfbkf/Plcfbkl CPs become BookmarkStart/ BookmarkEnd objects at their anchors. - Pictures: sprmCPicLocation points into the Data stream; the PICF header gives the display size (dxaGoal/dyaGoal scaled by mx/my) and the payload is format-sniffed (PNG/JPEG/GIF/BMP; metafiles are dropped with a warn). - Hostile proptests now truncate/flip a fixture with all of the above. * D5: .doc import hardening, caps and roadmap (#28) - Cap inline pictures at 5 000 per document. - Engine test: .doc/.dot bytes dispatch through the Word 97-2003 reader (garbage must error, never panic or open empty). - ROADMAP: Word 97-2003 import row (60% — text, formatting, styles, sections, headers/footers, tables, lists, notes, fields, bookmarks, raster pictures). Real-corpus validation stays opt-in: WORDCRAFT_DOCBIN_FIXTURES=<dir> cargo test -p wordcraft-docbin corpus_files_open. * Corpus fixes: compressed-piece FC space, PAPX query point, none-level labels (#28) Validated against two real files (kept locally under plan/word/fixtures, gitignored): a 1 MB Lorem-ipsum sample and a 2003 styles/lists/table sample. - FKP bin tables and pages address the WordDocument stream in true byte offsets; a compressed piece stores its base fc doubled (byte = fc/2), so fc_of_cp now unwinds the doubling. Before, every PAPX/CHPX lookup in compressed (cp1252) files landed on the wrong entry — tables collapsed to one row, lists and heading styles were lost. - The paragraph-properties query now uses the paragraph mark's own FC (inside the paragraph) instead of the FC after it, which picked up the next paragraph's FKP entry. - Fixture papx entries re-keyed to paragraph-start CPs, the real rgfc convention. - Counters::next_label returns nothing for NumFormat::None levels (Word's numFmt none shows no label; also fixes a latent docx-reader case). - Symbol/Wingdings bullet chars map to Unicode glyphs (• ▪ ○ ●). With this, both corpus files render headings, styles, nested and numbered lists, the tables and the PAGE field correctly; metafile-wrapped images and OLE objects stay dropped by policy (logged). * docbin: bound the text walk by real text; dedupe and cap pictures (#28) Hostile CP numbers no longer drive the work: - header/footnote/endnote story ranges from the Table stream are clamped to their subdocument (ccpHdd/ccpFtn/ccpEdn) with checked base additions, and every walk stops at the last piece's CP; a story spanning [0, 4e9) used to push billions of U+FFFD; - characters are found by binary search over the pieces (PieceTable::char_at), and an unreadable stretch (gap, truncated or out-of-stream piece) is skipped in one step rather than one U+FFFD per CP; the old per-character scan was O(chars x pieces); - the piece CPs are made non-decreasing at parse, so inverted pieces are empty and binary search stays sound; surrogate pairs now decode; - note references are looked up by binary search; the style's CharProps is borrowed, not cloned, per character; - pictures are interned by fc_pic (5000 anchors on one picture share one media entry and one copy) and the bytes copied per document are capped at 256 MB. Each fix has a crafted-bytes regression test; against the previous code the story, piece-claim, last-piece and many-pieces tests hang (killed at 60 s) and the shared picture test copies the picture once per anchor (80 GB). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docbin: checked offset arithmetic in lists, sections and table rows (#28) - list.rs: PlfLst/PlfLfo/LVL offsets (at + 2 + cLst*28, at + 4 + lfoMac*16, off += len, ...) saturate, which matters on 32-bit targets (wasm32) where fc + n can overflow usize; the LFO override path no longer indexes abstracts directly. - sections.rs: the Sepx range (fc_sepx + 2 + cb) is checked; Plcfhdd is read only as far as the stories a capped section count can use (it used to reserve lcb/4 entries). - table.rs: sprmTDyaRowHeight = i16::MIN negated in i16 (overflow panic in debug builds); now -(v as f32). Regression tests: the row height test failed before (overflow panic with debug assertions, a 0 pt height without); the list test pins the saturating offsets (it only overflowed on 32-bit targets). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Format the merged OPEN_EXTS list Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
802ba30441 |
Desktop and web: ask to save changes before New, Open or Close; AutoSave only files you saved (#151)
* Desktop and web: ask to save changes before New, Open, Close or quit New (Mod+N, File › New), Open (once a file is picked, recent files, dropped files), Close (Mod+W) and the window's close button replaced or closed a document with unsaved changes without asking, and its undo history went with it: `Session::set_document` drops both, and nothing checked `dirty` first. Closing or reloading the browser tab lost the document the same way. `WordApp::run`, the path every user action takes, now stops New, Open with a path and Close on a dirty document and shows "Do you want to save changes to <name>?" with Save / Don't Save / Cancel; the command runs once it is answered (`ui.saveChanges`, so agents can answer it too). Save goes through the normal save, or Save As for a document without a file, and a failed or cancelled save cancels. The window's close button is held with `ViewportCommand::CancelClose` until the prompt is answered. Scripts and agents keep the old behaviour: the control channel (and the MCP bridge over it) now calls `WordApp::execute`, which never asks, as programmatic calls never open dialogs. While the prompt is up, keys go to it rather than to the page behind (Enter would otherwise type a paragraph mark into the document being saved). On the web, a `beforeunload` listener asks the browser to confirm leaving with unsaved changes, and Export (PDF/PNG) no longer marks the document saved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AutoSave: only write to a file the user saved to, and report failures AutoSave was on for every document with a path, and `file.open` sets the path, so opening contract.docx and typing one character rewrote it in place about 2.5 s later. A save drops whatever the readers don't model (content controls, charts, SmartArt, macros…), so that content was deleted from the original without the user ever pressing Save, and undo can't bring it back. Save errors were discarded with `let _ =` and retried every 2.5 s. AutoSave now writes only to the file the document was explicitly saved to (Save / Save As) in this session; a file that was only opened is left alone until the user saves it. New and Open reset that. A failed AutoSave is shown in the status bar and logged, and turns AutoSave off for that file until the next explicit save instead of retrying forever. The title-bar toggle shows on only when AutoSave actually covers the document; clicking it on an unsaved or merely opened document starts Save As, as it already did for a new one. The Options label now says which documents AutoSave covers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Save Changes prompt: Enter clicks the focused button, not always Save With Cancel or Don't Save focused (Tab), egui turns Enter into a click on that button, but the dialog then also checked `key_pressed(Enter)` on its own and overwrote the answer with Save. Cancelling a Close from the keyboard therefore saved the file over itself and quit, and Don't Save saved too. The answer now comes from the button that was clicked; Enter means Save only when none of the three buttons has focus (the usual case, with the page behind still holding focus). Escape still cancels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Save Changes prompt: forget it when its document is replaced A prompt asked about document A kept its pending action after a script or agent (control channel, MCP) opened document B. Don't Save then ran the retained New or Open against B and threw away B's edits, under a question that named A. `Session` now has a `document_id` that changes only when `set_document` replaces the document (open, new, mail merge, recover), never on an edit. The prompt records the id it asked about. Any command that replaces the document drops a pending prompt, and an answer that arrives anyway (a click in the same frame, `ui.saveChanges` from an agent) is refused when the id no longer matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AutoSave: keep it on when the Open picker is cancelled `file.open` without a path only asks the UI for the file picker, and it returns Ok. The AutoSave bookkeeping cleared the saved-file permission on any successful `file.open`, so pressing Mod+O and cancelling turned AutoSave off for a document that never changed, until the next explicit save. The permission is now cleared when the document is actually replaced, which `Session::document_id` tells apart from an edit. That also covers the other commands that replace it (mail merge, recover), which the id-based check missed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Web: tell the leave-page guard about edits made while drawing The `beforeunload` guard reads a flag the web shell copied from `session.dirty` after `logic`. Typing and formatting happen later in the same frame, in `ui`, so after the first keystroke in a clean document the flag still said "saved" until the next frame's `logic`. Closing or reloading the tab in that window left without asking. The app now reports its unsaved state to the host after both passes (`Services::on_dirty`, called at the end of `logic` and of `ui`), and the web shell sets the guard's flag from that instead of copying it itself. That puts the timing in `ui-egui`, where a native test can cover it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Save Changes prompt: a save that drops content doesn't count as saved The prompt's Save accepted any `saved: true`. Save As to a format that doesn't keep the whole document (a .png of page 1, .txt, .md) reports that while leaving the document unsaved, so New, Open or Close went ahead and the document, with its undo history, was gone. Save now lets the command continue only when the document is clean afterwards, i.e. it went to a format `file.save` treats as the document's own file (docx, odt, rtf, json). Otherwise the command is cancelled, the document stays open, and the status bar says to save it as .docx. A document opened from a .txt file therefore needs Save As .docx (or Don't Save) before it can be replaced: the engine already treats a text save as a copy. The web shell's download path cleared the unsaved flag for any save, whatever the format, so a .png or .txt "save" also silenced the prompt and the leave-page guard. It now clears it only for those formats. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Web: a failed download is not a save The download service returned nothing, and the web shell caught and only logged a failure to start the download. Save therefore always cleared the unsaved flag and reported `downloaded`, so the Save Changes prompt went ahead with New or Open (replacing the document and its undo history) even when nothing was downloaded, and the leave-page guard stopped asking. `Services::download` now returns a Result and the web shell passes its error through. On an error, Save leaves the document unsaved, shows the failure in the status bar and returns it, and the prompt cancels the pending command. Limit: a browser doesn't tell the page what happens after the download starts (the user may cancel it or the file may be blocked), so a download that started is still treated as saved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M-i18n: Save Changes prompt and AutoSave strings in pt-br, es and uk The renamed AutoSave option, its failure message, the Save Changes prompt and the failed-download message had rows only in ja/zh catalogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Save Changes and AutoSave: .docm, .dotx and .dotm keep everything (#172) Since #172 these formats keep macros and content types, and file.save already treats them as the document's own file. keeps_everything() and AutoSave now share one list that includes them, so a web download of a .docm marks the document saved and AutoSave covers a .docm/.dotx/.dotm saved this session. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Save As: keep main's suggested name (.docm stays .docm) after the merge The merge kept the PR's save_as_dialog body, which reverted main's default_save_name() suggestion back to <title>.docx. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
02e2a51bb4 |
LaTeX (.tex) import and export (#7) (#11)
* LaTeX (.tex) import and export (#7) Open and save LaTeX source as a document format, next to Markdown and HTML. Export writes a standalone article (fontenc, ulem, xcolor, multirow, amsmath, hyperref, loaded only when used): unnumbered sections, itemize/enumerate, quote, center/flushright, verbatim, tabular with the document's column widths and merged cells, equations as $..$ / \[..\], character formatting, colours and links; metadata goes to \hypersetup. Import reads the everyday subset: preamble \title/\author, sectioning (\part/\chapter shift levels), paragraphs, lists with labels, quote, alignment environments, verbatim-like environments, tabular with \multicolumn/\multirow/\cellcolor and p{} widths, inline and display math, formatting commands and declarations, colours, links, refs, citations, footnotes, accents, TeX ligatures and special characters. Unknown commands keep their arguments' text; layout and definition commands are dropped with their arguments. Nesting is depth-limited and input is capped, so hostile files can't overflow the stack. The flow model gains an Equation inline so equations survive LaTeX round trips; the other text formats write their linear text as before. Wired into open/save dispatch, the file dialog filter, Backstage Export, the web app, CLI/MCP help, docs and the Linux desktop entry. Fixes #7. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FczntrDNYd6DupyBdpbL5 * #7: bound LaTeX column specs and equation export (review) - spec_widths: `*{n}{…}` repeats recurse with a depth argument and stop at MAX_NEST, so a deeply nested column spec no longer overflows the stack or exhausts memory. Column counts saturate and widths stop at MAX_COLS. - Column specs read from the source are capped at MAX_SPEC (4096) chars. - balanced() looks groups up in a table of closers built in one stack pass, so linear_to_latex no longer rescans to the end of the string for every unmatched `(` (quadratic: 50k took seconds). spec_widths uses it too. - env_name restores the position when `\begin{` / `\end{` has no `}` within 64 chars, so the `{` is no longer lost. - Regression tests: the 100k-deep `*{1}{` spec and a 200k-`(` equation in deep_nesting_is_bounded; unclosed_begin_keeps_its_brace and spec_widths_repeats_are_bounded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyVbT6Vv4BwWZ4hY8973qD * #7: linear-time labels, brackets and \begin{; no commands from the document in exports Applies @HaringIbon's review suggestions: 1. Parser::anchor keeps at most MAX_ANCHOR_RUN (64) bookmarks in a row with no text between them; text() looked back past every one, so a file of labels imported in quadratic time. Labels separated by text are all kept. 2. linear_to_latex_at checks for the `/` of a fraction before copying the group out, so deeply nested matched brackets export in linear time. 3. env_name searches only the next 65 bytes for `}`, so a run of unclosed `\begin{` imports in linear time. 4. An equation's `\name` is written as a command only when it is a known symbol, function or font/spacing command (MATH_COMMANDS); anything else becomes `\backslash name`. Two carets are never written side by side in equations or anchor names, since TeX reads `^^5c` as a backslash. Compiling an export can no longer `\input` files or `\write18` named by the document. Regression tests: three cases added to deep_nesting_is_bounded, plus latex_export_carries_no_commands_from_the_document and latex_labels_in_a_row_are_read_in_linear_time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyVbT6Vv4BwWZ4hY8973qD * Merge follow-ups: FTable.borderless (from #102) in the LaTeX importer; rustfmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
a45c8a41de |
DOCX: keep a .docm's macros and signatures on save; right content types for .docm/.dotx/.dotm (#172)
* DOCX: keep a .docm's macros on save; write .docm/.dotx/.dotm content types Saving a .docm deleted its VBA project and wrote the plain-docx main content type under the .docm name, which Word rejects; .dotx was written as a document, and .dotm could be opened but not saved. io_ext sent every Word extension to wordcraft_docx::write, which always emitted a .docx package, and the reader never kept the VBA parts. - wordcraft_docx::Flavor (docx/docm/dotx/dotm) picks the main part's content type; write_as(doc, flavor) writes it, write() stays .docx. - The reader keeps word/vbaProject.bin (and the vbaData.xml it relates to) as opaque bytes in Document::passthrough, the mechanism custom.xml already uses. Macro content is never parsed or run. The writer emits them, with their relationships and content types, only for .docm/.dotm; a .docx/.dotx leaves them out cleanly (as Word does) and the open document still holds them for a later Save As .docm. - Empty projects, missing or malformed VBA data, duplicate or odd relationships never panic and never stop the document from opening; part sizes stay under the existing package limits. - .dotm joins the open/save lists, and saving to .docm/.dotx/.dotm binds the session to that file like .docx. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * DOCX: carry vbaData.xml as opaque bytes, never parse it The first cut gated word/vbaData.xml through xml::parse on read and on write. That parser rejects UTF-16 (a valid XML encoding), so a UTF-16 vbaData.xml was silently dropped and the saved .docm kept vbaProject.bin without its VBA data; and it is lenient (no end-name check, auto-closes at EOF), so malformed data passed the gate anyway. WordCraft is a passthrough for macro parts: it should emit exactly what the user's file had. Now vbaProject.bin and vbaData.xml are both carried byte for byte (bounded by the reader's existing size limits); only the relationship part is read, to find the VBA data. A relationship to a missing VBA data part keeps the project and writes no dangling link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * DOCX: read UTF-16 XML parts instead of dropping them xml::parse rejected any part with a UTF-16 BOM, and Package::rels swallowed that error as "no relationships". A UTF-16 word/_rels/vbaProject.bin.rels (valid per OPC) therefore hid vbaData.xml, and saving the .docm dropped it and its relationship. parse now transcodes UTF-16 LE/BE with a BOM to UTF-8 (lossy, bounded at 1.5x, never panics on odd lengths or unpaired surrogates), so every part benefits. Unreadable relationship parts are now logged rather than silently treated as empty; the macro project is still kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Web Save keeps the opened Word format (.docm stays .docm) Browser Save (file.save without a path) always downloaded <stem>.docx, so an opened .docm/.dotm was written as a plain .docx and lost its macros on an ordinary Save. Save and the Save As suggestion now default to the opened file's extension when it is a Word package (.docx/.docm/.dotx/.dotm, via engine io::is_word_package), else .docx. Macros are only dropped on an explicit conversion to a macro-free name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * DOCX: carry every part a VBA project relates to, so signatures survive A signed .docm/.dotm lost its VBA signature on save: the reader kept only vbaProject.bin and vbaData.xml, and the writer rebuilt vbaProject.bin.rels with the wordVbaData relationship alone. Under a "signed macros only" policy the macros then stop running, although the project bytes (which the signature covers) were unchanged. Generalised instead of special-casing signature types: the reader now keeps every internal part vbaProject.bin.rels points to (VBA data, the legacy/Agile/V3 signatures, anything else) as opaque bytes, with its relationship type and its content type from [Content_Types].xml, in a small manifest in Document::passthrough. .docm/.dotm re-emit them all; .docx/.dotx still drop the whole VBA set. Bounded at 64 parts; targets outside word/, relationship parts, the project itself, missing parts and parts the writer produces itself are skipped, never shadowed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * DOCX: match VBA part names case-insensitively; keep colon names relative Two edge cases in carrying a VBA project's related parts: - The reader's word/ prefix check was case-sensitive, so a relationship to /WORD/vbaData.xml (the same part under OPC's case-insensitive part names, and found by Package::get) was skipped and lost on save. The directory is now matched case-insensitively and normalised to word/, the name the writer uses. - Stripping word/ from /word/sig:legacy.bin gave Target="sig:legacy.bin", which parses as a URI scheme rather than a package part. A target whose first segment has a colon is now written as "./sig:legacy.bin"; ordinary names keep the plain form Word already accepts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d95e09ba25 |
Clippy/fmt clean workspace, ROADMAP with honest parity estimate; xtask ci green
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d1a0ed39c0 |
M7.1: proofing — spelling with suggestions, grammar checks, squiggles, F7, right-click menu, thesaurus; web app; hyphenation dictionary
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |