New

docx-editor 1.x has shipped. Vue support, i18n, agents. Read the migration guide →

API Referencev1.0.2

@eigenpal/docx-editor-core/layout-engine/types

Layout Engine Types

Core types for the paginated layout engine. Converts document blocks + measurements into positioned fragments on pages.

Functions(1)

Exhaustiveness guard for `FlowBlock`-shaped switches. Call from the `default` arm with the still-typed value; TypeScript will refuse to compile if any variant of `FlowBlock` was missed. The thrown error names the calling site so runtime failures (e.g. an old adapter compiled against a newer core) point future debuggers at the contract.

declare function assertExhaustiveFlowBlock(block: never, site: string): never;

Type aliases(60)

Unique identifier for a block in the document. Format: typically `${index}-${type}` or just the block index.

type BlockId = string | number;

Border specification for paragraphs.

type BorderStyle = {
    style?: string;
    width?: number;
    color?: string;
    space?: number;
};

Cell borders (all four sides).

type CellBorders = {
    top?: CellBorderSpec;
    bottom?: CellBorderSpec;
    left?: CellBorderSpec;
    right?: CellBorderSpec;
};

Cell border specification for rendering.

type CellBorderSpec = {
    width?: number;
    color?: string;
    style?: string;
};

Column break block.

type ColumnBreakBlock = {
    kind: 'columnBreak';
    id: BlockId;
    pmStart?: number;
    pmEnd?: number;
};

Measurement result for column break (no visual size).

type ColumnBreakMeasure = {
    kind: 'columnBreak';
};

Column layout configuration.

type ColumnLayout = {
    count: number;
    gap: number;
    equalWidth?: boolean;
    separator?: boolean;
};

Position within the document model.

type DocumentPosition = {
    blockIndex: number;
    runIndex?: number;
    charOffset?: number;
    pmPos?: number;
};

A field run (PAGE, NUMPAGES, etc.) that gets substituted at render time.

type FieldRun = RunFormatting & {
    kind: 'field';
    fieldType: 'PAGE' | 'NUMPAGES' | 'DATE' | 'TIME' | 'OTHER';
    fallback?: string;
    pmStart?: number;
    pmEnd?: number;
};

Floating table positioning info (pixel values).

type FloatingTablePosition = {
    horzAnchor?: 'margin' | 'page' | 'text';
    vertAnchor?: 'margin' | 'page' | 'text';
    tblpX?: number;
    tblpXSpec?: 'left' | 'center' | 'right' | 'inside' | 'outside';
    tblpY?: number;
    tblpYSpec?: 'top' | 'center' | 'bottom' | 'inside' | 'outside' | 'inline';
    topFromText?: number;
    bottomFromText?: number;
    leftFromText?: number;
    rightFromText?: number;
};

Union of every block kind the layout engine knows about.

Three switches over `block.kind` must stay in sync with this type: - `runLayoutPipeline` in `layout-engine/index.ts` (this package) - `measureBlock` in `packages/react/src/paged-editor/PagedEditor.tsx` - `measureBlock` in `packages/vue/src/composables/useDocxEditor.ts`

All three end in `assertExhaustiveFlowBlock(block, '<site>')` so adding a new variant here without updating every site is a typecheck error.

type FlowBlock = ParagraphBlock | TableBlock | ImageBlock | TextBoxBlock | SectionBreakBlock | PageBreakBlock | ColumnBreakBlock;

Pre-calculated footnote content for layout and rendering.

type FootnoteContent = {
    id: number;
    displayNumber: number;
    blocks: FlowBlock[];
    measures: Measure[];
    height: number;
};

Union of all fragment types.

type Fragment = ParagraphFragment | TableFragment | ImageFragment | TextBoxFragment;

Base fragment properties common to all fragment types.

type FragmentBase = {
    blockId: BlockId;
    x: number;
    y: number;
    width: number;
    pmStart?: number;
    pmEnd?: number;
};

Header/footer content heights by variant type.

type HeaderFooterContentHeights = Partial<Record<'default' | 'first' | 'even' | 'odd', number>>;

Header/footer layout for a specific type.

type HeaderFooterLayout = {
    height: number;
    fragments: Fragment[];
};

Result of hit-testing a click position.

type HitTestResult = {
    pageIndex: number;
    fragment?: Fragment;
    localX?: number;
    localY?: number;
};

