mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-28 04:17:06 -06:00
74a279d5f211c935e4cea67f8b72c37e419fc230
49
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
74a279d5f2 |
Fonts: a missing roman font is drawn in Cambria, as in Word (#1613)
WordCraft chose a substitute for a font that isn't installed from its name alone, so an unknown brand font always came out sans. Word reads the font table instead: measured with the same missing font, it draws it in Cambria when w:family is roman and in Calibri when it is swiss or auto. A report whose font table calls its brand font roman got a sans footer where Word draws serif. The font table's roman fonts are read into the settings, and layout looks a missing one up as Cambria (Caladea where Cambria isn't installed). The other classes keep the existing substitutes, which already start with Calibri's metric clone. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
fe9d560b49 |
Layout: paragraph spacing adds up when HTML auto spacing is off (#1612)
space after and the second's space before, as Word does by default. Word adds them instead when the document's compatibility options include w:doNotUseHTMLParagraphAutoSpacing. Measured in Word with five paragraphs of 12 pt before and after: 24 pt between them with the option, 12 pt without, the same in compatibility modes 14 and 15. A 60-page report with the option lost 6 pt per paragraph, about 60 pt on its cover. The option is read, laid out in the body and in boxes, and saved back. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
e4e389cdb3 |
Free part, list and comment ids after a file's largest ones (#1338) (#1428)
New parts, lists and comments took "largest id + 1": after an id of u32::MAX (a .json model or a .docx can carry one) that overflowed, a panic in debug builds and a wrap to 0 in release, and list.define's saturating_add reused the taken id. free_id gives one past the largest, or the lowest free id once that would overflow; try_add_part, try_add_list and try_add_levels report ids running out and add nothing, and the commands that add parts and lists use them. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
3370fd1ba2 |
JSON open repairs hostile run lengths (#1337) (#1412)
Paragraph::normalize now uses saturating sums, moves a run end that splits a character to the next boundary, and makes the runs cover the text exactly. Opening a .json document normalizes every paragraph in every story. Co-authored-by: justinjohn0306 <babyjjbabyjj549@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
bfacaf3627 |
fix(layout): preserve legacy table paragraph spacing (#577)
Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
3d8f8144d9 |
File: recover unsaved work after a crash (#518) (#586)
* M10: retain local crash recovery on current main * i18n: Backstage Recover in every catalog 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> |
||
|
|
77da439d0b |
DOCX: carry unmodelled parts; keep comment ids, style defaults, latent styles, list ids and app props (#1034) (#1060)
* DOCX: merge settings.xml into the original instead of regenerating it (#1034) A save regenerated word/settings.xml from the model, so every setting the model doesn't hold was dropped: compatibility options (lost in 18 of 18 real-world Word files), rsids, document variables, proofing state and the colour scheme mapping. The reader now keeps the original part when it holds such settings, and the writer merges into it: the original root and every unowned child stay byte for byte; owned children follow the model in CT_Settings order; in w:compat only compatibilityMode is replaced. The privacy options are the model's since #1062, so they are owned too. Kept children that point at a relationship (a mail-merge data source) are dropped. Anything unexpected falls back to the generated part. Files WordCraft wrote itself keep nothing extra. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * DOCX: carry unmodelled parts and keep comment ids, style defaults, latent styles, list ids and app props (#1034) Builds on the settings.xml merge. - Parts the package or main document relate to that WordCraft neither models nor writes (webSettings, stylesWithEffects, people, add-in task panes, the thumbnail, future parts) are carried verbatim with their relationships and content types; relationship types the writer produces, and any the body refers to by id, are not (HANDLED_RELS). - commentsIds / commentsExtensible: durable ids and UTC dates are read into the model and regenerated against the written paragraph ids. - w:default on localized default styles, w:latentStyles, lists' nsid/tmpl/multiLevelType, and app.xml Template/Company/Manager/ HyperlinkBase round-trip; counts are not carried, and Company/Manager are dropped when the file asks to remove personal information. - Comment reference marks use the document's "annotation reference" style id instead of a hard-coded CommentReference. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
e29fbdb0bf |
Page background: picture and texture fills are drawn and kept on save (#999) (#1036)
* Page background: picture and texture fills are drawn and kept on save (#999) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * rustfmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
68501de224 |
One Page and the zoom are saved with the document and restored when it is reopened (#1023)
Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
99fa307cf2 |
Honour Remove personal information / date and time on save (#1062) (#1131)
* #1062: honour Remove personal information / Remove date and time on save w:removePersonalInformation and w:removeDateAndTime (ECMA-376 §17.15.1.69-70) are read into Settings, written back in CT_Settings order, and honoured by the DOCX writer: comment and revision authors saved as "Author" (initials "A"), no dc:creator / cp:lastModifiedBy, no w:date on comments and tracked changes. The open document keeps its real values. New file.privacyOptions command sets both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #1062: reading privacy options has no side effects; File › Options › Save check boxes file.privacy reads the settings (a pure command: no undo step, no unsaved changes); file.privacyOptions only changes them and refuses an empty call. File › Options › Save gets a "Privacy for this document" section with the two check boxes, translated in every catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
d6723153cc |
Drawings: VML text boxes keep their margins, fit and alignment; hidden drawings are kept but not shown (#1118)
* Text boxes: VML text boxes keep their internal margins, fit to their text and alignment
A VML text box's inset ("l,t,r,b", ECMA-376 Part 4 §19.1.2.22) was ignored,
so its text sat Word's default 7.2 pt in and 3.6 pt down even where the file
says 0,0,0,0, which is how letterheads and form headers place address blocks
exactly. It's now read like DrawingML's lIns/tIns/rIns/bIns: each value left
out keeps the default, a bare number is in EMUs, o:insetmode="auto" leaves
the defaults. mso-fit-shape-to-text (VML's "Resize shape to fit text") is read
as auto-fit, like a:spAutoFit, and the shape's v-text-anchor as the text's
vertical alignment. In a group, a member's margins go with that member's
text.
The reader feeds layout, so the editor, PDF and page images all change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drawings: hidden shapes and pictures are kept but not shown
A drawing marked hidden (VML visibility:hidden, floating or inline;
DrawingML wp:docPr/@hidden, or a group member's cNvPr/@hidden) was drawn,
took room and wrapped text. Word for Windows shows it in no view; it's now
kept and saved hidden, but left out of layout: not placed, no wrap area, no
room in its line, nothing drawn, its text box's notes not placed. Exports to
other formats and copies leave it out, the accessibility checker no longer
asks for its alt text, and the selection pane says it's hidden.
Word for Mac draws hidden VML shapes; Windows follows the markup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
|
||
|
|
fbacffd282 |
fix(doc): count generated numbered-list labels as words (#893) (#908)
Microsoft Word treats generated numbered-list labels as independent words in document word statistics, while omitting them from character counts. Previously, word counting in WordCraft inspected plain run text only, causing sample_document() to report 357 words instead of Word's 361. - Add Document::story_word_count and story_word_count_of to compute words directly, including generated numbered labels while skipping bullet markers and empty labels. - Handle LevelSuffix::Nothing correctly by counting labels as independent words without string concatenation artifacts. - Keep Document::plain_text unadorned to maintain exact run text across docbin, clipboard, and tests. - In review.wordCount, report words via story_word_count while preserving character counts from plain_text. - Add regression tests covering numbered lists, bullets, LevelSuffix::Nothing, and review.wordCount word vs character parity. Fixes #893 Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
0f9dc5c6ae |
#776: draw Wingdings, Wingdings 2/3, Webdings and Symbol text as Unicode symbols (#876)
Word's symbol fonts are proprietary and never bundled, so text set in them showed the substitute's letters. New wordcraft_doc::symbol_fonts maps every code (stored as U+F020-U+F0FF, e.g. w:sym, or as a plain 8-bit code) to its Unicode equivalent: Symbol from Unicode's Adobe symbol.txt, the Wingdings and Webdings sets from Unicode L2/12-368 (WG2 N4384), checked against UCD. Layout shapes the mapped characters (fallback faces draw them); PDF text, copy as text and TXT export read as the symbols; the stored text and docx round trips are unchanged. 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> |
||
|
|
2f833b6acf |
Sorting and table numbers follow the text's language (#836, #837) (#844)
* #836, #837: language-aware sorting and locale numbers in tables Sorts (Table > Sort, Home > Sort, index and its letter headings, bibliography, Table of Authorities) use the text language's alphabet via a small pure-Rust collation (crates/doc/src/collate.rs). Table sort and formulas read numbers with the cell language's decimal/group separators and write formula results with its decimal separator (crates/doc/src/locale_number.rs). Sort comparators are now total orders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #836, #837 review: ordinals, leading zeros, percentages; untagged text; decomposed letters - parse_number: a trailing separator with no digits is the whole number (`1.`, a numbered "Lp." column, sorts as 1); a first group of `0` is never grouping (`0.125` in Polish is 0.125, not 125); a trailing `%` is ignored (`12 %` is 12); an en dash is a minus. - Text with no language follows the document default, else the editing language (`cmd::default_lang`), as AutoCorrect does: a Polish interface reads `3,50` as 3.5 in an untagged document. Table sort and formulas, paragraph sort, the index, the bibliography and the Table of Authorities use it. - Collation composes a letter and a following combining mark first (table from Unicode NFC), so a decomposed `ż` (z + U+0307) sorts as `ż`. - Tests: every Polish letter and its index heading, decomposed letters, the ordinal column, an untagged Polish SUM, Polish bibliography order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
45abb5ded3 |
#743: freeforms in groups keep their shape; text boxes keep their margins and Resize to fit (#843)
A Word 365 cover page lost its look. The missing fills came from theme-coloured shapes (scheme colours and wps:style fill references), which the reporter's 0.5.0 build didn't read; #611 fixed that on main. Two causes were still open on main: - A freeform (a:custGeom) inside a wpg group was drawn as a rectangle. read_wsp now reads custom geometry, so group members get it as well. - Text boxes ignored wps:bodyPr lIns/tIns/rIns/bIns and a:spAutoFit. Layout always used Word's default margins, so a title box with no margins that was just tall enough for its text lost its second line. TextBody now carries the box's own margins and auto_fit. Layout uses those margins and doesn't cut the text of a box that resizes to fit. Both are written back on save. 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> |
||
|
|
e75ff36251 |
Layout: an inline picture wider than the text keeps its size, like Word (#745)
* Layout: an inline picture wider than the text keeps its size, like Word WordCraft shrank an inline picture, chart or shape that is wider than its line to the line's width, height with it. Word draws it at its own size: it runs past the margin and the page edge clips it, and the line is as tall as the picture. In docxide-pdf's fixtures that moved the rest of the page up (case179: a 1014 x 100 pt picture became 468 x 46 pt) and shrank a header's 1014 pt logo strip to fit (alfies_arc). All page outputs already clip at the page: the PDF page box, and the page bitmaps the rasteriser and the editor draw. Text boxes don't clip what runs past their sides yet, so a picture wider than a text box is still fitted to the box rather than spilling over the page (ParaEnv::fit_objects, part of the paragraph cache key). On the 390 fixtures: case179 SSIM 70.9 -> 99.9%, alfies_arc 18.4 -> 74.8%, case178 98.0 -> 99.9%, nabl_lab 33.2 -> 36.8%; no fixture got worse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Review fixes for #745: fit imported and pasted pictures to the text width Layout no longer shrinks oversized inline pictures, so pictures that come at their pixel size are fitted when they enter the document, as Insert › Pictures already does: - opening HTML (also from a path), Markdown, ODT, RTF and other formats-crate imports (io::fit_pictures, against the narrowest section text width); - pasting HTML/RTF (paste::adopt, against our text width). Only inline pictures wider than the text are scaled, proportionally. DOCX keeps its sizes. Test: tests::imported_pictures_fit_the_text_width. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
f0431abd23 |
M8.1: Bibliography DOCX round-trip: support extended fields (#410) and upstream sync (#532) (#790)
* M8.1: Bibliography sources DOCX round-trip Store bibliography sources in package-root custom XML (customXml/item1.xml) with datastore item properties (customXml/itemProps1.xml), relationships, root customXml relationship (_rels/.rels), and content type overrides per ECMA-376 §15.2. Map internal source types to Word b:SourceType values (Book, BookSection, JournalArticle, ArticleInAPeriodical, InternetSite, DocumentFromInternetSite, ElectronicSource, Report, ConferenceProceedings, Case, Art, Film, Interview, Patent, Performance, SoundRecording, Standard, Thesis, Misc) and parse them back. Write SelectedStyle and StyleName attributes on b:Sources. Use safe slice indexing for author names parsing. Scan customXml relationships and customXml/item*.xml on read, extracting sources from b:Sources in the bibliography namespace. Add roundtrip integration tests in crates/docx/tests/roundtrip.rs verifying all parts, relationships, and attributes, update second-generation kitchen sink stability test, add hostile-input tests in crates/docx/tests/malformed.rs, and verify integration with citation styles in engine citations test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Review fixes for #532: bounded source reading, real citation style - Read at most 10,000 bibliography sources and dedupe them with hash sets, so a hostile file with ~100k b:Source elements can't make opening quadratic; each custom XML part is read at most once (64 parts at most). Test: hostile_bibliography_source_count_is_capped. - Drop the rt::BIBLIOGRAPHY relationship branch (not an OPC relationship type) and the stray comment in write_as. - Write the document's citation style (References > Style) in b:Sources/@StyleName and @SelectedStyle instead of always APA: Settings::bib_style records it, the style command sets it, opening a file reads it back into the session. - The b: namespace goes with the other non-body namespaces (it isn't declared on document.xml), matching #612. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the custom XML properties relationship apart from customXml So #612, which adds the same rt::CUSTOM_XML line, merges with this branch without a conflict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * clippy: pass parse_persons by value (needless_borrows_for_generic_args) * rustfmt after merging main --------- Co-authored-by: yefan911 <yefan911@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
85698c4d79 |
Security: pictures travel in the reply, exported links keep only safe targets (#678, #679) (#681)
- `ui.render` and `ui.screenshot` without a `path` return the PNG as base64 `png` in the reply instead of writing a fixed name into the shared temporary folder, where another user could have planted a link to one of this user's files; the MCP bridge's `render_page` and `screenshot` use that and no longer round-trip through `temp_dir()/wordcraft-mcp-*.png`. A given `path` still works as before (#678). - One allow-list, `wordcraft_doc::link`: `is_web_or_mail_link` for Ctrl/⌘-click and `is_safe_href` (web, mail, `#bookmark`, relative path; schemes read the way browsers do, so `java\nscript:` is a script) for the HTML and Markdown exporters and the HTML importer. Scripts and other schemes export as plain text (#679). - Tests: the allow-list, exporters with hostile links, the MCP bridge against a fake app (no `path` ever asked for), the control channel's in-reply PNGs. Co-authored-by: petycomppety <30414255+petycomppety@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
3fd97c3e58 |
Complex scripts: shape Myanmar, Khmer and Indic text; caret and line breaks respect syllables (#604) (#652)
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> |
||
|
|
60cd1bf568 |
Numbering: Chinese, Japanese and Korean number formats (一、二、三 …) (#534) (#561)
* Numbering: Chinese, Japanese and Korean number formats (一、二、三 …) (#534) A list with w:numFmt="chineseCounting" and lvlText "%1、" showed "2、", and the digit was drawn with glyph ids of the CJK font that supplied the comma, so it came out as an unrelated letter: the list label was laid out in font runs but drawn with the style of its last run only. - NumFormat knows every ST_NumberFormat value (ECMA-376 §17.18.59), so a file saves back the format it was opened with; unknown values are decimal. - New doc::cjk_numbers: Chinese/Taiwanese counting (with 十 below 100, digit by digit above) and counting-thousand (百/千/万/亿, 零 for gaps), simplified and traditional legal numerals, Japanese counting/legal/digital ten-thousand, Korean counting/digital/legal, ideograph digits, heavenly stems, earthly branches and the sixty-year cycle, aiueo/iroha (half and full width), ganada/chosung, enclosed numbers (①, ⒈, ⑴, ㊀) and full-width digits. Values past a sequence's end fall back to decimal. - Legal numbering (w:isLgl) shows every level, the current one too, in decimal. - List labels keep their font runs and draw each with its own face. - A list level without w:ind no longer gets a 36pt indent on read. - .doc number format codes 0x00-0x27 map to the formats (0x16 decimalZero). - PAGE/NUMPAGES fields take \* ChineseNum1/2/3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * rustfmt after merging main * Layout test: end the list range inside the third paragraph (#477 skips a paragraph the range ends at the start of) --------- 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> |
||
|
|
e46133817a |
Templates and Add-ins: attached template, update styles, Organizer (#377) (#382)
* Templates and Add-ins: attached template, update styles, Organizer (#377) - Model + DOCX: Settings keep the attached template (w:attachedTemplate's external relationship of the settings part) and w:linkStyles; both round-trip, written in CT_Settings order (after w:mirrorMargins, before w:trackRevisions). - Engine: tools.templates (state), tools.attachTemplate, tools.detachTemplate, tools.linkStyles, tools.updateStyles (copy the template's styles and defaults, one undo step; path or base64 data), styles.organizer (list), styles.copyFrom (copies missing bases, linked halves and the lists a style numbers with), styles.rename. The template path comes from the document, so it is never read automatically (not on open either): only an explicit tools.updateStyles / attach with update reads it. - UI: View > Templates > Templates and Add-ins dialog (path, Attach..., automatically update document styles, Organizer...) and a two-list Organizer (Copy, Delete, Rename...); strings in every catalog, Estonian included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Review fixes for #382: never follow a template path to another machine A document's w:attachedTemplate can name \\host\share\T.dotx, //host/share or file://host/share. Opening it (Update Styles, Organizer) would make Windows authenticate to that host over SMB and leak the user's NTLM credentials. local_path now returns None for network paths; regression test added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #377: refresh catalog count and parity checklist after rebase --------- 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> |
||
|
|
2ffba93b51 |
WordArt text effects and transforms, and shape connectors (#416) (#481)
* WordArt text effects and transforms, and shape connectors (#416) - Model: WordArt text effects on runs (fill solid/gradient/none, outline, shadow, glow, reflection), text warps on shapes (all DrawingML presets kept, adjust values capped), connector kinds (straight, elbow, curved) with glued ends, arrowheads and drawing ids (ShapeExtra). - DOCX: w14:textFill/textOutline/shadow/glow/reflection in w:rPr, a:prstTxWarp in wps:bodyPr, wps:cNvCnPr a:stCxn/a:endCxn, a:headEnd/a:tailEnd; glued-to shapes keep their docPr ids. - Layout/render/PDF: text with effects drawn from glyph outlines as paint layers (gradients, shadow/glow bands, faded reflection); warped text boxes bent along arch up/down, circle, wave, slant, inflate/deflate, chevron and triangle; connectors and arrowheads drawn as paths. - Engine: insert.wordArt makes a floating WordArt text box with our own styles; format.textEffects, wordArt.style/textFill/textOutline/transform, shape.connect; glued connectors re-route after object edits. - UI: WordArt style gallery (Insert, Home › Text Effects, Shape Format › WordArt Styles), Text Fill/Outline/Effects menus, Transform previews drawn from the warp geometry, connectors in Insert › Shapes, drag a connector end onto a shape to glue it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix tests for ShapeExtra, regenerate parity checklist, fmt (#416) * Regenerate parity checklist after rebase (#416) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
053bb0fd85 |
Content controls: keep, show and edit w:sdt (#381) (#392)
* Content controls: keep, show and edit w:sdt (#381) Content controls are now part of the model instead of being unwrapped on open: ControlStart/ControlEnd markers around their content (inline or around whole paragraphs), with table, row and cell wraps for controls around tables, repeating-section rows and cells. All sdtPr properties round-trip (title, tag, id, lock, placeholder, temporary, rich/plain text, w14 check box, combo box, drop-down list, date, picture, building block gallery, w15 repeating sections); unknown children are kept verbatim. The writer plans per block list which controls are written inline and which around blocks so the XML always nests, and drops markers that lost their partner. Editing: deletions keep a marker whose partner is outside the range, typing replaces a shown placeholder, emptying a control brings it back, sdtLocked and contentLocked refuse with a status message. Developer tab with insert commands, Design Mode, Properties dialog; frames and title tabs on the page, check boxes toggle on click, list and calendar pickers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #381: refresh catalog count and parity checklist after rebase --------- 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> |
||
|
|
1a0db9ec65 |
Dialogs: Line Numbers, Hyphenation, Language, Envelopes and Labels (#407) (#414)
* Dialogs: Line Numbers, Hyphenation, Language, Envelopes and Labels (#407) - Line Numbers (Line Numbering Options…): add numbering, start at, from text (auto or a distance), count by, restart each page/section or continuous; `layout.lineNumbers` takes start/countBy/distance. - Hyphenation Options: automatic, words in CAPS, hyphenation zone, consecutive-hyphen limit; kept in DOCX (w:hyphenationZone, w:doNotHyphenateCaps, w:consecutiveHyphenLimit) and honoured by layout. Manual…: `layout.manualHyphenation` finds words at line starts that would fit hyphenated on the line above; Yes inserts an optional hyphen, No skips. - Language: localized language list, do not check spelling or grammar, detect language automatically (per-user preference), Set As Default (document default language). - Envelopes: delivery/return address, common sizes (#10, #9, Monarch, DL, C4, C5, C6, B5); Add to Document inserts an envelope section before the letter. Labels: common sheets (Letter and A4), full page or a single label at row/column, one per recipient; New Document builds the sheet with gap columns. - Ribbon: Line Numbering Options…, Hyphenation Options…, Manual…; the Language, Envelopes and Labels buttons open their dialogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Remove duplicate Label: keys in ru and tr (#407) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
56f9456f03 |
References dialogs: Caption, Index, Table of Contents, Create Source (#402) (#410)
* References dialogs: Caption, Index, Table of Contents, Create Source (#402) - Caption: labels (built-in plus New/Delete Label, kept with the interface settings), above/below the item, leave the label out, Numbering (format, chapter number from a heading level with a separator) -> SEQ \* \s and STYLEREF n \s fields; Update Field renumbers SEQ with \s restarts, \r, \c, \h. - Index: indented/run-in, columns, right-aligned page numbers with tab leader, letter headings -> INDEX \h \c \e \r; Mark Index Entry (subentry, cross-reference, page-range bookmark, bold/italic page numbers, Mark All) -> XE \t \r \b \i. The index is rebuilt honouring them; columns become a continuous section of their own. Index commands move to cmd/index.rs. - Custom Table of Contents: page numbers, right align, tab leader, hyperlinks, levels, Options (heading styles, other styles with levels, outline levels, TC fields), Modify for TOC 1-9 -> TOC \o \h \z \u \t \f \l \n \p; updating honours them (hyperlinks to _Toc bookmarks). - Source Manager and Create/Edit Source: source types with their fields, show every bibliography field (new Source fields); Insert Citation menu lists the sources and Add New Source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fmt (#402) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
30993ffb3f |
Track Changes: moves (w:moveFrom/w:moveTo) (#400) (#404)
Moved text is tracked as a move instead of a deletion plus an insertion: - Model: Revision::move_name links the source (a deletion revision) and the destination (an insertion revision) of a move; runs and paragraph marks keep using ins/del, so everything that hides deletions or exports the result treats the moved-away text like a deletion. - DOCX: w:moveFrom / w:moveTo inside w:moveFromRangeStart/End and w:moveToRangeStart/End (ECMA-376 17.13.5.21-28) are read as linked moves, paragraph marks included; on save the ranges are written around each moved stretch with the move's name, author and date, moved-away text in w:t. A move missing an end, or whose range never ends, opens as a plain deletion and insertion. - Recording: with Track Changes on, a cut of a paragraph or at least a sentence gets a revision of its own; pasting it right after makes both a move (pasted paragraph marks are tracked as inserted too). - Accept/Reject at either end resolves both; the Reviewing Pane lists moveFrom/moveTo with the move name and "Moved from"/"Moved to". - Markup: green, double strikethrough at the source and double underline at the destination; "Moved from"/"Moved to" balloons. 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> |
||
|
|
f8f1d80c38 |
Shape Format › Text: direction, alignment and linked text boxes (#369) (#380)
- Model: TextBody on text box parts (vert, anchor, next link); chain resolution ignores cycles, self-links and second claimants and caps chains at 256 boxes; pruning a box clears links to it. - Layout: text boxes lay out in their own frame and turn like table cells (rotate 90°/270°), stacked text one letter to a line, Align Text top/middle/bottom; linked boxes split the first box's story by line, each at its own line length. - DOCX: wps:bodyPr vert/anchor, wps:txbx id and wps:linkedTxbx id/seq read and written (ECMA-376). - Commands shape.textDirection, shape.alignText, shape.link, shape.breakLink; ribbon Shape Format › Text group with drawn icons; Create Link waits for a click on the target box. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c010b73428 |
Documents keep their own footnote separators and continuation notice (#361)
* Keep and draw a document's own footnote separators and continuation notice (#352) The docx reader used to drop the special footnotes and endnotes (w:type separator, continuationSeparator, continuationNotice), so a document's own separator lines and continuation notice were lost on screen, in PDF and on save. They are now kept on the document as paragraphs, with the separator lines themselves as run objects. Layout draws the document's separator above a page's footnotes and its continuation separator above notes continued from the page before: the separator objects become a short line or one across the column, and any other text or formatting is shown as given. The continuation notice goes under the part of a note that continues on the next page. The endnote separator is used above the endnotes. Documents without their own keep the default lines as before. The separators and notice can't be clicked into. On save the special notes are written back with Word's ids -1, 0 and 1 (the notes follow them, starting at 2 when there is a notice) and listed in the settings' footnotePr/endnotePr. Documents without them keep the default output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Merge fix: NoteSeps is Clone (the layout context that holds it is cloned) --------- 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> |
||
|
|
860f4ca260 |
Tight and Through wrapping follow the object's outline (#354) (#360)
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> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
e71e2d1c52 |
Draw tab: draw with pen, pencil and highlighter, erase ink, and hide ink (#315)
* Draw Table and Eraser: draw tables and split or merge cells with a pen (#303) Insert › Tables › Draw Table and Table Layout › Draw › Draw Table / Eraser. table.draw takes a pen stroke in page points: a rectangle outside a table inserts a one-cell table of that size at the paragraph under its start; a straight vertical or horizontal line inside a table splits the cells it crosses (new grid column or row; untouched cells span it). table.eraser merges the two cells beside the border nearest a point. Each stroke is one undo step. In the app the buttons toggle pen and eraser modes with a live preview, a crosshair with the tool's icon, and Esc to stop; canvas clicks don't move the caret while a tool is on. Splitting through part of a vertically merged cell and erasing beside one are refused with a clear error. Catalog 384/430 -> 387/430. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Draw tab: pen, pencil and highlighter ink, stroke eraser and Hide Ink (#307) Ink strokes are floating freeform shapes: a new Freeform geometry (paths in shape-local coordinates, opacity, and the pen that drew it) on InlineObject::Shape with ShapeKind::Freeform. draw.stroke inserts one anchored to the paragraph under the first point (one undo step; points capped per stroke, strokes capped per page, NaN dropped); draw.erase / draw.eraser with a point deletes the stroke under it; draw.pen, draw.pencil, draw.highlighter, draw.eraser and draw.select set the Draw tab's tool in the session view state; review.hideInk toggles showing ink on screen. The canvas draws a live stroke while a pen is dragged and commits it on release; the eraser deletes strokes it passes over (one undo step per drag); Esc or Select goes back to selecting. Draw tab buttons show the tool in use, with colour and thickness menus; Review gets an Ink group with Hide Ink. Ink renders with round caps and joins on screen and in PDF; highlighters are translucent and behind the text. DOCX writes freeforms as wps:wsp with a:custGeom (moveTo/lnTo) in a wp:anchor and reads them back, along with simple custom-geometry freeforms from other files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #315 review fixes: eraser undo test, sanitized tap dots, custGeom path cap, ink docs - draw.eraser with a point erases through draw.erase: test that it is one undo step (undo/redo) and marks the document dirty. - Render and PDF: a single-point stroke's dot is drawn at the sanitized point, not the raw first one. - DOCX custGeom reading stops once MAX_PATHS paths (subpaths included) are collected, across a:path elements. - docs/file-format-parity.md: ink is saved as custom geometry, not InkML, so Word shows it as a freeform. - widgets test: theme::apply takes an Appearance (#317). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Draw tab: pen, pencil and highlighter ink, stroke eraser and Hide Ink (#307) Ink strokes are floating freeform shapes: a new Freeform geometry (paths in shape-local coordinates, opacity, and the pen that drew it) on InlineObject::Shape with ShapeKind::Freeform. draw.stroke inserts one anchored to the paragraph under the first point (one undo step; points capped per stroke, strokes capped per page, NaN dropped); draw.erase / draw.eraser with a point deletes the stroke under it; draw.pen, draw.pencil, draw.highlighter, draw.eraser and draw.select set the Draw tab's tool in the session view state; review.hideInk toggles showing ink on screen. The canvas draws a live stroke while a pen is dragged and commits it on release; the eraser deletes strokes it passes over (one undo step per drag); Esc or Select goes back to selecting. Draw tab buttons show the tool in use, with colour and thickness menus; Review gets an Ink group with Hide Ink. Ink renders with round caps and joins on screen and in PDF; highlighters are translucent and behind the text. DOCX writes freeforms as wps:wsp with a:custGeom (moveTo/lnTo) in a wp:anchor and reads them back, along with simple custom-geometry freeforms from other files. 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> |
||
|
|
81bf9187f2 |
Wrap long display equations across lines like Word (#326) (#329)
A display equation wider than its column ran off the page. It now breaks at top-level binary operators and relations (never inside a fraction, radical, script or matrix), filling each line greedily; a term wider than the line overflows instead of breaking inside. - Break before the operator by default, or after it, or repeated on both lines, as the document's m:brkBin says; m:brkBinSub picks how a minus repeats. - Continuation lines are indented by m:wrapIndent (one inch by default) or set flush right with m:wrapRight. - Manual breaks (m:brk on a run) start a new line; m:alnAt aligns it at an operator of the first line. - DOCX: m:mathPr in settings.xml and m:brk in math runs are read and written back. Documents without them save as before. - Inline equations, numbered equations and m:jc alignment are unchanged. Tests: layout (a long sum wraps within the column with indented continuation lines; after/repeat modes; manual breaks and alnAt; short equations stay on one line; hostile break input) and DOCX round-trip. 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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
5d86dec250 |
Right-to-left and Persian text support (bidi, shaping, RTL paragraphs, DOCX) (#207)
* feat(doc,docx): complex-script run properties and RTL OOXML round-trip Model the formatting Word applies to Persian, Arabic and Hebrew text: cs, font_cs, size_cs, bold_cs, italic_cs and lang_bidi on CharProps, resolved with Word's fallbacks (ResolvedChar::complex). Size, bold and italic pair with their complex-script values so a run's size or bold also covers its Persian characters. Add doc::bidi script helpers, and keep ZWNJ and Arabic marks inside words for word selection. DOCX: read and write w:rFonts/@w:cs, w:szCs, w:bCs, w:iCs, w:cs and w:lang/@w:bidi in schema order; stop using the cs font as the Latin font. Tests round-trip Persian text with ZWNJ, digits and mixed Latin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(fonts): directional shaping and Persian font fallback Add shape_run to shape a run in a known bidi direction (Arabic joining, mirrored brackets). Prefer Persian-capable fonts (Vazirmatn, Noto Naskh/ Sans Arabic…) for right-to-left fallback, pick bold/italic fallback faces, and map common Persian document fonts (B Nazanin, B Lotus, B Titr, IRANSans…) to open substitutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(layout): Unicode Bidirectional Algorithm and right-to-left paragraphs Run UAX #9 per paragraph (unicode-bidi), shape each level run in its direction, keep marks and ZWNJ with their letter and a word in one fallback face. After line breaking, apply rules L1/L2 per line and give each cluster a visual box; right-to-left paragraphs are laid out from their start edge and mirrored, so indents, tabs, alignment, list labels, drop caps, floats and borders sit on the right. Caret, hit testing and selection use visual geometry (split selections, caret at bidi boundaries, DocLayout::visual_step for arrow keys). Display items carry glyph-to-text ranges, which the PDF writer now uses. Plain left-to-right lines are unchanged. No hyphenation of RTL words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(engine,ui): paragraph direction commands and RTL editing Add para.ltr / para.rtl (Word's text direction buttons) that change only the reading order, and make the alignment buttons visual in right-to-left paragraphs (para.align also takes start/end). Arrow keys move visually in bidirectional text, Ctrl+arrows follow reading order, and Bold/Italic/ Size/Font cover complex-script text. Ribbon buttons with code-drawn icons, a mirrored ruler for RTL paragraphs, translations, catalog, parity and roadmap updates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(contributors): add Saeed Azarehgar to people.toml Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(engine): para.align with an explicit value sets it, without toggling Only the alignment buttons toggle back to the start edge when pressed again. para.align (used by the Paragraph dialog, scripts and agents) now sets the requested alignment, as seen on the page, in each selected paragraph's own direction. Previously pressing OK in the dialog with Center or Right selected reset the paragraph to the start edge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): Direction option in the Paragraph dialog Add Word's Direction (Right-to-left / Left-to-right) to the Paragraph dialog's General section. The dialog now shows alignment and Left/Right indents as seen on the page, mapping them to the paragraph's logical start/end values on OK, so an unchanged OK no longer flips a right-to-left paragraph. OK logic moved to apply_paragraph, with tests; ja/zh translations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(layout): allow hanging trailing spaces in the RTL display test A wrapped right-to-left line's trailing spaces hang past its end edge (the left margin), as left-to-right lines hang them past the right. The test failed whenever the loaded fallback fonts made a line end there; it now checks only visible glyphs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
10eb8798cd |
Equations: Word (OMML) import, layout and save, plus an equation editor like Word's (#191)
* M5.5: Word equations (OMML) import, lay out and save like Word Equations were flattened to plain text on import. Now OMML is read into a math tree (fractions, scripts, radicals, n-ary operators, delimiters, functions, limits, accents, bars, boxes, equation arrays, matrices, phantoms), laid out with the OpenType MATH table (Cambria Math when installed, else STIX Two Math and other open math fonts), drawn to screen and PDF, and written back as the original OMML. Display equations sit on their own centred line. insert.equation parses the linear format into structure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M5.5: equation editor like Word's — Equation tab, in-place editing, # numbers Insert › Equation now opens an equation for in-place editing, with the Equation tab: ready-made equations, Unicode/LaTeX input and conversion (professional ↔ linear), Normal Text, eight symbol sets, and the eleven structure galleries (fraction, script, radical, integral, large operator, bracket, function, accent, limit and log, operator, matrix) with tiles rendered by the math engine. Typing builds up like Word (a/b, x^2, √, ∑… then space), Math AutoCorrect turns \alpha, \sqrt… into symbols, Tab moves between empty slots, arrows/Backspace work through structures, and the right-click menu has structure commands (fraction type, radical degree, limit position, matrix rows/columns, remove brackets…). A trailing #(n) numbers a display equation with the number at the right margin; # alone numbers it automatically. DOCX output keeps numbers Word-compatible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n: equation editor strings in Spanish, Brazilian Portuguese and Ukrainian The catalogs that landed on main after #191 was opened lacked the Equation tab and equation command labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Equations: our own names and order for the built-in equations Clean room: the ready-made equation list used another product's gallery entry names and order. Same formulas (and ids), with our own descriptive labels, grouped by subject (geometry, algebra, calculus, trigonometry, probability). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ROADMAP: equations row matches the landed equation editor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fmt after merge 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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
b64153f604 |
Layout: shrink spaces in justified lines like Word 2013+ (compatibility mode 15) (#109)
Word 2013 and later fit more text on a justified line by shrinking its spaces by up to 20% of their width, so long justified paragraphs broke differently in WordCraft (a one-page test document's summary took 8 lines instead of Word's 7). Lines that only fit with shrunk spaces now get them, the last line of a paragraph included; lines with tabs never shrink. The behaviour depends on the document's compatibility mode, which is now read from settings.xml (w:compatSetting compatibilityMode), kept in Settings::compat_mode and written back, so saving an older file no longer silently upgrades it to mode 15. A .docx that names no mode is treated as Word 2007 (12); new documents are 15. Co-authored-by: ssamstudio <267867645+ssamstudio@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
6e73fe40e3 |
Tables: Word 2013 table edge, and floating tables (w:tblpPr) (#137)
* Layout: place floating pictures and shapes the way Word does Pictures and shapes anchored in a .docx often landed in the wrong place: the reader folded every margin-relative position into "margin", ignored <wp:align>, and kept only distL as the distance from text. Floats in headers, footers and table cells were not drawn at all. Document model (doc): - Anchor gains the left/right/top/bottom/inside/outside margin areas, the anchor character and the anchor line - Float gains h_align/v_align (FloatAlign: start, center, end, inside, outside), which take precedence over the offsets, and separate dist_top/dist_bottom (dist stays the left/right distance) DOCX (docx): read and write wp:align, every relativeFrom value and distT/distB/distL/distR. Layout (layout): - float_rect positions against each reference area, with alignment - floats in headers, footers, table cells and text boxes are placed, drawn (behind-text ones under the text) and wrapped around; headers and footers get a page frame so page-relative floats land in page coordinates, also when the header's height is measured for the body top - a paragraph's first line pushed below a top-and-bottom float is drawn (and hit-tested) where it was laid out - floats follow their paragraph to a new column or page, and are placed at the paragraph's actual top (space before counted once, contextual spacing respected) - wrap areas use each distance as given; the old 9pt minimum is gone, so a float with distance 0 now touches its text as in Word - a list item's label is counted once (Ctx::next_label) and passed into every layout of its paragraph (Ctx::para_labelled), so laying it out again around floats doesn't advance the list counters twice; a paragraph whose top moved is only laid out again when the page has something to wrap around Engine: switching an inline object to a wrapping style gives it Word's default 9pt side distance; the position presets clear any alignment. Approximations: inside/outside margins and alignments ignore mirrored margins (treated as left/right); character- and line-relative positions fall back to the column and the paragraph top. Tests: docx fixture and round trip for alignment, reference areas and distances; layout tests for alignment, the pushed-down first line, floats moving to page two (with space before), header and cell floats, header floats not changing the body top, hostile distances, and list items counted once when laid out again (contextual spacing, page break before, beside a float in a table cell). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tables: place a table's edge by the document's Word compatibility mode Word 2013 and later (compatibilityMode 15) put a left-aligned table's border at the margin plus its indent, moved in by half the first cell's left border; earlier modes line the first cell's text up with the margin instead, the border one cell margin further out. We always did the latter, so in Word 2013+ documents (43% of docxide-pdf's Word reference corpus) every table and its text sat a cell margin (usually 5.4pt) too far left. Settings::compat_mode keeps the document's mode: read from w:compat/w:compatSetting, 12 when a document has no settings or doesn't state one (as Word treats it), 15 for new documents, and written back instead of a fixed 15. The legacy shift now uses the first cell's own left margin. Measured against docxide-pdf's corpus (365 documents, same open fonts): mean SSIM 40.4% -> 42.1%, documents passing SSIM 40 -> 50, passing ink overlap 142 -> 154; 71 documents better, 6 worse (tables nested in a cell whose text Word puts at the outer cell's edge, which docxide-pdf doesn't match either). Tests: a mode-15 table's text sits a cell margin and half a border inside the margin, a mode-12 table's at it; the mode round-trips through .docx and defaults to 12 without settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layout: floating tables (w:tblpPr) stand at their position, text wraps Floating tables were read as ordinary tables: laid out in the text flow with no regard to their position, and the text after them waited below instead of running beside them. 71 of the 382 documents in docxide-pdf's Word reference corpus have them, typically forms anchored to the text and the margin. TableProps::float (TableFloat) keeps w:tblpPr and w:tblOverlap: what x and y are measured from (column, margin or page; text, margin or page), offsets or alignments, distances from text and whether it may overlap other floating tables. The .docx reader and writer carry it. A floating table at the top level of the body is placed at its position (tblpY below the text where it stands, by default) and takes no room in the flow; its area plus its distances from text joins the page's wrap areas, so the following text runs beside it or below it. One that may not overlap moves below the floating tables in its way, keeping (in Word 2013+ layout) its distance from the text's left edge; in earlier modes an offset places the first cell's text, as an indent does. A table in the flow starts below the floating objects in its way. Floating tables inside table cells and headers are still laid out in their flow. Measured against docxide-pdf's corpus (365 documents, same open fonts), on top of the compatibility-mode commit: mean SSIM 42.1% -> 43.9%, mean line-break agreement 63.5% -> 66.6%, documents passing SSIM 50 -> 55, page count right 303 -> 305; 33 documents better, 4 worse by at most 3.1 points. Tests: a full-width and a narrow floating table that may not overlap stack, the narrow one keeping 9pt from the text edge, and the next paragraph runs beside it; floating table positions round-trip through .docx. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layout: a floating table taller than a page runs across pages A floating table was always placed whole at its position, so one taller than the page's body ran past the bottom margin and its rows were lost: a Bulgarian programme whose whole body is one floating table came out as 3 pages instead of Word's 6. Such a table now flows across pages like a table in the text (rows splitting and header rows repeating as usual), from the floating table's own left edge; the position is worked out by float_table_x, which the whole-table placement shares. In docxide-pdf's Word reference corpus that document has Word's page count again with all its rows; nothing else changes. Test: an 80-row floating table runs onto a second page, every row drawn above the bottom margin from the table's left edge (was one page). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tidy floating-table code: one alignment helper, named steps, shared mappings No change in behaviour; makes the floating-table code easier to read. - FloatAlign::from_ooxml/ooxml (added with floating pictures) replace the copies of the same mapping in the w:tblpPr reader and writer, and TableFloat::dist_from_text the copies of its clamping. - place_floating_table and float_table_x place by offset or alignment with align_in, like floating pictures do, instead of their own copies. - The overlap rule uses Rect::inset/intersects and names its condition (keeps_text_distance) instead of a one-line chain of comparisons. - below_floats names the step that starts a table in the flow below the floating objects in its way. - The floating-table functions move next to place_table, which calls them, from after `mod tests;` at the end of the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- 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> |
||
|
|
d95e09ba25 |
Clippy/fmt clean workspace, ROADMAP with honest parity estimate; xtask ci green
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
72262a2ea9 |
M3/M6/M12: PDF export (krilla), TXT/MD/HTML/RTF/ODT import+export, citations & bibliography, index, table of figures, cross-references, tools (repeat, macros, AutoCorrect, compare, accessibility, inspector, protection, versions, Quick Parts)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d1e80df4da |
M0.1–M0.6: workspace, geom, document model, fonts, layout, render
Clean-room foundations for WordCraft: Arc-shared document model with paragraph/run editing, styles, lists, sections and tables; Word-style font substitution; first-fit line breaking with tabs, lists, alignment, pagination, tables, headers/footers and hit testing; vello_cpu page rasteriser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |