Documentation
¶
Overview ¶
Package css implements a deliberately small but real subset of CSS: a value model, a tokenizer/parser for stylesheets and declaration blocks, tag/class/ id selectors with specificity, and a cascade with inheritance over a dom tree. It targets the handful of properties Phase 0 needs — display, color, background-color, font-size, font-weight, font-family, margin, padding, width, text-align — plus a user-agent default stylesheet for common tags.
Index ¶
- Constants
- Variables
- func ImportURLs(sheet string) (urls []string, medias []string)
- func InlineStyleText(root *dom.Node) string
- func MediaApplies(media string, vw float64) bool
- func MediaAppliesTo(media string, m Media) bool
- type AlignContent
- type AlignItems
- type AlignSelf
- type BgImage
- type BgImageKind
- type BgPosition
- type BgRepeat
- type BgSize
- type BgSizeKind
- type BorderSide
- type BorderStyle
- type Borders
- type BoxShadow
- type BoxSizing
- type Break
- type BreakInside
- type Clear
- type Color
- type ColorScheme
- type ColorStop
- type ContainerCondition
- type ContainerSize
- type ContainerType
- type Declaration
- type Display
- type Edges
- type Filter
- type FilterKind
- type FlexDirection
- type FlexWrap
- type Float
- type FontFace
- type FontFamily
- type FontSrc
- type Generic
- type Gradient
- type GradientGeometry
- type GradientSampler
- type GradientStop
- type GridFlow
- type GridLine
- type ImageRendering
- type Justify
- type Length
- type LineHeight
- type LinkRef
- type ListStylePosition
- type ListStyleType
- type Media
- type ObjectFit
- type Overflow
- type PageRule
- type PageSpec
- type Position
- type RadialExtent
- type RadialShape
- type Rule
- type Selector
- type Style
- type StyleMap
- func Cascade(root *dom.Node) StyleMap
- func CascadeMedia(root *dom.Node, m Media, externalSheets []string) StyleMap
- func CascadeMediaContainers(root *dom.Node, m Media, externalSheets []string, ...) StyleMap
- func CascadeVW(root *dom.Node, vw float64, externalSheets []string) StyleMap
- func CascadeVWContainers(root *dom.Node, vw float64, externalSheets []string, ...) StyleMap
- type TextAlign
- type TextTransform
- type TrackKind
- type TrackSize
- type VerticalAlign
- type Visibility
- type WhiteSpace
Constants ¶
const ( Screen = "screen" Print = "print" )
Media types a Media.Type can name. Any other type (speech, tv, …) never matches.
const DefaultViewportWidth = 1024
DefaultViewportWidth is the viewport width (CSS px) used to evaluate @media width queries when no explicit width is supplied. It matches a desktop render.
Variables ¶
var ( // Sans is the sans-serif bucket with no named family. Sans = FontFamily{Generic: GenericSans} // Serif is the serif bucket with no named family. Serif = FontFamily{Generic: GenericSerif} // Mono is the monospace bucket with no named family. Mono = FontFamily{Generic: GenericMono} )
The three generic families, for the places that mean the bucket itself: the UA default, and the bundled faces the font layer registers. Values rather than constants because Go has no struct constant; do not assign to them.
var Transparent = Color{}
Transparent is the fully-transparent colour (the initial background-color).
Functions ¶
func ImportURLs ¶
ImportURLs extracts the targets of leading @import at-rules from a stylesheet, in order. Per the CSS spec @import rules must precede all other rules (except @charset), so only the leading run is honoured; the first non-@import, non-@charset, non-comment token stops the scan. Both @import "url" and @import url(...) forms are supported, with an optional trailing media query (returned via the paired media slice, "" when absent).
func InlineStyleText ¶ added in v0.5.10
InlineStyleText returns the document's embedded stylesheet: the text of every <style> element under root, in document order. It is what the cascade itself reads besides the linked sheets, exported so a consumer of the OTHER at-rules a stylesheet carries (the engine's @font-face loading) sees the same CSS the cascade does.
func MediaApplies ¶
MediaApplies reports whether a media query (from a <link media> attribute or an @import) applies at viewport width vw. It reuses the same simplified evaluation as @media blocks: an empty query always applies, "print" never does, and min-width/max-width pixel features are honoured against vw. A comma-separated media list matches if ANY component matches.
func MediaAppliesTo ¶ added in v0.4.0
MediaAppliesTo is MediaApplies against a Media: a <link media="print"> applies under Media{Type: Print} and nowhere else, "screen" the reverse.
Types ¶
type AlignContent ¶
type AlignContent uint8
AlignContent distributes flex lines (or grid tracks) along the cross axis when there is spare cross-axis space (multi-line flex / align-content).
const ( // AlignContentStretch stretches lines to fill the cross axis (initial). AlignContentStretch AlignContent = iota // AlignContentStart packs lines at the cross-start edge. AlignContentStart // AlignContentEnd packs lines at the cross-end edge. AlignContentEnd // AlignContentCenter centres the lines as a group. AlignContentCenter // AlignContentSpaceBetween spreads lines with the ends flush. AlignContentSpaceBetween // AlignContentSpaceAround spreads lines with half-gaps at the ends. AlignContentSpaceAround // AlignContentSpaceEvenly spreads lines with equal gaps incl. the ends. AlignContentSpaceEvenly )
type AlignItems ¶
type AlignItems uint8
AlignItems is the cross-axis alignment (align-items). In a grid container it is also reused for the block-axis (align-items) and, via JustifyItems, the inline-axis (justify-items) alignment of items within their cells.
const ( // AlignStretch stretches items to fill the cross axis (initial value). AlignStretch AlignItems = iota // AlignFlexStart aligns items at the cross-start edge. AlignFlexStart // AlignFlexEnd aligns items at the cross-end edge. AlignFlexEnd // AlignCenterItems centres items on the cross axis. AlignCenterItems )
type AlignSelf ¶
type AlignSelf uint8
AlignSelf overrides a single item's cross-axis alignment. Auto (the initial value) defers to the container's align-items.
const ( // AlignSelfAuto uses the container's align-items value (initial). AlignSelfAuto AlignSelf = iota // AlignSelfStretch stretches this item on the cross axis. AlignSelfStretch // AlignSelfStart aligns this item at the cross-start edge. AlignSelfStart // AlignSelfEnd aligns this item at the cross-end edge. AlignSelfEnd // AlignSelfCenter centres this item on the cross axis. AlignSelfCenter )
func (AlignSelf) Resolve ¶
func (a AlignSelf) Resolve(container AlignItems) AlignItems
resolve returns the effective AlignItems for an item, falling back to the container's align-items when the item's align-self is auto.
type BgImage ¶
type BgImage struct {
Kind BgImageKind
URL string // raw url() argument (BgURL); resolved by the engine
Grad *Gradient // gradient spec (BgGradient)
}
BgImage is one background-image layer: a url() bitmap or a gradient. Layers are stored first-listed-first; the first layer paints on top (CSS order).
type BgImageKind ¶
type BgImageKind uint8
BgImageKind identifies the kind of a single background-image layer.
const ( // BgNone is an empty layer (the keyword `none`). BgNone BgImageKind = iota // BgURL is a url(...) bitmap layer. BgURL // BgGradient is a linear/radial gradient layer. BgGradient )
type BgPosition ¶
type BgPosition struct {
X, Y Length
}
BgPosition is a resolved background-position (percent/px per axis). The initial value is the top-left corner (0%, 0%).
type BgSize ¶
type BgSize struct {
Kind BgSizeKind
W Length // meaningful when SizeExplicit
H Length // meaningful when SizeExplicit; Auto keeps aspect ratio
}
BgSize is a resolved background-size value.
type BgSizeKind ¶
type BgSizeKind uint8
BgSizeKind selects how a background image is scaled.
const ( // SizeAuto uses the image's intrinsic size (gradients fill the box). SizeAuto BgSizeKind = iota // SizeCover scales to cover the whole box (may crop). SizeCover // SizeContain scales to fit inside the box (may letterbox). SizeContain // SizeExplicit uses the given width/height lengths (a length or auto each). SizeExplicit )
type BorderSide ¶
type BorderSide struct {
Width float64
Style BorderStyle
Color Color
}
BorderSide is one edge's border: its width, line style and colour.
type BorderStyle ¶
type BorderStyle uint8
BorderStyle is the subset of border-style the engine paints. Any non-none, non-hidden line style renders as a solid line (dashed/dotted/etc. collapse to solid at this fidelity).
const ( // BorderNone is the initial value: no border line (even if width > 0). BorderNone BorderStyle = iota // BorderSolid renders a solid line of the border colour. BorderSolid )
type Borders ¶
type Borders struct{ Top, Right, Bottom, Left BorderSide }
Borders is the four border edges of a box.
type BoxSizing ¶
type BoxSizing uint8
BoxSizing selects whether width/height apply to the content box or the border box.
type Break ¶ added in v0.4.0
type Break uint8
Break is a break-before / break-after value (CSS Fragmentation Level 3) as it matters to paged media: a forced page break (page, left, right), an avoided one, or neither. The column- and region-only values (column, avoid-column, region, avoid-region) say nothing about pages and map to BreakAuto. This engine's own screen layout never fragments; the value is carried on Style for a paginating consumer (go-pdfkit/html2pdf and the paginate package) to read.
const ( // BreakAuto is the initial value: neither force nor forbid a break. BreakAuto Break = iota // BreakAvoid avoids a page break at this edge (avoid, avoid-page). BreakAvoid // BreakPage forces a page break: page, the legacy `always`, all, recto // and verso — any forced break that does not also choose a page side. BreakPage // BreakLeft forces one or two page breaks so the next page is a left page. BreakLeft // BreakRight forces one or two page breaks so the next page is a right // page. BreakRight )
type BreakInside ¶ added in v0.4.0
type BreakInside uint8
BreakInside is a break-inside value for paged media.
const ( // BreakInsideAuto is the initial value: a page break may fall inside the // box. BreakInsideAuto BreakInside = iota // BreakInsideAvoid asks that no page break fall inside the box (avoid, // avoid-page). avoid-column / avoid-region say nothing about pages and // map to BreakInsideAuto. BreakInsideAvoid )
type Color ¶
type Color struct{ R, G, B, A uint8 }
Color is an 8-bit-per-channel RGBA colour. A==0 is treated as transparent.
type ColorScheme ¶ added in v0.5.10
type ColorScheme uint8
ColorScheme is the computed value of the inherited `color-scheme` property (CSS Color Adjust 1 §2.2) reduced to what this engine acts on: which of the two UA-supported schemes the element declares support for. The zero value is `normal` (no scheme declared). Custom-ident schemes are ignored, and so is `only` (it forbids UA overrides such as forced colours, which this engine never applies).
const ( ColorSchemeLight ColorScheme = 1 << iota // `light` listed ColorSchemeDark // `dark` listed )
func (ColorScheme) UsedDark ¶ added in v0.5.10
func (cs ColorScheme) UsedDark() bool
UsedDark reports whether the element's used colour scheme is dark, per the "determine the used color scheme" algorithm of CSS Color Adjust 1 §2.3 with this engine's fixed preference: when an element supports BOTH schemes the preferred one is used, which here is dark — the same fixed answer js/window.go's matchMedia gives for `prefers-color-scheme` (the bench suite's reference Chromium runs with a dark appearance). One supported scheme is used as is. With none (`normal`) the UA default scheme applies, which is light: Gecko's LookAndFeel::ColorSchemeForStyle ("Otherwise default content to light"), and Chromium likewise renders a page that never opts in light.
type ColorStop ¶
ColorStop is one gradient colour stop. Pos is the parsed position (a percent or px length); HasPos is false when the stop had no explicit position and its fraction is interpolated during normalisation.
type ContainerCondition ¶ added in v0.4.0
type ContainerCondition struct {
// Name filters which ancestor container this condition may match against
// ("" means: the nearest ancestor container of ANY name).
Name string
// Cond is the lowercased, trimmed size-feature condition text (e.g.
// "(min-width: 400px)"), or "" for a name-only condition with no size
// feature (matches unconditionally once a qualifying container exists).
Cond string
}
ContainerCondition is an `@container` rule's condition, attached to every Rule nested (directly or via @media/@layer) inside the at-rule. Unlike a `@media` condition — known before any element is visited, since it only depends on the viewport — a `@container` condition depends on an ANCESTOR ELEMENT's actual laid-out size, which is per-matched-element (different elements matching the same selector can have different nearest containers) and only known once at least one layout pass has run. So, unlike @media, this is not resolved away at parse time: it rides along on the Rule and is evaluated during cascade, per candidate element, against whatever container sizes the caller supplies (nil before the first layout pass, which makes every @container rule inactive until real geometry is available).
type ContainerSize ¶ added in v0.4.0
ContainerSize is a query container's resolved size, gathered from a layout pass and fed back into the cascade so `@container` conditions can be evaluated against it. InlineSize is the container's border-box width; BlockSize (border-box height) is only populated — and only ever consulted — when the container has size containment on both axes (ContainerType == ContainerTypeSize): querying the block axis of an inline-size-only container is invalid per spec and always fails to match here, the same way it does in a browser.
type ContainerType ¶ added in v0.4.0
type ContainerType uint8
ContainerType is the computed value of the `container-type` property: it controls whether an element establishes a query container that descendant `@container` rules can condition on, and which of its axes are available to query. It is NOT inherited (an element resets to ContainerNormal unless its own declarations set it).
const ( // ContainerNormal is the initial value: the element establishes no query // container (but may still be a size/layout containment target for other // reasons this engine does not model). ContainerNormal ContainerType = iota // ContainerInlineSize establishes a query container queryable on its // inline axis (width, in this engine's horizontal writing mode) only. ContainerInlineSize // ContainerTypeSize establishes a query container queryable on BOTH axes // (inline and block / width and height). Named ContainerTypeSize (rather // than the more obvious ContainerSize) because that name is already taken // by the ContainerSize struct just below, for a container's RESOLVED size // — a different, unrelated concept that also needed the name. ContainerTypeSize )
type Declaration ¶
Declaration is a single property: value pair. Important marks a trailing `!important` on the original declaration; it is consulted by the cascade, never by a property's value parser (Value never carries the marker).
func ParseDeclarations ¶
func ParseDeclarations(body string) []Declaration
ParseDeclarations parses a declaration block body ("a: b; c: d") into declarations, lowercasing property names and trimming values.
type Display ¶
type Display uint8
Display is the subset of the display property the engine understands.
const ( // DisplayInline is the default for unknown/inline elements. DisplayInline Display = iota // DisplayBlock stacks the box vertically in block flow. DisplayBlock // DisplayNone removes the element (and subtree) from layout. DisplayNone // DisplayInlineBlock is an atomic inline-level block (laid out as a block // but participating inline; Phase 1 treats it as block for simplicity). DisplayInlineBlock // DisplayFlex is a block-level flex container. DisplayFlex // DisplayTable is a block-level table box. DisplayTable // DisplayTableRow is a table row box. DisplayTableRow // DisplayTableCell is a table cell box. DisplayTableCell // DisplayTableRowGroup is a thead/tbody/tfoot grouping box (transparent to // the table's row collection). DisplayTableRowGroup // DisplayGrid is a block-level CSS grid container. DisplayGrid // DisplayContents makes the element generate no box of its own — its // children behave as if they were direct children of ITS parent instead // (the element itself becomes fully transparent to layout, though it still // exists in the DOM/cascade). See layout.renderedChildren, the single // chokepoint every layout algorithm (block, inline, flex, grid, table) // walks a container's children through, which is where this is resolved — // so a DisplayContents element never itself reaches box placement. DisplayContents // DisplayInlineFlex is a flex container that is itself an INLINE-level // box in its parent's flow (unlike DisplayFlex, which is block-level) — // distinct from DisplayFlex specifically so isBlockLevel and the inline- // collection walk can tell them apart; internally it lays out its // children with the identical flex algorithm. Confirmed live on // pkg.go.dev: `.go-Breadcrumb li{display:inline-flex}` stacked each // breadcrumb item on its own line instead of flowing in a row, because // this engine previously parsed `inline-flex` down to the SAME // DisplayFlex value as `flex`, losing the "inline" qualifier entirely and // making every such element block-level. See // layout.appendElementInline's own handling for how the atomic // inline-level box is produced. DisplayInlineFlex )
type Edges ¶
type Edges struct{ Top, Right, Bottom, Left float64 }
Edges holds the four sides of a margin or padding box, in pixels.
type Filter ¶ added in v0.4.0
type Filter struct {
Kind FilterKind
Amount float64 // multiplier / fraction / blur sigma (px) / hue radians
// Drop-shadow parameters (only meaningful when Kind == FilterDropShadow).
OffsetX, OffsetY float64
Blur float64
Color Color
UseCurrentColor bool // resolve Color from the element's `color` at paint time
}
Filter is one parsed `filter` function.
type FilterKind ¶ added in v0.4.0
type FilterKind int
FilterKind identifies one CSS `filter` function.
const ( // FilterBlur is blur(<length>): a Gaussian blur whose standard deviation is // Amount pixels. FilterBlur FilterKind = iota // FilterBrightness is brightness(<number|percentage>): a per-channel linear // multiply by Amount (Amount>=0, may exceed 1). FilterBrightness // FilterContrast is contrast(<number|percentage>): scales each channel about // the 0.5 mid-point by Amount. FilterContrast // FilterSaturate is saturate(<number|percentage>): a saturation matrix with // Amount as the saturation factor (may exceed 1). FilterSaturate // FilterGrayscale is grayscale(<number|percentage>): interpolates towards // luminance by Amount in [0,1]. FilterGrayscale // FilterSepia is sepia(<number|percentage>): interpolates towards a sepia // tone by Amount in [0,1]. FilterSepia // FilterHueRotate is hue-rotate(<angle>): rotates hue by Amount radians. FilterHueRotate // FilterInvert is invert(<number|percentage>): interpolates towards the // negative by Amount in [0,1]. FilterInvert // FilterDropShadow is drop-shadow(<offset-x> <offset-y> <blur>? <color>?): // a blurred, offset, coloured copy of the element's alpha painted behind it. FilterDropShadow )
type FlexDirection ¶
type FlexDirection uint8
FlexDirection is the main-axis direction of a flex container.
const ( // FlexRow lays items along the inline (horizontal) axis. FlexRow FlexDirection = iota // FlexColumn lays items along the block (vertical) axis. FlexColumn )
type FontFace ¶ added in v0.4.0
type FontFace struct {
// Family is the family name, lowercased: `font-family` inside the rule,
// which is what a `font-family` declaration elsewhere has to name to
// reach this face.
Family string
// Srcs are the `src` entries in declaration order — a consumer takes the
// first whose Format it can decode. `local()` entries are dropped: there
// is no system font database here to look a face up in.
Srcs []FontSrc
// Weight is the weight the files carry, 400 when the rule gives none. A
// range ("font-weight: 100 900", a variable font) collapses to its first
// value, since only a static instance can be loaded.
Weight int
// Italic is set for `font-style: italic` or `oblique`.
Italic bool
}
FontFace is one `@font-face` rule: a named family, the files it can be loaded from in preference order, and which weight and slant of that family the files carry.
A page that wants a typeface the engine does not bundle says so here and nowhere else, so this is the only route by which a document's own fonts can reach it. Before this existed, `@font-face` was skipped as an unrecognised at-rule and every run was set in one of the three bundled families — so a poster asking for IBM Plex Sans and Spectral was typeset in Inter and Lora, silently and at different metrics.
func ParseFontFaces ¶ added in v0.4.0
ParseFontFaces returns every `@font-face` rule in a stylesheet source, in document order, including those nested in an `@media` block whose condition matches m. A rule with no family or no usable src is dropped.
It is a separate pass from ParseStylesheetMedia rather than another Rule variant: an @font-face rule has no selector and takes no part in the cascade, so nothing that walks rules should have to learn to skip it.
type FontFamily ¶
type FontFamily struct {
// Names are the non-generic families named, lowercased and joined with
// commas in declaration order. Empty when the declaration named only
// generic keywords.
Names string
// Generic is the bucket to use when no named family is available — the
// generic keyword the declaration ended with, or the one implied by a
// name the engine recognises ("helvetica" means sans).
Generic Generic
}
FontFamily is a resolved `font-family`: the families the declaration named, in order, and the generic bucket to fall back on when none of them can be loaded.
It used to be the bucket alone, which meant a document could never ask for a typeface by name: a poster written for IBM Plex Sans and Spectral was typeset in the bundled Inter and Lora, silently and at different metrics. Names is what an @font-face rule (see FontFace) is matched against.
It stays comparable — a struct of a string and a byte — because it is a map key in the font layer, which caches a parsed face per (family, weight, slant).
func (FontFamily) NamedFamilies ¶ added in v0.4.0
func (f FontFamily) NamedFamilies() []string
NamedFamilies returns the families this declaration named, in the order it named them — what a consumer walks to find the first face it holds.
type FontSrc ¶ added in v0.4.0
type FontSrc struct {
// URL is the url() as written — relative to the stylesheet it came from,
// which is the caller's to resolve.
URL string
// Format is the format() hint, lowercased and unquoted ("truetype",
// "woff2", "opentype", …), or empty when the rule gave none. It is a
// HINT: a consumer that cannot decode a format should skip the entry,
// but an entry without one may still be anything.
Format string
}
FontSrc is one entry of an `@font-face` rule's `src`.
type Generic ¶ added in v0.4.0
type Generic uint8
Generic is one of CSS's generic font families — the bucket a run falls into when none of the families it named is available.
type Gradient ¶
type Gradient struct {
Radial bool
// Linear direction. Corner is 0 for an explicit angle (AngleDeg), else a
// corner code 1..4 (top-right/bottom-right/bottom-left/top-left) whose angle
// depends on the box size and is resolved in the sampler.
AngleDeg float64
Corner uint8
// Radial geometry.
Shape RadialShape
Extent RadialExtent
RadiusX, RadiusY Length // meaningful when Extent==ExtentExplicit
PosX, PosY Length // centre; default 50% 50%
Stops []ColorStop
}
Gradient is a parsed linear or radial gradient.
func (*Gradient) Sampler ¶
func (g *Gradient) Sampler(w, h float64) GradientSampler
Sampler builds a GradientSampler for a box of the given size (W×H, pixels).
type GradientGeometry ¶ added in v0.9.0
type GradientGeometry struct {
Radial bool
X0, Y0, X1, Y1 float64
CX, CY, RX, RY float64
Stops []GradientStop
}
GradientGeometry is a sampler's resolved shape, in the box-local pixel space At is queried in: what a vector backend needs to emit the same gradient as a shading instead of sampling it pixel by pixel.
For a linear gradient (Radial false) the colour at fraction t of the stops lies on the line through (X0,Y0) + t·((X1,Y1)−(X0,Y0)) perpendicular to that axis, clamped to the end colours beyond it. For a radial one it lies on the ellipse centred on (CX,CY) with radii t·RX and t·RY, clamped beyond t = 1.
type GradientSampler ¶
type GradientSampler struct {
// contains filtered or unexported fields
}
GradientSampler evaluates a gradient's colour at any point of a W×H box. It is built once per box (Gradient.Sampler) with its geometry and normalised stops precomputed, then queried per pixel with At.
func (GradientSampler) At ¶
func (s GradientSampler) At(px, py float64) Color
At returns the gradient colour at box-local point (px, py).
func (GradientSampler) Geometry ¶ added in v0.9.0
func (s GradientSampler) Geometry() GradientGeometry
Geometry returns the sampler's resolved shape and stops.
type GradientStop ¶ added in v0.9.0
GradientStop is one colour stop of a sampler, resolved: Pos is its fraction along the gradient line (linear) or ray (radial), non-decreasing from one stop to the next; two stops may share a position (a hard edge).
type GridLine ¶
GridLine is one endpoint of a grid item's placement. Auto means the endpoint is chosen by auto-placement; Span means the endpoint is "span N" tracks from the opposite edge; otherwise N is a 1-based grid line number.
type ImageRendering ¶ added in v0.4.0
type ImageRendering uint8
ImageRendering selects the resampling filter used to scale a raster image (an <img> or a background-image) to a size other than its intrinsic one. It inherits (per CSS Images 3).
const ( // IRAuto lets the engine pick a high-quality (smooth) filter — a bicubic // resample. It is the initial value and also covers `smooth` / `high-quality`. IRAuto ImageRendering = iota // IRPixelated preserves hard pixel edges by nearest-neighbour sampling, the // behaviour pixel art wants (`image-rendering: pixelated`). `crisp-edges` // maps here too: both ask the engine not to smooth the image. IRPixelated )
type Justify ¶
type Justify uint8
Justify is the main-axis distribution (justify-content).
const ( // JustifyStart packs items at the main-start edge (initial value). JustifyStart Justify = iota // JustifyEnd packs items at the main-end edge. JustifyEnd // JustifyCenter centres items on the main axis. JustifyCenter // JustifySpaceBetween distributes free space between items. JustifySpaceBetween // JustifySpaceAround distributes free space around items. JustifySpaceAround // JustifySpaceEvenly distributes free space evenly incl. the ends. JustifySpaceEvenly )
type Length ¶
type Length struct {
Px float64
Percent float64 // 0..1; only meaningful when IsPercent
IsPercent bool
Auto bool
}
Length is a resolved CSS length. Percentages are kept separately so the layout can resolve them against the containing block's width.
type LineHeight ¶
type LineHeight struct {
Px float64
Factor float64 // unitless multiplier of the element's own font-size (0 = none)
Normal bool
}
LineHeight is a computed line-height. Normal means "use the font's own line height". Otherwise it is EITHER a fixed pixel height (Px, from a length or percentage value) OR a unitless Factor of the element's own font-size.
The distinction is load-bearing for inheritance (CSS 2.1 §10.8.1): a length or percentage line-height computes to a fixed pixel value that descendants inherit unchanged, whereas a unitless number computes to the number itself and inherits AS the number, so each descendant re-multiplies by its OWN font-size. Collapsing a unitless line-height to pixels at the declaring element (as an earlier version did) leaks the ancestor's font-size onto larger/smaller descendants, making their line boxes too short and letting glyphs from adjacent lines overlap.
func (LineHeight) Resolve ¶
func (h LineHeight) Resolve(fontSize float64) (float64, bool)
Resolve returns the used line-height in pixels for an element whose computed font-size is fontSize, and whether an explicit height applies. It returns (0, false) for `normal` (defer to the font's natural metrics) and for a non-positive height. A unitless Factor is multiplied by fontSize; a fixed Px is returned unchanged.
type LinkRef ¶
type LinkRef struct {
Href string // the raw href attribute value (unresolved)
Media string // the raw media attribute value ("" when absent)
}
LinkRef is a reference to an external stylesheet declared by a <link rel="stylesheet" href="..."> element, with its media attribute.
func StylesheetLinks ¶
StylesheetLinks walks the DOM in document order and returns every <link rel="stylesheet"> href, preserving order (which fixes cascade precedence). Links whose rel does not contain the "stylesheet" token, or that carry the "alternate" token, or that have an empty href, are skipped.
type ListStylePosition ¶
type ListStylePosition uint8
ListStylePosition is where the marker sits relative to the item's content. It inherits.
const ( // ListOutside places the marker in the indent to the left of the content box // (the initial value). ListOutside ListStylePosition = iota // ListInside places the marker inside the content box, before the first line. ListInside )
type ListStyleType ¶
type ListStyleType uint8
ListStyleType is the marker style of a display:list-item box. It inherits.
const ( // ListDisc is a filled circle (the initial value / <ul> default). ListDisc ListStyleType = iota // ListCircle is a hollow (stroked) circle. ListCircle // ListSquare is a filled square. ListSquare // ListDecimal is an ascending decimal number ("1.", "2.", …) — the <ol> default. ListDecimal // ListNone paints no marker. ListNone )
type Media ¶ added in v0.4.0
type Media struct {
Type string // Screen ("" or "screen") or Print; compared case-insensitively
Width float64 // viewport width, CSS px
// Scale is the device pixel ratio: device pixels per CSS px (CSSOM View
// devicePixelRatio). 0 means 1. It is what the `resolution` media feature
// reports (Media Queries 4 §5.1, in dppx), and a cascade run against it
// computes every absolute length in device pixels (see scaleLengths).
Scale float64
}
Media is what a stylesheet is evaluated against: the media TYPE — screen (the zero value) or print — that "@media print", "@media screen" and a <link media="print"> select on, and the viewport width that min-width / max-width features compare with. A browser's screen render and its print preview of the same page differ by exactly this value: sites hide their navigation, sidebars and footers under @media print, and a print-only stylesheet is skipped on screen. Every Cascade*/ParseStylesheet* entry point that takes only a viewport width evaluates for screen.
type ObjectFit ¶ added in v0.4.0
type ObjectFit uint8
ObjectFit selects how a replaced element's (img/video) own content is scaled to its box, when that box's size (from width/height/aspect-ratio) doesn't match the content's own intrinsic aspect ratio — the equivalent of background-size for a foreground element rather than a background layer. Confirmed live (round 89) as `object-fit:cover`, the standard Tailwind `object-cover` utility: react.dev's own avatar/video-thumbnail images and tailwindcss.com's own gallery images. `object-position` is not modelled — no confirmed non-centered real use — so cover/contain always centre.
const ( // ObjectFitFill stretches the content to exactly fill the box, distorting // its aspect ratio if the box's doesn't match — the initial value, and // this engine's own prior behaviour for every image before this existed. ObjectFitFill ObjectFit = iota // ObjectFitCover scales the content, preserving its aspect ratio, to // cover the whole box, cropping any overflow — same mechanism as // background-size:cover, applied to a foreground element. ObjectFitCover // ObjectFitContain scales the content, preserving its aspect ratio, to // fit entirely inside the box, letterboxing any remainder. ObjectFitContain )
type Overflow ¶ added in v0.2.0
type Overflow uint8
Overflow is the computed overflow of one axis. For the engine's headless paint every non-visible value (hidden/clip/scroll/auto) clips descendant painting to the box's padding box — there is no interactive scrolling, so a scroll/auto container renders exactly its visible window, matching the first paint a user would see. This is what keeps the universal `sr-only` / visually-hidden pattern (position:absolute;width:1px;height:1px; overflow:hidden;clip) from painting its screen-reader text at full size.
type PageRule ¶ added in v0.4.0
type PageRule struct {
Selector string
Declarations []Declaration
}
PageRule is one @page block as written (CSS Paged Media Level 3), with its selector — "" for the unqualified rule, ":first", ":left", ":right", ":blank", or a named page ("cover") — and its declarations in order. A selector list (`@page :left, :right { … }`) yields one PageRule per selector, sharing the declarations. Margin boxes nested inside (`@top-center { content: counter(page) }`) are skipped — they are not modelled yet — and the declarations around them are kept.
func ParsePageRules ¶ added in v0.4.0
ParsePageRules returns the @page rules of a stylesheet, in order, honouring an enclosing @media (a @page inside `@media print` applies under Print only) and unwrapping @layer exactly as ParseStylesheetMedia does for style rules. Style rules and every other at-rule are passed over: the page context is read separately from the element cascade, which never sees a @page block (parseRules skips it as an unknown at-rule).
type PageSpec ¶ added in v0.4.0
type PageSpec struct {
Width, Height float64
Margin [4]float64 // top, right, bottom, left, CSS px
MarginSet [4]bool
}
PageSpec is a resolved page: the page box size and margins in CSS px. A zero Width/Height means size was not given (auto — the consumer's default page; `size: landscape` alone, an orientation for that default, also leaves them zero). MarginSet says which margins a rule set; an unset margin is the consumer's default too.
func DocumentPage ¶ added in v0.4.0
DocumentPage gathers the @page rules of every <style> in root (document order) and of externalSheets (external sheets first, as in the cascade: a <link> stylesheet loads in the head, before the document's own <style>), evaluated against m, and resolves selector — "" for the document's default page. externalSheets should come from the same Media-aware fetch the cascade uses (engine.LoadStylesheets), so a <link media="print"> sheet's @page is seen exactly when its style rules are.
func ResolvePage ¶ added in v0.4.0
ResolvePage applies, in source order, every rule whose selector is "" and then every rule whose selector equals selector, so a ":first" page inherits the unqualified rule's values and overrides what it sets. Understood: size (auto | one or two lengths — one length is a square page | a named size and/or portrait|landscape, in either order) and margin / margin-top|right|bottom|left (absolute lengths, em/rem at 16px; a percentage or auto is ignored). Anything else — margin boxes, page, bleed, marks — is ignored.
type Position ¶
type Position uint8
Position is the subset of the position property the engine understands.
const ( // PositionStatic is the initial value: the box is in normal flow with no // top/right/bottom/left offset applied. PositionStatic Position = iota // PositionRelative keeps the box in normal flow (it still reserves space) but // paints it shifted by its top/left/right/bottom offset. PositionRelative // PositionAbsolute removes the box from normal flow and positions it against // the padding box of the nearest positioned ancestor (else the initial // containing block). PositionAbsolute // PositionFixed removes the box from normal flow and positions it against the // initial containing block (the viewport). For a full-page static render it is // resolved to document coordinates so it paints once at its place. PositionFixed // PositionSticky is approximated as relative for a full-page static shot. PositionSticky )
func (Position) OutOfFlow ¶
OutOfFlow reports whether a position value takes the box out of normal flow (so it reserves no space in its parent's block/inline formatting context).
func (Position) Positioned ¶
Positioned reports whether a position value makes the box a containing block for absolutely-positioned descendants (anything other than static).
type RadialExtent ¶
type RadialExtent uint8
RadialExtent is the sizing keyword of a radial gradient's ending shape.
const ( // ExtentFarthestCorner is the default extent. ExtentFarthestCorner RadialExtent = iota // ExtentClosestSide sizes to the nearest edge. ExtentClosestSide // ExtentClosestCorner sizes to the nearest corner. ExtentClosestCorner // ExtentFarthestSide sizes to the farthest edge. ExtentFarthestSide // ExtentExplicit uses explicit RadiusX/RadiusY lengths. ExtentExplicit )
type RadialShape ¶
type RadialShape uint8
RadialShape selects a circular or elliptical radial gradient.
const ( // RadialEllipse is the default radial shape. RadialEllipse RadialShape = iota // RadialCircle forces equal radii. RadialCircle )
type Rule ¶
type Rule struct {
Selectors []Selector
Declarations []Declaration
// Container is non-nil when this rule came from (directly, or via nested
// @media/@layer) an `@container` at-rule: its selectors only take effect
// for a matched element when the condition holds against that element's
// nearest qualifying ancestor container, evaluated at cascade time (see
// container.go — this is deferred, unlike @media, because it depends on
// per-element ancestor geometry rather than a single known viewport
// width).
Container *ContainerCondition
}
Rule is a parsed style rule: a list of selectors sharing a declaration block.
func ParseStylesheet ¶
ParseStylesheet parses a full stylesheet into rules at the default viewport width. See ParseStylesheetVW to control @media evaluation.
func ParseStylesheetMedia ¶ added in v0.4.0
ParseStylesheetMedia is ParseStylesheetVW evaluated against a Media — the entry point that makes "@media print" blocks apply.
func ParseStylesheetVW ¶
ParseStylesheetVW parses a full stylesheet into rules, evaluating @media blocks against viewport width vw: a matching @media block's inner rules are included, a non-matching one is skipped. @layer blocks are unwrapped (their rules included as if the layer boundary were not there — this engine does not model cross-layer cascade priority, but that is far less wrong than dropping the content: Tailwind v4's default output, among many other frameworks, puts nearly all of its CSS inside @layer). @container blocks are always included, with their condition attached to each inner rule for the cascade to evaluate per-element once real layout geometry is available (see container.go) — @container style(...) queries are not supported and are skipped wholesale, like any other unrecognised at-rule (@font-face, @keyframes, @supports, @import, ...). Malformed rules are skipped defensively.
type Selector ¶
type Selector struct {
// contains filtered or unexported fields
}
Selector is a full complex selector: a chain of compound selectors joined by combinators. parts is in source order (left to right); parts[len-1] is the key selector matched against the candidate element, and each preceding part constrains an ancestor or sibling via combs[i] (the combinator between parts[i] and parts[i+1]).
func ParseSelectorList ¶
ParseSelectorList parses a comma-separated selector list, skipping empty and unparseable entries. Commas inside functional pseudo-classes (`:is(a, b)`) are respected (not treated as list separators), and `:is()` / `:where()` / `:matches()` wrappers are expanded into plain selectors — the form modern Tailwind emits for its dark-mode and variant rules (`:is(.dark .dark\:bg-wash-dark)`).
func (Selector) Matches ¶
Matches reports whether the selector matches element n, evaluating the combinator chain from the key selector leftwards. It is exactly MatchesHost(n, nil) — outside any shadow tree there is no host to bind ":host" to, so a Host compound anywhere in the selector always fails.
func (Selector) MatchesHost ¶ added in v0.4.0
MatchesHost is Matches with host bound as the enclosing shadow tree's host element (see cascade.go's shadow-scoped cascade walk), so ":host" and ":host(<selector>)" compounds — the shadow tree's one sanctioned way to style, or reach past, the host from inside its own scoped stylesheet — can match. It is the ONLY way a Host compound ever matches; every other compound is unaffected by host.
func (Selector) Specificity ¶
Specificity returns the (a, b, c) specificity packed into a single int, with a = id count, b = class count, c = tag count, summed over all compounds.
type Style ¶
type Style struct {
Display Display
Visibility Visibility // inherited; see the Visibility doc comment
Color Color
Background Color
FontSize float64 // px
FontWeight int // 400 = normal, 700 = bold
FontFamily FontFamily
Italic bool // font-style: italic|oblique (inherited)
// Underline is set by `text-decoration`/`text-decoration-line: underline`,
// cleared by any other explicit line value (including "none"). Real CSS
// text-decoration-line is NOT an inherited property — instead, a line set
// on a block/inline CONTAINER visually propagates through its descendant
// inline boxes unless a descendant sets its own line explicitly. This
// engine approximates that (real) propagation the same way an actually-
// inherited property would: copied from parent to child, then
// overwritten by an explicit declaration on the child itself — correct
// for the overwhelming majority of real content (an <a> or a heading
// carries `text-decoration:underline`; its own descendant <strong>/<code>
// inherits the line via this field exactly as it should), and only wrong
// for the rare case of a descendant deliberately opting OUT with its own
// `text-decoration-line:none` while an ancestor's line is meant to still
// show through — not modelled here. Only the "underline" line is tracked
// (line-through/overline/blink are not) and text-decoration-color/style/
// thickness are not modelled: the rendered line is always solid, in the
// text's own current colour — deliberately narrow, matching the
// overwhelmingly common real-world "a hyperlink is underlined" pattern
// this was found missing for entirely: text-decoration had NO Style
// field, no parser case and no paint code at all before this — every
// underline on every page this engine has ever rendered was silently
// dropped, confirmed live on developer.mozilla.org's own in-article
// links (`:is(.content-section a):not([href^="#"])
// {text-decoration:underline}`, present in real Chrome, absent here).
Underline bool
// Fill/Stroke are the SVG paint properties (inherited, like Color).
// FillSet/StrokeSet distinguish "CSS resolved a concrete colour" from
// "left to the SVG document's own presentation attribute" — svg.go's
// serializeSVG is the only reader: unset changes nothing (the element's
// own `fill`/`stroke` XML attribute, if any, survives untouched), set
// overrides it with the CSS value. FillNone/StrokeNone models
// `fill:none`/`stroke:none` (paint suppressed) separately, since Color{}
// (transparent black) is also a legitimate real colour value. Confirmed
// load-bearing live: tailwindcss.com's nav icons (search glyph, version-
// badge chevron, logo mark) and CTA underline are coloured entirely via
// Tailwind's `fill-*`/`stroke-*` utility classes, never an XML `fill=`
// attribute — before this, every such icon rasterised with SVG's initial
// fill (black), often invisible against a dark background.
Fill Color
FillSet bool
FillNone bool
Stroke Color
StrokeSet bool
StrokeNone bool
Margin Edges
Padding Edges
Border Borders
Width Length // Auto by default
MinWidth Length // Auto (== none) by default
MaxWidth Length // Auto (== none) by default
Height Length // Auto by default
MinHeight Length // Auto (== none) by default
MaxHeight Length // Auto (== none) by default
// AspectRatio is the preferred width/height ratio from `aspect-ratio:
// <W>/<H>` (0 means "auto", i.e. unset — no ratio constrains sizing).
// Confirmed load-bearing live (round 86): Next.js's own auto-generated
// image-placeholder wrapper divs (tailwindcss.com's blog/changelog cover
// images) size themselves ENTIRELY via `aspect-ratio` plus a resolved
// width, with no explicit height at all — reserving the correct space
// before the image itself loads, to avoid a layout shift. Only the
// width-known/height-auto direction is resolved (see usedHeight); the
// reverse (height known, width auto) has no confirmed real caller and is
// not attempted.
AspectRatio float64
// ObjectFit controls how a replaced element's OWN content (the loaded
// image bitmap) is scaled into its box when the two aspect ratios don't
// match — see ObjectFit's own doc comment. Fill (the zero value) is this
// engine's own pre-existing behaviour (stretch to exactly fill the box).
ObjectFit ObjectFit
BoxSizing BoxSizing
TextAlign TextAlign
WhiteSpace WhiteSpace
TextTransform TextTransform
LineHeight LineHeight
// LetterSpacing is `letter-spacing`'s resolved value in px (0 = `normal`,
// the initial value), added after EVERY character of a text run
// (including its last, matching real browsers — this is why a run's
// measured width grows even for a single-character string). Inherited,
// per spec. Only the line-wrapping width (layout.appendWords, the
// confirmed real path — tailwindcss.com's own large `tracking-tighter`
// hero heading and `tracking-widest` sidebar labels) and painting
// (paint.drawText) apply it; the shrink-to-fit/min-content intrinsic-
// sizing paths (preferredWidth, minContentWidth, flex/table sizing) do
// not add it to their own measurements — a documented, narrower scope
// than every possible consumer of Measure, matching this engine's
// established practice of shipping the confirmed-reachable path first.
LetterSpacing float64
// BorderSpacingH/V are `border-spacing`'s horizontal/vertical values in
// px (both 0 = the initial value, no gap). Inherited, per spec (one of a
// handful of table-specific properties CSS inherits despite table
// layout itself not otherwise being inherited). Confirmed real on
// en.wikipedia.org's own infobox (`.infobox{border-spacing:3px}`, cells
// styled with only a `border-bottom` — the gap this property adds
// between rows IS the infobox's visible row-to-row breathing room, not
// merely a cosmetic nicety). layout.table applies it unconditionally,
// without checking `border-collapse` (not modelled at all yet, so every
// table is implicitly always "separate", the default) — spec says
// border-spacing has no effect under `border-collapse:collapse`, but
// both of this engine's own confirmed real cases are unaffected by that
// simplification: Wikipedia's infobox never sets border-collapse at all
// (stays at the default 'separate' this engine already assumes), and
// pkg.go.dev's own `table{border-collapse:collapse;border-spacing:0}`
// reset sets spacing to zero anyway, so applying it unconditionally
// there is a no-op regardless.
BorderSpacingH, BorderSpacingV float64
// CenterAsBlock is TextAlign == AlignCenterBlocks for every element,
// EXCEPT it stays true for a <table> whose PARENT has TextAlign ==
// AlignCenterBlocks even in quirks mode, where the table's OWN TextAlign
// is separately reset to AlignLeft (see css/ua.go's quirks-mode table
// rule) so its cells' text is correctly left-, not centre-, aligned.
// Layout's "centre a definite-width block within its container" rule
// (the legacy <center>/align=center effect) reads THIS field rather than
// TextAlign directly, because real browsers keep the two questions
// (should MY OWN text be centred vs. should I, as a block, be centred
// within my parent) independent for exactly this one quirks-mode case —
// confirmed against the HTML standard, whose quirks-mode reset is scoped
// to the `table` selector alone. Every other element (including a
// <center> with its own definite width, which must centre ITSELF using
// its own AlignCenterBlocks default regardless of its parent) is
// unaffected: CenterAsBlock and TextAlign==AlignCenterBlocks always
// agree for it.
CenterAsBlock bool
// ImageRendering selects the scaling filter for raster images (inherited).
// The initial value IRAuto means high-quality (bicubic); IRPixelated asks
// for nearest-neighbour (pixel art).
ImageRendering ImageRendering
// ListItem marks a display:list-item box (it generates a marker). It is not
// inherited. ListStyleType and ListStylePosition are inherited and select the
// marker glyph and its placement.
ListItem bool
ListStyleType ListStyleType
ListStylePosition ListStylePosition
// BorderRadius is the corner radius applied to the border box when painting
// the background and border. It is a single (uniform) radius: the common real
// case (Tailwind `rounded-*`, pills, circles) sets all four corners equal.
// Differing per-corner radii are approximated by the last-applied value
// (documented in FIDELITY.md). A px length is used as-is; a percentage
// resolves against the box's smaller side at paint time. Zero (the initial
// value) means square corners.
BorderRadius Length
// Auto-margin flags: a margin explicitly set to `auto` centres or pushes the
// box; distinct from a 0 margin.
MarginLeftAuto bool
MarginRightAuto bool
// MarginLeft/RightIsPercent + MarginLeft/RightPercent (0..1) record a
// percentage margin-left/margin-right — resolved against the containing
// block's WIDTH at layout time in resolveWidths, the one place that
// already has cw available for exactly this purpose (mirroring how Width/
// Height keep percentages unresolved in a Length rather than eagerly
// computing a wrong value against an unknown containing block). Margin is
// a plain Edges (float64), not Length, everywhere else, so this is a
// narrower, side-channel addition rather than a wholesale type change.
// Scoped to margin-LEFT/RIGHT only — the confirmed real need (Wikipedia's
// own `.ambox{margin:0 10%}`, `@media(min-width:720px)`-gated, centring
// article cleanup-notice boxes within their column) only ever needs
// horizontal percentages; margin-top/margin-bottom percentages (also
// resolved against the containing block's WIDTH per spec, a common CSS
// quirk) have no confirmed real caller and are not modelled — a
// documented scope limit, not a silent gap for the case that matters.
MarginLeftIsPercent bool
MarginLeftPercent float64
MarginRightIsPercent bool
MarginRightPercent float64
// Overflow per axis (not inherited; initial value visible). Any non-visible
// value clips descendant painting to this box's padding box.
OverflowX Overflow
OverflowY Overflow
// HasClip/ClipRect model the legacy `clip: rect(top, right, bottom, left)`
// property — deprecated in favour of clip-path, but still a live, real-
// world pattern: confirmed load-bearing on pkg.go.dev's own "skip to main
// content" link (`clip:rect(0 0 0 0)`, the CSS2-era screen-reader-only
// idiom, predating the now-common `width:1px;height:1px;overflow:hidden`
// version this engine already honours via Overflow above — that one has
// no effect here since this element sets no explicit small width/height,
// only `clip`). Per spec this only applies when Position is absolute or
// fixed, and all four edges are distances from the box's own top-left
// BORDER edge (not the CSS shorthand's usual top/right/bottom/left box
// edges) — `rect(0,0,0,0)` is therefore a zero-size rectangle at the
// box's own corner, clipping it and its content to nothing while it
// still occupies its normal layout position (unlike display:none). Only
// the common case (all four values explicit lengths) is modelled; a
// `clip: auto` per edge, or any other value this engine's length parsing
// does not resolve, leaves HasClip false — clip:rect() with a genuinely
// variable edge is rare in practice (the sr-only idiom always hard-codes
// all four to 0), so this is not a real-world loss.
HasClip bool
ClipRect Edges // Top, Right, Bottom, Left offsets from the box's own corner
// Positioning that affects normal flow.
Float Float
Clear Clear
// CSS position and the box-offset properties. Top/Right/Bottom/Left are Auto
// by default (the initial value of each offset). ZIndex is meaningful only
// when ZIndexAuto is false; the initial value is auto (paint in tree order).
Position Position
Top Length // Auto by default
Right Length // Auto by default
Bottom Length // Auto by default
Left Length // Auto by default
ZIndex int
ZIndexAuto bool // true == "auto" (the initial value)
// TranslateX/TranslateY and RotateDeg are the TWO subsets of the
// `transform` property this engine understands, EACH ONLY on its own —
// a value naming any other function (scale, skew, matrix, 3D), or
// mixing more than one of these two together (`translate(...)
// rotate(...)` on the same element), is unsupported and leaves every
// transform field at zero, same as before either was understood at all
// — see FIDELITY.md's Known gaps. Translate (see parseTransformTranslate
// in parse.go) is a paint-time offset applied like a relative position
// shift, not a layout input; the initial/reset value (Length's Go zero
// value) is exactly "no translation", so unlike most other reset-not-
// inherited fields there is nothing to list explicitly in inheritFrom.
//
// RotateDeg (`rotate()`/`rotatez()`, see parseTransformRotate) is the
// angle in degrees to rotate the element's own border-box rectangle by,
// clockwise for a positive value — CSS's own convention. go-images'
// own Rotate is the OPPOSITE sign convention (its own doc comment:
// "rotated by angle degrees counter-clockwise", matching scikit-image),
// so paint.paintRotated negates the angle it passes in; get this backward
// and a page with any asymmetric rotated content (readable text/icons,
// not just tailwindcss.com's own near-symmetric card shapes) would spin
// the visibly wrong way, caught by checking the sign convention against
// the dependency's own doc comment before writing any code, not
// discovered after the fact. Unlike translate, this DOES need real
// coordinate-transform machinery: paint.paintRotated renders the box's
// own border-box rectangle to an offscreen buffer and rotates the
// PIXELS with go-images' Rotate (bilinear, growing the buffer to fit the
// rotated corners), rather than transforming any drawing coordinates —
// so it does not compose with `filter`/opacity/`mask-image` on the same
// element (paintBox only takes this path when none of those apply): no
// confirmed real trigger combines rotate with any of them, and doing so
// correctly needs the two offscreen-buffer mechanisms unified, a larger
// change than this one evidenced case justifies. transform-origin is
// not modelled either — always the default 50% 50% (the box's own
// centre), which is what every confirmed trigger already uses.
//
// This field backs BOTH `transform: rotate()`/`rotateZ()` (parsed by
// parseTransformRotate) and the newer, independent CSS Transforms Level
// 2 `rotate` property (parsed directly in `case "rotate"`, a bare
// angle with no function-call syntax at all — `rotate:90deg`,
// `rotate:none`) — two different spellings of the identical visual
// capability. Confirmed live (round 145): modern Tailwind (v4) compiles
// its `rotate-*` utilities to the STANDALONE property, not the
// `transform` function — `.rotate-(--angle){rotate:var(--angle)}` on
// tailwindcss.com's own "P3 colors" section, a grid of diagonal colour-
// name labels ("red", "orange", …) tilted -45°, confirmed directly from
// its compiled CSS bundle rather than assumed from the class name alone
// (an assumption that would have targeted the wrong property entirely
// and shipped a fix that silently did nothing on the one confirmed real
// page). `transform: rotate()` itself has no independently confirmed
// trigger of its own in this session's corpus — kept anyway since it is
// the same underlying, now-evidenced capability under CSS's older
// syntax, not a distinct speculative addition.
//
// Both spellings write the SAME field and so cannot compose with each
// other, or with `transform: translate()`/the (unimplemented) standalone
// `translate`/`scale` properties, correctly per spec — whichever this
// engine's cascade applies LAST simply overwrites the field. No
// confirmed trigger combines any of these, so this is disclosed rather
// than engineered around.
TranslateX Length
TranslateY Length
RotateDeg float64
// AppearanceNone is `appearance: none` (or `-webkit-appearance: none`),
// which suppresses a form control's native OS chrome so the author's own
// background/background-image/border/border-radius paint instead. Not
// inherited; the zero value (false) is the initial `auto`, matching every
// other form control this engine already renders with its own hardcoded
// UA-default look (see paintFormControl/paintCheckboxLike). Confirmed
// load-bearing live: developer.mozilla.org's own `<mdn-switch>` custom
// toggle-switch component styles a real `<input type=checkbox>` with
// exactly `appearance:none;background-color:...;background-image:radial-
// gradient(...);border-radius:9999px` UNCONDITIONALLY (not gated behind
// `:checked` — only the knob's position/colour REFINES on `:checked`), so
// a checkbox that sets this must stop taking this engine's own generic
// checkbox square and instead paint like any other styled box. Only the
// bare `none`/`auto` keywords are modelled; the CSS Basic UI vendor-
// specific appearance keywords (`textfield`, `menulist-button`, etc.) have
// no confirmed trigger and are left unsupported (equivalent to `auto`).
AppearanceNone bool
// Flex container properties (meaningful when Display == DisplayFlex).
FlexDirection FlexDirection
FlexWrap FlexWrap
JustifyContent Justify
AlignItems AlignItems
AlignContent AlignContent
// Gaps between flex lines/items and between grid tracks. RowGap is the
// cross-line / block-axis gap; ColumnGap is the main-axis / inline-axis gap.
RowGap Length
ColumnGap Length
// Flex/grid item properties (meaningful for a child of a flex/grid container).
FlexGrow float64
FlexShrink float64
FlexBasis Length // Auto == "auto" (use the item's width/content)
Order int // reorders items within a line (ascending)
AlignSelf AlignSelf
// Grid container properties (meaningful when Display == DisplayGrid).
GridTemplateColumns []TrackSize
GridTemplateRows []TrackSize
GridAutoRows TrackSize
GridAutoColumns TrackSize
GridAutoFlow GridFlow
// GridAutoFlowDense is the "dense" keyword on grid-auto-flow: the
// auto-placement cursor restarts from the very first grid cell before
// placing EACH item (potentially reordering items visually, out of DOM
// order, to backfill an earlier gap a bigger item left open) instead of
// only ever advancing forward from wherever the previous item landed
// (the default, "sparse", packing). See layout.placeItems.
GridAutoFlowDense bool
GridTemplateAreas [][]string // row-major grid of area names ("" == empty)
JustifyItems AlignItems // inline-axis alignment of items in their cell
// Grid item placement (meaningful for a child of a grid container).
GridColumnStart GridLine
GridColumnEnd GridLine
GridRowStart GridLine
GridRowEnd GridLine
GridArea string // named area this item is placed into (via grid-area)
JustifySelf AlignSelf
// CustomProps holds the element's resolved CSS custom properties (--name ->
// raw value). It is inherited from the parent and overridden by matched
// rules; var() references consult it at computed-value time. Nil until an
// element (or an ancestor) defines a custom property.
CustomProps map[string]string
// ColorScheme is the inherited `color-scheme` (CSS Color Adjust 1 §2.2);
// its UsedDark picks light-dark()'s branch. Zero value: `normal`.
ColorScheme ColorScheme
// Background image layers (gradients and url() bitmaps) and their paint
// parameters. Each list is indexed per layer (first-listed paints on top);
// a shorter size/position/repeat list repeats its last value. All nil == no
// background image (only the solid Background colour paints).
BackgroundImages []BgImage
BackgroundSize []BgSize
BackgroundPosition []BgPosition
BackgroundRepeat []BgRepeat
// MaskImage is the resolved absolute URL of a `mask-image`/
// `-webkit-mask-image: url(...)` — empty means no mask (the common case).
// Unlike background-image, a mask does not paint its own pixels: it is an
// alpha stencil applied over everything the ELEMENT ITSELF paints (colour,
// background, borders, text, children), stretched to fill the element's
// border box (this engine's one deliberate simplification — the real
// mask-size/mask-position/mask-repeat grammar is not modelled, matching
// how far this engine narrowly scoped `transform: translate()`). Confirmed
// load-bearing live: modern MediaWiki (Wikipedia's Vector-2022 skin, and
// most of its UI icon systems generally) renders EVERY toolbar icon —
// hamburger menu, search, language switcher — as an empty, solid-coloured
// <span> cut into shape by exactly this mechanism, so the icon recolours
// for dark mode without needing a second image asset. A gradient or other
// non-url() mask value is not modelled and leaves this empty.
MaskImage string
// BoxShadows are the element's box-shadow layers (first-listed paints on top).
BoxShadows []BoxShadow
// Opacity is the element's group opacity in [0,1]; HasOpacity distinguishes a
// genuine opacity from the zero value (an unset Style is fully opaque).
Opacity float64
HasOpacity bool
// Filters is the element's `filter` function chain, applied in order to the
// element's rendered output (its box plus subtree) as a group. Nil == no
// filter. Not inherited (`filter` is a non-inherited property).
Filters []Filter
// BackdropFilters is `backdrop-filter`'s function chain (same grammar and
// Filter type as Filters — see parseFilterList) applied to whatever has
// already been painted BEHIND this box's own border-box, before the box's
// own background/border/content paint on top — the standard "frosted
// glass" idiom (a translucent bar over blurred page content). Confirmed
// live on react.dev's own sticky nav bar and code-sandbox title bars
// (`backdrop-filter:blur(16px) saturate(2)` via Tailwind's `backdrop-blur-
// lg backdrop-saturate-200`, composed through CSS custom properties). Not
// inherited, matching Filters.
BackdropFilters []Filter
// ContainerType and ContainerName are `container-type`/`container-name`
// (or the `container` shorthand): whether this element establishes a
// query container for descendant `@container` rules, and under what
// name. Neither is inherited; the initial value is ContainerNormal / "".
ContainerType ContainerType
ContainerName string
// Fragmentation (CSS Fragmentation Level 3), as far as paged media is
// concerned — this engine's own screen layout ignores all five; a
// paginating consumer (the paginate package, go-pdfkit/html2pdf) reads
// them. break-before / break-after / break-inside are NOT inherited
// (initial auto); the legacy page-break-before/after/inside properties
// are aliases (always → page). orphans / widows ARE inherited (initial
// 2; only a positive integer is a valid value, anything else leaves the
// declaration ignored).
BreakBefore, BreakAfter Break
BreakInside BreakInside
Orphans, Widows int
// Multi-column layout (CSS Multi-column Layout Level 1): ColumnCount (0 =
// not set) and ColumnWidth (Auto = not set) are NOT inherited — a plain
// block box unless one or the other is set, exactly like `columns`'s own
// two longhands. The column gap itself is the SAME `column-gap`/`gap`
// property flex/grid already use (ColumnGap, declared with RowGap
// above) — real CSS defines it once, in the Box Alignment module, for
// all three layout modes, not a per-mode property. Its shared "unset"
// default is Length{Auto:true} (see initialStyle/inheritFrom), which
// flex/grid's own gapLen/gapPxCSS already treat identically to an
// explicit 0 — safe to give the field this default without touching
// their behaviour at all. Multi-column layout alone resolves Auto
// differently: to 1em (see layoutMultiCol), matching every browser's
// implementation of a value the spec itself only describes as "a
// reasonable value comparable to the width of a typical typeface's
// space character" — unlike flex/grid, where `normal` is defined to
// compute to a plain 0.
//
// Confirmed live (round 143/144): pkg.go.dev's own `.UnitFiles-fileList{
// columns:12.5rem 5;...}` — its package-documentation "Source Files"
// list, real Chrome laying ~29 short filenames out in up to 5 narrow
// columns instead of one long single column.
//
// What layout.layoutMultiCol actually models, deliberately narrower than
// the full spec, is exactly what that one confirmed trigger needs: N
// columns of even width, each in-flow ELEMENT child of the container
// (never a bare text node — no confirmed trigger mixes one into a
// columns container) placed as a single ATOMIC unit into whichever
// column a simple cumulative-height greedy pass assigns it to, never
// split across two columns even when a single child is themselves taller
// than a whole column (the same "atomic child, no fragmentation"
// simplification this engine already applies to a replaced element or a
// table row elsewhere). NOT modelled: column-span (spec keyword `all`,
// letting one child cross every column — no confirmed trigger),
// column-rule (the vertical divider line between columns — pure
// decoration, not modelled since the plain column gap alone is already
// the confirmed visual difference from a single column), TRUE column
// balancing (the real spec's iterative algorithm that minimises the
// tallest column's own height while respecting break-inside/break-before
// avoidance — this engine's single greedy pass, target = total÷count,
// approximates it closely for a roughly-uniform list like the confirmed
// trigger, but can leave a visibly taller last column for adversarial,
// very unevenly-sized content), and fragmenting a SINGLE child's own
// internal content (its own child elements, or its own wrapped text
// lines) across two columns — a browser's real balancing can split one
// long paragraph's lines between columns; this engine cannot, matching
// its own "atomic child" scope decision above.
ColumnCount int
ColumnWidth Length
// TabSize (CSS Text Module Level 3) is INHERITED, initial 8 — a preserved
// tab (white-space:pre/pre-wrap) advances to the next multiple of this
// many space-widths, counted as columns from the line's own start (see
// layout's own expandTabs). Confirmed live (round 91): pkg.go.dev's own
// `pre,textarea.code{tab-size:4}` on its real, tab-indented Go source
// samples — this engine's tab expansion already existed but read a
// hardcoded 8 regardless of this property, over-indenting by 2x. Only a
// bare, non-negative `<number>` is parsed; the sibling `<length>` form is
// spec-flagged "at risk" (may be dropped) and has no confirmed real use
// here, so an unparseable/negative value leaves the inherited value in
// place rather than resetting to some sentinel (0 is itself a valid,
// meaningful value: "don't render tabs").
TabSize int
// TextWrapBalance (CSS Text Module Level 4, `text-wrap`/`text-wrap-style`)
// is INHERITED, initial false ("auto", ordinary greedy wrapping) — `true`
// requests the `balance` value: redistribute a SHORT block's own line
// breaks so each line's leftover space is more even (a real browser's own
// algorithm is UA-defined; layout's own balanceLines approximates it by
// finding the narrowest width that still wraps to the SAME line count a
// plain greedy wrap already produces, then re-breaking at that width —
// the spec's own explicit invariant: balancing must never change the line
// count for 5 lines or fewer, and a UA "may" give up and fall back to
// `auto` past 10). Confirmed live (round 93): tailwindcss.com's own hero
// `<h1 class="... text-balance ...">` heading. Only `balance` itself is
// modelled — `pretty`/`stable`/`avoid-short-last-line` have no confirmed
// real use in this engine's own corpus and fall back to ordinary
// wrapping, same as the property's own initial `auto`.
TextWrapBalance bool
// TextWrapNowrap (CSS Text 4 `text-wrap`/`text-wrap-mode`) is INHERITED,
// initial false ("wrap") — `true` requests `nowrap`: per spec, "lines
// only break at forced line breaks; content that does not fit overflows"
// — exactly this engine's EXISTING white-space:nowrap wrapping behaviour
// (see layout.layoutInline's own `nowrap` parameter), but WITHOUT
// white-space's OTHER effect of collapsing runs of whitespace: the spec
// is explicit that text-wrap-mode affects line-breaking opportunities
// only, never whitespace collapsing/preservation. Confirmed live (round
// 93, flagged then as a follow-up rather than folded into that round's
// own `balance` fix): tailwindcss.com's own `.text-nowrap{text-wrap:
// nowrap}` on its code-toolbar filename labels.
TextWrapNowrap bool
// WordBreakAll is `word-break: break-all`, INHERITED, initial false. Per
// spec it makes any character boundary a break opportunity for a line
// that would otherwise overflow — this engine models only the ONE
// visible effect that has a confirmed real-world trigger: a single
// unbreakable "word" (one InlineItem — see appendWords) WIDER than the
// whole line gets split at a rune boundary instead of overflowing its
// container (layoutInline's splitOverlongWord). The stronger, more
// aggressive part of the real spec — breaking mid-word EAGERLY even when
// the word would otherwise have fit on its own line, purely for more
// even line lengths — is not modelled: no corpus page's own confirmed
// trigger (see OverflowWrapAnywhere below) needs it, and it would also
// change ordinary greedy-wrap line counts for text that already fits,
// a much larger blast radius for an unevidenced refinement. `keep-all`
// (suppress even CJK's own default inter-character breaks) is not
// modelled either — this engine does not implement CJK line-breaking in
// the first place, so there is nothing for it to suppress.
//
// See the Style method BreaksOverlongWords, which OR-combines this with
// OverflowWrapAnywhere: the two properties are independent (either one
// requests the same one effect this engine models), so they are kept as
// separate fields rather than one shared boolean — collapsing them at
// parse time would let an unrelated `word-break: normal` on a LATER
// declaration silently cancel an EARLIER `overflow-wrap: break-word`'s
// own effect, which real CSS's cascade would not do (they are different
// properties; each keeps its own computed value independently).
WordBreakAll bool
// OverflowWrapAnywhere is `overflow-wrap`/`word-wrap: break-word` (the
// unprefixed and legacy-alias property names for the same property) or
// `anywhere`, and also the deprecated `word-break: break-word` value
// (per spec, an alias for `word-break: normal; overflow-wrap: anywhere`,
// so it sets THIS field, not WordBreakAll). INHERITED, initial false.
// The spec distinguishes `break-word` (breaks only as an overflow-
// avoidance last resort, like this engine's one modelled effect) from
// `anywhere` (also affects min-/max-content intrinsic-size calculations,
// letting a container shrink narrower than its longest unbreakable
// word) — that intrinsic-sizing distinction is not modelled, since
// every confirmed real trigger (see BreaksOverlongWords) only needs the
// line-breaking effect the two values already share.
//
// Confirmed live: developer.mozilla.org sets `overflow-wrap:break-word`
// on `html` itself (inherited page-wide); pkg.go.dev's own package-doc
// pages set `word-break:break-all` on `.UnitFiles-fileList` (a `<ul>` of
// long bare filenames with no natural break points) and `word-break:
// break-word` on `.UnitDirectories td` (a subdirectory-listing table
// cell); news.ycombinator.com sets `word-break:break-word` on `.title a`
// (every story headline link, which occasionally IS a long bare URL).
// Before this, a real production page like any of these would overflow
// its own container with unbroken text exactly where a real browser
// wraps mid-word instead.
OverflowWrapAnywhere bool
// LineClamp is `-webkit-line-clamp`/`line-clamp`'s value: the maximum
// number of lines an element's inline content renders before being cut
// off, 0 meaning "not set" (unclamped — the CSS `none` keyword resolves
// to this too). Not inherited, matching `overflow` and `display`, the two
// properties `-webkit-line-clamp` is always paired with in real CSS (the
// "-webkit-box" multi-line-truncation idiom: `display:-webkit-box;
// -webkit-box-orient:vertical;-webkit-line-clamp:N;overflow:hidden`) —
// this engine models the actual clamping this idiom exists for without
// requiring the paired display/overflow declarations to also be present,
// since the confirmed real-world callers (see layout's own consumer)
// always clamp regardless of exactly which vendor ceremony wraps it.
// Truncating to N lines by DROPPING the rest is modelled; the trailing
// ellipsis glyph real browsers additionally insert on the last kept line
// is not — a documented, narrower scope than the full feature, matching
// the confirmed need (tailwindcss.com's own `line-clamp-2` on a `<p>`,
// which primarily needs the container to stop growing past two lines,
// not the exact glyph at the cut point).
LineClamp int
// VerticalAlign is `vertical-align`'s keyword value; VAlignBaseline (the
// zero value) is the default. Only VAlignTextBottom currently shifts an
// inline-level replaced element's position in its line — the sole
// confirmed real need (github.com's own octicon SVG icons, styled
// `vertical-align:text-bottom` throughout its Primer design system to
// align an icon's bottom edge with the surrounding text's font, not its
// baseline). The other keyword values are recognised here (so they don't
// silently fall through to some unrelated case) but not yet given
// positional effect — a narrower scope than the full property, matching
// this engine's established practice of shipping the confirmed-reachable
// value first. Not inherited, per spec.
VerticalAlign VerticalAlign
// TextOverflowEllipsis is `text-overflow:ellipsis` (vs. the default
// `clip`, false). It only has a visible effect combined with
// `white-space:nowrap` and a clipping `overflow-x` — the real, confirmed
// idiom (Tailwind's own `.truncate` utility: `overflow:hidden;
// text-overflow:ellipsis;white-space:nowrap`, found live on
// tailwindcss.com's own team-member role labels). `overflow:hidden`
// alone already clips the raw overflowing text with no code here at
// all; this field's only job is swapping that raw clip for a "…"-
// terminated one. Scoped to a line made ENTIRELY of plain text items (no
// nested inline element, image or forced break) — the confirmed real
// shape — a line mixing in any of those is left as plain overflow, a
// documented narrower scope than the full property (which also allows a
// custom replacement string, not modelled). Not inherited, per spec.
TextOverflowEllipsis bool
}
Style is the fully-computed style of an element, after cascade + inheritance.
func (*Style) BreaksOverlongWords ¶ added in v0.4.0
BreaksOverlongWords reports whether a single unbreakable "word" wider than its own line may be split at a rune boundary instead of overflowing — see WordBreakAll and OverflowWrapAnywhere's own doc comments for exactly which property values request this and why they are two independent fields rather than one.
type StyleMap ¶
StyleMap maps each element node to its fully-computed style.
func CascadeMedia ¶ added in v0.4.0
CascadeMedia is CascadeVW evaluated against a Media rather than a bare screen width: with Media{Type: Print, Width: w} the page's "@media print" rules apply and its "@media screen" rules do not — a print preview's cascade. externalSheets should then come from a fetch that selected <link media> the same way (engine.LoadStylesheets with the same Media).
func CascadeMediaContainers ¶ added in v0.4.0
func CascadeMediaContainers(root *dom.Node, m Media, externalSheets []string, containers map[*dom.Node]ContainerSize) StyleMap
CascadeMediaContainers is CascadeVWContainers against a Media — see CascadeMedia.
func CascadeVW ¶
CascadeVW computes a style for every element in the tree rooted at root, applying the user-agent stylesheet, author rules from every <style> element plus any externalSheets (already-fetched CSS text, e.g. <link> stylesheets), and inline style="" attributes, with proper specificity and inheritance. @media width queries are evaluated against viewport width vw. Every @container rule is treated as not matching (see CascadeVWContainers) since no container sizes are known yet — the caller re-cascades with CascadeVWContainers once a layout pass has measured them.
func CascadeVWContainers ¶ added in v0.4.0
func CascadeVWContainers(root *dom.Node, vw float64, externalSheets []string, containers map[*dom.Node]ContainerSize) StyleMap
CascadeVWContainers is CascadeVW extended with container-query support: containers maps a query-container element (one whose computed style sets container-type) to its size as measured by the most recent layout pass. A nil/empty containers (as CascadeVW passes) makes every @container rule evaluate to "not matching" — the correct, conservative behaviour before any layout has run, and the reason this is a separate entry point rather than a parameter CascadeVW always threads through: most callers (tests, the first cascade of a render) have no layout yet and want exactly today's CascadeVW behaviour with zero conceptual overhead.
Resolving an @container condition against real geometry is inherently a multi-pass affair — this single call only ever consults the containers map it is given; iterating cascade→layout→re-measure to a fixpoint is the caller's job (see engine's layoutWithContainers, which plays the same role for container queries that dynamic.go's settle loop plays for JavaScript: same shape, bounded passes, deterministic termination).
type TextAlign ¶
type TextAlign uint8
TextAlign is the horizontal alignment of inline content in a block.
const ( // AlignLeft is the initial value. AlignLeft TextAlign = iota // AlignCenter centres each line. AlignCenter // AlignRight right-aligns each line. AlignRight // AlignCenterBlocks centres inline content like AlignCenter and additionally // centres a definite-width block/table child within its container — the legacy // behaviour of the <center> element and the align="center" attribute (browsers' // -moz-center / -webkit-center). A plain text-align:center author rule never // produces it, so standards-mode blocks are unaffected. AlignCenterBlocks )
type TextTransform ¶ added in v0.4.0
type TextTransform uint8
TextTransform is `text-transform`: the capitalisation a run of text is RENDERED with, leaving the document's own characters untouched. It is applied where layout turns a text node into items, so measurement and painting see the same string — a label written "Partners" and styled uppercase must be measured as "PARTNERS", which is wider.
full-width and full-size-kana are not implemented; they parse as TTNone.
const ( // TTNone renders the text as the document wrote it. TTNone TextTransform = iota // TTUppercase renders every character in upper case. TTUppercase // TTLowercase renders every character in lower case. TTLowercase // TTCapitalize upper-cases the first letter of each word. TTCapitalize )
type TrackKind ¶
type TrackKind uint8
TrackKind is the sizing function of a single grid track.
const ( // TrackAuto sizes the track to its content (initial for implicit tracks). TrackAuto TrackKind = iota // TrackPx is a fixed pixel size. TrackPx // TrackPercent is a percentage of the container's content size on that axis. TrackPercent // TrackFr is a flexible fraction of the leftover space. TrackFr // TrackMinMax is a minmax(min,max) range; Min and Max are non-nil. TrackMinMax )
type TrackSize ¶
type TrackSize struct {
Kind TrackKind
Px float64 // TrackPx
Percent float64 // TrackPercent (0..1)
Fr float64 // TrackFr
Min, Max *TrackSize // TrackMinMax bounds
}
TrackSize is one column or row sizing function of a grid template.
type VerticalAlign ¶ added in v0.4.0
type VerticalAlign uint8
VerticalAlign is the `vertical-align` property's keyword value.
const ( VAlignBaseline VerticalAlign = iota VAlignTop VAlignBottom VAlignTextTop VAlignTextBottom VAlignMiddle VAlignSub VAlignSuper )
type Visibility ¶ added in v0.4.0
type Visibility uint8
Visibility is the visibility property: unlike Display, it is INHERITED, and hiding an ancestor still reserves its layout space and does not stop a descendant that resets `visibility:visible` from painting. `collapse` (its real effect is table-row-specific: the row's space is reclaimed) is treated identically to VisibilityHidden — a documented simplification, not a bug.
const ( // VisibilityVisible is the initial value. VisibilityVisible Visibility = iota // VisibilityHidden paints nothing for this box (background, border, // shadows, inline content) while still occupying its layout space; a // descendant box may re-show itself with its own `visibility:visible`. VisibilityHidden // VisibilityCollapse is treated the same as VisibilityHidden (see above). VisibilityCollapse )
type WhiteSpace ¶
type WhiteSpace uint8
WhiteSpace controls collapsing of whitespace and wrapping.
const ( // WSNormal collapses runs of whitespace and wraps at the block width. WSNormal WhiteSpace = iota // WSPre preserves spaces and newlines and does not wrap (as in <pre>). WSPre // WSNoWrap collapses whitespace like WSNormal but never wraps: the // block's content stays on one line, however wide. WSNoWrap // WSPreWrap preserves spaces and newlines and wraps; preserved spaces at // the end of a line hang (CSS Text 3 §3, §4.1.2). WSPreWrap // WSBreakSpaces is WSPreWrap except that preserved spaces never hang: // they take room and a line may break after any of them. WSBreakSpaces // WSPreLine collapses spaces like WSNormal but preserves newlines (as // forced breaks) and wraps. WSPreLine )
func (WhiteSpace) PreservesSpaces ¶ added in v0.8.5
func (w WhiteSpace) PreservesSpaces() bool
PreservesSpaces reports whether spaces and tabs are kept as written.