import { SourceAnchor } from './index.js'; import { LayoutBlockRef, LayoutFragmentId, LayoutSourceIdentity, LayoutStoryLocator } from './layout-identity.js'; /** * Schema version for the neutral segment-geometry substrate. * * Bump when a load-bearing field changes semantics. Pure additive growth * (new optional fields) does not require a bump. */ export declare const LAYOUT_SEGMENT_GEOMETRY_SCHEMA = "layout-segment-geometry/1"; /** Resolved inline direction for a measured line. */ export type NeutralTextDirection = 'ltr' | 'rtl'; /** * Geometry for a single measured run-segment within a line. * * `x` is **load-bearing**: it is the resolved container-space left edge of the * segment, with paragraph indent, list text-start, alignment offset, and * explicit tab positioning already applied. The producer's `LineSegment.x` is * intentionally optional (it only carries an explicit override for tab-aligned * runs); the readback resolves it to a concrete value here so the host never * needs fallback positioning logic. * * `fromChar` / `toChar` are visible-text offsets **within the segment's run** * (`runIndex`), matching the producer's `LineSegment` semantics. Joined with * `runIndex` they address the exact visible text the segment paints. */ export type NeutralSegmentGeometry = { /** Index of this segment within the line's segment list. */ segmentIndex: number; /** Run index (into the source block's runs) this segment belongs to. */ runIndex: number; /** Visible-text start offset within the run (inclusive). */ fromChar: number; /** Visible-text end offset within the run (exclusive). */ toChar: number; /** Resolved container-space x of the segment's start edge (px). Always present. */ x: number; /** Measured width of the segment (px). */ width: number; }; /** * Per-line advisory flags. Present only when the corresponding condition holds, * so consumers can treat an absent flag as "false" without ambiguity. */ export type NeutralLineGeometryFlags = { /** * Line participates in justified alignment. Segment `x`/`width` are the * natural (pre-justify) measured values; the painter distributes slack across * spaces at paint time, so downstream caret math past the first segment is * approximate. Paired with a `approximate-justify` diagnostic on the fragment. */ justified?: boolean; /** Line uses explicit tab/segment positioning (at least one segment has an explicit x). */ tabAligned?: boolean; }; /** * Geometry for a single measured line within a fragment. * * Vertical metrics follow the painter's line-box model (see DomPainter * `tab-run.ts`): the baseline sits at `top + halfLeading + ascent`, where * `halfLeading = max(0, (lineHeight - ascent - descent) / 2)`. `baseline` is * precomputed with that formula so the host does not reimplement it. */ export type NeutralLineGeometry = { /** 0-based line index within the source block's measure. */ lineIndex: number; /** Container-space top edge of the line box (px). */ top: number; /** Container-space baseline y (px) — `top + halfLeading + ascent`. */ baseline: number; /** Ascent above the baseline (px). */ ascent: number; /** Descent below the baseline (px). */ descent: number; /** Total line-box height (px). */ lineHeight: number; /** Container-space x where line content starts after indent + alignment (px). */ contentLeft: number; /** Measured natural content width of the line (px). */ contentWidth: number; /** Resolved inline direction for the line. */ direction: NeutralTextDirection; /** Advisory flags; omitted when none apply. */ flags?: NeutralLineGeometryFlags; /** Per-segment geometry in visual (left-to-right paint) order. */ segments: NeutralSegmentGeometry[]; }; /** * Diagnostics describing a case the substrate cannot fully represent. * * Emitted at the fragment level so a consumer can fail closed (skip caret/rect * placement) instead of trusting partial geometry. */ export type NeutralGeometryDiagnostic = /** Fragment kind has no per-line text geometry in this version (table, image, drawing). */ { code: 'unsupported-fragment-kind'; fragmentKind: string; } /** * Inline direction is RTL but the line is not a proven simple-RTL case (e.g. * tab-aligned, justified, or a rendered list marker whose RTL geometry is not * mirrored yet). Per-segment x is not resolved for this fragment. * * Simple horizontal RTL paragraph fragments DO resolve and carry no diagnostic * (V2 RTL Plan 004). Mixed-script bidi uses {@link 'unsupported-complex-bidi'} * and vertical writing modes use {@link 'unsupported-vertical-direction'}. */ | { code: 'unsupported-direction'; direction: NeutralTextDirection; } /** * The paragraph uses a vertical writing mode (`w:textDirection` tbRl/btLr, * resolved to a vertical `WritingMode`). Horizontal text geometry cannot place * carets here, so the fragment fails closed. (V2 RTL Plan 004 scope boundary.) */ | { code: 'unsupported-vertical-direction'; writingMode: string; } /** * An RTL paragraph line mixes strong-LTR letters or Unicode numbers into the * RTL flow, so the visual order is not a single-level reverse of the logical order. * Resolving it needs per-segment visual-order data the substrate does not have * yet, so the fragment fails closed rather than emit a mirrored-twice rect. * (V2 RTL Plan 004 scope boundary; Plan 005+ may extend.) */ | { code: 'unsupported-complex-bidi'; } /** Fragment has justified lines; segment x past the first is pre-justify (approximate). */ | { code: 'approximate-justify'; } /** The block measure was missing or mismatched; no geometry was produced. */ | { code: 'missing-measure'; } /** Fragment carried remeasured lines (narrow-column re-wrap); geometry used those lines. */ | { code: 'remeasured-lines'; }; /** * Geometry for one rendered fragment, joined back to neutral identity. * * Every record is addressable without ProseMirror: `identity` carries the * story, source block ref, stable fragment id, and (when available) the DOCX * source anchor. `bounds` is the fragment's container-space box; `lines` is the * resolved per-line/per-segment geometry (empty for unsupported fragments, * which carry a diagnostic instead). */ export type NeutralFragmentGeometry = { schema: typeof LAYOUT_SEGMENT_GEOMETRY_SCHEMA; /** Composite editor-neutral identity for the fragment. */ identity: LayoutSourceIdentity; /** Story locator for the fragment. */ story: LayoutStoryLocator; /** Source block reference. */ blockRef: LayoutBlockRef; /** Stable opaque fragment id. */ fragmentId: LayoutFragmentId; /** Optional cross-reference to the DOCX source anchor. */ sourceAnchor?: SourceAnchor; /** 0-based page index containing the fragment. */ pageIndex: number; /** Producer fragment kind (`para`, `list-item`, `table`, `image`, `drawing`). */ fragmentKind: string; /** Container-space bounding box of the fragment (px). */ bounds: { x: number; y: number; width: number; height: number; }; /** Resolved per-line geometry, in paint order. */ lines: NeutralLineGeometry[]; /** Per-fragment diagnostics; omitted when fully represented. */ diagnostics?: NeutralGeometryDiagnostic[]; }; /** * Result of a neutral segment-geometry readback over a layout. * * `layoutRevision` mirrors `layout.layoutEpoch` so a consumer can detect stale * geometry. `fragments` is in page-then-fragment order. */ export type NeutralSegmentGeometryReadback = { schema: typeof LAYOUT_SEGMENT_GEOMETRY_SCHEMA; layoutRevision: number; fragments: NeutralFragmentGeometry[]; /** Top-level diagnostics not attributable to a single fragment; omitted when none. */ diagnostics?: NeutralGeometryDiagnostic[]; };