mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-26 03:17:28 -06:00
2f8f304fc9e10540887c3e49e2d75d8ee7baac2a
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2f8f304fc9 |
Shape Format: Shape Effects — shadow, glow and soft edges (#275)
Shapes and text boxes get an effects model (outer shadow with colour, transparency, blur, distance and angle; glow with colour, size and transparency; soft-edge radius), the shape.effects command (presets, custom values or null per effect, applied to every selected shape, undoable, hostile numbers clamped), screen and PDF rendering with the same banded approximation of a blur, DOCX a:effectLst read/write (outerShdw, glow, softEdge) with a matching wp:effectExtent, and a contextual Shape Format tab with Shape Fill, Shape Outline and Shape Effects (Shadow, Glow, Soft Edges submenus) plus Arrange. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8b49ea7546 |
Table Layout › Text Direction turns the cell's text (#226) (#245)
* Tests: shapes and pictures can be clicked, resized, dragged and nudged Fixed by #46 but only text boxes were covered. These walk the editor's path for shapes (rectangle, ellipse, star, arrow) and pictures: hit test, select, resize with arrange.bounds, drag to float with Square wrap, nudge, undo. Covers #142 and #82. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Font list offers every weight a family installs, named as Word names them (#219) Fonts were listed and matched only by their typographic family name (name ID 16, e.g. "Roboto"), so a face other than Regular, Bold or Italic (Light, Medium, SemiBold, Black, Condensed...) never appeared in the font list, and a document asking for "Roboto Light" fell back to a substitute. Each face now also carries its legacy family name, the one Word lists and stores in documents: name ID 1 for static fonts ("Source Sans 3 Semibold"), or the family plus the style words other than Regular, Bold and Italic for a variable font's named instances ("Inter Light"). The font list includes these names, has_family and face() resolve them (bold and italic still choose or synthesise within the faces the name covers), and asking for a legacy name loads the whole installed family. The system font scan now also reads fvar, so a variable font's instances are listed before it is loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Absolutely positioned VML text boxes float instead of sitting inline (#149) A legacy VML shape (w:pict > v:shape / v:rect) with position:absolute in its style was read as an inline object, so a tall text box in a header made the header as tall as the box and pushed the body down the page. The reader now maps the VML style to the same float model DrawingML anchors use: left/margin-left and top/margin-top offsets, mso-position-*-relative reference areas, mso-position-horizontal/vertical alignment, mso-wrap-distance-* and w10:wrap (square, tight, through, topAndBottom; none or absent is in front of the text, or behind it at a negative z-index). Shapes without position:absolute stay inline. Saving writes the boxes as DrawingML anchors with the same placement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Reject All removes paragraph breaks inserted with Track Changes on (#229) Enter under Track Changes already recorded the new paragraph mark as an insertion, but accepting or rejecting only cleared that flag, so Reject All left the paragraphs split. Rejecting an inserted mark (or accepting a deleted one) now joins the paragraph with the next one again; Accept All, Reject All, Accept/Reject at the caret or over a selection, Next/Previous Change and the Reviewing Pane all see paragraph marks. Backspace at a paragraph start and Delete at a paragraph end now record a tracked deletion of the mark (or remove a mark the same author inserted) instead of only moving the caret; tracked deletions across paragraphs mark the paragraph marks too. DOCX reads and writes paragraph-mark revisions (w:ins / w:del in the mark's w:rPr), and a tracked mark is drawn in its author's colour when formatting marks are shown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Table Layout: Text Direction turns cell text (#226) The Text Direction button only flipped a flag that layout and rendering ignored (and docx always wrote it as btLr), so pressing it did nothing visible. Cells now carry a text direction (horizontal, top-to-bottom tbRl, bottom-to-top btLr). The button cycles through them like Word's, for every selected cell. Layout runs turned text along the cell's height: an exact row height sets the line length, otherwise the row grows to fit the unwrapped text. Turned lines are drawn in a turned frame on screen and in PDF, and clicks, caret, and selection follow the turn. docx reads and writes w:textDirection with the matching value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Chinese interface: embed the Chinese UI font in releases, fall back to an installed one (#241) The release workflow pinned storytold/craft-fonts at abb8331, the commit that added the Japanese fonts. Noto Sans CJK SC came one commit later (8dcdacd), so release builds embedded only the Japanese faces and the Simplified Chinese interface fell back to BIZ UDPGothic, which lacks about 157 simplified-only hanzi (页, 选, 删 …) and drew them as boxes. Document text was unaffected because it uses the system font fallback. - Bump the craft-fonts pin in the release and FreeBSD workflows to 8dcdacd. - With CRAFT_FONTS_REQUIRED, a native build now fails when the craft-fonts manifest has no Jpan or Hans font, so an outdated pin can't ship silently again. - When no embedded face covers the interface language, the UI font families get an installed CJK font (desktop only): a short per-platform list of files in the system font folders (Windows Deng/msyh/simhei/simsun, macOS PingFang/STHeiti, Linux Noto Sans CJK/WenQuanYi), header-checked, size-capped, and the face chosen by coverage (a SC face in a pan-CJK collection), so egui never sees a file it can't parse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add a System interface theme that follows the OS appearance (#115) The interface theme is now a three-way choice: Use system setting, Light or Dark, in File > Options > General and in a new Interface Theme menu in View > Dark Mode. System follows the light/dark appearance egui reports (winit on macOS, Windows and Linux; prefers-color-scheme on the web) at launch and live, and falls back to light when the OS reports none. egui's own theme is now pinned to ours, so an OS change no longer swaps in egui's unstyled style. Saved preferences migrate: the old "dark" switch becomes Dark or Light and is never written again. Light stays the default for new installs. New ui.theme command (light/dark/system); ui.dark still sets Light/Dark. The dark page (View > Switch Modes) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #248: read the installed CJK UI font only when CJK text is shown A build without craft-fonts read a large system CJK font on the first frame even for an English interface. Now it is loaded only for a CJK interface language, or once File > Options shows the language list (names in their own scripts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
302528d67e |
Web: use the browser clock everywhere on wasm (insert weekday, PDF dates, layout timing, UI timers) — follow-up to #214 (#230)
#214 gave now_iso the browser's clock on wasm; the other clock helpers were still stubs there: Insert Date's weekday was always Thursday, the PDF creation date was left out, and the layout and UI millisecond clocks returned 0, so browser AutoSave's timer never fired, the caret didn't blink and status messages never cleared. The engine's clock is now one now_unix() (std on native, js_sys::Date in the browser, as #214 does) that now_iso and the weekday share; the pdf, layout and ui-egui clocks read js_sys::Date the same way on wasm. Refs #188. Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
6c152055b9 |
Hidden text: no line breaks, hyphens, wrap areas or bookmark text from text that isn't shown (builds on #152) (#173)
* Tables: stop repeating a header row that leaves no room for the next row A repeating header row plus a row that could not fit below it on an empty page made place_table loop: after each page break the header was placed again, so the page was never "at top" when the row was retried, split_row had nothing to split, and the loop ran its 1,000 iterations per row. A 20-row table with a 640 pt header came out as 19,002 pages. After a page break the header now repeats only when the row (or its first split) still fits below it; otherwise the row starts at the top of the page, as Word does when a header row would fill the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layout: give runs with different links or decorations their own style Builder::style memoised StyleRuns by a key that folded strike, double strike and "has a link" into one boolean and left out the underline colour and baseline position. The display list reads all of these from the shared StyleRun, so in a paragraph of like-formatted links every link pointed at the first one's URL in the PDF (and an external link after an internal one became a bookmark jump), double strike drew as single strike after a struck run, and underline colours were copied. The key is now the whole resolved format, so runs share a StyleRun only when nothing the display list reads differs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * PDF and No Markup: leave tracked deletions out of the final text PDF export defaults to no markup, but it laid the document out with every deleted run in place and only switched off the strike and colour, so the PDF printed deleted text as ordinary text ("Keep DELETEDTEXT INSERTED"): neither the original nor the final document, and a leak of text the author removed. The No Markup view did the same on screen. A new LayoutOptions::hide_deleted lays deleted runs out like hidden text (zero-width markers, so offsets, carets and hit testing keep working). PDF export sets it whenever markup is off, the session layout sets it when markup is hidden, and the markup toggles relayout as Show/Hide marks does. The display list also never draws a deletion as plain text when markup is off, whatever layout it is given. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tables: repeat the header on every page and put the next row under it, as Word does The earlier fix for the runaway loop dropped the repeated header row on any page where the header and the next row did not fit together. Word never does that: measured on Word for Mac, it moves the row to a new page, repeats the header there and places the row under it even when the row then runs into the bottom margin (a 300 pt header with 400 pt rows gives one header + row per page; a 640 pt header with 100 pt rows does the same). place_table now remembers when the page holds only the repeated header rows. A row that cannot split is placed there anyway instead of breaking the page again, which is both Word's behaviour and what keeps the loop from spinning: every pass now places something. Rows of text taller than the room under the header still split across pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * PDF: keep tracked deletions out of bookmarks and heading tags Without markup the PDF pages show the final text, but bookmark titles and the titles of heading structure tags were still built from plain_text(), which includes deleted runs. A heading "Intro SECRET Part" with "SECRET " deleted printed as "Intro Part" yet bookmarked as "Intro SECRET Part", and a fully deleted heading still got a bookmark: text the author removed leaked into the outline and the tag tree. Paragraph::final_text() gives the text with deletions left out, and the exporter titles headings with it unless markup is included. A heading whose text is all deleted has an empty title and so no bookmark. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * No Markup: deleted note references take no number and print no endnote With deletions hidden, a tracked-deleted footnote or endnote reference disappears from the text, but note_numbers() still counted it. Deleting the first of two footnote references left the surviving one numbered 2, and a deleted endnote reference still printed its endnote after the last paragraph, because the endnote list is taken from the same map. note_numbers() now skips references inside tracked deletions when the layout hides them, so the remaining notes number from 1 and endnotes whose reference was deleted are not emitted. With markup shown nothing changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * No Markup: a deleted floating object leaves no wrap area behind With deletions hidden, a tracked-deleted floating picture or shape was not drawn, but place_para had already added its wrap exclusion from p.objects without looking at the anchor, so the final text still flowed around an invisible object (indented beside a square-wrapped shape). Floating objects whose anchor is a tracked deletion are now skipped when the layout hides deletions; deleted_objects() is shared with note numbering, which needs the same test. With markup shown nothing changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * No Markup: a deleted soft hyphen no longer breaks the word with a hyphen With deletions hidden, a tracked-deleted soft hyphen became a zero-width marker, but hyphenation_points() reads the paragraph's raw text and accepted marker clusters, so the line could still break there and draw "ab-" in a narrow column although the final text has no soft hyphen. When the layout hides deletions, clusters inside a tracked deletion are no longer hyphenation points (soft hyphens or dictionary points alike). With markup shown nothing changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * No Markup: tracked deletions add no line-break opportunities Break opportunities came from running UAX #14 over the paragraph's raw text, deleted runs included. With deletions hidden, a deleted soft hyphen or space still let the line break there: "xx ab<del>SHY</del>cdef" in a narrow column came out as "xx ab" / "cdef" instead of moving the word "abcdef" to the next line whole. When the layout hides deletions, the opportunities are computed over the text without them and mapped back to offsets in the paragraph. With markup shown, or with no deletions, the computation is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hidden text: no hyphenation points or line breaks in text that is not shown Hidden text that is not shown (the default, and always in print and PDF) is laid out as zero-width markers, but break opportunities came from UAX #14 over the paragraph's raw text and hyphenation_points() accepted marker clusters, so a hidden space or soft hyphen still let the line break there: "xx ab<hidden SHY>cdef" in a narrow column came out "xx ab-" / "cdef". Measured in Word for Mac, the hidden soft hyphen and the hidden space are no place to break: "xx" / "abcdef". left_out() is now the one test for text the layout leaves out (hidden text not shown, tracked deletions without markup). The run loop records the ranges it leaves out, and both the break opportunities and the hyphenation points come from that, so deletions behave exactly as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hidden text: a floating object anchored in it leaves no wrap area behind With hidden text not shown, a floating picture or shape anchored in a hidden run was not drawn (the run is a zero-width marker, so no object cluster places it), but place_para had already added its wrap exclusion, so the text still flowed around an invisible object. Measured in Word for Mac: a square-wrapped picture anchored in hidden text is not printed and the text runs the full width. deleted_objects() becomes left_out_objects(), which resolves each anchor's formatting through the styles (hidden text often comes from a character style) and applies the same left_out() test as the paragraph layout. Note numbering keeps counting hidden reference marks, as Word does, so it asks only about deletions; its behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hidden text: print the footnote of a hidden reference mark, as Word does A footnote whose reference mark is hidden text was numbered but never printed: the hidden run became zero-width markers without recording the note, so place_para never put the footnote at the foot of the page. Measured in Word for Mac with hidden text not printed, a hidden footnote or endnote reference still takes its number (the next visible footnote is 2) and both notes are printed. Endnotes already were, and the numbering already matched. A hidden reference mark now records its note like a visible one, so the footnote goes to the foot of the page holding the line it sits on. A deleted reference in the final text still records nothing, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * PDF: keep hidden text out of bookmarks and heading tags The PDF never prints hidden text (its layout does not show it), but the bookmark and heading-tag titles were built from final_text(), which drops tracked deletions only. A heading "Intro SECRET Part" with "SECRET " hidden printed as "Intro Part" yet bookmarked as "Intro SECRET Part", and a heading that is all hidden text still got a bookmark for a heading nobody can see. Paragraph::text_where() generalises final_text() to any per-run test, and the exporter titles headings with the runs that are neither hidden (resolved through the styles, since a character style can hide text) nor, without markup, deleted. A heading that is all hidden has an empty title and so no bookmark. Word's PDF export from AppleScript writes no outline, so there is no Word answer to match; this follows what the pages show, as for deletions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * PDF: titles leave out what the layout left out, table formatting included Bookmark and heading-tag titles resolved hidden text from the paragraph and run formatting alone, but the layout also applies the table style's conditional formatting (header row, first column, total row) beneath the runs. A Heading 1 in a table header row whose style hides it printed nothing yet still got a bookmark, and a partly hidden heading leaked the hidden part into its tag. ParaLayout now records the byte ranges it laid out as nothing (left_out), resolved exactly as laid out, and the exporter titles a heading from the text outside them (plus tracked deletions without markup, as before), so the titles cannot disagree with the pages. text_where() gives way to text_without() over byte ranges; final_text() is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hyphenation: hyphenate words as laid out, without hidden or deleted text Automatic hyphenation looked up the words of the paragraph's raw text and only dropped points that fell inside text the layout leaves out. Words and the minimum-letters limits still came from the raw text: with "accep" hidden in "acceptable", the visible "table" could break as "t-able" (the point after "accept"), and a hidden space split "foot<space>ball" into two words, so the visible "football" never hyphenated. Soft hyphens and dictionary points are now found in the same text as laid out that the UAX #14 break opportunities already use, and mapped back to paragraph offsets. With hidden text shown and markup on, that text is the paragraph's own and nothing changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layout, PDF: skip left-out text in one pass, not a scan per char Building the text as laid out tested every char against every left-out range, and Paragraph::text_without did the same for PDF titles. Hidden text in many runs (formatting that alternates, a common result of imports) made both quadratic: a 100,000-char hidden heading in one-char runs took 13.9 s to convert with the release CLI, 400,000 chars minutes. The layout now merges adjacent left-out runs as it records them and walks the ordered ranges with a cursor; text_without sorts its ranges by start (they may come in any order and overlap) and walks them the same way. Both are linear: 0.20 s for that heading, 0.81 s at 400,000 chars, with the same output. No left-out text still means no filtering at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hyphenation: check the limits of each point in constant time hyphen_points() found each candidate point's letter run by scanning out from the point and counted the letters on each side by rescanning the run, and the syllable heuristic rebuilt the rest of the word as a string for every position. Both are quadratic in the word's length. Ordinary words are short, but with hidden separators the layout now hyphenates the visible word, so "hyphenation " repeated 8,192 times with every space hidden is one 90,112-letter word: 2.0 s to convert with the release CLI. Letter and uppercase counts before each char, the letter run of each char and the heuristic's next vowel and byte offsets are now computed once, so every point is checked in constant time: that document converts in 0.09 s. The output is unchanged: the old functions stay in the tests as the reference, compared over awkward words and every combination of limits, and a hyphenated document's PDF text is identical. No maximum word length is imposed: the work is linear, so a cap would only change output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layout: find the formatting of every object anchor in one walk left_out_objects() asked props_of_char() for each floating object's anchor, and props_of_char() scans the runs from the start, so a paragraph with many anchors in their own runs cost a scan per anchor, quadratic, and paid it on every layout because it runs before the paragraph cache is consulted: 40,000 hidden anchors took 0.69 s, against 0.05 s for 10,000. The runs and the object offsets are both in order, so one walk now finds each anchor's run, resolving a run's formatting once; past the last run it is the paragraph mark, as props_of_char() gives. With hidden text shown and markup on nothing can be left out and no work is done at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layout: find a run's style without formatting it as a string #152 keyed each run's style by its whole format's Debug string, built for every shaped piece; on a long document with many runs that doubled layout time (3000 paragraphs x 40 linked runs: 12.7 s vs 6.4 s for the old key). Key by a hash of the commonly varying fields and confirm by comparing formats: 3.8 s. A capped bucket scan keeps formats that never compare equal (NaN) linear. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layout: floats hidden in table cells take no room, table style included Test the layout_box path the merge extended (a float anchored in hidden text in a table cell is not placed and leaves no wrap area), and resolve the anchors' formatting with the table style's character formatting under the runs' own, as the cell's text is laid out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: linear-time checks compare 8x the input against a 24x limit At 4x with an 8x limit a busy machine could push a linear layout over the line (8.8x seen once in a full workspace run); at 8x a linear layout takes about 8x and a quadratic one about 64x, so 24x stays far from both. 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> |
||
|
|
bb88278b98 |
PDF: put the shown text of fields and note numbers in the text layer (#133)
* PDF: fields and note numbers put their shown text in the text layer A field, a note reference or an equation is shaped as one cluster that stands for the object's placeholder character (U+FFFC) in the paragraph text. The display list took a glyph run's text from the paragraph, so a footer's "Page 1 of 5" went into the PDF's text layer as "Page  of ": copying, search and screen readers got the placeholder. 76 of the 365 PDFs written from docxide-pdf's Word reference corpus had it; Word's have none. ParaLayout::shown keeps, by cluster index, the text such a cluster draws (filled by Builder::shape_atomic), and the display list takes that text for those clusters. The drawing doesn't change. Now none of the 365 PDFs carries the placeholder; mean line-break agreement with Word 62.9% -> 63.4% (the scorer reads the text layer). Test: a footer's PAGE and NUMPAGES fields read "Page 1 of 2" and "Page 2 of 2" in the exported PDF's text (was "Page  of "). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: a REF field's result is extractable PDF text (from #112 by @LloydNicholson) Co-Authored-By: LloydNicholson <LloydNicholson@users.noreply.github.com> 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> |
||
|
|
d47f2f47fe |
Tests: the PDF text extractor counts a synthetic-bold glyph once
Synthetic bold fills and then strokes each glyph, and the test helper appended text on both draws, so bold text read "bboolldd". Whether a run gets synthetic bold depended on font resolution under parallel tests, which made exports_pages_text_and_structure flaky (3/40 runs once #133's tests were added). Regression test: synthetic_bold_text_is_extracted_once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fbb688657d |
PDF: draw fonts krilla can't subset as outlines instead of failing (#132)
krilla subsets fonts only when the document is finished, so one font its
subsetter rejects ("failed to subset font: malformed font") failed the
whole PDF export. Two of the 365 documents in docxide-pdf's Word
reference corpus hit this on Linux: Caladea Italic and Bold Italic
(ttf-caladea, our metric-compatible stand-in for Cambria) and Noto Sans
New Tai Lue Bold.
When finishing fails on a font, the exporter finds the face krilla
names, logs a warning and writes the document again with that face's
glyphs drawn as filled outlines (FontDb::outline), including in the
watermark; every other font is still embedded as selectable text. At
most 32 faces fall back before the export gives up with the error.
kurbo moves from dev-dependencies to dependencies of wordcraft-pdf (it
was already in the workspace).
Test: a copy of the bundled Source Sans 3 with one glyph turned into a
truncated composite fails the old export exactly as the corpus documents
did; it now exports, with no text glyphs from that font and its glyphs
drawn as paths.
Co-authored-by: Oliver Zieschang <4963183+naxvog@users.noreply.github.com>
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> |