Skip to content

Comments

fix(layout,converter): text box rendering and page-relative anchor positioning (SD-1331, SD-1838)#2034

Merged
tupizz merged 4 commits intomainfrom
tadeu/sd-1331-render-text-boxes-and-content-inside-fix
Feb 17, 2026
Merged

fix(layout,converter): text box rendering and page-relative anchor positioning (SD-1331, SD-1838)#2034
tupizz merged 4 commits intomainfrom
tadeu/sd-1331-render-text-boxes-and-content-inside-fix

Conversation

@tupizz
Copy link
Contributor

@tupizz tupizz commented Feb 15, 2026

Summary & Demo

Fixes two independent bugs that prevented text boxes from rendering correctly:

1. Invisible text box borders — SD-1331

  • Missing a:prstClr support: extractStrokeColor() and extractFillColor() only handled a:schemeClr (theme colors) and a:srgbClr (hex RGB). DOCX text boxes commonly use a:prstClr (preset colors like black, white, red), which fell through to the style reference fallback returning null — no border rendered.
  • Hairline stroke width: OOXML w="0" means "hairline" (thinnest visible stroke, ~0.75pt in Word), not zero/invisible. extractStrokeWidth() was returning 0, producing an invisible SVG stroke.

Changes:

  1. getPresetColor() — new lookup function with all 140+ ECMA-376 preset color names
  2. extractColorFromSolidFill() — shared helper consolidating color + modifier extraction for all three color types (a:schemeClr, a:srgbClr, a:prstClr), eliminating duplicated logic across stroke/fill functions
  3. Hairline minimumextractStrokeWidth() returns 0.75 for w="0" instead of 0
CleanShot 2026-02-15 at 20 47 30

2. Page-relative anchored drawings invisible — SD-1838

Text boxes with "Move object with text" unchecked in Word (relativeFrom="page") were completely invisible. These page-relative anchored drawings are collected by collectPreRegisteredAnchors() and their positions pre-computed, but the main layout loop only checked preRegisteredPositions for image blocks — drawing blocks fell through to layoutDrawingBlock() which returned early for anchored items, creating no fragment.

Changes:

  1. Added preRegisteredPositions check for drawing blocks in the main layout loop, mirroring the existing image block pattern
  2. Creates a DrawingFragment with the pre-computed position when a pre-registered anchor is found
Microsoft Word 2026-02-15 21 26 41

Test plan

  • All existing vector-shape-helpers tests pass
  • New tests for getPresetColor(), hairline stroke width, and preset color extraction in both stroke and fill paths
  • New layout engine tests for page-relative and margin-relative anchored drawings with wrapNone
  • Load "Text Box in body and footer.docx" — text boxes should show visible black borders
  • Load DOCX with "Move object with text" OFF — text box should render at correct page position

Closes SD-1331
Closes SD-1838

…SD-1331)

Text box borders were invisible because:
1. Color extraction only handled a:schemeClr and a:srgbClr but not
   a:prstClr (preset colors like "black"). DOCX text boxes commonly
   use preset colors, so extraction fell through returning null.
2. Stroke width w="0" was treated as invisible, but in OOXML it means
   "hairline" (~0.75pt in Word).

Adds getPresetColor() with all ECMA-376 preset colors and refactors
into shared extractColorFromSolidFill() helper.
Copilot AI review requested due to automatic review settings February 15, 2026 23:46
@linear
Copy link

linear bot commented Feb 15, 2026

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves DOCX DrawingML color and stroke extraction so text box borders render correctly by adding support for preset colors (a:prstClr) and treating w="0" strokes as hairlines rather than invisible.

Changes:

  • Add getPresetColor() mapping for ECMA-376 preset color names and use it during color extraction.
  • Introduce extractColorFromSolidFill() helper to centralize color + modifier + alpha parsing across scheme/sRGB/preset colors.
  • Treat a:ln w="0" as a minimum hairline width (0.75) and add/extend related unit tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
