/** * Project a catalog's layout and typography facts into the **annotation manifest** * a preview server draws over a compare panel. * * The compare page diffs pixels: it shows *that* a render and its design * reference differ, never *why*. An annotation carries the spec a designer reads * off the mock — the padding and gap, the type style and size — anchored to a * region, so the two sides can be read against each other rather than eyeballed. * * Nothing here measures anything. The layout layer is the {@link Redline} set * `redlines.ts` already walks out of the semantics tree; the typography layer is * the per-node `tokens.typography` the renderer resolved. This module only * reshapes them into the transport the server consumes * (`compose-preview-annotations/v1`) and formats the one-line labels. * * Two things the labels must never blur, both of which cost the compare page its * comparison when they do: * * - **Units.** A render resolves `dp`/`sp`; a design board reports its own * pixels. Quoting a 3× board's 52.5px as `52.5sp` invents a threefold * discrepancy (issue #277). A tree that carries a `density` is converted to the * code's units so the two columns can be read against each other; one that does * not has its own unit named and its numbers left alone. * - **Provenance.** Spacing a source *declared* is a spec; spacing measured off * child geometry, because the source declared none, is an observation. Derived * phrases are prefixed `≈` and tagged in the detail, so a reference's measured * inset never reads as a number the design file actually asserts. * * Pure functions over core types — no I/O, trivially testable. */ import type { DesignReference, SemanticTree } from "@design-parity/core"; import type { CatalogComponent } from "./types.js"; /** Schema id the preview server validates before reading a manifest. */ export declare const ANNOTATION_SCHEMA = "compose-preview-annotations/v1"; /** Which spec layer an annotation belongs to; a viewer toggles them separately. */ export type AnnotationKind = "layout" | "typography"; export interface AnnotationBounds { x: number; y: number; width: number; height: number; } /** One annotation, anchored in the annotated image's own pixel space. */ export interface DesignAnnotation { kind: AnnotationKind; bounds: AnnotationBounds; /** * One-line spec as a designer would read it, e.g. `"pad 16dp · gap 8dp"`. * A `≈` prefix marks a phrase measured off geometry rather than declared. */ label: string; /** Node / slot name, shown as the annotation's title. */ role?: string; /** * Structured payload for machine consumers: the same numbers as the label, in * the unit `detail.unit` names. When the layer was converted from a design * board's pixels, `detail.density` is the factor applied and * `detail.sourceUnit` the unit it came from, so the source number is * recoverable. `detail.spacingSource` is `"derived"` on a measured redline. */ detail?: Record; } export interface AnnotationManifest { schema: typeof ANNOTATION_SCHEMA; /** Keyed by exact compose-preview id — annotations over the *rendered* frame. */ previews: Record; /** Keyed by design-reference id — annotations over the *reference* raster. */ references: Record; } /** * The unit a **spacing** measurement is quoted in — the layout-layer counterpart * of {@link TypeUnit}, and paired with it: a source that reports type in its own * pixels reports padding in them too. */ export type SpaceUnit = "dp" | "px"; /** * The unit a type size is quoted in. * * A candidate's semantics resolve real `sp` — the value in the code. A design * tool reports the frame's own pixels, which are only `sp` if the frame happens * to be authored 1:1 with the code's density; on a 2x/3x board they are not, and * quoting them as `sp` states a spec several times larger than the design's. * Naming the unit keeps the number checkable instead of quietly wrong. */ export type TypeUnit = "sp" | "px"; /** Both layers for one component, in draw order (layout beneath typography). */ export declare function componentAnnotations(component: CatalogComponent): DesignAnnotation[]; /** * Both layers for any bounded tree — the shape the two sides of a comparison share. * * A catalog component arrives with its redlines already walked, so * {@link componentAnnotations} reuses them; a design reference carries only the * raw tree, so this walks it first. Same extraction either way, which is the * point: the reference and actual columns have to be built the same way or * comparing them means comparing two different measurements. * * `unit` names the units the tree's own numbers are in. When the tree also * carries a {@link SemanticTree.density} they are converted to `dp`/`sp` and the * unit given here becomes the recorded `sourceUnit` — that conversion is what * makes the two columns of a compare page numerically comparable rather than * merely honestly labelled. */ export declare function treeAnnotations(tree: SemanticTree | undefined, unit?: TypeUnit): DesignAnnotation[]; /** * Both layers for a design reference, from the geometry its adapter captured. * * Empty for a source that captures no geometry — `layout` is optional on * {@link DesignReference}, and a reference that is only a raster has nothing to * annotate. That is a property of the source, not a failure. */ export declare function referenceAnnotations(reference: DesignReference, unit?: TypeUnit): DesignAnnotation[]; /** * Add reference-side layers to a manifest, keyed by the id the *publisher* uses. * * The key has to be the serve/catalog reference id, which is minted by whoever * writes `references/index.json` — not something this package can derive from a * {@link DesignReference}. So callers pass the mapping they already hold rather * than having a guess baked in here; a wrong key is invisible at build time and * silently draws nothing. * * References that produced no annotations are skipped, keeping the manifest to * entries that will actually draw. */ export declare function withReferenceAnnotations(manifest: AnnotationManifest, references: Readonly>): AnnotationManifest; /** * Build the manifest for a catalog's components. * * Keyed by `previewId`, which is what the preview server routes on — a component * whose images carry no preview id cannot be addressed by the compare page, so it * is skipped rather than keyed by something the server will never look up. A * component that produced no annotations is likewise omitted, keeping the * manifest to entries that will actually draw. * * The `references` map is left empty here: reference-side annotations describe a * *design tool's* geometry, which this code-led catalog is not the source of. * Fill it with {@link withReferenceAnnotations}, which needs the publisher's * reference ids. */ export declare function buildAnnotationManifest(components: readonly CatalogComponent[]): AnnotationManifest; /** True when a manifest would draw nothing — callers skip writing it entirely. */ export declare function isEmptyAnnotationManifest(manifest: AnnotationManifest): boolean; //# sourceMappingURL=annotations.d.ts.map