mirror of
https://github.com/storytold/wordcraft.git
synced 2026-11-14 14:32:31 +00:00
1d0dff03a41745ee013e6483bd4dfd7b2e6e7c42
67
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8bd3427012 |
View: Hide White Space between pages in Print Layout (#645) (#684)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
f7a84fbfb8 |
Print Markup: comments in PDF, print, images and text copies (#589) (#667)
Rebased onto main as one commit (the branch had merge commits from main). 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> |
||
|
|
39f51ca292 |
More Colors… and a Colors dialog for custom colours (#606) (#666)
Rebased onto main as one commit (the branch had merge commits from main). 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> |
||
|
|
ab49e4f5fa |
Open playlists and other text files (.m3u, .m3u8, .log, .ini, …) (#569) (#648)
Rebased onto main as one commit (the branch had merge commits from main). 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> |
||
|
|
14080c614c |
Drag after a double/triple-click extends the selection by words/paragraphs (#549) (#649)
Rebased onto main as one commit (the branch had merge commits from main). 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> |
||
|
|
f91ce21f90 |
Find: each Find Next in the dialog moves one match (#468) (#556)
The Find and Replace dialog ran edit.find and then edit.findNext on every click (and Enter). edit.find already selects the first match after the caret, so the step after it skipped one: with four matches only the second and fourth were ever reached, with two only the second. edit.findNext and edit.findPrevious now take the same optional search options as edit.find (text, matchCase, wholeWord, regex), and the dialog sends one edit.findNext with its options. Calls without options behave as before (F3, Mod+G). generated with Claude Code 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> |
||
|
|
dab5092e83 |
Font dialog: separate Asian and Latin text fonts (#505) (#536)
* Font list: installed Chinese fonts are listed, by their Chinese names in a Chinese interface (#505) The font database read only the English family name of each font, so a Chinese interface listed SimSun, Microsoft YaHei and WenQuanYi Zen Hei by their English names and never as 宋体, 微软雅黑 or 文泉驿正黑, and a document naming a font by its Chinese name fell back to another font. - fonts: keep each face's localized family names (name IDs 16 and 1, every face of a collection, read during the scan without loading the file); FontDb::families_in(lang) lists them in place of the English names for a matching interface language (zh-hans, zh-hant, ja, ko); face, has_family and styles accept either name. - ui: the ribbon and mini toolbar font boxes show the interface language's names and store the English name. - East Asian font: choosing a Chinese, Japanese or Korean font also sets it as the East Asian font (w:eastAsia), read and written in DOCX; layout draws CJK text in a run's installed East Asian font. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Font dialog: separate Asian and Latin text fonts (#505) Word's Font dialog sets the font of Chinese, Japanese and Korean text apart from the font of Latin text; WordCraft's had one font box. - Font dialog: an "Asian text font" box (the installed Chinese, Japanese and Korean fonts, by their interface-language names, type to find) above the "Latin text font" box, shown for a Chinese or Japanese interface, East Asian text in the selection or a selection with an Asian font. OK sets only the fonts changed, in one undo step. - format.font takes "eastAsia" (w:eastAsia) besides "name"; either or both; format.state reports fontEastAsia. - fonts: the scan reads each font's OS/2 code pages, so FontDb::east_asian_families lists CJK fonts without loading them. - Tests: dialog sets each font on its own; layout sets CJK text in the run's Asian font and Latin text in its Latin font (skipped without an installed CJK font); command and undo; the scan finds a CJK font. 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> |
||
|
|
7484a6bd11 |
Find and Replace: special characters (^p, ^t, …) and wildcards (#543) (#553)
- Find what understands Word's ^ codes (^p ^t ^? ^# ^$ ^^ ^l ^m ^n ^b ^+ ^= ^s ^~ ^- ^w ^g ^% ^v,
^nnn / ^unnnn); Replace with adds ^& (found text) and ^c (clipboard). `special: false` reads
the text literally.
- Use wildcards: ? * [abc] [a-z] [!x] {n} {n,} {n,m} @ < > ( ), \1-\9 in Replace with; always
matches case, like Word. Patterns are translated to the linear-time regex engine (no
catastrophic backtracking), length and counts capped, bad patterns are errors.
- ^p matches paragraph marks between sibling paragraphs; a ^p in the replacement splits the
paragraph. Replace All stays one undo step.
- Match prefix / suffix, Ignore punctuation / white-space characters.
- Dialog: Use wildcards checkbox, Search Options with the new options and a Special menu that
inserts codes into the focused field (a shorter list with wildcards); strings in all 9 catalogs.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
46d5e99b3e |
Styles: Update to Match Selection takes the selection's formatting (#555) (#562)
styles.updateToMatch copied only the direct run/paragraph formatting at the caret onto the style, so text whose look came from its own style (Normal, Title, ...) changed nothing; it then wiped that paragraph's direct formatting without applying the style, so the selection lost its look too. Now the style takes the selection's effective formatting (paragraph style chain, character style chain, direct formatting) as a diff against what the style currently gives, keeping its outline level and numbering. The selected paragraphs take the style and drop only the direct formatting it now carries. The Home styles gallery tiles get the right-click menu (Update to Match Selection, Modify...). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
6a22d7257b |
Insert › SmartArt: create and edit list, process, cycle, hierarchy, pyramid, radial, matrix and venn graphics (#497) (#503)
- Model (doc::smart_art): layout, items (text + level), colour variant; capped at 100 items, 255 characters, 8 levels. - Layout algorithms of our own (docx::smart_art) place rounded rects, circles, bands, arrows and connectors; WordCraft draws the graphic from the same dsp:drawing it saves. - DOCX: data (dgm:dataModel points and connections), our own layout/quick-style/colour definitions under the built-in identifiers, the drawing part, relationships and content types. A diagram reads back editable only when its data part is exactly what WordCraft writes. - Commands: insert.smartArt, smartArt.items/addShape/promote/demote/layout/colors/reset/textPane, one undo step each; picture.size relays out a SmartArt graphic. - UI: layout picker with previews drawn in code, SmartArt Design tab, Text Pane; strings in all 9 catalogs. - Diagram reader draws `line` presets top-left to bottom-right (was the other diagonal). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
900c0f4412 |
Outline view: Outlining tab, levels, collapse/expand, Show Level (#493) (#499)
* Outline view: Outlining tab, levels, collapse/expand, Show Level (#493) - View › Outline is its own view mode: the body on one long sheet, each paragraph indented by its outline level behind a symbol drawn in code (circled plus for a heading with content, circled minus for one without, a dot for body text); paragraph indents and alignment hidden. - Collapsed headings hide their subtree and get a wavy line; Show Level 1-9 / All; Show First Line Only; Show Text Formatting off draws everything in the Normal style. All of it is view state (ViewState.outline), not saved; collapsed headings are found again by their text after edits, and a caret entering hidden text moves on to the next shown paragraph. - Commands: outline.promoteToHeading1/promote/demote/demoteToBody/level/moveUp/moveDown, outline.expand/collapse/toggle/selectSubtree, outline.showLevel (+ showLevel1-9, showAll), outline.firstLineOnly, outline.showFormatting, outline.close, outline.state. In Outline view Move Up/Down and Promote/Demote take a collapsed heading's hidden content along. - Keys: Alt+Shift+Plus/Minus expand/collapse, Alt+Shift+1-9 and Alt+Shift+A Show Level. - Outlining tab (next to File while in Outline view): level box, promote/demote, move, expand/collapse, Show Level, the two check boxes, Close Outline View. Clicking a symbol selects the heading with its content; double-clicking expands or collapses it. - Catalog: Outlining tab rows (421/453, 92.9%); translations in all nine catalogs; docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Docs: drop the stale shortcut and dialog rows left by the rebase (#493) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
968c66a13e |
Fixes: tracked paste marks, plain paste blank lines, ^p in Replace, Delete/Split columns and cells, Font dialog keeps untouched formatting (#417, #422, #423, #431, #432, #466) (#479)
- Paste under Track Changes marks the paragraph marks it adds as inserted, so Reject All joins the paragraphs again (#417). - Keep Text Only / Paste Special text paste treat paragraph separators as text, not Enter: an empty list item between them no longer ends the list (#422). - Untracked Replace All splits paragraphs at CR / U+2029 in the replacement like a single Replace does (#423). - Delete Columns deletes every grid column the selected cells cover (#431). - Split Cells refines the grid inside the split cell only; other rows' cells widen over the new grid columns, keeping the table width (#432). - Font dialog sends only the properties the user changed and shows small caps, all caps, hidden and spacing from the selection (format.state reports them) (#466). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
50505d9e62 |
Editing fixes: Center range, undo grouping, Change Case runs and offsets, Bold undo at caret (#418, #419, #420, #421, #424) (#477)
- Paragraph formatting skips the paragraph a non-empty selection ends at the start of (Select Paragraph's end), via Document::para_paths_in (#418). - A non-mutating command that moves the selection closes the typing group (#419). - Change Case converts char by char and replaces only changed stretches inside one run, keeping each run's formatting (#421) and mapping the selection to the new offsets (#420). - Undo/redo steps carry the pending caret formatting (#424). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4b5ed293d7 |
Compare, Restrict Editing and AutoCorrect Options dialogs (#412) (#476)
* Compare, Restrict Editing and AutoCorrect Options dialogs (#412) - Compare / Combine Documents dialog: original (this document or a file) and revised document, label changes with, More >> comparison settings, word or character level, show changes in. review.compare honours case changes, white space, formatting (tracked formatting changes), tables (compared and kept, no longer dropped) and character level; the LCS no longer drops text past its size cap. Unsupported settings are accepted and reported. - Restrict Editing pane: formatting restrictions (locked styles, w:formatting), editing restrictions, exceptions for everyone (w:permStart/permEnd), Start Enforcing Protection with an optional password (SHA-512, salt, spin count 100000; ECMA-376 §17.15.1.29) and Stop Protection. Enforced in Session::run: outside the exceptions nothing may change; undo can't lift protection; passwords never land in Repeat/macros. DOCX round trip of protection, hash attributes, locked styles and ranges. - AutoCorrect Options dialog: replacement list (add/replace/delete, built-in entries too), capitalization options with exceptions, AutoFormat As You Type switches, Math AutoCorrect list. Saved per user in ui.json; damaged values read as defaults. Names of days are capitalised. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restrict Editing: verify Word's legacy protection hash; refuse unsupported forms (#412) - Stop Protection checks Word's legacy w:documentProtection hash (w:cryptAlgorithmSid/w:hash/w:salt/w:cryptSpinCount) like Word: the password becomes the legacy 32-bit key (ECMA-376 Part 4 §14.8.1 initial code array and encryption matrix), its reversed bytes as an upper-case hex string are hashed with the salt prepended and the iteration number appended. A wrong password is rejected; the form no longer unlocks with any password. - Unsupported or damaged hashes (MD2/MD4/MD5 and other SIDs, w:algIdExt, unknown algorithmName, spin counts above 10,000,000, bad base64) refuse with "This password type isn't supported" and the protection stays on. A protection without a hash still stops without a password. - Known-answer tests: the specification's "Example" key 0x64CEED7E and an independently computed SHA-1 hash; pane message in all 9 catalogs. 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> |
||
|
|
fd014ea9a1 |
Insert › Chart: create and edit column, bar, line, pie, area, scatter and doughnut charts (#409) (#415)
* Insert › Chart: create and edit column, bar, line, pie, area, scatter and doughnut charts (#409) - Chart model (wordcraft_doc::chart::ChartSpec): type (clustered/stacked column and bar, line, area, pie, doughnut, scatter), categories, series, title, legend, data labels; sanitized (255 series, 4000 points, 100k cells, 255-char text, non-finite values dropped). - DOCX: charts with a model are written as word/charts/chartN.xml (c:chartSpace, ECMA-376 §21.2, data as c:strLit/c:numLit, no c:externalData) with relationship and content type. On read, a chart part becomes editable only when writing its model again gives the same markup; every other chart stays read-only and kept as before (#319). - Drawing reuses read/chart.rs (the model is drawn from its generated part); data labels (c:dLbls showVal) are now drawn for bar, line, area, scatter, pie and doughnut charts. - Commands: insert.chart, chart.type, chart.editData, chart.title, chart.legend, chart.dataLabels (one undo step each); picture.size redraws an editable chart at its size. - UI: Insert › Chart gallery with previews drawn in code, Chart Design contextual tab, Edit Data grid dialog; strings in all 9 catalogs; catalog 408/439. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #409: refresh catalog count and parity checklist after rebase --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6526b134be |
Document grid: lines per page and characters per line (#391) (#401)
- Model: `SectionProps::doc_grid` (`w:docGrid` type, line pitch, charSpace) and the paragraph `w:snapToGrid` flag; DOCX read and write (ECMA-376 §17.6.5). - Layout: with a line grid, lines of snapping paragraphs take whole line pitches with their text centred; with a character grid, East Asian characters take whole cells of the character pitch (all text with snapToChars), Latin text stays proportional. Hostile pitches and charSpace are clamped. Headers and footers don't snap. - Command `layout.documentGrid` (type, linesPerPage, charsPerLine, linePitch, charSpace), one undo step, section scope; catalog row under Layout › Page Setup. - Page Setup dialog: Document Grid section; View › Gridlines draws the document grid of a section that has one. UI strings in every catalog (incl. et, sr, sr-latn). - Tests: DOCX round trip, line pitch and lines per page, character pitch placement, command with clamping and undo. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4e710a920b |
Footnote and Endnote options per section (#385) (#390)
- Model: NoteProps (pos, numFmt, numStart, numRestart) in the settings and each section; Document::note_options resolves section > document > defaults. - DOCX: w:footnotePr / w:endnotePr read and written in settings and every w:sectPr in schema order; hostile numStart clamped (ECMA-376 §17.11). - Layout: footnotes below the text, endnotes at the end of each section, numbering restarted per section or per page (from where notes land, at most three passes), per-section number formats. - Commands: references.noteOptions takes kind/pos/numFmt/numStart/ numRestart/scope; references.convertNotes (footnote/endnote/swap, one undo step); references.footnote/endnote take a custom mark. - UI: Footnote and Endnote dialog from the Footnotes group launcher; strings in every catalog. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
09921dfae8 |
Right-to-left tables and sections (#362) (#365)
Tables marked right to left (w:bidiVisual) now lay out mirrored: the first
column stands at the right edge, start alignment and indent are measured
from the right margin, and each cell's start margin and border sit on its
right. Merged cells, turned text and nested tables follow; clicks and the
caret follow the drawn geometry while Tab keeps the logical cell order.
Right-to-left sections (w:bidi) run their newspaper columns from the right,
with the line between columns mirrored.
The table direction is read and written in .docx (section direction already
round-tripped). New commands table.rtl and layout.sectionRtl, a "Table
direction" choice in Table Properties ("rtl" in table.properties) and a
"Section direction" choice in Page Setup, translated in every catalog.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
8a2a8bb60d |
Insert > Icons: an original icon library, a searchable picker and Icon Color (#309) (#318)
Adds wordcraft_render::icon_lib, 61 monochrome icons in ten categories
(Arrows, Communication, Office, People, Nature, Weather, Shapes, Symbols,
Technology, Transport). They are original WordCraft artwork: simple
geometric line drawings designed on a 24x24 grid as path data in code, with
no icon files and nothing copied from another icon set.
insert.icon {"id", "color"?, "size"?} renders the chosen icon with vello_cpu
to a transparent PNG at about 300 ppi and inserts it at the caret through the
ordinary picture path, so it saves to .docx and every other format. Without
an id it lists the library (narrowed by query and category) and asks the
front end for the picker. picture.iconColor repaints a one-colour picture
in a new colour and keeps its transparency.
Insert > Illustrations > Icons opens a dialog with a search box, category
chips and a grid of icons drawn live by the egui painter from the same path
data; Insert or a double click places the selection. Picture Format > Adjust
gains an Icon Color menu. New strings are translated in every catalog.
Catalog parity 393/434 -> 394/434 (90.8%).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
a5b17eb3a4 |
Enclose Characters: put a character in a circle, square, triangle or diamond (#301)
* Home › Font › Enclose Characters: circle, square, triangle or diamond around a character (#297) New command format.enclose {"shape": "circle|square|triangle|diamond" | null, "style"?: "shrink|enlarge", "text"?: string} puts one full-width or up to two half-width characters in an enclosure, changes its shape, or (null) takes the text back out. It is stored the way ECMA-376 describes it: an EQ field with the overstrike switch, eq \o\ac(○,字), with the text as its plain-text result. Layout now draws EQ \o fields: the arguments overstruck as one cluster (centred, \al or \ar). An enclosure symbol among them is drawn as a vector shape around the text in the text's colour, so it never depends on the font having ○ □ △ ◇; "shrink text" scales the text into a one-em shape, "enlarge symbol" grows the shape around text at its own size. Screen and PDF both get it through the display list. Imported Word documents with enclosed characters used to show nothing (the field has no result); they now render. .docx: written as Word stores enclosed characters (begin, the code split so the symbol and the text have their own run sizes, end, no result); the reader recovers the style from those sizes. The style lives in the model as a trailing \* enlarge on the code, never written to a file. Home › Font gets an Enclose Characters button that opens a dialog (Style: None / Shrink text / Enlarge symbol, Text, Enclosure list, a drawn preview); scripts with a shape never see it. Strings in every interface catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Merge fix: keep both sides (enclosures and ruby; tests); regenerate parity checklist --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
dba6e66d53 |
Choose which side of an object text wraps on (#364)
* Tight and Through wrapping follow the object's outline (#354) Text now wraps around a Tight or Through object's shape instead of its bounding box. The outline is the object's wrap polygon (wp:wrapPolygon, in the 21600-unit space of its frame) when it has one; otherwise it is derived: a shape's geometry (ellipse, triangle, star, heart... flattened), the convex hull of a picture's opaque pixels (alpha threshold on a coarse grid, at most 64 points), or the rectangle. The outline turns and flips with the object, keeps the left/right and top/bottom distances from text, and is built once per placed object; each line asks it which stretches of its band it covers. Through also lets text into gaps inside the outline that are wide enough. DOCX reads and writes wp:wrapPolygon (with edited) for wrapTight and wrapThrough; a shape without one gets its outline written, other objects their rectangle, since the schema requires a polygon. Square, top and bottom, behind and in front of text are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Choose which side of an object text wraps on (#354) Square, Tight and Through objects now have a wrap side, like Word's "Wrap text" choice: both sides (the default), left only, right only, or the largest side. Left keeps the band to the object's right empty on every line beside it, Right the band to its left, and Largest picks the wider side line by line. It works with contour wrapping and with turned objects, and right-to-left paragraphs mirror it. The setting is Float::wrap_text, read from and written to wrapText on wp:wrapSquare, wp:wrapTight and wp:wrapThrough (previously always written as bothSides). The new arrange.wrapText command {"side": "bothSides|left|right|largest"} sets it on the selected object as one undo step. The ribbon's Wrap Text button is now a menu with the wrap styles and a Wrap Text Sides submenu, translated in every interface language. 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> |
||
|
|
eb068d3823 |
Add Word's common keyboard shortcuts (#330) (#338)
Bind Word's documented default shortcuts that were missing to existing commands, plus a few small new commands: Reset Paragraph Formatting (Ctrl+Q), Reset Character Formatting (Ctrl+Space, which used to clear all formatting), em dash, copyright, registered, trademark and ellipsis, DATE / TIME / PAGE fields (Alt+Shift+D/T/P), Print Layout / Outline / Draft / Split views, Word Count (Ctrl+Shift+G), Bookmark, Alt+F7, Ctrl+Page Up/Down find browsing, Shift+F12 / Ctrl+F12 / Ctrl+Shift+F12 / Ctrl+F2 / Ctrl+F4, Mark Index Entry, Mark Citation and the mail merge keys. Where macOS claims Word's key (Cmd+Q quits, Cmd+Space is Spotlight, Cmd+Shift+G is Find Previous, Option+letter types symbols), the binding follows Word for Mac or is left off there. F4 Repeat, Q and F2 now reach the registry from the keyboard, and an Alt shortcut no longer also types its key's character. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
1581f30176 |
Import/export fixes: TXT encodings and list labels, RTF Unicode, ODT styles and cell colours, HTML borders and pre spaces, LaTeX alignment groups (#433–#440) (#482)
- TXT: the engine opens and saves .txt through wordcraft-formats (BOM/unmarked UTF-16 sniffing, Windows-1252 fallback; list labels and a tab on export) instead of its own narrower decoder and body plain text. - RTF: \uN and \ucN are handled in document info, bookmark names and field instructions, not only in body text. - ODT: common paragraph styles (and common parents of automatic styles) give their text formatting, except the style that sets the paragraph kind; table-cell-properties fo:background-color becomes cell shading. - HTML: tables without style or visible borders export without cell borders; white-space:pre paragraphs keep leading/trailing spaces. - LaTeX: brace groups restore the paragraph alignment set inside them. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4c6dd898c2 |
Tables: Delete Columns never leaves a table with no rows (#397)
Delete Cells removes a cell without shrinking the grid, so a row can be shorter than the grid. Delete Columns then removed that row's only cell, dropped every row left without cells, and kept the now empty table block (`rows: []`), an invalid state that anything indexing the first row would turn into a panic. When no rows are left, remove the table, as deleting the only column already does. Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
15f2f01475 |
Tabs and Borders and Shading dialogs (#327)
* Add Tabs and Borders and Shading dialogs (#320) Tabs: lists the paragraph's tab stops with position, alignment and leader, Set / Clear / Clear All and the default interval. Opens from the Paragraph dialog's Tabs… button, by double-clicking a tab marker on the ruler, or para.tabs without params. OK is one para.tabs call, which now takes set / clear / clearAll / default (clearing a style's stop records a clear stop over it). Borders and Shading: Borders (None / Box / Shadow / 3-D / Custom, style, colour, width, paragraph or text, preview with clickable sides), Page Border (same, whole document or this section) and Shading (fill, paragraph or text) tabs. Opens from Home › Paragraph › Borders › Borders and Shading… and Design › Page Borders. OK runs only the changed parts as one undo step. para.borders and design.pageBorders take exact "sides" (and design.pageBorders a style and applyTo). Shadow and 3-D are drawn as heavier sides because the model has no border shadow flag; shading patterns are omitted because the model keeps a solid fill only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep charts, SmartArt and OLE objects when saving DOCX (#319) Charts and SmartArt diagrams were drawn since #292 but the writer skipped them, and OLE objects kept only their picture, so opening and saving a document silently dropped them. The reader now keeps each object's own markup (the a:graphic of a chart or diagram, the whole w:object of an OLE object) on the model object, and every part it reaches through relationships (chart part, embedded workbook, colour and style parts; diagram data, layout, quick style, colours and drawing; OLE embedding and picture) as opaque bytes in Document::passthrough, with their content types and relationships in a manifest, as the VBA project already is. The writer emits the object with fresh relationship ids pointing at the re-added parts, keeping each part's own relationship ids and content types. A chart or diagram gets its frame from the object's current size and position; an OLE object gets its new size written into its VML shape. A part whose name the writer uses itself is renamed, and an object pasted more than once gets its own copies of every part (sharing past a size budget). Part counts, relationship counts, markup size and copy bytes are all capped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Columns, Symbol and Field dialogs (#321) Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/ Left/Right presets, number of columns, per-column width and spacing, Equal column width, Line between, a page preview and Apply to (whole document, this section, this point forward). OK is one layout.columns call, which now takes unequal widths and an apply target (forward adds a continuous section break first). Insert > Symbol > More Symbols... opens a Symbol dialog: font picker, Unicode subset list that jumps through a grid of only the characters the font has (drawn in that font), recently used symbols, a hex character code, and a Special Characters tab. insert.symbol takes a font for the symbol alone; typing after it goes on in the text's font. The ribbon's Symbol gallery shows recently used symbols first. Insert > Quick Parts > Field... opens a Field dialog: categories and the fields WordCraft computes, date pictures, number formats, bookmark / sequence / merge field names, preserve formatting, a live field code and a Field Codes view to type the code directly; OK runs insert.field. Also: date pictures read correctly when switches follow them, and am/pm and seconds work in date and time pictures. Strings in every catalog; gaps #7 and ui-parity updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #321: Field dialog descriptions in our own words (clean room) Reword the one-line field descriptions and the empty-selection prompt so none reads like Word's; translations keep their meaning under the new keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #320: test that a default-tab-only para.tabs change undoes and redoes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep charts, SmartArt and OLE objects when saving DOCX (#319) Charts and SmartArt diagrams were drawn since #292 but the writer skipped them, and OLE objects kept only their picture, so opening and saving a document silently dropped them. The reader now keeps each object's own markup (the a:graphic of a chart or diagram, the whole w:object of an OLE object) on the model object, and every part it reaches through relationships (chart part, embedded workbook, colour and style parts; diagram data, layout, quick style, colours and drawing; OLE embedding and picture) as opaque bytes in Document::passthrough, with their content types and relationships in a manifest, as the VBA project already is. The writer emits the object with fresh relationship ids pointing at the re-added parts, keeping each part's own relationship ids and content types. A chart or diagram gets its frame from the object's current size and position; an OLE object gets its new size written into its VML shape. A part whose name the writer uses itself is renamed, and an object pasted more than once gets its own copies of every part (sharing past a size budget). Part counts, relationship counts, markup size and copy bytes are all capped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Columns, Symbol and Field dialogs (#321) Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/ Left/Right presets, number of columns, per-column width and spacing, Equal column width, Line between, a page preview and Apply to (whole document, this section, this point forward). OK is one layout.columns call, which now takes unequal widths and an apply target (forward adds a continuous section break first). Insert > Symbol > More Symbols... opens a Symbol dialog: font picker, Unicode subset list that jumps through a grid of only the characters the font has (drawn in that font), recently used symbols, a hex character code, and a Special Characters tab. insert.symbol takes a font for the symbol alone; typing after it goes on in the text's font. The ribbon's Symbol gallery shows recently used symbols first. Insert > Quick Parts > Field... opens a Field dialog: categories and the fields WordCraft computes, date pictures, number formats, bookmark / sequence / merge field names, preserve formatting, a live field code and a Field Codes view to type the code directly; OK runs insert.field. Also: date pictures read correctly when switches follow them, and am/pm and seconds work in date and time pictures. Strings in every catalog; gaps #7 and ui-parity updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Merge fix: #340's rotation round-trip test builds an Image with #333's ole field Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add Tabs and Borders and Shading dialogs (#320) Tabs: lists the paragraph's tab stops with position, alignment and leader, Set / Clear / Clear All and the default interval. Opens from the Paragraph dialog's Tabs… button, by double-clicking a tab marker on the ruler, or para.tabs without params. OK is one para.tabs call, which now takes set / clear / clearAll / default (clearing a style's stop records a clear stop over it). Borders and Shading: Borders (None / Box / Shadow / 3-D / Custom, style, colour, width, paragraph or text, preview with clickable sides), Page Border (same, whole document or this section) and Shading (fill, paragraph or text) tabs. Opens from Home › Paragraph › Borders › Borders and Shading… and Design › Page Borders. OK runs only the changed parts as one undo step. para.borders and design.pageBorders take exact "sides" (and design.pageBorders a style and applyTo). Shadow and 3-D are drawn as heavier sides because the model has no border shadow flag; shading patterns are omitted because the model keeps a solid fill only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * cargo fmt after merging main --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4b450f1b00 |
Open and save password-protected Word documents (#55) (#281)
* Clipboard pane: collect the last 24 copies and cuts and paste any of them again
Copy and Cut now also keep each item (rich fragment plus plain text) in a
session list of up to 24, newest first. Items over ~4 MB (estimated) are
skipped and the list stays under ~24 MB in total; copying the same thing
twice in a row adds it once. The system clipboard is untouched.
New commands: edit.clipboardPane (toggle the pane, also the Clipboard
group's dialog launcher), edit.clipboardItems (list with previews),
edit.pasteClipboardItem {index}, edit.pasteAllClipboard,
edit.deleteClipboardItem {index} and edit.clearClipboard. Pasting goes
through the normal paste path, so it is one undo step and respects Track
Changes. The pane lists the items with a short preview; clicking one pastes
it at the caret, and each has Delete, plus Paste All and Clear All.
Parity: 378 -> 379 of 428 features.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Layout > Arrange > Group: group pictures, shapes and text boxes into one object
Shift+click (or Ctrl+click) adds pictures, shapes and text boxes to the
selection; Group (arrange.group) combines the selected floating objects
into one group object that moves, wraps and resizes as one, with its
members keeping their places and scaling with the group. Ungroup
(arrange.ungroup) splits it back, where the group showed them, and
leaves them selected so Group puts them back together.
- Model: InlineObject::Group with the members' coordinate space (like
DrawingML a:chExt), so resizing the group scales its members.
- Layout draws the members inside the group's frame; hit testing
selects the group; text boxes in a group show their text.
- DOCX writes wpg:wgp with a:chOff/a:chExt and each member's a:xfrm,
and reads Word's groups (including inside mc:AlternateContent and
nested groups, flattened).
- select.addObject makes multi-selection scriptable (pos or Selection
Pane index).
Parity 378 -> 379 of 428 (88.6%); floor raised to 88.5%.
Fixes #265
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Paragraph: Asian Typography settings, with kinsoku and mid-word Latin wrapping in layout (#269)
Paragraphs gain the Asian Typography flags (line-breaking rules, wrapping
Latin text in the middle of a word, hanging punctuation, compressing
punctuation at the start of a line, and automatic space between Asian and
Latin text and between Asian text and numbers). They inherit through styles,
round-trip through .docx pPr, are set by the new para.asianTypography
command and appear on a new Asian Typography tab of the Paragraph dialog.
Layout applies the kinsoku lists (no line starts with closing East Asian
punctuation or ends with opening punctuation), breaks between any East Asian
characters when kinsoku is off, and breaks Latin words anywhere when word
wrap is off. Text without East Asian characters breaks as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Manage Styles: list, preview, delete and show or hide styles
Home › Styles › Manage Styles (styles.manage) opens a dialog from the
Styles pane listing the document's paragraph, character, linked and table
styles, sorted as recommended or alphabetically, with a preview line and a
description (type, formatting it sets, what it is based on). From there:
Modify… and New Style… reuse the existing dialogs and come back, Delete
removes custom styles, and two checkboxes show the style in the Styles
gallery (w:qFormat) or hide it from style lists (w:semiHidden).
styles.manage returns the list for scripts (name, type, builtIn,
inGallery, hidden, basedOn, priority, own formatting). styles.delete now
moves paragraphs, runs and tables that used the style to its base (in
every story), rebases styles built on it and takes a custom linked
character half with it; built-in styles are refused and it is undoable.
New styles.setVisibility {style, gallery?, hidden?} sets the flags, which
round-trip through .docx.
Fixes #266
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Create and modify your own table styles, saved in the .docx (#146)
New Table Style and Modify Table Style (Table Design > Table Styles) make
real table styles in the document's style sheet: name, based on, and
formatting for the whole table, the header row and banded rows (borders,
fill, bold, font colour). The table at the caret takes the new style, it
shows in the gallery, and other tools that read table styles by name
(pandoc's custom-style, for one) see it.
- engine: table.newStyle / table.modifyStyle with documented params
(regions take borders, fill, bold, italic, colour, size, font; bandSize;
para); table.style only accepts table styles now.
- doc/layout: table styles gain header-row and row-band cell borders,
row-band character formatting and rows per band, applied by layout.
- docx: tblStyleRowBandSize and firstRow/band1Horz tcBorders and rPr are
read and written, so custom styles round-trip.
- ui: a compact dialog from the Styles menu next to the gallery; table
style tiles redraw when a style changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Table styles: every region, a live preview, delete, custom styles first (#146)
Follow-up to the custom table styles work: the New/Modify Table Style
dialog now covers every region a table style formats, shows what the
style will look like, and table styles can be deleted.
- engine: table.newStyle / table.modifyStyle take lastRow (total row),
firstColumn, lastColumn and bandedColumns regions with the same props
as the others. New table.deleteStyle removes a custom table style:
built-in ones are refused, styles based on it are re-based on its own
base with its formatting folded in (so they look the same), and tables
using it fall back to Table Grid. Undoable like any command.
- doc/layout: table styles gain fill and cell borders for the first and
last column and total row, and fill, text and borders for column
bands; layout applies them in the standard order (column bands, row
bands, first/last column, header/total row).
- docx: firstCol/lastCol/lastRow/band1Vert shading and cell borders are
read and written.
- ui: the dialog's "Apply formatting to" lists all seven regions and a
small live preview table shows the pending changes; Styles menu gets
Delete Table Style (disabled for built-ins); the gallery shows the
document's own table styles first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Paste pictures from the system clipboard (screenshots, Copy Image, copied files) (#45)
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>
* Shape Format: Shape Effects — shadow, glow and soft edges (#275)
Shapes and text boxes get an effects model (outer shadow with colour,
transparency, blur, distance and angle; glow with colour, size and
transparency; soft-edge radius), the shape.effects command (presets,
custom values or null per effect, applied to every selected shape,
undoable, hostile numbers clamped), screen and PDF rendering with the
same banded approximation of a blur, DOCX a:effectLst read/write
(outerShdw, glow, softEdge) with a matching wp:effectExtent, and a
contextual Shape Format tab with Shape Fill, Shape Outline and Shape
Effects (Shadow, Glow, Soft Edges submenus) plus Arrange.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Open and save password-protected Word documents (#55)
Opening a password-protected .docx used to fail as a broken zip and showed
a blank document. Encrypted packages (an OLE compound file with
EncryptionInfo + EncryptedPackage) are now detected and decrypted with
ECMA-376 agile encryption, written from the public [MS-OFFCRYPTO] spec:
password key derivation with spinCount, verifier check, data-integrity
HMAC, AES-CBC in 4096-byte segments (AES-128/192/256 with SHA-1/256/384/512
on read; AES-256 + SHA-512, spinCount 100000 on save). Standard (Office
2007) and certificate encryption give a clear "not supported" error.
- docx: crypt module (decrypt, encrypt, is_encrypted, read_with_password),
PasswordRequired / WrongPassword / Encryption errors; hostile files are
bounded (stream caps, spinCount <= 10,000,000, checked lengths).
- engine: file.open/file.save take "password"; file.encrypt
{"password": string|null} sets or removes the save password (pure, so it
never lands in Repeat or recorded macros); a document opened with a
password is saved with it again; file.info reports "encrypted".
Passwords are held in a zeroizing type whose Debug prints ***.
- ui: a password prompt when a user-opened file needs one (scripts get the
error), File > Info > Protect Document with Encrypt with Password (typed
twice) and Remove Password; web downloads are encrypted too.
- deps: aes, sha1, sha2, hmac, base64, getrandom (wasm_js in the browser),
zeroize.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Table styles: Table Grid and Normal Table can't be deleted even when a .docx marks them custom
A .docx can set w:customStyle on any style, which made Word's built-in table styles look custom
and deletable. table.deleteStyle and the ribbon's Delete Table Style now also recognise them by
id and name, so no table is left on a style that's gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Manage Styles: deleting a table style runs Table Design's table style deletion
styles.delete on a table style now calls table_style::delete (#263): its tables take Table Grid,
styles based on it keep their look, and Word's built-in table styles are refused even when a
.docx marks them custom (the Manage Styles list reports them built-in too). Document::restyle no
longer retargets tables' own styles, so there is one place that does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Paste pictures: Mod+Shift+V (Paste Formatting) no longer pastes a clipboard picture
The key-release fallback that pastes the system clipboard's picture treated every Mod+V release as
Paste. It now requires plain Mod+V or the Paste key (no Shift, no Alt), with a unit test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Asian typography: test that kinsoku keeps a manual line break before a closing bracket
Manual line breaks end the line by cluster kind, not by break opportunity, so kinsoku's filter
can't drop one before 」; pin that with a layout test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Encrypted saves: the data-integrity HMAC key is hashSize bytes
MS-OFFCRYPTO §2.3.4.14 makes the HMAC salt as long as the hash (64 bytes for SHA-512); it was
16. Reading already accepted both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: password dialog and File › Info strings in every catalog
Rows for the Password and Encrypt with Password dialogs, the Protect Document section of
File › Info and the password status messages in zh-hans, zh-hant, ja, pt-br, es, uk, sr and
sr-latn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Open and save password-protected Word documents (#55)
Opening a password-protected .docx used to fail as a broken zip and showed
a blank document. Encrypted packages (an OLE compound file with
EncryptionInfo + EncryptedPackage) are now detected and decrypted with
ECMA-376 agile encryption, written from the public [MS-OFFCRYPTO] spec:
password key derivation with spinCount, verifier check, data-integrity
HMAC, AES-CBC in 4096-byte segments (AES-128/192/256 with SHA-1/256/384/512
on read; AES-256 + SHA-512, spinCount 100000 on save). Standard (Office
2007) and certificate encryption give a clear "not supported" error.
- docx: crypt module (decrypt, encrypt, is_encrypted, read_with_password),
PasswordRequired / WrongPassword / Encryption errors; hostile files are
bounded (stream caps, spinCount <= 10,000,000, checked lengths).
- engine: file.open/file.save take "password"; file.encrypt
{"password": string|null} sets or removes the save password (pure, so it
never lands in Repeat or recorded macros); a document opened with a
password is saved with it again; file.info reports "encrypted".
Passwords are held in a zeroizing type whose Debug prints ***.
- ui: a password prompt when a user-opened file needs one (scripts get the
error), File > Info > Protect Document with Encrypt with Password (typed
twice) and Remove Password; web downloads are encrypted too.
- deps: aes, sha1, sha2, hmac, base64, getrandom (wasm_js in the browser),
zeroize.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Reland: catalog coverage 390/431 (90.5%) in ROADMAP and target-app parity
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Manage Styles: mark table styles with ⊞, which the interface fonts have (▦ has no glyph, #305's check)
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>
|
||
|
|
29e978d6d8 |
Re-land #264, #263, #270, #271, #267, #280: clipboard pane, table styles, Manage Styles, Asian Typography, Group, Shape Effects (#323)
The original squash merges of these six PRs were empty. Integrated and tested together on current main. Co-authored-by: Valentin Klein <vangel1976@googlemail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
08e8d4ade0 |
Table sizing: Height/Width boxes, AutoFit to contents and a Table Properties dialog (#287)
* Table sizing: Height/Width boxes, real AutoFit Contents, Table Properties dialog (#44) - Table Layout > Cell Size gets Height and Width boxes (inches) for the caret cell's row and column; each edit runs table.rowHeight / table.columnWidth, a drag is one Undo. - AutoFit Contents now measures every column's narrowest (longest word) and widest (unwrapped text) content with the layout's text shaping, shares the text width the way Word does, and writes the widths into the table grid and cell widths. - Table Properties opens a dialog (table width, alignment, indent; row height and rule, break across pages, header row; column width; cell width and vertical alignment). OK applies only what changed as one table.properties step; with settings the command still works headlessly, and without them it reports the current values. - table.columnWidth clears the table's preferred width so the column keeps the new size; table.rowHeight takes an optional exact rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Progress docs: Table Properties dialog and AutoFit Contents landed (#44) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Table sizing: saturating column-span sum in sync_cell_widths Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Merge fix: one region_text (main's) after #287 met the landed table-style code 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> |
||
|
|
c0bbcbac24 |
Charts and SmartArt: draw them instead of dropping them (#292)
A chart or SmartArt diagram in a DOCX was dropped on import: it took no room, so the text after it moved up (five of the six chart documents in docxide-pdf's fixtures came out a page short), and nothing was drawn. They are now an InlineObject::Graphic: it takes its room, floats and wraps like a picture, and draws a list of neutral items (shapes, paths, one-line texts, pictures) that layout scales to the object's box. - Charts (read/chart.rs) are drawn from the values the chart part caches: clustered, stacked and 100% bar and column charts, line, area, pie, doughnut and scatter charts, with title, legend, gridlines, axis labels and Excel number and date formats. - SmartArt (read/diagram.rs) is drawn from the drawing Word stores beside the diagram: preset shapes, theme colours, text and pictures. - Theme colour handling is shared (read/drawing_color.rs). - Each chart or diagram part is read once, only through a relationship of its type, under a per-file budget of items. - Tagged PDF tags them as figures with their alternative text, and the accessibility check reports charts without one. Not yet: charts and diagrams aren't written back when saving a DOCX (they weren't before either), so the editor doesn't let you move or resize them. Radar, stock, 3-D and combination charts draw their first supported chart type only; SmartArt pictures aren't clipped to their shape. docxide-pdf bench (365 documents, vs main + #135): page counts right 320 -> 326 (all five chart documents and one SmartArt document), Jaccard passes 208 -> 215, SSIM passes 76 -> 79; eight documents better, none worse. Co-authored-by: Oliver Zieschang <4963183+naxvog@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
4a75c57f58 |
Comment balloons edit in place, and the ruler drags page margins (#167) (#289)
Clicking a comment balloon in the markup area selects it like Word: its text becomes editable in place, with Reply, Resolve/Reopen and Delete, and replies are shown (and editable) under the comment they answer. Each edit is written back through a new review.editComment command, one undo step per editing session; the Comments pane uses the same path (it used to write the text without an undo step) and gains a Reply button. The margin edges on the horizontal ruler (left/right) and the vertical ruler (top/bottom) can be dragged to set the section's margins through layout.margins, one undo step per drag, snapped to 1/16 inch and clamped so at least an inch of text remains. Indent markers keep priority where they overlap an edge. Setting margins by number no longer switches off mirrored margins; only a preset decides that. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
88ad402581 |
Gridlines: the document's own grid spacing, and table gridlines on by default (#69, follow-up to #243) (#283)
View › Gridlines now draws the drawing grid at the document's own spacing: DOCX keeps it in settings.xml as w:drawingGridHorizontalSpacing and w:drawingGridVerticalSpacing (ECMA-376 §17.15.1.44/45), which WordCraft now reads (clamped) and writes (only when it differs from the default), with a round-trip test. Documents that don't say use the spec's 180 twentieths of a point (1/8 inch) instead of a fixed 0.5 cm; across and down can differ. Table Layout › View Gridlines (table.viewGridlines) is on by default, as in Word, so tables without borders still show their cells. Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8ac57f0312 |
i18n: add Serbian, both scripts (sr, sr-latn) (#250)
Serbian is officially digraphic — Cyrillic and Latin are both in
everyday use, with Cyrillic the default per BCP 47 when no script is
given. Adds both as separate catalogs, same as the existing
Simplified/Traditional Chinese split handles script rather than
region:
- sr-latn.tsv: the actual translation, written clean-room from the
plain meaning of each English label (never from Microsoft Word's or
another product's Serbian localisation), covering the full existing
catalog (874 entries, same key set as zh-hans.tsv).
- sr.tsv: mechanically transliterated from sr-latn.tsv by a small
script (not committed — a one-off; the mapping is the standard
digraph-aware Serbian Latin→Cyrillic table). Brand/product/format
names (WordCraft, PDF, Discord, Zotero, SmartArt, file extensions…)
and `{placeholders}` are protected from transliteration, same names
that stay in Latin letters in every other catalog. Hand-editing this
file isn't the workflow — change sr-latn.tsv and re-transliterate.
- mod.rs: registers `sr` (Cyrillic) and `sr-latn`. No special-casing
needed in `candidates()` for the script split, unlike Chinese's
region-implies-script fallback — `sr-Latn-RS` already produces
`sr-latn` as a candidate before falling back to plain `sr`, and a
bare `sr`/`sr-RS` (no script tag, the common case) resolves straight
to `sr` since that's the registered default-script code.
- tests.rs: locale-matching coverage for both scripts, full-catalog
parity with the reference language, and bundled-font glyph coverage
for both Cyrillic and Serbian Latin's extended letters (š č ć ž đ),
mirroring the existing Ukrainian/Portuguese tests.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
15f5ad274e |
Column Selection: Alt+drag selects a block of text (#237)
select.column (Mod+Shift+F8) selects the same x range on a run of lines, from two positions, two page points (Alt+drag), or by toggling column mode where the arrow keys extend the block. Copy/Cut give one line per row, Delete/Backspace remove only the block, character formatting and Change Case apply per row, typing/paste replace the block. Any ordinary caret move drops it. F8 keys now reach their shortcuts in the UI. 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> |
||
|
|
e71c2b6c5b |
Style Inspector: styles.inspector pane, styles.inspect query and per-level clearing (#236)
styles.inspector shows/hides the Style Inspector pane and returns the paragraph style, character style and the direct formatting that differs from each; styles.inspect is the pure query; styles.inspectorClear resets one level (paragraphStyle, paragraphFormatting, characterStyle, characterFormatting) as a single undo step. The pane opens from the Styles pane, with a clear button per level. Parity floor raised to 87%. 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> |
||
|
|
8bc4e6c999 |
Paste Special: paste the clipboard as formatted text, RTF, HTML or unformatted text (edit.pasteSpecial) (#235)
New edit.pasteSpecial command ({"as"?, "text"?, "html"?, "rtf"?}). Without "as" it
lists the clipboard's formats and asks the front end for the dialog; with "as" it pastes in
that format. HTML/RTF go through the wordcraft-formats importers, with their lists, styles and
images merged into the document under fresh ids. Respects Track Changes (now also inside pasted
tables) and caps payloads at 16 MB. UI: Paste Special dialog (radio list + result hint),
Mod+Alt+V and Paste menu entry; strings in all four catalogs. Parity 355/405, floor 87.5%.
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>
|
||
|
|
9c3ad84d33 |
Advanced Find: every match, Reading Highlight, search within a selection (#234)
Home › Editing › Find › Advanced Find (edit.advancedFind) opens the Find
and Replace dialog. With params it returns every match in the body, or
only inside the selection ("in": "selection"), and "highlight": true
turns on Reading Highlight, which marks all matches on the page and
follows edits until it's turned off. The ribbon's Find button becomes a
menu with Find, Advanced Find and Go To; the Find dialog gets a Reading
Highlight toggle. Parity 355/405.
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>
|
||
|
|
d8f71959d3 |
Create and modify your own table styles, saved in the .docx (#146) (#256)
New Table Style and Modify Table Style (Table Design > Table Styles) make real table styles in the document's style sheet: name, based on, and formatting for the whole table, the header row and banded rows (borders, fill, bold, font colour). The table at the caret takes the new style, it shows in the gallery, and other tools that read table styles by name (pandoc's custom-style, for one) see it. - engine: table.newStyle / table.modifyStyle with documented params (regions take borders, fill, bold, italic, colour, size, font; bandSize; para); table.style only accepts table styles now. - doc/layout: table styles gain header-row and row-band cell borders, row-band character formatting and rows per band, applied by layout. - docx: tblStyleRowBandSize and firstRow/band1Horz tcBorders and rPr are read and written, so custom styles round-trip. - ui: a compact dialog from the Styles menu next to the gallery; table style tiles redraw when a style changes. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
19e2e37ca6 |
View › Gridlines shows the drawing grid; Table Layout › View Gridlines outlines cells (#69) (#243)
View › Show › Gridlines only outlined table cells, so with no table on the page it seemed to do nothing. It now draws a light 0.5 cm grid over the page's text area, inside the margins, in Print Layout. Table Layout › View Gridlines gets its own command, table.viewGridlines, with its own view flag, and draws the table cell outlines as before. The ribbon button, keytip and parity catalog point at it. Both are screen-only overlays drawn by the canvas; print, PDF and image exports never show them. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
69eaf9925c |
View tab: Zoom In/Out buttons that step from the shown zoom (#67) (#232)
Zoom In/Out stepped from the stored manual zoom, which goes stale while a fit mode (Page Width, One Page, Multiple Pages) sizes the page: from a 163% Page Width, Zoom In jumped to 110%, i.e. zoomed out. The canvas now keeps view.zoom equal to the shown zoom while a fit mode is on, so Zoom In/Out, the Zoom dialog and view.state all start from what the user sees. The View › Zoom group gains Zoom In / Zoom Out buttons (code-drawn icons; the catalog already lists them there), and the Zoom dialog's slider is logarithmic and wider so a short drag no longer jumps from 100% to 480%. 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> |
||
|
|
8b49ea7546 |
Table Layout › Text Direction turns the cell's text (#226) (#245)
* 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> * 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> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
302528d67e |
Web: use the browser clock everywhere on wasm (insert weekday, PDF dates, layout timing, UI timers) — follow-up to #214 (#230)
#214 gave now_iso the browser's clock on wasm; the other clock helpers were still stubs there: Insert Date's weekday was always Thursday, the PDF creation date was left out, and the layout and UI millisecond clocks returned 0, so browser AutoSave's timer never fired, the caret didn't blink and status messages never cleared. The engine's clock is now one now_unix() (std on native, js_sys::Date in the browser, as #214 does) that now_iso and the weekday share; the pdf, layout and ui-egui clocks read js_sys::Date the same way on wasm. Refs #188. Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4c4dc14a54 |
Reject All removes paragraph breaks inserted with Track Changes on (#229) (#244)
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> |
||
|
|
6565a0f0ce |
Headless: file.setAuthor takes a name; review.trackChanges lists its value param (#90) (#224)
file.setAuthor now documents {"name": string} in the command list, trims the
name, strips control characters, caps it at 255 characters and refuses a blank
name. wordcraft-cli run and headless mcp take --author NAME. review.trackChanges
lists {"value"?: bool} so scripts find the explicit, idempotent form.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
919d74bee6 |
Shift+F3 runs Change Case again, not Find Previous (#186) (#221)
* Shift+F3 runs Change Case again, not Find Previous (#186) edit.findPrevious and format.changeCase were both bound to Shift+F3. Registry::by_shortcut returns the first match, so Find Previous won and Change Case never ran from the keyboard. Find Previous keeps Mod+Shift+G. A new test fails if any two commands share a shortcut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Contributors: credit vangel76 by their GitHub profile name 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> |
||
|
|
a9eb233a9d |
Track changes: keep other authors' deletions, credit paragraph splits, exact edit snapshots (split from #54) (#125)
* Track changes: a tracked delete keeps other authors' deletions Deleting a range with Track Changes on marked every run in it as deleted by the current author, also text another author had already deleted. That author's deletion was lost: the change list showed it under the wrong name, and rejecting the second author's changes brought back text the first author had deleted. Text that is already deleted now keeps its deletion and its author, like Word. * Track changes: a tracked paragraph split credits the new mark to its author Pressing Enter with Track Changes on inserts a paragraph mark by the current author, but the paragraph after the split took its mark's revisions from the text at the caret. Inside or at the end of another author's insertion, the original paragraph mark then showed as inserted by that author. The paragraph after the split now keeps the original mark's revisions; only the new mark is the splitting author's. * Engine: edit snapshots that take commands back exactly Session::edit_snapshot and Session::restore let a caller run a command and take it back without a trace: the document, the selection, the undo and redo stacks, the open typing group, a pending join_next_undo and the dirty flag come back as they were, and the layout is recomputed. Edit > Undo cannot do this: it leaves a redo step, loses the redo stack the command cleared, and closes the typing group. At the undo limit (500 steps) a command pushes the oldest step out. The snapshot keeps the oldest steps when the stack is near the limit and restore puts them back, so the stack is exact there too (up to four commands between snapshot and restore). Restore is exact for commands that add undo steps; edit.undo and file.new/file.open in between are outside its contract (documented). The snapshot destructures every Session field, so a new field does not build until someone decides whether a snapshot must cover it. Session::undo_depth and the snapshot's doc/dirty/undo_depth accessors tell a caller whether the command changed anything. * Tests: hostile_params_never_panic leaves out review.readAloud Read Aloud hands the document's text to the system's speech synthesizer (say, PowerShell, spd-say or espeak), so running the test made the computer speak the sample document. Like the file commands, it is left out of the sweep. * Undo: evictions at the limit are counted everywhere edit snapshots rely on them Merging main brought two paths #125's eviction counter didn't know: push_undo (typing parity, #204) dropped the oldest step without counting it, and a failed command (#153) put the whole stacks back but left the count raised. Either made restore() bring back a step that had never left. Both now keep the count right; restore_is_exact_at_the_undo_limit covers each (both fail without the fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: lpgn <2556951+lpgn@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e4456efb84 |
Text boxes and floating objects: editing, drag/resize, wrapping, overflow, footnotes (#46)
* fix text boxes: type into a new box, click in and out, Escape leaves it Insert › Text Box left the caret in the body after the box and clicks inside a box never reached its story, so its text could not be edited. - insert.textBox puts the caret in the new box (when anchored in a top-level body paragraph, the only place layout shows box text today). - Layout emits a hit-test-only Placed::TextBox area; story_at prefers a box under the point (body text wins over boxes behind it) and ignores box lines outside their box. - Canvas: click enters a box, clicking outside goes back to the body (not while editing a header/footer); double-click in a box selects a word there. - Escape in a box puts the caret just after it (Document::text_box_anchor). - Editing a box doesn't dim the body or show "Editing header/footer". - Tests: engine (caret in box, typing, undo, table cell unchanged), layout (story_at inside/outside/behind), doc (anchor lookup). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix pasted text boxes sharing the original's text A copied fragment only carried the text box's part id, so a pasted box showed the original's story: editing either changed both, edits made after copying leaked into the paste, and pasting into another document (or Insert › Text from File) pointed at whatever part had that id there, e.g. a header. - Fragment carries copies of the text box stories it shows (nested boxes too). - insert_fragment gives every pasted box a new story of its own; a box whose story can't be found keeps none rather than showing an unrelated part. Nesting is depth-limited and one paste creates at most 4096 stories. - Insert › Text from File uses Document::body_fragment, which brings the file's text boxes along. - Tests: doc (own story, copy-time snapshot, other document with clashing ids, box in a table, fragment without parts, self-nested box), engine (copy/paste/type/undo through commands). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * word count includes text boxes, footnotes and endnotes (Word's default) The status bar and Word Count dialog counted only the body, so words typed into text boxes (and notes) were missing. - Document::counted_stories / word_count_including_notes: the body plus the text boxes, footnotes and endnotes it actually shows (nested too); orphaned stories (deleted or cut boxes), headers, footers and comments don't count. - Session::count_notes (default on) with Session::word_count for the status bar; review.wordCount takes {"includeTextBoxes"?: bool} and reports it. - Word Count dialog: "Include text boxes, footnotes and endnotes" checkbox. - Document::word_count stays body-only (docx Words property, NUMWORDS, stats). - Tests: doc (used vs orphaned stories, nesting, self-nested box), engine (default on, toggle sticks, deleting a box drops its words). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * canvas: select, drag to move and resize pictures, shapes and text boxes Click a picture or shape (or a text box's border) to select it: a frame with eight handles. Drag it to move it, drag a handle to resize it (pictures keep their proportions from a corner, Shift toggles), arrows nudge a floating one (Ctrl/Alt: 1 pt), Escape cancels a drag. While a text box's text is edited its frame shows dashed with handles. Typing or Enter with a text box selected goes into it. Reusable pieces, no per-kind code: - layout: Placed::Object, one hit area for every picture/shape/text box (replaces the text-box-only item); DocLayout::object_at / object / text_box / find_object (page-hinted, no whole-document scans per frame). - engine: arrange.bounds {width,height,x,y,page} (floating objects keep their anchors on the same page; inline ones float where dropped; another page re-anchors to the nearest line) and arrange.nudge; objects::min_size shared with the UI. - ui: frame.rs, a document-agnostic transform frame (handles, hit testing, drag/resize maths, painting); objects.rs wires it to document objects. - geom: Rect::expand. control: ui.press / ui.release for half-way drags. Rendering: nothing is laid out or rasterised while dragging. The preview is the object's own pixels from the cached page texture (moves, picture resizes) or a light outline (text box/shape resizes); the drop is one command, one relayout, one undo step. Tests: frame maths, layout hit areas, engine move/resize/nudge across anchors and pages, hostile values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * moving an object anchors it to the paragraph it's dropped on, so text wraps A floating object only pushes aside text from its anchor paragraph down, and a move kept the old anchor: drag a text box above its paragraph and the text there ran straight under it. - arrange.bounds now anchors the object to the paragraph under its top edge (the last one starting at or above it on that page) and positions it relative to that paragraph's column and top, as Word does: the text it lands on wraps, and it moves with that text. On a page where no paragraph starts it anchors to the nearest line, positioned on the page. - layout: Placed::Object / ObjectHit carry the paragraph origin that column- and paragraph-relative offsets start from. - engine: edit_obj / edit_float change an object at a position (with_obj uses it). - Test: a box dropped over an earlier paragraph anchors there and the lines beside it stop short of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * text boxes hide text that doesn't fit, as in Word Text past a text box's bottom spilled out over the page below it. - layout: fit_box keeps what fits in a box: whole lines that fit (a line that only partly fits is hidden too) and other items starting inside; the first line always stays so even a tiny box shows something. Done in layout, so the canvas, PNG and PDF agree and hidden text can't be clicked; no renderer changes. Reusable for anything laid out into a fixed height. - BOX_INSET_X / BOX_INSET_Y name the text box internal margins (were 7.2/3.6 literals). - Test: overflow hidden, every shown line inside the box, a tall box shows everything, a minimum-size box shows its first line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * square wrap flows text on both sides of a floating object Text only used the wider side of a floating picture, shape or text box; Word's Square wrap fills both sides when there's room. - para: row_spans gives the spans of a row around the objects in its way (each at least 0.5" wide, else the line goes below); a row cut in two is two Lines sharing top and height, the second marked `beside`. Everything per line (alignment and justification per side, hit testing, caret, selection, drawing, PDF) works unchanged; a row is as tall as its tallest part. - layout: a row stays on one page; line numbers count rows, not parts. - Exclusions that don't overlap a line horizontally no longer narrow it. - Test: text both sides of a mid-column box (same row, left then right, hittable), a too-narrow gap stays empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * drop a deleted or cut text box's text from the document Deleting or cutting a text box left its story in Document::parts: never shown or saved to .docx, but listed by document.inspect and kept in memory. - doc: prune_text_boxes removes the text box stories no box shows (nested ones too); free without text boxes, one paragraph pass otherwise. Session runs it after every editing command. Undo snapshots and the clipboard hold their own copies, so undo and cut/paste are unaffected. - doc: reachable() walks the stories objects lead to; counted_stories (word count) and prune_text_boxes share it, and it walks blocks directly instead of building paragraph paths. - Tests: orphaned and nested-orphan boxes go, shown ones (body, nested, in a header) stay; delete then undo, cut then paste. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * word count's include-text-boxes setting survives a restart The Word Count dialog's "Include text boxes, footnotes and endnotes" was a session field, so it reset to on every launch. - engine: Prefs, the editing preferences the front end saves between runs (count_notes moves there; Session::prefs is the one source of truth). - ui: SavedPrefs pairs UiState with the engine Prefs; WordApp::saved_prefs / restore_prefs. UI fields stay at the top level of ui.json, so existing files still load (the new `editing` section defaults). - wordcraft app: saves and restores SavedPrefs instead of UiState alone. - Tests: an old prefs file loads with Word's default; the setting round-trips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * text boxes and floating objects outside the body; footnotes in text boxes Text boxes in table cells, headers, footers, footnotes and other text boxes showed an empty frame (or nothing), and floating pictures and shapes there weren't drawn. Footnotes inserted inside a text box were never numbered or placed. - layout: place_objects, one routine for the pictures, shapes and text boxes in a paragraph's lines (floating ones drawn, every one's area, text boxes' text), used by body paragraphs and by layout_box (cells, headers, footers, notes, box contents). Floats are positioned against an AnchorFrame (column, paragraph, margins, page); headers and footers pass where the page is, so page/margin-anchored objects land right (a footer is laid out again only when it has floating objects); elsewhere they're placed in their container. Nested boxes show MAX_TEXT_BOX_DEPTH (doc) levels deep. - Placed::Object / ObjectHit carry their story; DocLayout::object(&Pos), object lookups include headers and footers, header_footer_text_box_at. A part's lines are looked up in headers and footers too (caret, typing). - Footnotes: numbered in reading order with text boxes where they are (Document::objects_in_reading_order); a box's footnotes go on the page of its line (Document::notes_in_text_box), sharing the body's reservation. - engine: insert.textBox always puts the caret in the new box; disabled inside a text box (as in Word). - canvas: click into a header/footer's text boxes while editing it; leaving a box returns to where it is; body dimming computed once per frame and used for both the render and its cache key. objects: only body objects are selectable, only body paragraphs' objects movable; frames off the body show without handles. - Tests: boxes in header (inline and page-placed), footer, cell and note show text and take the caret; footnotes in a box numbered and placed in order; a box in a table cell takes the caret and its text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * word count: a selected object counts the whole document, in the dialog too With a picture, shape or text box selected the status bar showed the document's total but Review > Word Count counted the selection (0 words). - engine: cmd::objects::object_selection, "the selection is exactly one object"; review.wordCount counts the document then. The UI's objects::selected now uses it (one definition). - Test: an object selection counts the document; a text selection itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * review: bound nested text box expansion; tidy-ups Blocker: laying out a text box lays out the boxes inside it, which could fan out unboundedly (a box whose shapes show itself, or chains of boxes each showing the next many times) - 30 shapes made 1.7M layout items. Footnote numbering's walk fanned out the same way. - doc: BoxBudget, one bound for both: a box isn't expanded inside itself, nesting stops at MAX_TEXT_BOX_DEPTH, an outermost box expands at most 64 boxes inside it (so one hostile box can't starve the next page's header boxes) and a pass at most 20,000. - layout: Ctx carries a BoxBudget for the whole layout; place_objects asks it before laying out a box's text. DocLayout::text_boxes reports how many were. - doc: walk_objects (objects_in_reading_order, notes_in_text_box) uses it too. - Tests (crafted documents): a box showing itself 30 times lays out once; a five-level chain fanning out 30 ways stays within the budget, for layout and for the walk; a box inside a box still shows its text. Weakening the budget makes them fail (20,000 boxes / 600,001 visits). Tidy-ups: - para: row spans in a VecDeque (pop_front, not remove(0)). - fit_box: a text box's text area excludes both the top and bottom margins. - theme: Tokens::handle / handle_shadow for the frame handles (light, dark); the drag preview's opacity is a named constant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * box budget: no overall cap; bound the .docx writer too; tests An audit of the previous commit found: - The 20,000-box overall cap starved ordinary long documents: one budget per layout, spent by body boxes first, then one per page for header boxes (two for footers with floats), so a long document's header boxes went blank on every page. It wasn't needed: only nesting grows exponentially, and MAX_NESTED caps that per outermost box, keeping the work linear. Removed. - The .docx writer still expanded a shape's story only depth-limited (a box showing itself 30 times saved as ~28,000 nested copies, ~7 MB zipped). It now uses the same BoxBudget, and only for text box parts. - Layout expanded a shape's story whatever its part kind (a shape pointing at a header showed the header's text); now text box parts only, as the walk does. The story's blocks are cloned only once the budget agrees. - Translations of "Count words in text boxes and notes" said footnotes only; now footnotes and endnotes, in each catalog's own terms. - Tests: no timing assert or leftovers; a long document with 400 boxes and a header box shows all of them on every page; notes_in_text_box follows nested boxes once; saving a self-showing box stays small (fails at 7 MB without the bound). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Arrange: nudging or dragging an aligned float keeps it in place first main (#136) reads w:align into Float.h_align/v_align, which overrides the offset; the PR's nudge only changed the offset and drag only reset the anchors. Nudge now converts an aligned axis into a column/paragraph offset that keeps the object where it is, and a drag clears the alignment. Test included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fonts: a lookup that races another thread's load of the same family no longer falls back FontDb::face fell back to Source Sans 3 when its load_cataloged added nothing, which happens when another thread loaded the family between the miss and the load. word::resolve memoises that, so the wrong font stuck for the process; it made layout tests on main flaky (justified_lines_shrink_spaces_in_word_2013_mode, space_shrinking_is_only_for_justified_text failed 6 of 15 runs on origin/main). Look again after loading whatever it returns. Regression test races 8 threads on fresh databases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layout tests: text box and two-sided wrap tests measure what they mean square_wrap_flows_text_on_both_sides compared xs.last() (the end of a trailing space, which may hang past the edge) with the edge; compare the last non-space cluster's right edge. text_box_hides_text_that_does_not_fit compared a tall box's lines with another document's line count; compare with its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Weeb Labs <weeblabs@VFIEVOX3.Router> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.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> |
||
|
|
fe7e79ad70 |
Read Aloud: player with speed control, sentence skipping, skips citations (#190)
* Zotero 1: range fields and custom document properties round-trip Citation managers' ADDIN fields (Zotero, Mendeley, EndNote) are now kept as range fields: FieldStart/FieldEnd markers with the result as ordinary, formatted content between them, possibly spanning paragraphs (a Zotero bibliography). Before, the result was flattened to plain text and a multi-paragraph field lost its end. - doc: InlineObject::FieldStart/FieldEnd, Document::field_ranges, balance_field_ranges; CustomProp and custom_props with get/set/remove. - docx: read ADDIN fields as ranges (field codes up to 2 MiB), write them back as begin/instr/separate…end; orphan markers are dropped on save so files always hold whole fields. docProps/custom.xml is parsed and regenerated (simple types as text, vectors/blobs verbatim) instead of passed through, so Zotero's ZOTERO_PREF_n settings are editable. - engine: file.properties reads and sets custom properties. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 2: wire-protocol client and document bridge New crate wordcraft-zotero (L5) speaks Zotero's published word-processor protocol (port 23116; 32-bit transaction id + big-endian length frames, JSON payloads, ERR: replies): - client: sends an integration command (addEditCitation, addEditBibliography, addNote, refresh, removeCodes, setDocPrefs) and answers Zotero's calls until Document_complete or the connection closes (cancelled dialog). Answers go through a callback so the app can apply them on its UI thread; run_on_session answers directly. - Bridge: implements Zotero's Document_*/Field_* calls on a Session. Fields are stored as Word's plugin stores them (ADDIN ZOTERO_ range fields; prefs in ZOTERO_PREF_n, with Zotero's ReferenceMark field type kept as Word's Field in the file), so documents stay Word-compatible. Rich text (RTF or HTML) keeps citation formatting; bibliographies get a Bibliography style from setBibliographyStyle; note styles create footnotes/endnotes, and deleting a lone note citation removes its note; placeholder links convert to fields. One transaction = one undo step. Field ids stay stable through inserts and deletes. - Tests: a fake Zotero over a real socket, every call, docx round trip, hostile arguments for every method, random frames (proptest). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 3: Zotero in the app and the CLI, tested with the real Zotero - ui.zotero.<command> (addEditCitation, addEditBibliography, addNote, refresh, removeCodes, setDocPrefs), ui.zotero.status and ui.zotero.answer. The socket runs on a background thread; each of Zotero's calls is answered on the UI thread against the session, and Zotero's questions become a dialog the session waits on. Web builds report that Zotero needs the desktop app. - wordcraft-cli zotero <command> <file> [--save] [--trace] [--port]. - Bridge: a selection Zotero makes to show a citation is put back when the session ends. - ui_shot: {"wait": ms} keeps frames running for background work. - docs/zotero.md. Checked against Zotero 10.0.6: refresh of a citation and bibliography (APA), its "modified citation" question answered in the app's dialog, RTF results with italics, Bibliography style, prefs round trip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 4: switching between in-text and note styles moves citations Document_convert and Field_convert now move a citation between the text, footnotes and endnotes, as Zotero asks when the document's style changes between an in-text and a note style: into a new note at the same place, or back into the text where the note mark was (removing a note that held only the citation; keeping one with other text). Field ids survive the move, formatting is kept, and each Zotero session stays one undo step. Checked against Zotero 10.0.6: unlink citations (removeCodes, with its confirmation question) and a refresh after switching the document to Chicago notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 5: Zotero tab in the ribbon A Zotero tab (before Help) with Add/Edit Citation, Add Note, Add/Edit Bibliography, Refresh, Document Preferences and Unlink Citations. The buttons are disabled while a Zotero command is running and in the web build. Three new icons drawn in code (document preferences, unlink, add note); the others reuse the References tab's. Labels and status messages translated (zh-hans, zh-hant, ja). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero: Add/Edit Bibliography inserted nothing Zotero reads a newly inserted bibliography field back before filling it, and unlinks a field whose text is empty, taking it for a bibliography the user deleted. Our new fields were empty, so Add/Edit Bibliography ended with Field_removeCode and nothing in the document. New fields now hold "{Citation}" until Zotero writes the result. Found by tracing the session with Zotero 10.0.6; regression test added. wordcraft-cli zotero takes --cmd (run before the Zotero command, e.g. caret.docEnd) to reproduce sessions from a given caret position. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero: log each command's outcome and Zotero's calls to the app log Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read Aloud player: speed, previous/next sentence, skips citations Read Aloud now speaks sentence by sentence with the system speech program (say / espeak / Windows SAPI) on a worker thread, so it can pause, skip and change speed. A floating player window has previous / play-pause / next / stop, a 0.5-3x speed slider and "Skip citations & bibliography" (on by default: Zotero, Mendeley, EndNote and Word citation fields). The spoken sentence is highlighted and the caret follows it. New commands: readAloud.playPause/next/previous/stop/speed/ skipCitations/status, and caret.pastCitation (Zotero > Move Past Citation). Speed and skip preference persist with the UI prefs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cargo.lock: wordcraft-zotero at the workspace version (0.4.0) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read Aloud: no unused BASE_WPM warning on wasm Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read Aloud: fuzz review.readAloud in hostile_params_never_panic #190 makes the engine's speech backend silent (Hold) under cfg(test), so the #75 skip is no longer needed; assert the backend instead so a test can never start the system synthesiser. 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> |
||
|
|
43c0be5735 |
Zotero integration: cite while you write, with a Zotero tab (#189)
* Zotero 1: range fields and custom document properties round-trip Citation managers' ADDIN fields (Zotero, Mendeley, EndNote) are now kept as range fields: FieldStart/FieldEnd markers with the result as ordinary, formatted content between them, possibly spanning paragraphs (a Zotero bibliography). Before, the result was flattened to plain text and a multi-paragraph field lost its end. - doc: InlineObject::FieldStart/FieldEnd, Document::field_ranges, balance_field_ranges; CustomProp and custom_props with get/set/remove. - docx: read ADDIN fields as ranges (field codes up to 2 MiB), write them back as begin/instr/separate…end; orphan markers are dropped on save so files always hold whole fields. docProps/custom.xml is parsed and regenerated (simple types as text, vectors/blobs verbatim) instead of passed through, so Zotero's ZOTERO_PREF_n settings are editable. - engine: file.properties reads and sets custom properties. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 2: wire-protocol client and document bridge New crate wordcraft-zotero (L5) speaks Zotero's published word-processor protocol (port 23116; 32-bit transaction id + big-endian length frames, JSON payloads, ERR: replies): - client: sends an integration command (addEditCitation, addEditBibliography, addNote, refresh, removeCodes, setDocPrefs) and answers Zotero's calls until Document_complete or the connection closes (cancelled dialog). Answers go through a callback so the app can apply them on its UI thread; run_on_session answers directly. - Bridge: implements Zotero's Document_*/Field_* calls on a Session. Fields are stored as Word's plugin stores them (ADDIN ZOTERO_ range fields; prefs in ZOTERO_PREF_n, with Zotero's ReferenceMark field type kept as Word's Field in the file), so documents stay Word-compatible. Rich text (RTF or HTML) keeps citation formatting; bibliographies get a Bibliography style from setBibliographyStyle; note styles create footnotes/endnotes, and deleting a lone note citation removes its note; placeholder links convert to fields. One transaction = one undo step. Field ids stay stable through inserts and deletes. - Tests: a fake Zotero over a real socket, every call, docx round trip, hostile arguments for every method, random frames (proptest). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 3: Zotero in the app and the CLI, tested with the real Zotero - ui.zotero.<command> (addEditCitation, addEditBibliography, addNote, refresh, removeCodes, setDocPrefs), ui.zotero.status and ui.zotero.answer. The socket runs on a background thread; each of Zotero's calls is answered on the UI thread against the session, and Zotero's questions become a dialog the session waits on. Web builds report that Zotero needs the desktop app. - wordcraft-cli zotero <command> <file> [--save] [--trace] [--port]. - Bridge: a selection Zotero makes to show a citation is put back when the session ends. - ui_shot: {"wait": ms} keeps frames running for background work. - docs/zotero.md. Checked against Zotero 10.0.6: refresh of a citation and bibliography (APA), its "modified citation" question answered in the app's dialog, RTF results with italics, Bibliography style, prefs round trip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 4: switching between in-text and note styles moves citations Document_convert and Field_convert now move a citation between the text, footnotes and endnotes, as Zotero asks when the document's style changes between an in-text and a note style: into a new note at the same place, or back into the text where the note mark was (removing a note that held only the citation; keeping one with other text). Field ids survive the move, formatting is kept, and each Zotero session stays one undo step. Checked against Zotero 10.0.6: unlink citations (removeCodes, with its confirmation question) and a refresh after switching the document to Chicago notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 5: Zotero tab in the ribbon A Zotero tab (before Help) with Add/Edit Citation, Add Note, Add/Edit Bibliography, Refresh, Document Preferences and Unlink Citations. The buttons are disabled while a Zotero command is running and in the web build. Three new icons drawn in code (document preferences, unlink, add note); the others reuse the References tab's. Labels and status messages translated (zh-hans, zh-hant, ja). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero: Add/Edit Bibliography inserted nothing Zotero reads a newly inserted bibliography field back before filling it, and unlinks a field whose text is empty, taking it for a bibliography the user deleted. Our new fields were empty, so Add/Edit Bibliography ended with Field_removeCode and nothing in the document. New fields now hold "{Citation}" until Zotero writes the result. Found by tracing the session with Zotero 10.0.6; regression test added. wordcraft-cli zotero takes --cmd (run before the Zotero command, e.g. caret.docEnd) to reproduce sessions from a given caret position. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero: log each command's outcome and Zotero's calls to the app log Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cargo.lock: wordcraft-zotero at the workspace version (0.4.0) 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> |