Hyperlink information for a run.

type HyperlinkInfo = {
    href: string;
    tooltip?: string;
    noDefaultStyle?: boolean;
};

An anchored/floating image block.

type ImageBlock = {
    kind: 'image';
    id: BlockId;
    src: string;
    width: number;
    height: number;
    alt?: string;
    transform?: string;
    anchor?: {
        isAnchored?: boolean;
        offsetH?: number;
        offsetV?: number;
        behindDoc?: boolean;
    };
    hlinkHref?: string;
    pmStart?: number;
    pmEnd?: number;
};

An image fragment positioned on a page.

type ImageFragment = FragmentBase & {
    kind: 'image';
    height: number;
    isAnchored?: boolean;
    zIndex?: number;
};

Measurement result for an image block.

type ImageMeasure = {
    kind: 'image';
    width: number;
    height: number;
};

An inline image run.

type ImageRun = {
    kind: 'image';
    src: string;
    width: number;
    height: number;
    alt?: string;
    transform?: string;
    position?: ImageRunPosition;
    wrapType?: string;
    displayMode?: 'inline' | 'block' | 'float';
    cssFloat?: 'left' | 'right' | 'none';
    distTop?: number;
    distBottom?: number;
    distLeft?: number;
    distRight?: number;
    cropTop?: number;
    cropRight?: number;
    cropBottom?: number;
    cropLeft?: number;
    opacity?: number;
    pmStart?: number;
    pmEnd?: number;
};

Position data for floating/anchored images.

type ImageRunPosition = {
    horizontal?: {
        relativeTo?: string;
        posOffset?: number;
        align?: string;
    };
    vertical?: {
        relativeTo?: string;
        posOffset?: number;
        align?: string;
    };
};

Final layout output ready for rendering/painting.

type Layout = {
    pageSize: {
        w: number;
        h: number;
    };
    pages: Page[];
    columns?: ColumnLayout;
    headers?: Record<string, HeaderFooterLayout>;
    footers?: Record<string, HeaderFooterLayout>;
    pageGap?: number;
};

Options for the layout engine.

type LayoutOptions = {
    pageSize: {
        w: number;
        h: number;
    };
    margins: PageMargins;
    finalPageSize?: {
        w: number;
        h: number;
    };
    finalMargins?: PageMargins;
    columns?: ColumnLayout;
    pageGap?: number;
    defaultLineHeight?: number;
    headerContentHeights?: HeaderFooterContentHeights;
    footerContentHeights?: HeaderFooterContentHeights;
    titlePage?: boolean;
    evenAndOddHeaders?: boolean;
    footnoteReservedHeights?: Map<number, number>;
    bodyBreakType?: 'continuous' | 'nextPage' | 'evenPage' | 'oddPage';
};

A line break run.

type LineBreakRun = {
    kind: 'lineBreak';
    pmStart?: number;
    pmEnd?: number;
};

List numbering properties for a paragraph.

type ListNumPr = {
    numId?: number;
    ilvl?: number;
};

Union of all measurement types.

type Measure = ParagraphMeasure | ImageMeasure | TableMeasure | TextBoxMeasure | SectionBreakMeasure | PageBreakMeasure | ColumnBreakMeasure;

A measured line within a paragraph.

type MeasuredLine = {
    fromRun: number;
    fromChar: number;
    toRun: number;
    toChar: number;
    width: number;
    ascent: number;
    descent: number;
    lineHeight: number;
    leftOffset?: number;
    rightOffset?: number;
    segments?: MeasuredLineSegment[];
};
type MeasuredLineSegment = {
    fromRun: number;
    fromChar: number;
    toRun: number;
    toChar: number;
    width: number;
    leftOffset: number;
    availableWidth: number;
};

A rendered page containing positioned fragments.

type Page = {
    number: number;
    fragments: Fragment[];
    margins: PageMargins;
    size: {
        w: number;
        h: number;
    };
    orientation?: 'portrait' | 'landscape';
    sectionIndex?: number;
    headerFooterRefs?: {
        headerDefault?: string;
        headerFirst?: string;
        headerEven?: string;
        footerDefault?: string;
        footerFirst?: string;
        footerEven?: string;
    };
    footnoteIds?: number[];
    footnoteReservedHeight?: number;
    columns?: ColumnLayout;
};

Explicit page break block.

