Commit Graph
48 Commits
Author SHA1 Message Date
bflatastic ea239829e4 Export tests: a two-column page and a quoted poem keep their lines apart
Review asked for the near misses of joining wrapped lines. A short line,
a numbered item and a symbol-font bullet were already covered; these add
a line beside another column and a poem between paragraphs.
2026-11-09 01:18:37 +07:00
bflatastic 79e260f949 Merge #5404: exports keep a wrapped paragraph and a bulleted list as paragraphs
Resolved against main's ruled tables (#5154, #6626) and clipboard export:
the new tests pass main's rules argument to tables(), and the parity
notes keep both sides' wording.
2026-11-09 01:13:17 +07:00
bflatastic 1ea1c63f3d Merge remote-tracking branch 'origin/main' into merge-5498
# Conflicts:
#	parity/acrobat-features.toml
2026-11-08 23:33:35 +07:00
BFlat edc5c757d3 Word tables get outer borders only where the page rules them (#6626)
A grid drawn with inside lines only no longer gets a box in Word.
2026-11-09 00:09:23 +08:00
NetterSilver f750589812 Keep ruled tables when exporting a PDF to Word (#5154)
Fixes #5149
2026-11-08 19:28:07 +08:00
KenM76andClaude Opus 5.5 d3ca6f6609 M3: copy with formatting: keep columns apart, name families by their last part
- A line change into a block of another style is a wrap only when the next
  line starts within half a line below the last one; a line far above it
  (the next column) starts a paragraph. The size compare treats NaN as
  "not the same size".
- A smaller block must overlap a block's interior, not just touch its edge,
  to stop the block being used as a hint.
- font_family drops only the part after the last hyphen, and only when it
  names a style (Segoe-UI-Bold gives Segoe UI).
- text_extract's description and html_truncated doc now cover a page's own
  copy caps and say that rect HTML is in line order.
- Parity: the new tests are evidence; column selection HTML is listed as
  missing (plain text only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-05 07:43:57 -05:00
KenM76andClaude Opus 5.5 7a7103c414 M3: copy with formatting: review fixes (spaces, family names, caps, wraps)
- Spaces HTML would collapse keep their width across runs, at a
  paragraph's start and after a break.
- font_family returns the families to try: spaced, then as spelt
  ('Sim Sun','SimSun'); a hyphen splits only before a style word, so
  MS-Gothic stays whole.
- clip_html reports its cut; empty paragraphs count against the cap;
  the span cap is checked before a paragraph opens; ClipBuilder's
  Default starts a paragraph; a negative size clamps to 1pt.
- RichCopy::truncated: a capped copy is marked, the clipboard then puts
  plain text only, and text_extract sets html_truncated. No HTML for any
  page after the shared budget runs out.
- A style change at a line end is a wrap, not a paragraph; the block
  hint is skipped when a smaller block overlaps it; no editor reports
  styles_incomplete.
- A column selection copies plain text only.

Each new test fails with its fix reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-05 06:37:33 -05:00
oracola91 a427cd2de5 Word takes the script a language tag names
pa-Arab-PK is Punjabi in Arabic script, which Word keeps as right-to-left
text, but word_lang looked only at the language and wrote w:val. A script
subtag (Arab, Hebr, Syrc, Thaa; Hans, Hant, Hani, Jpan, Kore, Hang, Hira,
Kana; Latn, Cyrl, Grek) now decides over the language. It is read only in
its own place, after the language and any extended languages, so private
use such as en-x-arab names no script.
2026-11-04 23:00:11 +02:00
oracola91 561cc0d707 A language tag can't end in a singleton
A singleton (one letter or digit) starts an extension, or with x private
use, and BCP 47 needs a subtag after it. en-x and en-a were written as the
document's language. They and a singleton straight after another are now
left out; one-character subtags inside private use stay allowed.
2026-11-04 23:00:11 +02:00
oracola91 0cae1c518b A language tag longer than 35 characters is still a tag
BCP 47 sets no maximum length; 35 characters is only the least a protocol
must hold. A well formed tag such as en-US-u-ca-gregory-nu-latn-co-phonebk
was left out of both exports. The bound is now 255, kept only so a PDF's
/Lang can't put an unbounded string in the file.
2026-11-04 23:00:11 +02:00
oracola91 a699e13305 Word and HTML exports name the document's language
Word export named no language, so Word checked the spelling of every
export as English and underlined a whole document in any other
language. The PDF already says its language in the catalog's /Lang.
When that is a well formed language tag, Word gets it as the default
language of the text (w:docDefaults), in the attribute Word keeps for
its script: w:eastAsia for Chinese, Japanese and Korean, w:bidi for
Arabic, Hebrew, Persian and Urdu, w:val for the rest. Both HTML
exports, with and without CSS, get <html lang>. Without /Lang, or with a
malformed one, the files are exactly as before.

docx_with_language, html_with_language and html_simple_with_language
take the language; docx, html and html_simple stay as they are and call
them with none. The engine reads /Lang through the existing initial
view.

The new export and engine tests fail without the change.
2026-11-04 23:00:11 +02:00
KenM76andClaude Opus 5.5 29ea66cd4e M3: the clip text-cap test is bounded, so it fails without the cap
The test pushed until the builder refused; with the cap removed it never
stopped. It now pushes at most two chunks past the cap and checks the cap
stopped it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-04 15:54:28 -05:00
KenM76andClaude Opus 5.5 a955373428 M3: copy with formatting keeps one clipboard open and is tested through a stand-in
Review fixes for Copy with formatting.

- ui-egui: the HTML copy goes through one arboard Clipboard, created on
  first use and kept in the app (os_clipboard::with_kept, under the same
  process-wide lock), instead of a fresh one per copy. On X11 the copied
  data is served only while its Clipboard lives, so a fresh one dropped
  at once could leave nothing to paste without a clipboard manager. A
  failed create or set falls back to egui's plain-text copy.
- ui-egui: html_clipboard_override stands in for the system clipboard in
  tests. With the system clipboard on, Copy sets HTML and plain text in
  one set and emits no CopyText; when the set is refused it emits
  CopyText with the plain text. The HTML is built only when a clipboard
  will take it (system clipboard on, not the web build). The
  last_copied_html field is gone.
- export: clipboard HTML text is escaped in one pass and also drops C1
  controls, a bare carriage return and the bidirectional overrides and
  isolates. Font sizes are written within 1 to 1000 pt.
- engine: the glyph-to-block step budget is a parameter, tested; the copy
  says styles_incomplete only when matching actually ran out of steps.
- automation: text_extract format "html" has one 32 MiB HTML budget
  across all pages; a page past it gets none, and the result carries
  html_truncated. The tool description says so.
- parity: the new tests are evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-04 15:51:57 -05:00
KenM76andClaude Opus 5.5 352355ab77 M10: copy keeps formatting as HTML (Copy with formatting)
Copying selected page text now also puts an HTML fragment on the system
clipboard, so a paste into a word processor or mail client keeps
paragraphs, bold, italic, size, colour and font family. The plain text
is unchanged and stays on the clipboard as the alternative.

- export: clip.rs, the clipboard model (ClipText of paragraphs of
  styled spans), its builder with caps (20 000 spans, 2 MiB of text,
  16 MiB of HTML), font_family (subset tag and style suffixes off,
  camel case spaced) and clip_html. Family names are reduced to
  letters, digits, space, '-', '_' and '.', so a font name cannot leave
  the CSS string; text goes through esc, which drops control
  characters.
- engine: Document::rich_copy(page, text, glyphs). Glyphs (view space)
  take the style of the reading block their centre lies in; blocks are
  style-homogeneous. A line wrap inside a block is a space (none after
  a hyphen), a change of block a new paragraph. Refused when the
  document does not allow copying. compare.rs's display unit and
  weight/slant inference are shared, not duplicated.
- automation: text_extract takes format "text" (default) or "html";
  html adds each page's html and styles_incomplete, for the whole page
  or only what rect takes. Other values are InvalidArgs.
- ui-egui: Copy of a run or column selection writes HTML plus the
  plain text in one arboard set (desktop only); the web build and
  Select All of the whole document stay plain text. last_copied_html
  records what was produced, for tests.
- parity: view.copy-with-layout is partial; notes list what is missing
  (RTF, underline/strike-through, tables, Select All, web, glyphs
  outside reading blocks).

Formatting inference and the HTML serialiser are adapted from pdfcer
(MIT, same author) at 540b00ef1a405d1baa67c92f3858535037c7cde8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
2026-11-04 11:58:06 -05:00
eb696a3fdf Add CSS-free HTML export (#5938)
* feat(export): add simple HTML mode

* test(automation): reject simple HTML mode for other formats

* test(export): check simple HTML without new parser dev-dependencies

The parser test pulled html5ever, markup5ever_rcdom and seven more crates
into Cargo.lock for one test; plain string checks cover the same markup.

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>
2026-11-03 09:20:23 -05:00
oracola91 78b8dfbaef Exports join wrapped Chinese and Japanese lines without a space
Chinese and Japanese text wraps between characters, so the word space
put at each joined line break showed up inside the text. No space is
added now when both sides of the break are Chinese or Japanese
characters, their punctuation or fullwidth forms; Korean, which spaces
its words, still gets one. A line ending in a full-width full stop,
exclamation or question mark (also inside closing corner brackets) now
ends its paragraph like one ending in a full stop.

The new test lines fail without the change. The wrapped sentence test
now checks the Markdown export too, which goes through the same
joining since it was added.
2026-10-31 00:51:26 +02:00
oracola91 b98b20633c Exports keep a bulleted list as paragraphs, not a table
Word and LibreOffice often draw a list's bullet in a symbol font, a
tab stop before the item's text, so each bullet is a block of its own.
Table detection read the bullets and the items as two columns, and
every export wrote the list as a borderless two-column table.

Each bullet is now paired with the text that starts within three times
its size after it on its line. The text runs on from its bullet, and
bullets stay out of table detection, so Word gets one paragraph per
item with the bullet first. The Symbol and Wingdings bullets Word
writes (U+F0B7, U+F0A7) count. A dash or a middle dot counts only when
nothing starts left of it on its line, so a dash that stands for
"none" in a table row stays a cell.

The new export and automation tests fail without the change.
2026-10-31 00:47:05 +02:00
oracola91 ce12a12788 Exports keep a wrapped paragraph as one paragraph
Reading splits a line wherever its style changes, and export joined
those blocks along one line only (#4158). A sentence with a bold name
in one line and an italic name in the next came out of Word, HTML, RTF
and PowerPoint as one paragraph per line.

A paragraph whose last line reaches the right edge of the page's text
now runs on into the line just below it, when that line starts at the
same left edge in the same size. Headings, list items, a line that
ends a sentence, an indented line and a line after paragraph space
keep their break. A slanted run whose box leans into the next word by
up to 0.3 of its size still runs on into it.

The new export and automation tests fail without the change.
2026-10-31 00:47:04 +02:00
Adrian 20d396d180 M10: Markdown emphasis follows CommonMark's rules at run boundaries, and the export has a parity entry (#1595) 2026-10-28 15:56:25 -04:00
Adrian bcaead5404 M10: PDF ▸ Markdown export, through doc_export_office with a .md path (#1595) 2026-10-28 15:49:39 -04:00
Dylan M. Taylor 5e75b9c3e6 Refactor export for strict Clippy checks 2026-10-27 23:12:51 +00:00
aiongg 703efef6cc refactor(export): split lib.rs into layout, as_html, as_docx, as_rtf
New modules: layout.rs (data types and layout analysis), as_html.rs, as_docx.rs, as_rtf.rs.
2026-10-26 22:18:50 +00:00
aiongg f700dc3f2e refactor(export): move pptx.rs's tests to pptx/tests.rs 2026-10-26 20:51:19 +00:00
aiongg e0033d3989 refactor(export): move lib.rs's tests to tests.rs 2026-10-26 20:51:19 +00:00
58d368f1f0 M10: Word export keeps an italic run inside its sentence's paragraph (#4158) (#4411)
The reading blocks keep style boundaries, so "Before Chevron after." with an
italic case name reached the Office exporters as three blocks, and each writer
emitted one paragraph per block. A block that continues another on the same
line (the test `continuations` already applies to keep such blocks out of
table cells) is now a run in its own size, bold/italic and colour inside the
paragraph of the block it continues, in Word, HTML, RTF and PowerPoint.

- `continued_from` names the block each one continues (nearest end);
  `continuations` is its `is_some`, so table detection is unchanged.
- `paragraphs` groups blocks left to right, joining only a block that starts
  strictly further left, with the same rotation, and never a table cell or the
  slide title.
- Block text is trimmed, so the engine records the source's spaces at its ends
  (`Block::space_around`, from the reading block's editor text); runs keep that
  space, or one where the page shows a gap.
- A paragraph is a heading only if its runs are (a bold lead-in no longer makes
  its line a heading); a smaller footnote marker does not demote a heading.

Style changes that wrap onto the next line still split paragraphs; that needs
the reading-block grouping change and is left for later.


Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-25 10:11:00 -04:00
aiongg c097653162 Merge PR #4329: M10: preserve table text from covered export slots
Conflicts: ROADMAP.md, docs/roadmap.md: both sides' status, progress and change-log lines; M10 row has both sentences. parity/acrobat-features.toml: export.docx evidence has both tests, notes has both statements.
2026-10-24 06:22:30 +00:00
aiongg 8b1f21f77a Merge PR #4311: M10: write DOCX cell widths from their grid columns
Conflicts: ROADMAP.md and docs/roadmap.md took both sides' status lines, progress-log and change-log rows; M10 table row from #4311, M11 row from #4310.
2026-10-24 06:21:54 +00:00
a047c6ba09 chore: resolve SonarCloud maintainability issues (#4356)
* chore: resolve SonarCloud maintainability issues

* fix(packaging): use verify_sha256 helper function

* style: cargo fmt

* fix: restore wildcard imports and fix method-reference type errors

Revert the glob-import expansions (they missed SomIndices and left unused
imports), revert char::is_ascii_digit where the iterator yields char by
value, and pass the content slice to prune_doomed_xobjects as &[Vec<u8>].

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep XObject decodability check without binding content in interp

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
2026-10-23 23:28:18 -04:00
Kyle Kassen 9dc4bd598c M10: preserve table text from covered export slots 2026-10-23 16:37:10 -05:00
Kyle Kassen 30d85bbf15 M10: write DOCX cell widths from their grid columns 2026-10-23 16:37:10 -05:00
aiongg 923e499597 Merge upstream/main into fix/coderabbit-c5-forms-misc
Conflict in crates/content/src/lib.rs: main ends inline images where the
renderer's tokenizer ends them and counts the images whose end is a guess
(Parsed::ambiguous_images); this branch reads operators through the
streaming Ops iterator that parse collects. The iterator's BI branch takes
main's inline_image result and keeps the ambiguous-image count, which parse
copies into Parsed and Ops::ambiguous_images reports.
2026-10-22 20:51:14 +00:00
aiongg fcedb15689 fix(export): find font size and colour slots through a map
The body-size tally and the RTF colour table searched every slot seen so
far for each block, and the RTF writer searched the colour table again for
every paragraph and cell, so a document with many distinct sizes or colours
made export quadratic. Both keep their first-seen order and look slots up
in a hash map.
2026-10-22 14:15:50 -04:00
aiongg 467c8556ec fix(engine): encode each image once for Office export
Office export encoded and kept a separate copy of an image for every place
a page drew it, so a short content stream repeating Do on one large image
multiplied it into gigabytes before the writers' limits applied. Each image
object is now encoded once and its bytes shared (pdfcraft_export::Image
holds an Arc<[u8]>), distinct pictures share a 512 MiB budget whose
overflow lists the page as truncated, and RTF, which leaves pictures out,
prepares none.
2026-10-22 18:02:33 +00:00
aiongg 1b73c11e0d Integration: #2994 put the part() doc comment back above part 2026-10-22 01:45:34 +00:00
mtlcl410andClaude Opus 5.5 8707e87aff M10: give the #2806 export tests' pages a background after rebasing (#2992)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-20 16:55:20 +08:00
mtlcl410andClaude Opus 5.5 d85d49a4c6 M10: Export to Word keeps the page background colour (#2992)
A page coloured with Edit a PDF > Background exported to Word as a
white page: export pages carried text and images only, and the Word
section has no page colour.

- pdfcraft-edit `page_background`: the colour of the first thing a
  page paints, when that is an opaque fill (no soft mask, Normal blend)
  in a plain gray, RGB or CMYK colour, of a path with a rectangle
  covering the visible page, not clipped smaller; `None` otherwise.
- `export::Page::background` carries it; the Word writer sets it as
  the document background (`w:background`, plus the
  `w:displayBackgroundShape` setting Word needs to show it) when every
  page shares one non-white colour. Word has one background per
  document, so differing colours are left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-20 16:55:20 +08:00
mtlcl410andClaude Opus 5.5 f9db1e31fd M10: running text is not a table row in Word, HTML and PowerPoint export (#2806)
Table detection took a sentence whose link was set in another colour
and font, split into blocks that run on into each other, for two table
columns, and took a paragraph's lines, whose box overlaps the line
before, for a cell beside it. The table it built put the paragraph's
first line in one cell, a fragment of the link in another, and dropped
the paragraph's later lines. A block that starts where another on the
same line ends now adds no column of its own, and a row whose cells
overlap is not a table row; a superscript beside a number stays in its
table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-16 16:47:49 +08:00
5fffd4cacd Export to Word: place pictures on positioned pages as DrawingML anchors and bound the pictures (#1936) (#1981)
A VML rectangle with an image did not show the picture in Word. Pictures on
positioned pages are now wp:anchor drawings behind the text, sharing the
picture XML with inline pictures. At most 1000 pictures / 512 MiB are embedded;
empty images are skipped.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 02:35:50 -04:00
84a75eae1b M10: Presentation (.pptx) export (#1137) (#1162)
* feat: presentation (.pptx) export (#1137)

- Implement OpenXML PresentationML (.pptx) generator in crates/export/src/pptx.rs:
  - Generate [Content_Types].xml, _rels/.rels, core properties, presentation.xml
  - Build slide master, blank slide layout, and DrawingML theme
  - Render slides per page: largest text block as title shape, remaining paragraphs as body text box with font size, bold, italic, and color formatting
  - Embed images with proper DrawingML picture shapes and relationships
  - Generate DrawingML table shapes with column grid and span merging
- Wire OfficeFormat::Pptx into crates/engine/src/compare.rs and export_office
- Register export.pptx command and mark Presentation (.pptx) Ready in catalog
- Support pptx in automation doc_export_office tool
- Wire export.pptx into UI commands and file save dialog
- Add unit and integration tests across crates/export, engine, automation, and ui-egui
- Update create.export-pptx parity feature evidence and docs

* pptx export: saturating slide positions, matching cell-border tags, theme ea/cs fonts, package test

- Heights and the running vertical position use clamped/saturating arithmetic, so a hostile
  text size or image rectangle (1e300, NaN, inf) can't overflow i64; the item loop uses get().
- The table cells' a:lnR and a:lnT borders were closed with </a:lnL>, which made every slide
  with a table malformed XML; they now close with their own tags.
- The theme's major/minor fonts carry the schema-required a:ea and a:cs children.
- New test walks the produced zip entry by entry (sizes, inflation, central directory), checks
  every XML part's tags balance, the required parts, content-type overrides, relationship
  targets and that no placeholders are used; a hostile-sizes test covers the overflow.
- Test literals gain main's Block/Image rotation and size fields.
- Parity notes say what is still unverified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: KrzysztofC <krzysztofc@localhost-live.lan>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:55:25 -04:00
7caaa35a81 Fix Word invoice layout and pagination (#773) (#1126)
* M10: keep invoice overlays out of Word body flow (#773)

Export inferred tables without visible borders. Keep text and images
positioned on pages with artwork, rotated or large text, and retain
each source page's size, crop rotation and units.

Cover invoice conversion through the automation tools, and handle
unavailable Windows symlink setup in the confinement test.

Co-authored-by: Codex <noreply@openai.com>

* M10: add @lollinoo's chosen contributor credit (#773)

The contributor requested their public GitHub profile name while preparing the #773 PR.

Co-authored-by: Codex <noreply@openai.com>

* Word export: float a page only when text overlaps an image or watermark

positioned() turned every page with any image, or with text twice the body
size, into absolutely positioned VML text boxes, so a page with a logo or a
big title lost its flowing paragraphs, headings and tables. Now a page floats
only when it has rotated text, or when an image or a watermark-sized text
block actually overlaps other text (bounded pairwise test). Other pages flow
with images inline. Adds a regression test for a page whose image and title
sit clear of the text, and updates the docs and parity notes.

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:10:32 -04:00
Brandon ThomasandClaude Opus 5.5 2aca0692fc Fix #526: keep text colour in Word, HTML and RTF export (#547)
The export model (`pdfcraft_export::Block`/`Cell`) had no colour, so
`export_pages` dropped the fill colour that `edit::TextBlock` already
carries and every run came out black.

Block and Cell now carry an sRGB colour, mapped from TextBlock.color.
Word runs get `<w:color w:val="RRGGBB"/>`, HTML paragraphs, headings and
cells a `style="color:#RRGGBB"`, and RTF a colour table with `\cfN`.
Black text keeps each format's default; NaN and out-of-range components
clamp.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:49:21 -04:00
Erige005 bc1d67cb61 Export to Word: detect tables of at most 63 columns, Word's limit (#479) (#483) 2026-10-09 09:14:34 -04:00
Brandon ThomasandClaude Opus 5.5 d3db99818f Rename PrintCraft to PdfCraft (#174)
The app, crates, binaries, bundle id, packaging, icons, screenshots and docs are now
PdfCraft / pdfcraft (repo storytold/pdfcraft). On first launch the desktop app moves the
PrintCraft settings and crash-recovery folders to the new name, and both apps read settings
saved under the old storage key, so upgrades keep recent files, preferences and unsaved work.
The signing test fixtures keep their PrintCraft subject names (they are binary).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 16:03:57 -04:00
bflatasticandClaude Opus 5.5 8b25d88450 Export to Word: files open in Word again (#72)
Text extracted from a PDF can hold C0 control characters (fonts without
a usable /ToUnicode), which XML forbids, and Word refuses the whole
document over one ("Word experienced an error trying to open the
file"). The Word, HTML and RTF writers now drop the characters XML
forbids. Also:
- the page size stays within Word's 22 in (a till receipt is taller,
  and Word read it as a negative height), and margins shrink on small
  pages so text keeps room; images fit the real text width;
- a non-finite font size falls back to 12 pt;
- zip entries get a valid DOS date (1980-01-01) instead of zero.

Checked by opening the exports in Microsoft Word: control characters,
U+FFFE/U+FFFF, a 33 in receipt with a 40-row table, narrow, tiny and
wide pages all open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:31:37 +07:00
Brandon ThomasandClaude Opus 5.5 604c560e5f Export tables: linear column lookups, a column cap, RTF spans
Review follow-ups on top of the table detection:
- Each block's column comes from a precomputed map and a binary search
  instead of scanning every column's members, so pages with many text
  blocks stay linear.
- A table has at most 64 columns. Rows are materialized to every column, so
  a hostile page laid out as a staircase of blocks would otherwise make
  (blocks / 2)^2 cells. Test: a_staircase_of_blocks_is_not_a_huge_table.
- RTF: a row's cell boundaries are written before its cells (RTF requires
  it), and a spanning cell ends at the last column it spans. The test checks
  the Totals row now spans two columns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 07:38:17 -04:00
Brandon Thomas 28108ec555 Export detected tables to office formats 2026-10-06 07:38:17 -04:00
Brandon ThomasandClaude Opus 5.5 1434db626a Never crash: deny the panic lints in crates that are already clean
annot, compare, content, cos, export, geom, model, ocr, preflight, print and redact have no
production `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!`/`unimplemented!`. The attribute
keeps it that way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 13:12:21 -04:00
Brandon ThomasandClaude Opus 5.5 70280a44d0 M10: Export to Word (.docx), HTML and RTF (new export crate with a small zip writer): paragraphs, headings, bold/italic, images, page breaks; doc_export_office tool
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:40:46 -04:00