Documentation
¶
Overview ¶
Package engine is the pipeline from a document's blocks to pixels: it turns the block and inline tree the compiler builds into layout boxes, and draws and hit-tests them on a canvas.Canvas.
It covers the block and inline types with their layouts, line layout, the rendering context (Context), the lazily laid-out top level (StackBox, which answers position queries but keeps no position of its own), sideways-scrolling blocks (ScrollBox) and diagram blocks. It depends only on contracts (ast, styling, fonts, images, canvas) and the image cache, never on themes, the compiler or the public whynot package.
State is not its job: the scroll position, sideways offsets, hover and scrollbar timing belong to whynot.View, which hands drawing what it needs through Context (the ScrollOffset and Scrollbar hooks). Neither is parsing Markdown, input handling, or the public API.
Index ¶
- Constants
- func DrawBlockLayout(box BlockLayout, dst canvas.Canvas, x, y int, now time.Duration)
- type Axis
- type Block
- type BlockLayout
- type BlockquoteBlock
- type BlockquoteBox
- type CellAlignment
- type CodeBlock
- type Context
- type DiagramBlock
- type EmptyBox
- type Hit
- type ImageBox
- func (b *ImageBox) Bounds() image.Rectangle
- func (b *ImageBox) BoundsAndAdvance() (image.Rectangle, int)
- func (b *ImageBox) DrawInline(dst canvas.Canvas, x, y int, now time.Duration)
- func (b *ImageBox) Glued() bool
- func (b *ImageBox) HitTest(p image.Point, x, y int) (Hit, image.Point)
- func (b *ImageBox) PendingImages() []string
- func (b *ImageBox) Source() Source
- func (b *ImageBox) SpaceWidth() int
- type Inline
- type InlineImage
- type InlineLayout
- type InlineText
- type LineBox
- type ListItemHeadBlock
- type MarginBlock
- type RuleBox
- type ScrollBox
- type ScrollRegion
- type ScrollbarFunc
- type Source
- type StackBlock
- type StackBox
- func (b *StackBox) AddSpacers(top, bottom int)
- func (b *StackBox) Bounds() image.Rectangle
- func (b *StackBox) BoxAt(i int) BlockLayout
- func (b *StackBox) DrawFrom(dst canvas.Canvas, c StackCursor, x, y int, now time.Duration)
- func (b *StackBox) HitTest(p image.Point) (Hit, image.Point)
- func (b *StackBox) Invalidate(i int)
- func (b *StackBox) MoveCursor(c StackCursor, dy float64) StackCursor
- func (b *StackBox) NormalizeCursor(c StackCursor) StackCursor
- func (b *StackBox) PendingImages() []string
- func (b *StackBox) Source() Source
- type StackCursor
- type StackSlot
- type TableBlock
- type TableCell
- type TaskCheckbox
- type TextBlock
- type TextBox
- func (b *TextBox) Bounds() image.Rectangle
- func (b *TextBox) BoundsAndAdvance() (image.Rectangle, int)
- func (b *TextBox) DrawInline(dst canvas.Canvas, x, y int, now time.Duration)
- func (b *TextBox) Glued() bool
- func (b *TextBox) HitTest(p image.Point, x, y int) (Hit, image.Point)
- func (b *TextBox) PendingImages() []string
- func (b *TextBox) Source() Source
- func (b *TextBox) SpaceWidth() int
- type ThematicBreakBlock
Constants ¶
const NaturalWidthMeasure = 1 << 20
NaturalWidthMeasure is an effectively-unbounded width passed to a cell's GetBlockLayout purely to measure its natural (unwrapped) width via the resulting BlockLayout's Bounds() - large enough that no realistic cell content would ever wrap against it.
Variables ¶
This section is empty.
Functions ¶
func DrawBlockLayout ¶
DrawBlockLayout is the sole entry point for drawing a BlockLayout: it skips drawContents entirely when box's bounds don't overlap dst, so every BlockLayout gets that for free regardless of who's calling it or where it sits in the tree.
Types ¶
type Block ¶
type Block interface {
Source
GetBlockLayout(ctx Context, width int) BlockLayout
Margins(ctx Context) styling.Margins
}
func NewDiagramBlock ¶
NewDiagramBlock returns a Block that renders img's image once it resolves, and fallback's own layout - typically the same CodeBlock that would've rendered without a plugin - while it's still pending or failed, so a diagram's raw source stays visible rather than a bare placeholder message. See codeblocks.Plugin.
type BlockLayout ¶
type BlockLayout interface {
Bounds() image.Rectangle
Source() Source
HitTest(p image.Point) (hit Hit, offset image.Point)
// PendingImages returns the resolved image srcs this box - or
// anything nested inside it - is still waiting to settle (still
// fetching, or failed but eligible for retry). Empty once nothing
// nested is still unsettled - such a box never needs revisiting due
// to image state, only a genuine structural change (width, scale,
// StyleSheet), same as any other content. Not memoized: only called
// from View.invalidateChangedImages, itself only reached when
// ImageCache reports something actually changed - not a per-frame
// cost - and a resolved top-level slot's subtree never mutates in
// place (see StackBox.boxAt), so there's nothing to go stale.
PendingImages() []string
// contains filtered or unexported methods
}
BlockLayout is drawn via the package-level DrawBlockLayout, not by calling drawContents directly, so that every BlockLayout gets the same off-screen skip for free regardless of where it sits in the tree. drawContents holds only the type-specific drawing logic.
HitTest identifies what's at p, a point in this BlockLayout's own Bounds() frame - callers must check p is in Bounds() first, since an implementation never re-checks. A nil hit means no match (dead space, e.g. a margin or padding); offset is where hit.Bounds() should be placed to land in the same frame p arrived in - a composite BlockLayout recursing into a child must add its own offset to the child's.
type BlockquoteBlock ¶
type BlockquoteBlock struct {
Inner Block
ASTNode *ast.Node
// contains filtered or unexported fields
}
BlockquoteBlock is a quoted group of ordinary blocks (`> ...`). Unlike list-item indentation, which relies on the parent StackBlock's generic left-margin wrapping, a blockquote positions its own content and draws its own left-edge bar - self-contained, so nested blockquotes (each level's bar drawn independently) just work without the parent needing to know anything about it. inner is the quoted content as a single Block - already a StackBlock if there was more than one, resolved once at compile time rather than rebuilt on every GetBlockLayout call.
func (*BlockquoteBlock) GetBlockLayout ¶
func (b *BlockquoteBlock) GetBlockLayout(ctx Context, width int) BlockLayout
func (*BlockquoteBlock) Node ¶
func (b *BlockquoteBlock) Node() *ast.Node
type BlockquoteBox ¶
type BlockquoteBox struct {
Indent int
Inner BlockLayout
// contains filtered or unexported fields
}
BlockquoteBox draws a vertical bar down the left edge and positions its inner content (the quote's own blocks, already laid out at the reduced width) to the right of it - the visual marker for a `>` blockquote.
func (*BlockquoteBox) Bounds ¶
func (b *BlockquoteBox) Bounds() image.Rectangle
func (*BlockquoteBox) HitTest ¶
HitTest treats the whole indent strip (bar plus any padding before inner starts) as the blockquote's own, and falls back to it too whenever inner declines to match (content narrower than the indented width, or a gap between quoted paragraphs) - every HitTest implementation degrades gracefully on an out-of-bounds point, so there's no need to bounds-check before delegating.
func (*BlockquoteBox) PendingImages ¶
func (b *BlockquoteBox) PendingImages() []string
func (*BlockquoteBox) Source ¶
func (b *BlockquoteBox) Source() Source
type CellAlignment ¶
type CellAlignment int
const ( AlignNone CellAlignment = iota AlignLeft AlignRight AlignCenter )
type CodeBlock ¶
type CodeBlock struct {
// Lines is one slice per visual line, each holding that line's spans -
// usually a single element (one InlineText for the whole line), but
// more than one when a code-block plugin has split the line into classified
// tokens (see highlightLines).
Lines [][]Inline
ASTNode *ast.Node
// contains filtered or unexported fields
}
func (*CodeBlock) GetBlockLayout ¶
func (b *CodeBlock) GetBlockLayout(ctx Context, width int) BlockLayout
type Context ¶
type Context struct {
Scale float64
FaceSelector fonts.FaceSelector
Styles styling.Styles
// HighlightNode is the ast.Node currently under the mouse (e.g. a
// hovered link), or nil - see ResolvedColor. Set by View's hover, which
// rebuilds the layout tree when it changes.
HighlightNode *ast.Node
// ImageCache resolves, fetches, and decodes images, caching the
// result - see imagecache.Cache. NewView always sets one (images.FileSource
// unless overridden via WithImageSource). If nil, images aren't
// loaded at all: an image renders as its alt text, and a diagram as
// its source code block.
ImageCache *imagecache.Cache
// ScrollOffset is how far source's content is scrolled along axis,
// unclamped: a ScrollBox clamps it to its content. Scroll state is the
// View's; nil means nothing ever scrolls.
ScrollOffset func(source Block, axis Axis) float64
// Scrollbar is called as each ScrollBox is drawn, with where its
// scrollbar would go; nil means no scrollbars.
Scrollbar ScrollbarFunc
// Time is elapsed time since the embedder started rendering (its
// own reference point - only ever used relative to itself, never
// compared against a wall-clock timestamp), set by every View.Draw. The one consumer today is imagecache.Animation.CurrentFrame,
// reached via DrawInline - kept here rather than read from a direct
// time.Now() call so frame selection stays a pure, deterministic
// function of its inputs, easy to test without any real waiting.
Time time.Duration
}
func (Context) ResolvedColor ¶
ResolvedColor walks node's ancestry (node itself first) for the nearest non-nil Color contribution - mirrors CSS's `color`, which inherits down from the nearest ancestor that sets it.
func (Context) ResolvedTextStyle ¶
ResolvedTextStyle merges node's ancestry's TextStyle contributions into one TextStyle - the nearest ancestor (node itself first) to set a given field wins, so e.g. Strong nested inside Emphasis picks up both a bold weight (from Strong) and an italic style (from Emphasis). Walks one step past the root (node == nil) so StyleSheet's baseline contribution can fill in any field nothing along the way ever claimed.
func (Context) ScaledMargins ¶
func (Context) ScaledViewMargins ¶
type DiagramBlock ¶
type DiagramBlock struct {
Image images.AsyncImage
Fallback Block
// contains filtered or unexported fields
}
func (*DiagramBlock) GetBlockLayout ¶
func (b *DiagramBlock) GetBlockLayout(ctx Context, width int) BlockLayout
func (*DiagramBlock) Node ¶
func (b *DiagramBlock) Node() *ast.Node
type EmptyBox ¶
type EmptyBox struct {
// contains filtered or unexported fields
}
func NewEmptyBox ¶
func (*EmptyBox) HitTest ¶
HitTest always misses: an EmptyBox is pure spacing (a margin/gap between blocks or lines), never content.
func (*EmptyBox) PendingImages ¶
type Hit ¶
Hit is what HitTest returns for an actual match: something that can report its own Bounds() and Source() - every BlockLayout and InlineLayout qualifies, so HitTest can return either kind of leaf uniformly. A composite's Source() is nil when it aggregates children with no single identity of their own (see StackBox.Source), the same way StackBlock.Node does for the semantic tree.
type ImageBox ¶
type ImageBox struct {
// Image is the already-decoded image imagecache.Cache.Load returned -
// carried forward from InlineImage.GetInlineLayout so DrawInline
// can pass it straight to Canvas.DrawImage, which never fetches or
// decodes anything itself. nil while the image is still pending but
// its dimensions are already known - bounds is still the correct,
// final (scaled) size, so DrawInline draws a placeholder rect
// instead, and nothing needs to reflow once Image is filled in later.
// anim is set instead of Image for an animated GIF - exactly one of
// the two is non-nil on a settled ImageBox.
Image image.Image
Animation *imagecache.Animation
Rect image.Rectangle
// Pending is the one resolved src this box is still waiting on,
// while img == nil - see PendingImages.
Pending []string
// contains filtered or unexported fields
}
func (*ImageBox) DrawInline ¶
func (*ImageBox) PendingImages ¶
func (*ImageBox) SpaceWidth ¶
type Inline ¶
type Inline interface {
Source
// GetInlineLayout builds this Inline's layout, width the same
// effectively-unbounded-or-real wrap width a Block's own
// GetBlockLayout receives (see naturalWidthMeasure) - only
// InlineImage uses it (see fitWidth), every other implementation
// ignores it.
GetInlineLayout(ctx Context, width int) InlineLayout
}
type InlineImage ¶
type InlineImage struct {
Src string
Alt string
Title string
ASTNode *ast.Node
// FallbackNode is a ast.TagUnsupported child of node - see the
// compiler's KindImage case (internal/markdown/inlines.go) for why
// it's precomputed once, at parse time, rather than created on demand
// here.
FallbackNode *ast.Node
// Glued - see InlineLayout.Glued's doc comment and InlineText.Glued.
Glued bool
}
func (*InlineImage) GetInlineLayout ¶
func (i *InlineImage) GetInlineLayout(ctx Context, width int) InlineLayout
GetInlineLayout resolves, fetches, and decodes src via ctx.ImageCache (letting the embedder decide the resolution/fetch policy - relative to a document's location, over http(s), from an archive, whatever it needs) - never blocking, since imagecache.Cache.Load never does. With no image cache, the image isn't loaded and its fallback text is shown. Otherwise there are three outcomes:
- Ready: the decoded image, scaled by ctx.Scale like every other sized quantity in the layout system (Context.ScaledMargins and friends), so images grow and shrink along with zoom/DPI instead of staying pixel-locked - then capped to width, preserving aspect ratio, if that scaled size would still be wider (see fitWidth) - the inline layer's equivalent of CSS's max-width: 100%, so an image dropped into a document at its own native resolution doesn't overflow the page.
- Pending with bounds already known (the header-peek in imagecache.Cache.fetchAndDecode beat the caller here): an ImageBox at the correct, final (scaled and width-capped) size, but with no image yet - DrawInline draws a placeholder instead, and nothing needs to reflow once the real pixels arrive later.
- Pending with no bounds yet, or Failed (unreadable, undecodable, or mid-retry-cooldown): falls back to text instead of a silent zero-size gap - reusing InlineText's own GetInlineLayout, so it renders exactly like any other construct whynot can't handle (see appendUnsupportedInline). Either way the resulting TextBox is stamped with which src it's standing in for, so View.invalidateChangedImages knows to revisit it once that changes.
func (*InlineImage) Node ¶
func (i *InlineImage) Node() *ast.Node
type InlineLayout ¶
type InlineLayout interface {
// BoundsAndAdvance returns the part's ink bounds relative to its pen
// position, and how far it moves the pen.
BoundsAndAdvance() (image.Rectangle, int)
Bounds() image.Rectangle
Source() Source
SpaceWidth() int
// Glued reports whether this item directly abuts the previous one in
// its line with no source whitespace between them - e.g. a word right
// next to a code span, or a non-breaking space's own neighbors. A
// glued boundary gets no gap and is never a line-break point (see
// lineBuilder.gap and wrapLines). Meaningless for an item that's
// never anything but a line's first part (listItemMarkerBox,
// checkboxBox) - never queried there.
Glued() bool
// DrawInline's now - see BlockLayout.drawContents's identical parameter.
DrawInline(dst canvas.Canvas, x, y int, now time.Duration)
HitTest(p image.Point, x, y int) (hit Hit, offset image.Point)
// PendingImages - see BlockLayout's identical method.
PendingImages() []string
}
InlineLayout is one part of a line of inline content, placed by lineBuilder. DrawInline and HitTest take the pen position LineBox placed it at: x the start of its advance, y the baseline.
type InlineText ¶
type InlineText struct {
Text string
ASTNode *ast.Node
// Glued - see InlineLayout.Glued's doc comment. Set by the compiler
// (compiler.appendString/pendingSpace) from whether the
// source actually had whitespace immediately before this item;
// false (the zero value) for every InlineText built directly rather
// than through the compiler, matching the old always-space behavior.
Glued bool
}
func (*InlineText) GetInlineLayout ¶
func (t *InlineText) GetInlineLayout(ctx Context, width int) InlineLayout
func (*InlineText) Node ¶
func (t *InlineText) Node() *ast.Node
type LineBox ¶
type LineBox struct {
// contains filtered or unexported fields
}
LineBox is one line of inline content, each part at a precomputed x. Parts are placed once, by lineBuilder, and drawing, hit-testing and bounds all read those positions, so what's measured is what's drawn.
func NewLineBox ¶
func NewLineBox(parts []InlineLayout, glue bool) *LineBox
NewLineBox places parts on a single line, never wrapping. glue abuts parts with no gap between them - for parts that are already contiguous slices of one string, whitespace included (CodeBlock's token spans).
func (*LineBox) Bounds ¶
Bounds only compensates for a negative bounds.Min.X, not a positive one: a code line's leading indentation has advance but no ink, so it's occupied space starting at the line's x=0, not excess to crop away.
func (*LineBox) HitTest ¶
HitTest checks each part where drawContents draws it for external y=0, which lands a part's footprint in Bounds()'s frame - the frame p is in.
func (*LineBox) PendingImages ¶
type ListItemHeadBlock ¶
type ListItemHeadBlock struct {
Marker Inline
Parts []Inline
ASTNode *ast.Node
// contains filtered or unexported fields
}
ListItemHeadBlock is a list item's own paragraph text, flowed with the marker hanging off the first line - see GetBlockLayout. It always reports zero margins: a list item's indentation and item-to-item spacing belong to the StackBlock compileListItem wraps it in (along with any trailing content, e.g. a nested list), not to the head on its own - it has no business claiming indentation whether or not there's a trailing part.
func (*ListItemHeadBlock) GetBlockLayout ¶
func (b *ListItemHeadBlock) GetBlockLayout(ctx Context, width int) BlockLayout
func (*ListItemHeadBlock) Node ¶
func (b *ListItemHeadBlock) Node() *ast.Node
type MarginBlock ¶
MarginBlock adds margins to an existing Block, collapsing Top/Bottom with whatever it already reports. Left/Right are just MarginBlock's own value: nothing it wraps ever reports a nonzero Left/Right to collapse with - StackBlock, the only Block with any derived margins, only ever derives Top/Bottom from its children.
Its own margins are resolved from ctx.Styles via node - unscaled, matching Block.Margins' convention (StackBlock.GetBlockLayout, the one real caller, scales the result via ctx.ScaledMargins).
func (*MarginBlock) Node ¶
func (b *MarginBlock) Node() *ast.Node
Node delegates to the wrapped Block rather than returning b.node: a loose list item's head wraps a ListItemHeadBlock in a MarginBlock keyed to a synthetic ast.TagParagraph node (purely so its margins resolve like a paragraph's) - the wrapped content's ast.TagListItem is the more correct identity for hit-testing.
type RuleBox ¶
type RuleBox struct {
// contains filtered or unexported fields
}
RuleBox is a single filled horizontal bar - the box for a thematic break (`---`). Its own height is just the bar's thickness; the visual spacing above and below comes from ThematicBreakBlock's Margins, same as any other block.
func (*RuleBox) PendingImages ¶
type ScrollBox ¶
type ScrollBox struct {
// contains filtered or unexported fields
}
ScrollBox shows a window, width wide, onto content that's wider, scrolled horizontally by the offset its View keeps for source. An edge fades into the page background wherever there's hidden content, and a scrollbar is drawn by the Context's Scrollbar hook, if any.
func (*ScrollBox) PendingImages ¶
type ScrollRegion ¶
type ScrollRegion struct {
Source Block
Axis Axis
Box image.Rectangle // the whole region, in its Canvas's coordinates
Visible image.Rectangle // Box clipped to what was actually drawn
ContentSize int // the content's size along Axis
Offset int // how far the content is scrolled, as drawn
}
ScrollRegion is a part of the document whose content is bigger than what it shows, as drawn this frame: where a scrollbar for it would go.
func (ScrollRegion) MaxOffset ¶
func (r ScrollRegion) MaxOffset() int
MaxOffset is how far r's content can scroll.
type ScrollbarFunc ¶
type ScrollbarFunc func(dst canvas.Canvas, r ScrollRegion, now time.Duration)
ScrollbarFunc draws, or not, the scrollbar of a region as it's drawn onto dst at now. Whoever keeps scroll state provides it (the View), and decides how a scrollbar looks and behaves; it can also note the region, to match input against it later.
type Source ¶
Source is the common ground between Block and Inline: something with a semantic identity in the compiled ast.Node tree. Leaf boxes expose it so a hit-test result traces back to its origin - and since the concrete value is the real Block/Inline, a caller can type-assert further for anything beyond the node itself.
type StackBlock ¶
type StackBlock struct {
Blocks []Block
}
func (*StackBlock) GetBlockLayout ¶
func (b *StackBlock) GetBlockLayout(ctx Context, width int) BlockLayout
GetBlockLayout builds the slot skeleton only - gap sizes from Margins(), which is cheap (no text measurement) - deferring each block's own GetBlockLayout call to StackBox.boxAt, on first access to that slot. A resize only needs to re-derive gap sizes and widths up front; the expensive part (actually laying out each block's content) only happens for slots something later asks for, e.g. those near a scroll anchor.
func (*StackBlock) Margins ¶
func (b *StackBlock) Margins(ctx Context) styling.Margins
Margins reports Top/Bottom as its first/last child's own margin - the same collapsing GetBlockLayout applies between siblings, extended to its own edges. Left/Right are zero: as a stack of blocks arranged vertically, StackBlock has no notion of a horizontal edge to derive from a child - only whatever wraps it (see MarginBlock) has a real Left/Right.
func (*StackBlock) Node ¶
func (b *StackBlock) Node() *ast.Node
Node always returns nil: a StackBlock aggregates other Blocks, each with their own identity, so it has none of its own - hit-testing that reaches a bare StackBlock should already have recursed into whichever child slot actually matched.
func (*StackBlock) StackLayout ¶
func (b *StackBlock) StackLayout(ctx *Context, width int) *StackBox
StackLayout is GetBlockLayout with its concrete result type, laying out slots against *ctx when they're first needed (see StackBox.ctx).
type StackBox ¶
type StackBox struct {
Slots []StackSlot
// Context is what unresolved slots are laid out with, when first needed.
// For a View's top-level StackBox this is the View's own live context,
// so a change that only affects slots laid out from then on (e.g. the
// hovered link) needs no rebuild, just invalidating the affected slots.
Context *Context
Width int
// contains filtered or unexported fields
}
func (*StackBox) AddSpacers ¶
AddSpacers surrounds b's slots with empty slots of the given heights, omitting a zero-height one.
func (*StackBox) BoxAt ¶
func (b *StackBox) BoxAt(i int) BlockLayout
BoxAt returns the child at index i, building it from its Block and memoizing the result on first access if it isn't already resolved. A caller that only ever asks for slots near a scroll cursor only ever pays to build those.
func (*StackBox) DrawFrom ¶
DrawFrom draws starting at c, so that c's position lands at (x, y) on dst - unlike DrawBlockLayout, it never calls Bounds() on the whole tree first, and never resolves or draws slots before c.index. Intended for View to call at the scroll cursor.
func (*StackBox) HitTest ¶
HitTest walks slots from the start looking for the one p.Y falls in - a plain linear scan, unlike normalizeCursor's cursor-relative walk, since resolving this way would force-build every slot up to the match regardless. That's fine for a StackBox reached via nested structure (a list's items, a blockquote's paragraphs - typically small), but would defeat the point for the top-level, potentially huge document StackBox - View.HitTest handles that one specially, starting from the current scroll cursor instead of slot 0.
A miss on the matched slot (outside its bounds, or its own HitTest declines - e.g. clicking past the end of a paragraph's short last line) falls back to this StackBox itself when it has a Source - the same fallback-to-self BlockquoteBox/tableBox already do. When it doesn't (an aggregate of unrelated blocks - see Source), it's a genuine miss instead, same as before Source existed.
func (*StackBox) Invalidate ¶
Invalidate discards slot i's layout; boxAt rebuilds it when next needed.
func (*StackBox) MoveCursor ¶
func (b *StackBox) MoveCursor(c StackCursor, dy float64) StackCursor
MoveCursor returns c shifted by dy (in the offset's own sign convention: positive moves forward through the document) and normalized, for callers that have a cursor already and want to move it rather than construct a new one from scratch - e.g. View.ScrollBy. Layout's resize re-anchoring isn't a move like this (offset is recomputed from a ratio through a slot's new height, not shifted from its old value), so it calls normalizeCursor directly instead.
func (*StackBox) NormalizeCursor ¶
func (b *StackBox) NormalizeCursor(c StackCursor) StackCursor
NormalizeCursor adjusts c so that 0 <= offset < boxAt(index)'s height, walking to neighboring slots as needed rather than scanning from the start: only the slots actually walked over are touched (via boxAt), so cost is proportional to how far a position moved, not to how far into the document it already was.
Past the very end of the document, NormalizeCursor clamps to (lastIndex, height(lastIndex)) - offset equal to the height, not less than it - rather than reporting an ever-growing out-of-range offset. NormalizeCursor is idempotent everywhere except exactly that clamped value.
func (*StackBox) PendingImages ¶
PendingImages aggregates only over already-resolved slots (slot.box != nil) - an unresolved slot hasn't been looked at yet, so there's nothing pending to report about it until something actually asks for it (see boxAt). Not memoized (see the BlockLayout interface's own doc comment) - in particular, the top-level document StackBox's own slots keep resolving lazily over time, so caching this would go stale; View.invalidateChangedImages only ever calls this on individual already-resolved slots' boxes (via documentStack), never on that top-level aggregate itself.
type StackCursor ¶
StackCursor is a position within a StackBox: which slot, and how far into it. Like a text cursor, it's only meaningful relative to the specific StackBox it was resolved against - the same (index, offset) pair means something different for a different StackBox. offset is float64 so scroll deltas can accumulate sub-pixel amounts directly; DrawFrom truncates to a pixel only right before it becomes a screen coordinate.
type StackSlot ¶
type StackSlot struct {
Box BlockLayout
Block Block
Width int
// contains filtered or unexported fields
}
StackSlot is a StackBox child that's either already resolved (box set - true of gap spacers, which are cheap enough to build eagerly) or needs building from a Block on first access (block set). A content slot's width is already margin-reduced if wrap is set.
type TableBlock ¶
type TableBlock struct {
Header []TableCell
Rows [][]TableCell
ASTNode *ast.Node
// contains filtered or unexported fields
}
TableBlock is a GFM table. header and each row in rows hold one tableCell per column.
func (*TableBlock) GetBlockLayout ¶
func (b *TableBlock) GetBlockLayout(ctx Context, width int) BlockLayout
func (*TableBlock) Node ¶
func (b *TableBlock) Node() *ast.Node
type TableCell ¶
type TableCell struct {
Content *TextBlock
Alignment CellAlignment
}
type TaskCheckbox ¶
TaskCheckbox is a GFM task list item's checkbox marker (- [ ]/- [x]). Resolved at layout time, not compile time: whether to draw the real ballot-box glyph or a procedurally-drawn box (see checkboxBox) depends on whether the active FaceSelector/StyleSheet's resolved font actually has that glyph, which can change across a re-layout (zoom, theme, a caller registering a different font) even though the semantic tree itself never does - most fonts, including the bundled Go fonts, don't have it.
func (*TaskCheckbox) GetInlineLayout ¶
func (c *TaskCheckbox) GetInlineLayout(ctx Context, width int) InlineLayout
func (*TaskCheckbox) Node ¶
func (c *TaskCheckbox) Node() *ast.Node
type TextBlock ¶
type TextBlock struct {
Parts []Inline
ASTNode *ast.Node
// contains filtered or unexported fields
}
func (*TextBlock) GetBlockLayout ¶
func (b *TextBlock) GetBlockLayout(ctx Context, width int) BlockLayout
type TextBox ¶
type TextBox struct {
Text string
Face font.Face
Color color.Color
// StrikeThickness is the strikethrough line's thickness in pixels; 0
// means no strikethrough.
StrikeThickness int
// LineHeight is the multiplier applied to Face's natural Ascent+Descent
// to get this box's vertical extent (StyleSheet.LineHeight) - the
// font's own metrics don't reliably encode comfortable line spacing
// (see BoundsAndAdvance), so this is what actually separates
// consecutive wrapped lines within a paragraph. The zero value (an
// unset TextBox, e.g. built directly by a test rather than through
// InlineText.GetInlineLayout) falls back to the pre-LineHeight
// behavior - bare Ascent+Descent, no extra space.
LineHeight float64
// contains filtered or unexported fields
}
func (*TextBox) BoundsAndAdvance ¶
Text/Face never change after construction, and a TextBox is always rebuilt from scratch (never mutated) whenever the source Block tree is re-laid out, so this measurement is valid for the entire lifetime of the instance: compute it once, on first use.
func (*TextBox) DrawInline ¶
func (*TextBox) PendingImages ¶
func (*TextBox) SpaceWidth ¶
type ThematicBreakBlock ¶
ThematicBreakBlock is a horizontal rule (`---`). Unlike the other Block types it has no inline content to lay out - just a color; its vertical spacing comes from whatever MarginBlock wraps it.
func (*ThematicBreakBlock) GetBlockLayout ¶
func (b *ThematicBreakBlock) GetBlockLayout(ctx Context, width int) BlockLayout
func (*ThematicBreakBlock) Node ¶
func (b *ThematicBreakBlock) Node() *ast.Node