type PageBreakBlock = {
    kind: 'pageBreak';
    id: BlockId;
    pmStart?: number;
    pmEnd?: number;
};

Measurement result for page break (no visual size).

type PageBreakMeasure = {
    kind: 'pageBreak';
};

Page margin configuration.

type PageMargins = {
    top: number;
    right: number;
    bottom: number;
    left: number;
    header?: number;
    footer?: number;
};

Paragraph block attributes.

type ParagraphAttrs = {
    alignment?: 'left' | 'center' | 'right' | 'justify';
    spacing?: ParagraphSpacing;
    spacingExplicit?: {
        before?: boolean;
        after?: boolean;
    };
    indent?: ParagraphIndent;
    keepNext?: boolean;
    keepLines?: boolean;
    pageBreakBefore?: boolean;
    styleId?: string;
    contextualSpacing?: boolean;
    bidi?: boolean;
    borders?: ParagraphBorders;
    shading?: string;
    tabs?: TabStop[];
    numPr?: ListNumPr;
    listMarker?: string;
    listIsBullet?: boolean;
    listMarkerHidden?: boolean;
    listMarkerFontFamily?: string;
    listMarkerFontSize?: number;
    defaultFontSize?: number;
    defaultFontFamily?: string;
    suppressEmptyParagraphHeight?: boolean;
};

A paragraph block containing runs.

type ParagraphBlock = {
    kind: 'paragraph';
    id: BlockId;
    runs: Run[];
    attrs?: ParagraphAttrs;
    pmStart?: number;
    pmEnd?: number;
};

Paragraph borders.

type ParagraphBorders = {
    top?: BorderStyle;
    bottom?: BorderStyle;
    left?: BorderStyle;
    right?: BorderStyle;
    between?: BorderStyle;
    bar?: BorderStyle;
};

A paragraph fragment positioned on a page. May span only part of the paragraph's lines if split across pages.

type ParagraphFragment = FragmentBase & {
    kind: 'paragraph';
    fromLine: number;
    toLine: number;
    height: number;
    continuesFromPrev?: boolean;
    continuesOnNext?: boolean;
};

Paragraph indentation configuration.

type ParagraphIndent = {
    left?: number;
    right?: number;
    firstLine?: number;
    hanging?: number;
};

Measurement result for a paragraph block.

type ParagraphMeasure = {
    kind: 'paragraph';
    lines: MeasuredLine[];
    totalHeight: number;
};

Paragraph spacing configuration.

type ParagraphSpacing = {
    before?: number;
    after?: number;
    line?: number;
    lineUnit?: 'px' | 'multiplier';
    lineRule?: 'auto' | 'exact' | 'atLeast';
};

Union of all run types.

type Run = TextRun | TabRun | ImageRun | LineBreakRun | FieldRun;

Common run formatting properties applied to text runs.

type RunFormatting = {
    bold?: boolean;
    italic?: boolean;
    underline?: boolean | {
        style?: string;
        color?: string;
    };
    strike?: boolean;
    color?: string;
    highlight?: string;
    fontFamily?: string;
    fontSize?: number;
    letterSpacing?: number;
    superscript?: boolean;
    subscript?: boolean;
    allCaps?: boolean;
    smallCaps?: boolean;
    positionPx?: number;
    horizontalScale?: number;
    kerningMinPt?: number;
    imprint?: boolean;
    emboss?: boolean;
    textShadow?: boolean;
    textOutline?: boolean;
    emphasisMark?: 'dot' | 'comma' | 'circle' | 'underDot';
    hidden?: boolean;
    rtl?: boolean;
    textEffect?: 'blinkBackground' | 'lights' | 'antsBlack' | 'antsRed' | 'shimmer' | 'sparkle';
    hyperlink?: HyperlinkInfo;
    footnoteRefId?: number;
    endnoteRefId?: number;
    commentIds?: number[];
    isInsertion?: boolean;
    isDeletion?: boolean;
    changeAuthor?: string;
    changeDate?: string;
    changeRevisionId?: number;
};

Section break block defining page layout changes.

type SectionBreakBlock = {
    kind: 'sectionBreak';
    id: BlockId;
    type?: 'continuous' | 'nextPage' | 'evenPage' | 'oddPage';
    pageSize?: {
        w: number;
        h: number;
    };
    orientation?: 'portrait' | 'landscape';
    margins?: PageMargins;
    columns?: ColumnLayout;
};

