Background¶
A new layer type, distinct from Text Foundation's per-layer
props.background padding fix — that fix is about a text layer's own paint-time pill; this is a
standalone layer that derives its box from other layers.
background added to the layerSchema discriminated union:
{
type: "background",
layout: {
mode: "derived", // the fourth layout mode; background is its only user
"16:9": Derived, "9:16": Derived, "1:1": Derived,
},
props: { fill, opacity?, radius?, padding?, stroke? }, // paint only
}
type Derived = {
of: LayerId[], // the members whose union is this layer's box
ofBounds: "content" | "extents", // which of the members' bounds the union takes
}
The relationship lives in layout, not props, for the same reason pinTo and flowIn do: it's
what positions the layer, and like them it's per-ratio — a layer hidden in portrait drops out of
the portrait of list without touching landscape (Positioning).
props keeps only paint. layout.mode: "derived" is what keeps "every layer has a layout.mode"
true for the validator and the Builder without pretending a background has a box of its own to
author (Positioning); validate.ts
rejects derived on any other layer type. Its box is derived (never authored) from the union of
its members' ofBounds boxes, plus its own padding and radius — that padding is the background layer's own paint-time inset around its
derived box (consistent with the padding-is-paint-only invariant
Text Foundation establishes, applied to this layer type too); member layers'
own text-background padding never contributes to the union.
Which bounds the union derives from is itself authored
ofBounds picks which of the members' bounds (Coordinate System & Sizing)
the union takes — never raster, which is internal-only, the same restriction pinToBounds
has. extents is the sensible default, so a background sitting behind its members clears their
painted stroke/shadow rather than just their tight ink, but content, hugging only the ink, is
a real, wanted case too, not an edge case to rule out.
background declares which layers it references, the opposite direction from pin/flow,
where the member declares what it's attached to — not an inconsistency, the same rule applied
from the other side: whichever side's own layout depends on the other is the side that declares
the reference. A pinned/flowed layer's own position depends on its target/container, so it
declares the reference; a background's own box depends on its members, and its members' layout
is untouched by the background, so the background declares the reference instead.
of is always a LayerId[], permanently, even once flowBox (Positioning)
exists — there is no separate groupId variant, ever. Referencing a flowBox's members as a
group is just referencing the flowBox's own id through this same list, since flowBox is a real
layer with its own id and resolved box.
Re-derivation is not solve-only. A background reads its members' current nodes from
the motion graph every frame — solve alone can't be the final answer
for a spanning background any more than it can for an animating pin target or flow member, so a
background whose members are themselves animated re-derives every frame in animate, tracking
their motion, not just once at authoring-time move/resize.
Validation: an of entry naming a layer id that doesn't exist in the same container fails
validation with the same friendly message a dangling pinTo gets — of is an edge in the motion
graph like pinTo and flowIn, and it goes through the same reference,
cycle, and container-local checks. An empty of list fails validation too, and so does an of list whose
members carry no intrinsic size between them (a background spanning only an empty flowBox),
the same empty-container rule every container
gets — so a mutation stripping a background down to nothing it can derive a box from is rejected
where it's proposed. The renderer keeps one defensive
fallback for a document that reaches it anyway: derive from whatever members it can find, render
nothing if none.
Done when¶
The spanning-background hard case renders and re-derives when a member moves.