Перейти к содержимому
Docxcelerate

Узлы

Shape

A drawn rectangle with the document's own words on top of it.

Эта страница ещё не переведена на ваш язык, поэтому показана на английском.

Word draws a real rectangle and the paragraphs sit on its fill rather than beside it. What separates it from a paragraph with a background is the size: a shape is a box you decided the dimensions of and does not grow with its text, which is exactly what a banner needs — one that changed depth with its wording would be a different notice on every letter. When the box *should* grow with what is in it, a `Table` with one cell is the thing to reach for.

Хелперы
Shape
Вид узла
shape
Категория
Медиа
Разрешается
Both
Дочерние узлы
Paragraphs, usually. Text goes straight in for the one-line case.
Опция Тип Что делает
id string Stable address for the node. Engines target it and build artifacts diff on it, so treat a rename as a breaking change. Optional: a node without one takes an id from where it sits, which is what keeps branches and loops from forcing you to invent names. Two nodes claiming one id is an error rather than a race.
width number How wide the box is drawn, in points. The full text column unless it is said, which is the width a banner wants and the only one that needs no arithmetic from the document.
height number How deep the box is drawn, in points. A shape does not grow to fit its words, so this is a decision the document or its theme makes — the block style's heightPt is taken when the node says nothing.
variant string A block style the theme looks up — "band", "badge", "panel". Names what the node is, never what it looks like: the appearance lives in the style's blocks, so a document restyles without a node changing. A name the theme has not heard of draws as an ordinary block rather than failing.
derivers DeriverInvocation[] Values the engine computes before the node resolves, written to derived.* and readable from a template token. Built with derive(). These survive publishing and run per document — use them for anything computed from request data. useDeriver runs one during the build instead.

Writing one

A shape needs two things from you: how big it is, and what goes on it.

import { Paragraph, Shape, useFormat, useState } from "docxcelerate/template";

export const RenewalBanner: Shape = () => {
  const { date } = useFormat("en-GB");
  const [state] = useState((data: MemberData) => ({
    plan: data.plan,
    renewsOn: data.renewsOn,
  }));

  return (
    <Shape id="renewal-banner" variant="banner" height={44}>
      <Paragraph id="renewal-banner-line">
        {state.plan} renews on {date(state.renewsOn)}
      </Paragraph>
    </Shape>
  );
};

width and height are in points. A shape with no width fills the text column, which is what a banner wants; saying one makes a block that sits in the column rather than spanning it. For the one-line case the text can go straight in — <Shape id="paid">Paid in full</Shape> reads as one.

Why it is not a paragraph with a background

Because a shape is a box you decided the size of, and that is usually the whole reason you wanted one.

A banner that grew with its wording would be a different depth on every document it went out on. The one saying “Paid” and the one saying “Overdue — 14 days past the due date” would be two different blocks, and a reader flicking through a stack reads the shorter one as a different kind of notice. A shape holds its dimensions whatever is written on it.

The trade runs the other way too, and it is worth knowing which side you are on. When the box should grow with what is in it — a description, a quoted clause, anything whose length you do not control — a Table with one cell in it is the thing to reach for. It draws the same fill, rule and padding, and it grows. Neither is a worse version of the other.

What it looks like is the theme’s

variant names what the banner isbanner, stamp, bannerOverdue — and never what colour it should be. The fill, the rule, the room inside and how the words sit on it all come from the style’s blocks, so restyling a document restyles its shapes without a node changing. A variant the theme has not heard of draws in the palette’s accent, which is a banner without an opinion rather than a broken one.

The block properties a shape reads are the ones a table cell reads: fill, border, borderWidthPt, paddingPt, align, and heightPt — which a shape takes as its depth when the node has not given one, because a block declaring a height is already saying exactly that.

How it is packed, and why it matters

A shape is written as VML — a w:pict holding a v:rect — rather than as the DrawingML shape Word writes itself.

Word reads both identically: it opens either as a real Rectangle with the width, height, fill and text the file gives it. The difference is on the other side. docx-preview renders VML and has no reading of DrawingML shapes at all, so the modern form would draw correctly in Word and show nothing in your preview. Packing the form both engines read is what keeps the preview showing the document rather than an opinion about it.

The same reasoning is why rounded corners are not here yet. v:roundrect is not something the preview can draw, and a radius that draws in Word and vanishes on screen is worse than no radius at all. It goes in when both engines can.

A banner across the column

src/nodes/shape/basic.node.tsx

A shape with no width, filling the text column at a stated depth.

import { Paragraph, type Shape as ShapeComponent, Shape, useFormat, useState } from "docxcelerate/template";
import type { SampleData } from "../sample-data.ts";

/**
 * A drawn rectangle with the document's own words on it.
 *
 * The height is the point. A banner that grew with its wording would be a
 * different depth on every letter it went out on, and a reader flicking
 * through a stack would read the shorter one as a different kind of notice.
 */
export const RenewalBanner: ShapeComponent = () => {
  const { date } = useFormat("en-GB");
  const [state] = useState((data: SampleData) => ({
    plan: data.plan,
    renewsOn: data.renewsOn,
  }));

  return (
    <Shape id="renewal-banner" variant="bannerAttention" height={44}>
      <Paragraph id="renewal-banner-line">
        {state.plan} renews on {date(state.renewsOn)}
      </Paragraph>
    </Shape>
  );
};
shape · a banner across the column Open ↗
Во что это разрешается

Узел в том виде, в каком он попадает в DocumentModel: JSON, который получает рендерер. Без стилей и без вёрстки.

{
  "id": "renewal-banner",
  "kind": "shape",
  "height": 44,
  "children": [
    {
      "id": "renewal-banner-line",
      "kind": "paragraph",
      "mode": "static",
      "text": "Peak Anytime renews on 1 October 2026"
    }
  ],
  "variant": "bannerAttention"
}

A block of its own size

src/nodes/shape/sized.node.tsx

The same element given a width, so it sits in the column rather than spanning it.

import { Paragraph, type Shape as ShapeComponent, Shape } from "docxcelerate/template";

/**
 * The same element given a width as well as a height.
 *
 * A shape with no width fills the text column, which is what a banner wants.
 * Saying one makes a block that sits in the column rather than spanning it —
 * a stamp, a callout, a badge beside prose. The tone is the theme's, named
 * for the state it reports rather than the colour it happens to be.
 */
export const PaidStamp: ShapeComponent = () => (
  <Shape id="paid-stamp" variant="bannerPositive" width={160} height={56}>
    <Paragraph id="paid-stamp-line">Paid in full</Paragraph>
  </Shape>
);
shape · a block of its own size Open ↗
Во что это разрешается

Узел в том виде, в каком он попадает в DocumentModel: JSON, который получает рендерер. Без стилей и без вёрстки.

{
  "id": "paid-stamp",
  "kind": "shape",
  "width": 160,
  "height": 56,
  "children": [
    {
      "id": "paid-stamp-line",
      "kind": "paragraph",
      "mode": "static",
      "text": "Paid in full"
    }
  ],
  "variant": "bannerPositive"
}

Изменить эту страницу на GitHub ↗ Читать эту страницу в Markdown