Measurement result for section break (no visual size).

type SectionBreakMeasure = {
    kind: 'sectionBreak';
};

Tab stop alignment types

type TabAlignment = 'start' | 'end' | 'center' | 'decimal' | 'bar' | 'clear';

A table block containing rows.

type TableBlock = {
    kind: 'table';
    id: BlockId;
    rows: TableRow[];
    columnWidths?: number[];
    width?: number;
    widthType?: string;
    justification?: 'left' | 'center' | 'right';
    indent?: number;
    floating?: FloatingTablePosition;
    pmStart?: number;
    pmEnd?: number;
};

A table cell with content.

type TableCell = {
    id: BlockId;
    blocks: FlowBlock[];
    colSpan?: number;
    rowSpan?: number;
    width?: number;
    widthValue?: number;
    widthType?: string;
    verticalAlign?: 'top' | 'center' | 'bottom';
    background?: string;
    borders?: CellBorders;
    padding?: {
        top: number;
        right: number;
        bottom: number;
        left: number;
    };
    noWrap?: boolean;
};

Measurement result for a table cell.

type TableCellMeasure = {
    blocks: Measure[];
    width: number;
    height: number;
    colSpan?: number;
    rowSpan?: number;
};

A table fragment positioned on a page. May span only part of the table's rows if split across pages.

type TableFragment = FragmentBase & {
    kind: 'table';
    fromRow: number;
    toRow: number;
    height: number;
    isFloating?: boolean;
    continuesFromPrev?: boolean;
    continuesOnNext?: boolean;
    headerRowCount?: number;
};

Measurement result for a table block.

type TableMeasure = {
    kind: 'table';
    rows: TableRowMeasure[];
    columnWidths: number[];
    totalWidth: number;
    totalHeight: number;
};

A table row containing cells.

type TableRow = {
    id: BlockId;
    cells: TableCell[];
    height?: number;
    heightRule?: 'auto' | 'atLeast' | 'exact';
    isHeader?: boolean;
};

Measurement result for a table row.

type TableRowMeasure = {
    cells: TableCellMeasure[];
    height: number;
};

A tab character run.

type TabRun = RunFormatting & {
    kind: 'tab';
    width?: number;
    pmStart?: number;
    pmEnd?: number;
};

Tab stop definition

type TabStop = {
    val: TabAlignment;
    pos: number;
    leader?: 'none' | 'dot' | 'hyphen' | 'underscore' | 'heavy' | 'middleDot';
};

Text box block — positioned container with paragraph content.

type TextBoxBlock = {
    kind: 'textBox';
    id: BlockId;
    width: number;
    height?: number;
    fillColor?: string;
    outlineWidth?: number;
    outlineColor?: string;
    outlineStyle?: string;
    margins?: {
        top: number;
        bottom: number;
        left: number;
        right: number;
    };
    content: ParagraphBlock[];
    displayMode?: 'inline' | 'float' | 'block';
    cssFloat?: 'left' | 'right' | 'none';
    wrapType?: string;
    wrapText?: WrapTextDirection;
    anchorTarget?: 'followingBlock';
    position?: ImageRunPosition;
    distTop?: number;
    distBottom?: number;
    distLeft?: number;
    distRight?: number;
    pmStart?: number;
    pmEnd?: number;
};

A text box fragment positioned on a page.

type TextBoxFragment = FragmentBase & {
    kind: 'textBox';
    height: number;
    isFloating?: boolean;
    zIndex?: number;
};

Measurement result for a text box block.

type TextBoxMeasure = {
    kind: 'textBox';
    width: number;
    height: number;
    innerMeasures: ParagraphMeasure[];
};

A text run within a paragraph.

type TextRun = RunFormatting & {
    kind: 'text';
    text: string;
    hyperlink?: HyperlinkInfo;
    pmStart?: number;
    pmEnd?: number;
};
type WrapTextDirection = 'bothSides' | 'left' | 'right' | 'largest';

Variables(2)

Default internal margins for text boxes (OOXML defaults in pixels)

DEFAULT_TEXTBOX_MARGINS: {
    top: number;
    bottom: number;
    left: number;
    right: number;
}

Default text box width in pixels when no width is specified

DEFAULT_TEXTBOX_WIDTH = 200