Skip to content

perf(docx): typed paragraph measurement path - #232

Open
eliahilse wants to merge 2 commits into
mainfrom
perf/docx-typed-measure
Open

perf(docx): typed paragraph measurement path#232
eliahilse wants to merge 2 commits into
mainfrom
perf/docx-typed-measure

Conversation

@eliahilse

Copy link
Copy Markdown
Member

TL;DR:

Summary:

  • paragraphs measure through a typed request (MeasureRequest<'a>) instead of serializing block+font-chains to JSON and parsing them back per call
  • drops the whole-paragraph clone, font-chain map copy, envelope serialize/parse and extent serialize/parse on every measurement (open, keystroke, headers, table cells)
  • public MeasureInput/measure_paragraph unchanged; wasm JSON path delegates to the same typed core; malformed-input fallback parity pinned by tests incl. serde struct-from-seq quirks

Test plan:

  • cargo test -p betteroffice-ooxml-text -p betteroffice-docx-layout green
  • open + typing latency spot-check on a large document
  • canvas fallback path (UNSUPPORTED) still triggers where it did before

@openooxml-bot

openooxml-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA — thank you! ✍️ ✅

Posted by the CLA bot.

@greptile-apps

greptile-apps Bot commented Aug 23, 2026

Copy link
Copy Markdown

Greptile Summary

The PR replaces DOCX layout’s per-paragraph JSON serialization boundary with a borrowed typed measurement request while retaining the public JSON and MeasureInput APIs.

  • Adds a typed bridge from DOCX paragraph, run, float, and formatting values into ooxml-text.
  • Refactors the text measurement core to consume MeasureRequest with borrowed hash-map or B-tree font chains.
  • Converts typed measurement output back into layout extents and preserves synthetic fallback behavior.
  • Adds parity coverage for text, bidi, tabs, list markers, fields, images, floating zones, malformed values, and unsupported runs.

Confidence Score: 4/5

The PR appears safe to merge, with only non-blocking cleanup needed for newly added comments and docstrings that exceed the repository’s concision standard.

The typed conversion preserves the existing measurement and synthetic-fallback boundaries, and the investigated mappings did not establish a runtime regression; the remaining accepted issue is documentation maintainability.

Files Needing Attention: crates/docx-layout/src/typed_measure.rs, crates/ooxml-text/src/measure/input.rs

Important Files Changed

Filename Overview
crates/docx-layout/src/typed_measure.rs Adds the manual typed conversion and output bridge with broad JSON-path parity tests; its explanatory comments exceed the repository’s concision rule.
crates/docx-layout/src/measure_blocks.rs Replaces the resident JSON envelope round trip with typed measurement while retaining synthetic fallback on failure.
crates/ooxml-text/src/measure/input.rs Introduces the borrowed MeasureRequest and dual-map FontChains abstraction without changing the owned public input.
crates/ooxml-text/src/measure/mod.rs Moves the measurement implementation onto the typed request and delegates both owned and JSON entry points to it.
crates/docx-layout/src/lib.rs Adds the crate-local resident typed measurement entry point against the existing font store.

Sequence Diagram

sequenceDiagram
    participant Layout as DOCX layout
    participant Bridge as Typed conversion
    participant Text as OOXML text measurement
    participant Fonts as Resident font store
    Layout->>Bridge: ParagraphBlock + MeasurementConfig
    Bridge->>Text: MeasureRequest (borrowed)
    Text->>Fonts: Resolve font chains and shape runs
    Fonts-->>Text: Font metrics and glyph data
    Text-->>Bridge: ParagraphExtentOut
    alt Measurement succeeds
        Bridge-->>Layout: ParagraphExtent
    else Conversion or measurement fails
        Bridge-->>Layout: None
        Layout->>Layout: Build synthetic extent
    end
Loading

Reviews (1): Last reviewed commit: "perf(docx): typed paragraph measurement ..." | Re-trigger Greptile

Comment on lines +1 to +7
//! Typed bridge between layout blocks and `ooxml-text` measurement.
//!
//! Field-for-field equivalent of the JSON envelope this used to serialize:
//! every mapping here mirrors what serde did at that boundary, including its
//! strictness — a value the JSON parse would have rejected makes
//! [`measure_paragraph`] return `None` so the caller falls back to the
//! synthetic extent, exactly as an `"invalid: "` error used to.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Overlong typed-path documentation

The new typed measurement path adds several multi-line explanations where the names, types, and implementation already convey the behavior, increasing maintenance burden and the opportunity for documentation to drift. Please keep these docstrings concise and reserve inline comments for behavior that is not otherwise understandable.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@eliahilse
eliahilse force-pushed the perf/docx-typed-measure branch 3 times, most recently from f9dab51 to e95c3e2 Compare August 26, 2026 14:32
The typed path diverged from the envelope it replaced, found by a randomised
differential against the old JSON path:

- non-finite floats were cast straight to f32, where `serde_json` wrote them
  as `null` and the envelope read them back as absent. Affected run
  fontSize/fontSizeCs/letterSpacing/horizontalScale/kerningMinPt, image
  width/height/distTop/distBottom, every spacing and indent number,
  defaultFontSize/listMarkerFontSize/defaultTabStopTwips, and
  paragraphYOffset: the paragraph fell back to the synthetic extent where it
  used to measure at the default.
- `defaults` and `compat` accepted only objects, but serde's derived
  `Deserialize` also takes a struct from a positional sequence, so
  `[12.0, "Arial"]` and `[true, false]` measured before and synthesized
  after. Both now defer to the same derive the envelope parse used, as
  `rotationBounds` already does.
- required engine inputs (tab positions, float-zone coordinates) reached the
  engine carrying a non-finite value and were refused there, where the
  envelope had failed to parse before delivering anything. The outcome always
  agreed; constructing them fallibly makes the request delivered to the
  engine identical at every stage, not just the result.

Adds a randomised whole-surface differential (3,000 paragraphs, every mapped
field, both compat flags, defaults, `fullWidthBlock` and
`authoritativeShaping` varied) that compares the `BlockIn` reaching the
engine, the fallback decision, and the f32-narrowed extent, plus a sweep of
all 24 numeric sites against NaN and both infinities. The existing fixtures
did not discriminate: deleting any of eighteen field mappings left them
green, including the list-marker and bidi mappings they are named for.
@eliahilse
eliahilse force-pushed the perf/docx-typed-measure branch from e95c3e2 to 411898e Compare August 26, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant