From 19e9c8e481edd0bb31e50130eaa105339cc3cdec Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Wed, 28 Oct 2026 12:43:03 +0000 Subject: [PATCH 1/2] fix: write a text box of auto height 1in tall, so WPS Office fits it to its text Since 9.8.0 (#3541) WPS Office draws Textbox, which it showed blank before (#3171), but it makes a box of auto height too short by its top and bottom insets, cutting off the text's last line. Word, WPS, LibreOffice and Pages all fit the box to its text from any definite height of 8pt or more (mso-fit-shape-to-text is always written), so a text box without a height, or with "auto", is now written 1in tall. Word's and LibreOffice's layouts are unchanged; docx-preview, which doesn't fit the box, draws it 1in tall rather than at its own default. text-box.md's "Fixed Size" section said a height fixes the box's size, which none of those applications do, so Sizing now says what height does. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01X5zGDL3sgGVBMXPfny376c --- docs/usage/text-box.md | 26 ++------------------------ src/file/textbox/shape/shape.spec.ts | 16 +++++++++++++++- src/file/textbox/shape/shape.ts | 13 ++++++++++++- src/file/textbox/textbox.spec.ts | 2 +- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/docs/usage/text-box.md b/docs/usage/text-box.md index f691cf2690e..a5e87459ca0 100644 --- a/docs/usage/text-box.md +++ b/docs/usage/text-box.md @@ -44,7 +44,7 @@ const doc = new Document({ ## Sizing -### Fixed Size +`width` sets a text box's width. Word, WPS Office, LibreOffice and Pages make it as tall as its text, whatever its `height`, which is how tall applications that don't, such as the preview on this page, draw it. A text box without a `height`, or with `"auto"`, is written 1 inch tall, as WPS Office makes a box of auto height too short, cutting off its last line. ```ts live import { Document, Paragraph, Textbox } from "docx"; @@ -54,29 +54,7 @@ const doc = new Document({ { children: [ new Textbox({ - children: [new Paragraph("Fixed size textbox")], - style: { - width: "200pt", - height: "100pt", - }, - }), - ], - }, - ], -}); -``` - -### Auto Height - -```ts live -import { Document, Paragraph, Textbox } from "docx"; - -const doc = new Document({ - sections: [ - { - children: [ - new Textbox({ - children: [new Paragraph("Auto height based on content")], + children: [new Paragraph("As tall as its text")], style: { width: "200pt", height: "auto", diff --git a/src/file/textbox/shape/shape.spec.ts b/src/file/textbox/shape/shape.spec.ts index ce114d8b839..b82e23e8d33 100644 --- a/src/file/textbox/shape/shape.spec.ts +++ b/src/file/textbox/shape/shape.spec.ts @@ -19,7 +19,7 @@ describe("createShape", () => { expect(tree).toStrictEqual({ "v:shape": [ - { _attr: { id: "test-id", type: "#_x0000_t202", style: "width:10pt" } }, + { _attr: { id: "test-id", type: "#_x0000_t202", style: "width:10pt;height:1in" } }, { "v:textbox": [ { _attr: { style: "mso-fit-shape-to-text:t;" } }, @@ -55,4 +55,18 @@ describe("createShape", () => { ], }); }); + + it("should write a height of auto as 1in, which Word, WPS Office, LibreOffice and Pages resize to fit the text", () => { + const tree = new Formatter().format(createShape({ id: "test-id", style: { width: "200pt", height: "auto", zIndex: "auto" } })); + + expect(tree["v:shape"][0]).toStrictEqual({ + _attr: { id: "test-id", type: "#_x0000_t202", style: "width:200pt;height:1in;z-index:auto" }, + }); + }); + + it("should keep a height that is given", () => { + const tree = new Formatter().format(createShape({ id: "test-id", style: { width: "200pt", height: "50pt" } })); + + expect(tree["v:shape"][0]).toStrictEqual({ _attr: { id: "test-id", type: "#_x0000_t202", style: "width:200pt;height:50pt" } }); + }); }); diff --git a/src/file/textbox/shape/shape.ts b/src/file/textbox/shape/shape.ts index be7c50dcef4..57b9505111b 100644 --- a/src/file/textbox/shape/shape.ts +++ b/src/file/textbox/shape/shape.ts @@ -23,6 +23,17 @@ export type { VmlShapeStyle } from "@file/vml"; /** Identifier of the text box shape type as referenced by shapes. */ const SHAPE_TYPE = "#_x0000_t202"; +/** + * The height a text box is written with when it has none, or `"auto"`. Word, WPS Office, LibreOffice and Pages make the + * box as tall as its text (`mso-fit-shape-to-text`) from any height of 8pt or more, but WPS makes a box of auto height + * too short by its top and bottom insets, cutting off its last line. Applications that don't fit the box to its text, + * such as docx-preview, draw it this tall. + */ +const FIT_FROM_HEIGHT = "1in"; + +const withFitFromHeight = (style?: VmlShapeStyle): VmlShapeStyle | undefined => + style && (style.height === undefined || style.height === "auto") ? { ...style, height: FIT_FROM_HEIGHT } : style; + /** * Options for creating a VML shape. * @@ -90,6 +101,6 @@ export const createShape = ({ id, children, type = SHAPE_TYPE, style }: ShapeOpt createVmlShape({ id, type, - style, + style: withFitFromHeight(style), children: [createVmlTextbox({ style: "mso-fit-shape-to-text:t;", children })], }); diff --git a/src/file/textbox/textbox.spec.ts b/src/file/textbox/textbox.spec.ts index 76dbc0937f2..073900dadba 100644 --- a/src/file/textbox/textbox.spec.ts +++ b/src/file/textbox/textbox.spec.ts @@ -22,7 +22,7 @@ const TEXTBOX_RUN = { "w:pict": [ { "v:shape": [ - { _attr: { id: expect.any(String), type: "#_x0000_t202", style: "width:10pt" } }, + { _attr: { id: expect.any(String), type: "#_x0000_t202", style: "width:10pt;height:1in" } }, { "v:textbox": [ { _attr: { style: "mso-fit-shape-to-text:t;" } }, From 94d7d8c10405b9bc1d642bea603dbca6e622eccf Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Wed, 28 Oct 2026 14:01:58 +0000 Subject: [PATCH 2/2] docs: say a text box needs a style with a width, and that a style's missing height is written 1in The docs said any text box without a height is written 1 inch tall, but a text box with no style is written without one. Such a box has no width either: Word draws it 50pt wide and LibreOffice gives it none, and writing height:1in on it changes neither, so it stays without a style. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01X5zGDL3sgGVBMXPfny376c --- docs/usage/text-box.md | 4 +++- src/file/textbox/shape/shape.spec.ts | 2 +- src/file/textbox/shape/shape.ts | 9 +++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/usage/text-box.md b/docs/usage/text-box.md index a5e87459ca0..513ccd7e397 100644 --- a/docs/usage/text-box.md +++ b/docs/usage/text-box.md @@ -44,7 +44,9 @@ const doc = new Document({ ## Sizing -`width` sets a text box's width. Word, WPS Office, LibreOffice and Pages make it as tall as its text, whatever its `height`, which is how tall applications that don't, such as the preview on this page, draw it. A text box without a `height`, or with `"auto"`, is written 1 inch tall, as WPS Office makes a box of auto height too short, cutting off its last line. +`width` sets a text box's width, so give every text box a `style` with one. Without a `style`, Word draws the box 50pt wide and LibreOffice gives it no width at all. + +Word, WPS Office, LibreOffice and Pages make a text box as tall as its text, whatever its `height`, which is how tall applications that don't, such as the preview on this page, draw it. A `style` without a `height`, or with `"auto"`, is written 1 inch tall, as WPS Office makes a box of auto height too short, cutting off its last line. ```ts live import { Document, Paragraph, Textbox } from "docx"; diff --git a/src/file/textbox/shape/shape.spec.ts b/src/file/textbox/shape/shape.spec.ts index b82e23e8d33..e403c6ac1d6 100644 --- a/src/file/textbox/shape/shape.spec.ts +++ b/src/file/textbox/shape/shape.spec.ts @@ -34,7 +34,7 @@ describe("createShape", () => { }); }); - it("should create default styles", () => { + it("should write no style when given none, as a height alone doesn't change how the box is drawn", () => { const tree = new Formatter().format( createShape({ id: "test-id", diff --git a/src/file/textbox/shape/shape.ts b/src/file/textbox/shape/shape.ts index 57b9505111b..c3dae614a97 100644 --- a/src/file/textbox/shape/shape.ts +++ b/src/file/textbox/shape/shape.ts @@ -24,10 +24,11 @@ export type { VmlShapeStyle } from "@file/vml"; const SHAPE_TYPE = "#_x0000_t202"; /** - * The height a text box is written with when it has none, or `"auto"`. Word, WPS Office, LibreOffice and Pages make the - * box as tall as its text (`mso-fit-shape-to-text`) from any height of 8pt or more, but WPS makes a box of auto height - * too short by its top and bottom insets, cutting off its last line. Applications that don't fit the box to its text, - * such as docx-preview, draw it this tall. + * The height a text box's style is written with when it has none, or `"auto"`. Word, WPS Office, LibreOffice and Pages + * make the box as tall as its text (`mso-fit-shape-to-text`) from any height of 8pt or more, but WPS makes a box of auto + * height too short by its top and bottom insets, cutting off its last line. Applications that don't fit the box to its + * text, such as docx-preview, draw it this tall. A text box with no style is left without one: it has no width either, + * so Word draws it 50pt wide and LibreOffice gives it none, and a height changes neither. */ const FIT_FROM_HEIGHT = "1in";