packages/super-editor/src/core/super-converter/v3/handlers/wp/helpers/vector-shape-helpers.js Adds preset color lookup, consolidates solid fill parsing, and enforces hairline stroke width behavior.
packages/super-editor/src/core/super-converter/v3/handlers/wp/helpers/vector-shape-helpers.test.js Adds tests for preset colors, hairline stroke width, and a:prstClr extraction for stroke/fill.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…1838)

Page-relative anchored drawings (move object with text = OFF) were
invisible because the main layout loop only checked preRegisteredPositions
for image blocks, not drawing blocks. The drawing block path called
layoutDrawingBlock() which returned early for anchored items, resulting
in no fragment being created.

Adds the same preRegisteredPositions check that exists for image blocks
to the drawing block path, creating a DrawingFragment with the
pre-computed position.
@tupizz tupizz changed the title fix(converter): add preset color and hairline stroke for text boxes (SD-1331) fix(layout,converter): text box rendering and page-relative anchor positioning (SD-1331, SD-1838) Feb 16, 2026
@caio-pizzol
Copy link
Contributor

⚠️ AI Risk Review — potential issues found

  • extractColorFromSolidFill is defined but never called - all callsites still inline within extractStrokeColor and extractFillColor in the visible diff, though the function exists it's only used within the refactored helpers themselves

Via L3 deep analysis · critical risk

@tupizz 👀

@tupizz
Copy link
Contributor Author

tupizz commented Feb 16, 2026

@caio-pizzol
could you please double check 👀

The comment is wrong. extractColorFromSolidFill is called in 4 places:

  • Line 368: in extractStrokeColor — for a:solidFill inside a:ln
  • Line 392: in extractStrokeColor — for style lnRef fallback
  • Line 413: in extractFillColor — for a:solidFill on spPr
  • Line 452: in extractFillColor — for style fillRef fallback

That's the whole point of the refactor — the duplicated inline color extraction logic in both
extractStrokeColor and extractFillColor was consolidated into the shared helper. The AI reviewer misread the
diff.

Copy link
Contributor

@caio-pizzol caio-pizzol left a comment

Choose a reason for hiding this comment

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

Nice work @tupizz!

Solid fixes, left a few comments feel free to merge after addressing the relevant ones

@superdoc-dev superdoc-dev deleted a comment from github-actions bot Feb 17, 2026
- Rename extractColorFromSolidFill → extractColorFromElement (it also
  handles lnRef/fillRef, not just solidFill)
- Extract applyModifiersAndAlpha() helper to deduplicate modifier logic
  across schemeClr, srgbClr, and prstClr branches
- Apply shade/tint/lumMod/lumOff modifiers to srgbClr (previously only
  alpha was handled, inconsistent with the other two color types)
@tupizz tupizz enabled auto-merge (squash) February 17, 2026 20:30
@github-actions
Copy link
Contributor

github-actions bot commented Feb 17, 2026

Visual diffs detected

Pixel differences were found in visual tests. This is not blocking — reproduce locally with cd tests/visual && pnpm docs:download && pnpm test to review diffs.

Copy link
Collaborator

@harbournick harbournick left a comment

Choose a reason for hiding this comment

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

LGTM

@tupizz tupizz disabled auto-merge February 17, 2026 21:06
@tupizz tupizz enabled auto-merge (squash) February 17, 2026 21:06
@tupizz tupizz merged commit 3947f39 into main Feb 17, 2026
4 checks passed
@tupizz tupizz deleted the tadeu/sd-1331-render-text-boxes-and-content-inside-fix branch February 17, 2026 21:34
@superdoc-bot
Copy link
Contributor

superdoc-bot bot commented Feb 17, 2026

🎉 This PR is included in superdoc v1.15.0-next.2

The release is available on GitHub release

harbournick pushed a commit that referenced this pull request Feb 20, 2026
# [1.15.0](v1.14.0...v1.15.0) (2026-02-20)

### Bug Fixes

* **ai-actions:** preserve html/markdown insertion and prevent repeated formatted replacement ([#2117](#2117)) ([9f685e9](9f685e9))
* **ai:** support headless mode in EditorAdapter.applyPatch ([#1859](#1859)) ([cf9275d](cf9275d))
* **collaboration:** memory leaks, Vue stack overflow, and Liveblocks stability (SD-1924) ([#2030](#2030)) ([a6827fd](a6827fd)), closes [#prepareDocumentForExport](https://github.com/superdoc-dev/superdoc/issues/prepareDocumentForExport)
* **collab:** prevent stale view when remote Y.js changes bypass sdBlockRev increment ([#2099](#2099)) ([0895a93](0895a93))
* **converter:** handle null list lvlText and always clear numbering cache ([#2113](#2113)) ([336958c](336958c))
* **document-api:** remove search match cap and validate moveComment bounds ([6d3de67](6d3de67))
* export docx blobs with docx mime type ([#1849](#1849)) ([1bc466d](1bc466d))
* **export:** prevent DOCX corruption from entity encoding and orphaned delInstrText (SD-1943) ([#2102](#2102)) ([56e917f](56e917f)), closes [#replaceSpecialCharacters](https://github.com/superdoc-dev/superdoc/issues/replaceSpecialCharacters) [#1988](#1988)
* **layout-bridge:** correct cell selection for tables with rowspan ([#1839](#1839)) ([0b782be](0b782be))
* **layout,converter:** text box rendering and page-relative anchor positioning (SD-1331, SD-1838) ([#2034](#2034)) ([3947f39](3947f39))
* **layout:** route list text-start calculations through resolveListTextStartPx ([02b14b8](02b14b8))
* **painter-dom:** use absolute page Y for page-relative anchors in header/footer decorations ([0b9bc72](0b9bc72))
* preserve selection highlight when opening toolbar dropdowns ([#2097](#2097)) ([a33568e](a33568e))
* structured content renders correct on hover and select ([#1843](#1843)) ([dab3f04](dab3f04))
* **super-editor:** add unsupported-content reporting across HTML/Markdown import paths ([#2115](#2115)) ([84880b7](84880b7))
* **super-editor:** handle partial comment file-sets and clean up stale parts on export ([#2123](#2123)) ([f63ae0a](f63ae0a))
* **super-editor:** restore <hr> contentBlock parsing and harden VML HR export fallback ([#2118](#2118)) ([da51b1f](da51b1f))
* table headers are incorrectly imported from html ([#2112](#2112)) ([e8d1480](e8d1480))
* table resizing regression ([#2091](#2091)) ([20ed24e](20ed24e))
* table resizing regression ([#2091](#2091)) ([9a07f1c](9a07f1c))
* **tables:** align tableHeader attrs with tableCell to fix oversized DOCX export widths ([#2114](#2114)) ([38f0430](38f0430))
* **tables:** fix autofit column scaling, cell width overflow, and page break splitting ([#1987](#1987)) ([61a3f6f](61a3f6f))
* **tables:** prevent tblInd double-shrink when using tblGrid widths (SD-1494) ([8750ece](8750ece))
* track changes comment text for formatting changes ([#2013](#2013)) ([b2a43ff](b2a43ff))
* wire DocumentApi to Editor.doc with lifecycle-safe caching ([57326ea](57326ea))

### Features

* cropped images ([#1940](#1940)) ([3767a49](3767a49))
* extend document-api with format, examples, create.heading ([#2092](#2092)) ([fdf8c7c](fdf8c7c))
* **lists:** support hidden list indicators via w:vanish ([#2069](#2069)) ([#2080](#2080)) ([0bed0fd](0bed0fd))
* the document API limited alpha ([#2087](#2087)) ([091c24c](091c24c))
@harbournick
Copy link
Collaborator

🎉 This PR is included in superdoc v1.15.0

The release is available on GitHub release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants