Documentation
¶
Index ¶
- func DrawBlockLayout(box BlockLayout, dst Canvas, x, y int, now time.Duration)
- type ASTNode
- type ASTPath
- type ASTTag
- type AnimatedImage
- type Block
- type BlockLayout
- type BlockquoteBlock
- type BlockquoteBox
- type BlockquoteGeometry
- type Canvas
- type CheckboxBox
- func (b *CheckboxBox) Bounds() image.Rectangle
- func (b *CheckboxBox) BoundsAndAdvance() (image.Rectangle, int)
- func (b *CheckboxBox) DrawInline(dst Canvas, x, y int, now time.Duration) int
- func (b *CheckboxBox) Glued() bool
- func (b *CheckboxBox) HitTest(p image.Point, x, y int) (Hit, image.Point, int)
- func (b *CheckboxBox) PendingImages() []string
- func (b *CheckboxBox) Source() Source
- func (b *CheckboxBox) SpaceWidth() int
- type CodeBlock
- type ContainerBox
- type CustomFontFaceSelector
- func (s *CustomFontFaceSelector) AddFont(family FontFamily, weight font.Weight, style font.Style, data []byte, ...) error
- func (s *CustomFontFaceSelector) AddFontCollection(family FontFamily, data []byte, matchFamily string) (registered int, err error)
- func (s *CustomFontFaceSelector) AddFontCollectionFile(family FontFamily, path string, matchFamily string) (registered int, err error)
- func (s *CustomFontFaceSelector) AddFontFile(family FontFamily, weight font.Weight, style font.Style, path string, ...) error
- func (s *CustomFontFaceSelector) SelectFace(style TextStyle) (font.Face, error)
- func (s *CustomFontFaceSelector) SetDPI(dpi float64)
- type CustomFontFaceSelectorOption
- type DefaultStyleSheet
- func (s *DefaultStyleSheet) BackgroundColor() color.Color
- func (s *DefaultStyleSheet) BlockquoteGeometry(node *ASTNode) BlockquoteGeometry
- func (s *DefaultStyleSheet) BorderColor(node *ASTNode) color.Color
- func (s *DefaultStyleSheet) Color(node *ASTNode) color.Color
- func (s *DefaultStyleSheet) HighlightColor() color.Color
- func (s *DefaultStyleSheet) LineHeight(node *ASTNode) float64
- func (s *DefaultStyleSheet) Margins(node *ASTNode) Margins
- func (s *DefaultStyleSheet) ScrollbarColor(hover, pressed bool) color.Color
- func (s *DefaultStyleSheet) StrikeThickness(node *ASTNode) float64
- func (s *DefaultStyleSheet) TableGeometry(node *ASTNode) TableGeometry
- func (s *DefaultStyleSheet) TextStyle(node *ASTNode) PartialTextStyle
- func (s *DefaultStyleSheet) ThematicBreakThickness(node *ASTNode) float64
- func (s *DefaultStyleSheet) ViewMargins() Margins
- type EmptyBox
- type FaceSelector
- type FileImageSource
- type FontFamily
- type GoFontFaceSelector
- type HighlightSpan
- type Highlighter
- type Hit
- type ImageBox
- func (b *ImageBox) Bounds() image.Rectangle
- func (b *ImageBox) BoundsAndAdvance() (image.Rectangle, int)
- func (b *ImageBox) DrawInline(dst Canvas, x, y int, now time.Duration) int
- func (b *ImageBox) Glued() bool
- func (b *ImageBox) HitTest(p image.Point, x, y int) (Hit, image.Point, int)
- func (b *ImageBox) PendingImages() []string
- func (b *ImageBox) Source() Source
- func (b *ImageBox) SpaceWidth() int
- type ImageCache
- type ImageChange
- type ImageResult
- type ImageSource
- type ImageStatus
- type Inline
- type InlineImage
- type InlineLayout
- type InlineText
- type LineBox
- type ListItemHeadBlock
- type ListItemMarkerBox
- func (b *ListItemMarkerBox) Bounds() image.Rectangle
- func (b *ListItemMarkerBox) BoundsAndAdvance() (image.Rectangle, int)
- func (b *ListItemMarkerBox) DrawInline(dst Canvas, x, y int, now time.Duration) int
- func (b *ListItemMarkerBox) Glued() bool
- func (b *ListItemMarkerBox) HitTest(p image.Point, x, y int) (Hit, image.Point, int)
- func (b *ListItemMarkerBox) PendingImages() []string
- func (b *ListItemMarkerBox) Source() Source
- func (b *ListItemMarkerBox) SpaceWidth() int
- type MarginBlock
- type Marginer
- type Margins
- type MarkdownCompiler
- func (c *MarkdownCompiler) AppendInlineNode(items []Inline, node gmast.Node, astNode *ASTNode) []Inline
- func (c *MarkdownCompiler) CompileBlock(node gmast.Node, parent *ASTNode) Block
- func (c *MarkdownCompiler) CompileDocument(node gmast.Node) Block
- func (c *MarkdownCompiler) CompileListItem(node gmast.Node, index int, marker byte, tight bool, parent *ASTNode) Block
- func (c *MarkdownCompiler) CompileNode(node gmast.Node, parent *ASTNode) Block
- func (c *MarkdownCompiler) CompileTable(node gmast.Node, parent *ASTNode) Block
- func (c *MarkdownCompiler) CompileTableRow(node gmast.Node, parent *ASTNode) []tableCell
- type ParseOption
- type PartialTextStyle
- type RenderingContext
- func (c RenderingContext) ResolvedColor(node *ASTNode) color.Color
- func (c RenderingContext) ResolvedTextStyle(node *ASTNode) TextStyle
- func (c RenderingContext) ScaledBlockquoteGeometry(node *ASTNode) BlockquoteGeometry
- func (c RenderingContext) ScaledMargins(m Marginer) Margins
- func (c RenderingContext) ScaledStrikeThickness(node *ASTNode) float64
- func (c RenderingContext) ScaledTableGeometry(node *ASTNode) TableGeometry
- func (c RenderingContext) ScaledThematicBreakThickness(node *ASTNode) float64
- func (c RenderingContext) ScaledViewMargins() Margins
- type RuleBox
- type ScrollPosition
- type ScrollbarColors
- type ScrollbarStyleSheet
- type Source
- type StackBlock
- type StackBox
- type StyleSheet
- type SyntaxColors
- type TableBlock
- type TableBox
- type TableGeometry
- 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, x, y int, now time.Duration) int
- func (b *TextBox) Glued() bool
- func (b *TextBox) HitTest(p image.Point, x, y int) (Hit, image.Point, int)
- func (b *TextBox) PendingImages() []string
- func (b *TextBox) Source() Source
- func (b *TextBox) SpaceWidth() int
- type TextStyle
- type TextStyleField
- type ThematicBreakBlock
- type TokenClass
- type View
- func (v *View) DocumentBounds() image.Rectangle
- func (v *View) Draw(dst Canvas, x, y int)
- func (v *View) HitTest(x, y int) (hit Hit, offset image.Point)
- func (v *View) Hover(x, y int) (destination string, ok bool)
- func (v *View) Layout(width, height int, scale float64, now time.Duration)
- func (v *View) LinkAt(x, y int) (destination string, ok bool)
- func (v *View) RestoreScrollPosition(p ScrollPosition)
- func (v *View) ScaledViewMargins() Margins
- func (v *View) Scroll(dy float64)
- func (v *View) ScrollPosition() ScrollPosition
- func (v *View) ScrollToAnchor(id string) bool
- func (v *View) ScrollToRatio(ratio float64)
- func (v *View) SetStyleSheet(s StyleSheet)
- func (v *View) Title() (string, bool)
- func (v *View) VisibleViewBounds(viewportSize image.Point) image.Rectangle
- type ViewOption
- type WithoutMargins
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DrawBlockLayout ¶
func DrawBlockLayout(box BlockLayout, dst Canvas, x, y int, now time.Duration)
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 ASTNode ¶
type ASTNode struct {
Tag ASTTag
Parent *ASTNode
// Destination is the URL a TagLink node points to. Empty for every
// other tag.
Destination string
// ID is a heading's anchor id (goldmark's auto-generated slug, e.g.
// "my-heading" for "## My Heading") - what a link's URL fragment
// (the part after '#') targets. Empty for every non-heading tag.
ID string
// contains filtered or unexported fields
}
ASTNode is a node in the semantic tree built alongside the Block/Inline tree during compilation - structural only (what kind of thing this is, and where it sits), with no rendering or style baked in. Public because it's the input a StyleSheet queries, and potentially useful beyond styling later (document navigation, folding).
func (*ASTNode) AddChild ¶
AddChild creates a new ASTNode tagged with tag, parented to n, and returns it - the usual way to extend the tree during compilation. Works with a nil n (a fresh root node): the result only stores n as its Parent rather than dereferencing it.
func (*ASTNode) AncestorTag ¶
AncestorTag returns n itself, or the nearest ancestor, tagged with tag - or nil if neither n nor any ancestor is. The pointer-returning counterpart to HasAncestorTag, for callers that need the node itself (e.g. to compare identity against), not just whether one exists.
func (*ASTNode) HasAncestor ¶
HasAncestor reports whether n itself, or any of its ancestors, is target - matching a specific node's identity (e.g. "is this part of the currently hovered link") rather than a tag. A multi-word link's words don't all share one ASTNode when styled sub-spans are involved (e.g. a bold word inside a link gets its own child node), so matching by identity alone isn't enough - this walk is what makes matching the link's own node still catch every word inside it.
func (*ASTNode) HasAncestorTag ¶
HasAncestorTag reports whether n itself, or any of its ancestors, is tagged with tag - for style decisions that are a structural yes/no (e.g. "is this text struck through") rather than a value to resolve.
func (*ASTNode) Path ¶
Path returns the tag ancestry from the root down to and including n - a convenience for StyleSheet implementations that want ancestry without walking Parent themselves. Computed once and cached.
slices.Concat, not append(parentPath, n.Tag) - append can leave two siblings aliasing the same backing-array slot.
type ASTTag ¶
type ASTTag int
ASTTag identifies the semantic role of an ASTNode - which part of a StyleSheet a value tagged with it should resolve its appearance against. Exported because StyleSheet implementations switch on it.
const ( TagParagraph ASTTag = iota TagHeading1 TagHeading2 TagHeading3 TagHeading4 TagHeading5 TagHeading6 TagList TagListItem TagCodeBlock TagThematicBreak TagBlockquote TagTable TagTableCell TagLink TagCodeSpan TagEmphasis TagStrong TagStrikethrough TagImage TagUnsupported // TagCodeKeyword..TagCodeFunction classify a syntax-highlighted code // token - see Highlighter. Appended after TagUnsupported deliberately: // headingTag() relies on TagHeading1..TagHeading6 staying consecutive, // so nothing may be inserted before them. TagCodeKeyword TagCodeString TagCodeNumber TagCodeComment TagCodeType TagCodeFunction )
type AnimatedImage ¶
type AnimatedImage struct {
// contains filtered or unexported fields
}
AnimatedImage holds every frame of a decoded GIF, already composited to full-canvas, disposal-correct frames (see decodeAnimatedGIF). CurrentFrame is a pure function of now (elapsed time since rendering started - RenderingContext.Time) and the animation's own total duration - there's no per-animation "start" to track: now is already "time since start", so a looping animation just always shows whichever frame now % total currently lands in, as if it had been playing since time zero. Deterministic and easy to test without any real waiting, and no clock-skew concerns between decode time and draw time to reason about, since nothing is stamped at decode time. Always loops forever.
func (*AnimatedImage) Bounds ¶
func (a *AnimatedImage) Bounds() image.Rectangle
Bounds is every frame's shared size - GIF frames may individually be smaller (see decodeAnimatedGIF), but every composited frame here is full-canvas.
func (*AnimatedImage) CurrentFrame ¶
func (a *AnimatedImage) CurrentFrame(now time.Duration) image.Image
CurrentFrame returns whichever frame now lands in, looping forever.
type Block ¶
type Block interface {
Source
GetBlockLayout(ctx RenderingContext, width int) BlockLayout
Margins(ctx RenderingContext) Margins
}
func Parse ¶
func Parse(source []byte, opts ...ParseOption) Block
Parse compiles Markdown source into a Block tree ready for layout via Block.GetBlockLayout. All appearance (fonts, colors, margins, ...) is resolved later, from RenderingContext.StyleSheet against each Block/Inline's own ASTNode - Parse itself only builds structure.
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 {
WithoutMargins
// 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 RenderingContext, width int) BlockLayout
func (*BlockquoteBlock) Node ¶
func (b *BlockquoteBlock) Node() *ASTNode
type BlockquoteBox ¶
type BlockquoteBox struct {
// 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 BlockquoteGeometry ¶
BlockquoteGeometry is the dimensional constants a blockquote's own bar and indent need - grouped since a blockquote's GetBlockLayout always reads both together.
type Canvas ¶
type Canvas interface {
Bounds() image.Rectangle
DrawText(s string, face font.Face, x, y int, clr color.Color)
DrawImage(img image.Image, x, y, width, height int)
DrawRect(x, y, w, h int, clr color.Color)
}
Canvas is the draw destination for a BlockLayout/InlineLayout tree - the boundary between layout (which never depends on a rendering backend) and actual drawing (which necessarily does). DrawImage receives an already-decoded, backend-agnostic image.Image - whynot.ImageCache owns resolving, fetching, and decoding (see InlineImage.GetInlineLayout), so a Canvas implementation never fetches anything itself; its own job is purely whatever backend-specific conversion drawing it needs (e.g. uploading it as a GPU texture), which it's free to cache itself, keyed by img's own identity - the same resolved image comes back from the cache every time it's asked for. width/height are the size to draw it at - already scaled by RenderingContext.Scale, and not generally img's own pixel size - so an implementation must scale what it draws to fit, not blit it 1:1.
type CheckboxBox ¶ added in v0.3.0
type CheckboxBox struct {
// contains filtered or unexported fields
}
CheckboxBox is a GFM task list item's checkbox marker, drawn with plain filled rects (Canvas.DrawRect) rather than a font glyph - TaskCheckbox's fallback for a face that doesn't have the ballot-box glyphs (☐/☑; most fonts, including the bundled Go fonts, don't). An outlined square for unchecked, the same outline with a filled square inside for checked - deliberately not a baked icon image: this way it needs no embedded asset, scales naturally from the resolved face's own metrics (already DPI/zoom-scaled, like every face SelectFace returns - so it tracks font size and zoom together without a separate StyleSheet dimensional constant), and always matches the resolved text color instead of being a fixed-color raster that could clash with a custom StyleSheet.
func (*CheckboxBox) Bounds ¶ added in v0.3.0
func (b *CheckboxBox) Bounds() image.Rectangle
func (*CheckboxBox) BoundsAndAdvance ¶ added in v0.3.0
func (b *CheckboxBox) BoundsAndAdvance() (image.Rectangle, int)
BoundsAndAdvance places the box entirely above the baseline (Min.Y = -size, Max.Y = 0, matching how a same-sized glyph like □ itself sits), so it lines up with surrounding text the same way TaskCheckbox's glyph path does.
func (*CheckboxBox) DrawInline ¶ added in v0.3.0
func (*CheckboxBox) Glued ¶ added in v0.3.1
func (b *CheckboxBox) Glued() bool
Glued is always false - a CheckboxBox only ever appears as a ListItemMarkerBox's Marker (a list item's task checkbox), so like ListItemMarkerBox itself it's never queried by the gap/break logic.
func (*CheckboxBox) PendingImages ¶ added in v0.3.0
func (b *CheckboxBox) PendingImages() []string
func (*CheckboxBox) Source ¶ added in v0.3.0
func (b *CheckboxBox) Source() Source
func (*CheckboxBox) SpaceWidth ¶ added in v0.3.0
func (b *CheckboxBox) SpaceWidth() int
type CodeBlock ¶
type CodeBlock struct {
WithoutMargins
// contains filtered or unexported fields
}
func (*CodeBlock) GetBlockLayout ¶
func (b *CodeBlock) GetBlockLayout(ctx RenderingContext, width int) BlockLayout
type ContainerBox ¶
type ContainerBox struct {
// contains filtered or unexported fields
}
func NewContainerBox ¶
func NewContainerBox(inner BlockLayout, w, h, x, y int) *ContainerBox
func (*ContainerBox) Bounds ¶
func (b *ContainerBox) Bounds() image.Rectangle
func (*ContainerBox) HitTest ¶
HitTest has no source of its own - a pure positioning wrapper (for indentation, or cell alignment in a table) - so a miss on inner (e.g. content narrower than the space it was given) reports no match rather than falling back to anything.
func (*ContainerBox) PendingImages ¶
func (b *ContainerBox) PendingImages() []string
func (*ContainerBox) Source ¶
func (b *ContainerBox) Source() Source
Source delegates to inner: a pure positioning wrapper (for indentation, or cell alignment in a table) has no identity of its own, but always wraps exactly one thing, unlike StackBox.
type CustomFontFaceSelector ¶ added in v0.3.0
type CustomFontFaceSelector struct {
// contains filtered or unexported fields
}
CustomFontFaceSelector is a FaceSelector backed by caller-supplied TTF/OTF font bytes, registered per (FontFamily, weight-bucket, style) slot via AddFont. Any (family, weight, style) combination with nothing registered is delegated to a fallback FaceSelector - NewGoFontFaceSelector by default, overridable via WithFallback - so callers only need to supply the fonts they actually care about overriding. WithFallback(nil) means "no fallback": SelectFace returns an error for any unfilled slot instead of delegating.
func NewCustomFontFaceSelector ¶ added in v0.3.0
func NewCustomFontFaceSelector(dpi float64, opts ...CustomFontFaceSelectorOption) *CustomFontFaceSelector
NewCustomFontFaceSelector returns a CustomFontFaceSelector with nothing registered yet - every SelectFace call delegates to its fallback FaceSelector (NewGoFontFaceSelector(dpi) unless overridden via WithFallback) until AddFont/AddFontFile is called.
func (*CustomFontFaceSelector) AddFont ¶ added in v0.3.0
func (s *CustomFontFaceSelector) AddFont(family FontFamily, weight font.Weight, style font.Style, data []byte, index int) error
AddFont registers data (TTF/OTF bytes, or a .ttc/.otc collection) for exactly the (family, weight, style) slot given, replacing whatever was registered for that slot before. index picks which subfont within data to use - 0 for an ordinary single-font file (opentype.ParseCollection treats it as a 1-font collection), or a specific subfont's index within a known .ttc/.otc. weight and style are bucketed exactly like a resolved TextStyle is in SelectFace (see normalizedFontKey).
data is parsed immediately, so a malformed font or an out-of-range index is reported here, at registration time, rather than surfacing later from SelectFace. On error nothing is registered and any already-cached faces are left untouched.
Use AddFontCollection instead to register every subfont a collection contains that can be confidently classified, rather than picking one by index yourself.
func (*CustomFontFaceSelector) AddFontCollection ¶ added in v0.3.0
func (s *CustomFontFaceSelector) AddFontCollection(family FontFamily, data []byte, matchFamily string) (registered int, err error)
AddFontCollection registers every subfont in data (a .ttc/.otc collection, or a plain single-font TTF/OTF) that classifies cleanly into a (weight, style) slot for family, via each subfont's own name-table subfamily string (see classifySubfamily in fontclassify.go). If matchFamily is non-empty, a subfont is also required to have its own name-table family reasonably match it (case/space/punctuation-insensitive, either direction as a substring - see subfontBelongsToFamily) - pass "" to register every classifiable subfont regardless of its own family name, e.g. when data is already known to hold only the one family wanted.
Anything unrecognized (Light/Thin/Black/SemiBold/Condensed/... - see classifySubfamily's doc comment for why these are skipped rather than guessed) or family-mismatched is simply not registered, not an error - those slots are served by the fallback FaceSelector instead. The only error this returns is a failure to parse data at all; it returns how many subfonts were registered otherwise.
func (*CustomFontFaceSelector) AddFontCollectionFile ¶ added in v0.3.0
func (s *CustomFontFaceSelector) AddFontCollectionFile(family FontFamily, path string, matchFamily string) (registered int, err error)
AddFontCollectionFile is AddFontCollection, reading data from path instead of taking it directly.
func (*CustomFontFaceSelector) AddFontFile ¶ added in v0.3.0
func (s *CustomFontFaceSelector) AddFontFile(family FontFamily, weight font.Weight, style font.Style, path string, index int) error
AddFontFile is AddFont, reading data from path instead of taking it directly. A convenience for the common case of loading a font straight from disk; font bytes coming from anywhere else (embed.FS, a network fetch) still go through AddFont directly, the same way NewView itself only ever takes []byte.
func (*CustomFontFaceSelector) SelectFace ¶ added in v0.3.0
func (s *CustomFontFaceSelector) SelectFace(style TextStyle) (font.Face, error)
func (*CustomFontFaceSelector) SetDPI ¶ added in v0.3.0
func (s *CustomFontFaceSelector) SetDPI(dpi float64)
type CustomFontFaceSelectorOption ¶ added in v0.3.0
type CustomFontFaceSelectorOption func(*CustomFontFaceSelector)
CustomFontFaceSelectorOption customizes a CustomFontFaceSelector at construction, via NewCustomFontFaceSelector's opts parameter.
func WithFallback ¶ added in v0.3.0
func WithFallback(fallback FaceSelector) CustomFontFaceSelectorOption
WithFallback overrides the FaceSelector NewCustomFontFaceSelector otherwise defaults to (NewGoFontFaceSelector) for any (family, weight, style) slot nothing was registered for. Pass nil to disable fallback entirely - SelectFace then returns an error for an unfilled slot instead.
type DefaultStyleSheet ¶
type DefaultStyleSheet struct {
ParagraphMargins Margins
ParagraphTextStyle PartialTextStyle
HeadingMargins [6]Margins
HeadingTextStyles [6]PartialTextStyle
ListMargins Margins
ListItemMargins Margins
ListItemTextStyle PartialTextStyle
CodeBlockMargins Margins
CodeBlockTextStyle PartialTextStyle
// CodeBlockColor is deliberately a neutral gray rather than an accent
// color: a whole block of code in a bright color reads as garish and
// fights with syntax-highlighted spans inside it (see SyntaxColors) -
// unlike CodeSpanColor, a single inline `code` word stays fine as an
// accent since it's a small, isolated highlight within prose.
CodeBlockColor color.Color
// UnsupportedColor is the text color for a Markdown construct
// whynot doesn't understand - rendered as a code block (see
// CodeBlockMargins/CodeBlockTextStyle, shared with TagCodeBlock) but
// in this distinct color so it reads as an error, not as normal code.
UnsupportedColor color.Color
// CodeSpanTextStyle, EmphasisTextStyle, StrongTextStyle: inline spans
// have no margins of their own to bundle alongside, unlike the
// block-level tags above.
CodeSpanTextStyle PartialTextStyle
// CodeSpanColor is an accent color, unlike CodeBlockColor - see its
// own doc comment.
CodeSpanColor color.Color
EmphasisTextStyle PartialTextStyle
StrongTextStyle PartialTextStyle
ThematicBreakMargins Margins
ThematicBreakColor color.Color
LinkColor color.Color
BlockquoteMargins Margins
BlockquoteBarColor color.Color
TableCellTextStyle PartialTextStyle
TableMargins Margins
TableFrameColor color.Color
// ImagePlaceholderColor is the rect InlineImage.GetInlineLayout draws
// in place of an image that's still pending but already knows its
// final size.
ImagePlaceholderColor color.Color
// TextColor is the root-level fallback color for tags with no color
// of their own. BaseTextStyle is TextStyle's equivalent - unlike the
// per-tag PartialTextStyle fields above, it's a plain TextStyle,
// not a PartialTextStyle: being the ultimate fallback means it
// always claims every field by definition (see TextStyle below), so
// there's no meaningful subset for a Set mask to express.
TextColor color.Color
BaseTextStyle TextStyle
// Background is the whole viewport's fill color - see BackgroundColor.
// Named without the "Color" suffix only to avoid colliding with the
// BackgroundColor method (color.Color already makes the field's own
// meaning unambiguous).
Background color.Color
// ViewMargin is the whole viewport's own inset - see ViewMargins. Named
// in the singular only to avoid colliding with the plural ViewMargins
// method (Margins is a struct - like the field, not a color.Color -
// so unlike Background/BackgroundColor there's no type-derived word
// to hang the distinction on instead).
ViewMargin Margins
// Highlight is the text color for whatever's under
// RenderingContext.HighlightNode - see HighlightColor. Named without
// the "Color" suffix for the same reason Background is.
Highlight color.Color
// Scrollbar is the thumb's three interaction-state colors - see
// ScrollbarColor. Unlike ThematicBreakColor/BlockquoteBarColor/
// TableFrameColor (whynot's own content decorations), a scrollbar is
// drawn outside whynot entirely, by whatever embeds a View - this
// field only supplies the color that embedder asks for. Named
// without the "Color" suffix for the same reason Background/
// Highlight are.
Scrollbar ScrollbarColors
// Syntax is the palette a Highlighter's classified code tokens draw
// from - see SyntaxColors.
Syntax SyntaxColors
// contains filtered or unexported fields
}
DefaultStyleSheet is the concrete, configurable StyleSheet implementation whynot's built-in themes (NewDarkStyleSheet, NewLightStyleSheet) are both built from - the two differ only in the field values their constructors fill in, not in shape. A caller who wants to change one thing can start from either constructor and mutate a field (ParagraphMargins, LinkColor, ParagraphTextStyle.Size, ...), or embed it in a custom StyleSheet and override individual methods for full control. The PartialTextStyle fields (as opposed to a plain TextStyle) mean a tag's contribution can be extended, not just adjusted - e.g. style.EmphasisTextStyle.Weight = font.WeightBold; style.EmphasisTextStyle.Set |= FieldWeight makes emphasis bold as well as italic, which a plain TextStyle field couldn't express: the fields it doesn't set wouldn't be distinguishable from fields deliberately set to their zero value.
func NewDarkStyleSheet ¶
func NewDarkStyleSheet() *DefaultStyleSheet
NewDarkStyleSheet returns whynot's built-in dark theme - light text on a dark background - and is what NewView uses when no StyleSheet is given.
func NewLightStyleSheet ¶
func NewLightStyleSheet() *DefaultStyleSheet
NewLightStyleSheet returns whynot's built-in light theme - dark text on a light background. Everything but color is identical to NewDarkStyleSheet (margins, sizes, weights, dimensional constants), so it's built from it rather than repeating them: ThematicBreakColor, BlockquoteBarColor, TableFrameColor, and ImagePlaceholderColor are also left as NewDarkStyleSheet's mid-grey, which reads fine against either a light or dark background, unlike TextColor/Background/LinkColor/CodeBlockColor/CodeSpanColor/Scrollbar, which need real light-appropriate values.
func (*DefaultStyleSheet) BackgroundColor ¶
func (s *DefaultStyleSheet) BackgroundColor() color.Color
func (*DefaultStyleSheet) BlockquoteGeometry ¶
func (s *DefaultStyleSheet) BlockquoteGeometry(node *ASTNode) BlockquoteGeometry
func (*DefaultStyleSheet) BorderColor ¶
func (s *DefaultStyleSheet) BorderColor(node *ASTNode) color.Color
func (*DefaultStyleSheet) Color ¶
func (s *DefaultStyleSheet) Color(node *ASTNode) color.Color
Color returns node's own contribution to the cascading text color - nil for any tag with no opinion, so ResolvedColor's ancestry walk passes through it to an outer contributor (or the TextColor default at the root). Only Link, the two code tags, and Unsupported have an opinion; block-level decoration colors live on BorderColor instead, since e.g. a blockquote's bar color must never leak into its inner text color.
func (*DefaultStyleSheet) HighlightColor ¶
func (s *DefaultStyleSheet) HighlightColor() color.Color
func (*DefaultStyleSheet) LineHeight ¶ added in v0.3.0
func (s *DefaultStyleSheet) LineHeight(node *ASTNode) float64
func (*DefaultStyleSheet) Margins ¶
func (s *DefaultStyleSheet) Margins(node *ASTNode) Margins
func (*DefaultStyleSheet) ScrollbarColor ¶ added in v0.2.0
func (s *DefaultStyleSheet) ScrollbarColor(hover, pressed bool) color.Color
func (*DefaultStyleSheet) StrikeThickness ¶
func (s *DefaultStyleSheet) StrikeThickness(node *ASTNode) float64
StrikeThickness returns 0 unless node (or an ancestor) is tagged TagStrikethrough - encoding "should this be struck" and "how thick" in one value, the same way TextStyle's Weight being font.WeightNormal means "not bold".
func (*DefaultStyleSheet) TableGeometry ¶
func (s *DefaultStyleSheet) TableGeometry(node *ASTNode) TableGeometry
func (*DefaultStyleSheet) TextStyle ¶
func (s *DefaultStyleSheet) TextStyle(node *ASTNode) PartialTextStyle
TextStyle returns each tag's own contribution - only the fields Set flags are meaningful; ResolvedTextStyle merges these across a node's ancestry, so e.g. Strong nested inside Emphasis picks up both. At the root (node == nil) it returns BaseTextStyle claiming every field, the fallback ResolvedTextStyle reaches if no ancestor ever set some field - guarantees e.g. Size is never silently left at 0.
func (*DefaultStyleSheet) ThematicBreakThickness ¶
func (s *DefaultStyleSheet) ThematicBreakThickness(node *ASTNode) float64
func (*DefaultStyleSheet) ViewMargins ¶
func (s *DefaultStyleSheet) ViewMargins() Margins
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 FaceSelector ¶
type FaceSelector interface {
SelectFace(TextStyle) (font.Face, error)
// SetDPI updates the DPI used to rasterize faces going forward,
// invalidating any cached font.Face built at the old DPI. Called every
// View.Layout frame (see view.go), so implementations must treat a
// call with an unchanged dpi as a cheap no-op.
SetDPI(float64)
}
type FileImageSource ¶
type FileImageSource struct{}
FileImageSource is the default ImageSource: src is opened exactly as written (via the OS's own working-directory-relative resolution), with no further resolution - today's pre-existing behavior, used unless an embedder opts into WithImageSource.
func (FileImageSource) Open ¶
func (FileImageSource) Open(resolved string) (io.ReadCloser, error)
type FontFamily ¶
type FontFamily int
const ( Proportional FontFamily = iota Monospace SmallCaps )
func (FontFamily) String ¶ added in v0.3.0
func (f FontFamily) String() string
String names f for logging/debugging - e.g. a FaceSelector reporting which family a font-resolution decision was made for.
type GoFontFaceSelector ¶
type GoFontFaceSelector struct {
// contains filtered or unexported fields
}
func NewGoFontFaceSelector ¶
func NewGoFontFaceSelector(dpi float64) *GoFontFaceSelector
func (*GoFontFaceSelector) SelectFace ¶
func (s *GoFontFaceSelector) SelectFace(style TextStyle) (font.Face, error)
func (*GoFontFaceSelector) SetDPI ¶
func (s *GoFontFaceSelector) SetDPI(dpi float64)
type HighlightSpan ¶ added in v0.3.0
type HighlightSpan struct {
Text string
Class TokenClass
}
HighlightSpan is one classified run of a Highlighter's output.
type Highlighter ¶ added in v0.3.0
type Highlighter interface {
// Highlight splits code (a whole fenced/indented block's source,
// potentially multiple lines) into consecutive spans whose Text
// concatenates back to code exactly. language is the fence's own
// info string (e.g. "go"), "" if there was none (including every
// indented code block, which has no fence to carry one).
// Implementations are free to return a single TokenPlain span
// covering all of code (e.g. an unrecognized language) - equivalent
// to no highlighting for that block.
Highlight(language, code string) []HighlightSpan
}
Highlighter classifies a code block's source into consecutive typed spans, used to color it token-by-token instead of as one flat run. Parse/NewView have no highlighter by default - every code block then renders in one flat color (StyleSheet's existing TagCodeBlock case), exactly as before this existed.
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 {
// contains filtered or unexported fields
}
func (*ImageBox) DrawInline ¶
func (*ImageBox) PendingImages ¶
func (*ImageBox) SpaceWidth ¶
type ImageCache ¶
type ImageCache struct {
// contains filtered or unexported fields
}
ImageCache wraps an ImageSource with the "management of image data" the library owns: an image is resolved, fetched, and decoded at most once, however many times Load is called for it afterwards - every layout rebuild asks again. A Canvas implementation never fetches its own copy either - ImageBox carries the already-decoded image.Image forward from here, straight into Canvas.DrawImage.
Load never blocks: a cache miss (or a failed entry whose retry delay has elapsed) returns ImagePending immediately and starts a fetch on a background goroutine, which is why this type is safe for concurrent use (Load from the layout goroutine, the goroutine's own writes as a fetch progresses) - unlike everything else in this codebase, which is only ever touched from ebiten's single game-loop goroutine and has no need to be.
func NewImageCache ¶
func NewImageCache(source ImageSource) *ImageCache
func (*ImageCache) ChangedSince ¶
func (c *ImageCache) ChangedSince(mark uint64) (changed []ImageChange, newMark uint64)
ChangedSince returns every change since a previous ChangedSince (or an initial call with mark 0) returned mark, plus a new mark to pass next time. Cheap: each entry just remembers the cache's own version counter at the moment it last changed, so this is a scan of however many distinct images exist, not a growing log.
func (*ImageCache) Load ¶
func (c *ImageCache) Load(src string) (resolved string, result ImageResult)
Load resolves src and returns its current state, starting a fetch in the background on a genuine miss, or on a failed entry old enough to retry - never blocking on the fetch itself. resolved is still returned when Resolve itself failed - InlineImage's fallback text names it in a missing- or broken-image message.
type ImageChange ¶
type ImageChange struct {
Src string
// BoundsRevealed is true if, since the mark passed to
// ChangedSince, this src's bounds became known for the first time
// - the one transition that can change a slot's height
// unpredictably (everything else - becoming ready, a retry's
// outcome - happens at an already-known, already-laid-out size).
// See View.invalidateChangedImages.
BoundsRevealed bool
}
ImageChange is one entry ImageCache.ChangedSince reports.
type ImageResult ¶
type ImageResult struct {
Status ImageStatus
// Bounds is the image's native pixel size - known once Status ==
// ImageReady, but may already be known earlier, while Status is
// still ImagePending (see ImageCache's header-peek). Zero while
// genuinely unknown.
Bounds image.Rectangle
// Image is the decoded image - valid only when Status == ImageReady
// and this isn't an animated GIF (Animation is set instead; exactly
// one of the two is set on a ready result).
Image image.Image
// Animation is set instead of Image when the decoded image is an
// animated GIF - see AnimatedImage.
Animation *AnimatedImage
// Err says why the most recent attempt failed - valid only when
// Status == ImageFailed.
Err error
}
ImageResult is what ImageCache.Load returns for one src.
type ImageSource ¶
type ImageSource interface {
// Resolve turns src into a stable, absolute identifier - the cache
// key, and what a missing- or broken-image's fallback message
// names. Pure and cheap: no I/O, just interpreting src against
// whatever base the ImageSource knows about.
Resolve(src string) (string, error)
// Open fetches the bytes at a value Resolve returned.
Open(resolved string) (io.ReadCloser, error)
}
ImageSource resolves and fetches an image's raw bytes - the embedder-supplied policy for wherever an InlineImage's src actually points (relative to a document's own location, over http(s), from an archive, whatever it needs). Kept out of the library's own hands the same way document loading and link resolution already are (see cmd/whynot/main.go's loadDocument/resolveLink) - only the seam lives here. Always used through an ImageCache (see NewImageCache), never called directly by GetInlineLayout, so resolving and fetching an image only ever happens once no matter how many times it's asked for - and always from a background goroutine, since Open can block (a slow disk, a slow or hanging server) and ImageCache.Load must never block its caller.
type ImageStatus ¶
type ImageStatus int
ImageStatus is an image's state within an ImageCache.
const ( // ImagePending means a fetch is in flight (or about to start) - not // yet ready, not (yet, or any longer) failed. Bounds may already be // known even in this state - see ImageCache's own doc comment. ImagePending ImageStatus = iota // ImageReady means Image holds the fully decoded result. ImageReady // ImageFailed means the most recent attempt failed - Err says how. // Not necessarily permanent: see ImageCache's retry behavior. ImageFailed )
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 RenderingContext, width int) InlineLayout
}
type InlineImage ¶
type InlineImage struct {
// contains filtered or unexported fields
}
func (*InlineImage) GetInlineLayout ¶
func (i *InlineImage) GetInlineLayout(ctx RenderingContext, 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; falls back to i.ownCache, lazily created from FileImageSource, if ctx was built as a bare RenderingContext{} with no ImageCache set, the same default NewView itself uses - so direct RenderingContext callers that never touch an image, like most of this package's own tests, don't need to know ImageCache exists) - never blocking, since ImageCache.Load never does. There are three outcomes:
- Ready: the decoded image, scaled by ctx.Scale like every other sized quantity in the layout system (RenderingContext.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.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() *ASTNode
type InlineLayout ¶
type InlineLayout interface {
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 rendered gap (splitBoxes, LineBox's
// BoundsAndAdvance/HitTest/drawContents) and is never a line-break
// point (splitBoxes). 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, x, y int, now time.Duration) int
HitTest(p image.Point, x, y int) (hit Hit, offset image.Point, nextX int)
// PendingImages - see BlockLayout's identical method.
PendingImages() []string
}
InlineLayout's HitTest mirrors DrawInline's own calling convention exactly - same (x, y), same "next x" return - so LineBox.HitTest can drive the identical accumulation loop drawContents does, just calling HitTest instead of DrawInline at each step.
type InlineText ¶
type InlineText struct {
// contains filtered or unexported fields
}
func (*InlineText) GetInlineLayout ¶
func (t *InlineText) GetInlineLayout(ctx RenderingContext, width int) InlineLayout
func (*InlineText) Node ¶
func (t *InlineText) Node() *ASTNode
type LineBox ¶
type LineBox struct {
// Glue, when true, abuts parts directly with no inter-part gap -
// for a line whose parts are already-contiguous substrings of one
// original string (e.g. CodeBlock's classified token spans, each a
// verbatim slice of the source line, any whitespace between tokens
// already included in a plain-class span's own text) rather than
// separately word-split text (TextBlock's parts, built by
// appendString's strings.Fields, which discards whitespace and so
// needs a real inter-word gap reconstructed from SpaceWidth()).
Glue bool
// contains filtered or unexported fields
}
func (*LineBox) Bounds ¶
Bounds only compensates for a negative bounds.Min.X (an overshooting left bearing), not a positive one: a code line's leading indentation has real advance but no ink, so it's occupied space starting at this box's own x=0, not excess to crop away. Cropping it under-reported indented lines' width and broke hit-testing on their right-hand side.
func (*LineBox) BoundsAndAdvance ¶
Same reasoning as TextBox: parts are fixed at construction and a LineBox is never reused across a re-layout, so this is safe to compute once and reuse for the instance's whole life. Inter-word spacing here must match drawContents/splitBoxes exactly (each word's own SpaceWidth(), not a fixed value) - this is what a caller measures via Bounds() to decide how much room the line actually needs, so any mismatch with what actually gets drawn silently under- or over-reports it.
func (*LineBox) HitTest ¶
HitTest mirrors drawContents' own accumulation loop, calling each part's HitTest instead of DrawInline at each step, so the two can't drift apart. p needs no shift: y is the same internal y drawContents computes for external y=0, which already lands a part's footprint in Bounds()'s frame - the same frame p arrives in.
func (*LineBox) PendingImages ¶
type ListItemHeadBlock ¶
type ListItemHeadBlock struct {
WithoutMargins
// 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 RenderingContext, width int) BlockLayout
func (*ListItemHeadBlock) Node ¶
func (b *ListItemHeadBlock) Node() *ASTNode
type ListItemMarkerBox ¶
type ListItemMarkerBox struct {
Marker InlineLayout
}
func (*ListItemMarkerBox) Bounds ¶
func (b *ListItemMarkerBox) Bounds() image.Rectangle
func (*ListItemMarkerBox) BoundsAndAdvance ¶
func (b *ListItemMarkerBox) BoundsAndAdvance() (image.Rectangle, int)
func (*ListItemMarkerBox) DrawInline ¶
func (*ListItemMarkerBox) Glued ¶ added in v0.3.1
func (b *ListItemMarkerBox) Glued() bool
Glued is always false - a ListItemMarkerBox is only ever a line's first part (ListItemHeadBlock.GetBlockLayout), so it's never queried by the gap/break logic that Glued exists for.
func (*ListItemMarkerBox) HitTest ¶
HitTest mirrors DrawInline exactly: the marker is drawn hanging off to the left of x (DrawInline's x-advance-space), not at x itself, so a naive check against BoundsAndAdvance's own (zero-width) bounds would never match a click on the visible marker glyph.
func (*ListItemMarkerBox) PendingImages ¶
func (b *ListItemMarkerBox) PendingImages() []string
func (*ListItemMarkerBox) Source ¶
func (b *ListItemMarkerBox) Source() Source
func (*ListItemMarkerBox) SpaceWidth ¶
func (b *ListItemMarkerBox) SpaceWidth() int
type MarginBlock ¶
type MarginBlock struct {
Block
// contains filtered or unexported fields
}
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.StyleSheet via node - unscaled, matching Block.Margins' convention (StackBlock.GetBlockLayout, the one real caller, scales the result via ctx.ScaledMargins).
func (*MarginBlock) Margins ¶
func (b *MarginBlock) Margins(ctx RenderingContext) Margins
func (*MarginBlock) Node ¶
func (b *MarginBlock) Node() *ASTNode
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 TagParagraph node (purely so its margins resolve like a paragraph's) - the wrapped content's TagListItem is the more correct identity for hit-testing.
type Marginer ¶
type Marginer interface {
Margins(ctx RenderingContext) Margins
}
Marginer is anything that reports its own logical (unscaled) Margins - every Block satisfies it, but it's kept narrow so RenderingContext. ScaledMargins doesn't need the rest of the Block interface.
type MarkdownCompiler ¶
type MarkdownCompiler struct {
// contains filtered or unexported fields
}
MarkdownCompiler compiles Markdown source into a Block/ASTNode tree - structure only, no appearance. See Parse.
func (*MarkdownCompiler) AppendInlineNode ¶
func (c *MarkdownCompiler) AppendInlineNode(items []Inline, node gmast.Node, astNode *ASTNode) []Inline
AppendInlineNode walks an inline subtree, appending each leaf as an Inline. astNode is node's parent in the ASTNode tree - updated only by the constructs that get their own ASTTag (Emphasis, Strong, Link, CodeSpan, Strikethrough) via AddChild, and threaded straight through everywhere else. Appearance (font, color, strike) is never resolved here - each produced Inline just carries the ASTNode it was created under, resolved later by RenderingContext against a StyleSheet.
func (*MarkdownCompiler) CompileBlock ¶
func (c *MarkdownCompiler) CompileBlock(node gmast.Node, parent *ASTNode) Block
func (*MarkdownCompiler) CompileDocument ¶
func (c *MarkdownCompiler) CompileDocument(node gmast.Node) Block
CompileDocument compiles the document root. Always the top of the tree - gmast.KindDocument is never nested - so unlike CompileBlock/CompileListItem it needs no parent *ASTNode to thread through.
func (*MarkdownCompiler) CompileListItem ¶
func (*MarkdownCompiler) CompileNode ¶
func (c *MarkdownCompiler) CompileNode(node gmast.Node, parent *ASTNode) Block
func (*MarkdownCompiler) CompileTable ¶
func (c *MarkdownCompiler) CompileTable(node gmast.Node, parent *ASTNode) Block
CompileTable compiles a Table node. The header is mandatory (GFM requires it); the body is not - a table can legitimately have zero data rows, in which case Table has no TableBody child at all.
func (*MarkdownCompiler) CompileTableRow ¶
func (c *MarkdownCompiler) CompileTableRow(node gmast.Node, parent *ASTNode) []tableCell
CompileTableRow compiles the cells of a TableHeader or a TableRow - both have TableCell children directly, no intermediate node, so one method handles both despite the different AST kinds.
type ParseOption ¶ added in v0.3.0
type ParseOption func(*MarkdownCompiler)
ParseOption customizes Parse - see WithSyntaxHighlighter.
func WithSyntaxHighlighter ¶ added in v0.3.0
func WithSyntaxHighlighter(h Highlighter) ParseOption
WithSyntaxHighlighter sets the Highlighter Parse uses to color code blocks token-by-token. Unset, code blocks render in one flat color, exactly as before this option existed. See WithHighlighter for the equivalent NewView option.
type PartialTextStyle ¶
type PartialTextStyle struct {
TextStyle
Set TextStyleField
}
PartialTextStyle is what a single node's own tag contributes to TextStyle - only the fields flagged in Set are meaningful; every other field is inherited from further up the node's ancestry (see RenderingContext.ResolvedTextStyle).
type RenderingContext ¶
type RenderingContext struct {
Scale float64
FaceSelector
StyleSheet StyleSheet
// HighlightNode is the ASTNode currently under the mouse (e.g. a
// hovered link), or nil - see ResolvedColor. Set by View.Hover, which
// rebuilds the layout tree when it changes.
HighlightNode *ASTNode
// ImageCache resolves, fetches, and decodes an InlineImage's src,
// caching the result - see ImageCache's own doc comment. Defaults
// to NewImageCache(FileImageSource{}) (NewView), overridable via
// WithImageSource.
ImageCache *ImageCache
// 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 every View.Layout
// call. The one consumer today is AnimatedImage.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 (RenderingContext) ResolvedColor ¶
func (c RenderingContext) ResolvedColor(node *ASTNode) color.Color
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 (RenderingContext) ResolvedTextStyle ¶
func (c RenderingContext) ResolvedTextStyle(node *ASTNode) TextStyle
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 (RenderingContext) ScaledBlockquoteGeometry ¶
func (c RenderingContext) ScaledBlockquoteGeometry(node *ASTNode) BlockquoteGeometry
func (RenderingContext) ScaledMargins ¶
func (c RenderingContext) ScaledMargins(m Marginer) Margins
func (RenderingContext) ScaledStrikeThickness ¶
func (c RenderingContext) ScaledStrikeThickness(node *ASTNode) float64
func (RenderingContext) ScaledTableGeometry ¶
func (c RenderingContext) ScaledTableGeometry(node *ASTNode) TableGeometry
func (RenderingContext) ScaledThematicBreakThickness ¶
func (c RenderingContext) ScaledThematicBreakThickness(node *ASTNode) float64
func (RenderingContext) ScaledViewMargins ¶
func (c RenderingContext) ScaledViewMargins() Margins
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 ScrollPosition ¶
type ScrollPosition struct {
// contains filtered or unexported fields
}
ScrollPosition is an opaque snapshot of a View's scroll position, captured by ScrollPosition and restored by RestoreScrollPosition.
type ScrollbarColors ¶ added in v0.2.0
ScrollbarColors is the three colors DefaultStyleSheet.ScrollbarColor picks between - idle, hovered, and actively-dragged - kept as one struct (like BlockquoteGeometry/TableGeometry) since overriding one almost always means reconsidering all three together, as one "gets more prominent as you interact" progression.
type ScrollbarStyleSheet ¶ added in v0.2.0
type ScrollbarStyleSheet interface {
// ScrollbarColor returns the color to draw the scrollbar thumb in.
// hover is true whenever the thumb is highlighted (including while
// dragging); pressed is true only while actively being dragged.
ScrollbarColor(hover, pressed bool) color.Color
}
ScrollbarStyleSheet is an optional StyleSheet capability: a StyleSheet that also knows how to color a scrollbar thumb. It's optional, not part of StyleSheet itself, because a scrollbar is drawn by whatever embeds a View (cmd/whynot today, a future ebitenrenderer.Panel later) - View itself never draws one - so a StyleSheet that's never paired with a scrollbar (e.g. a test StyleSheet) isn't forced to implement it. A caller that wants a scrollbar type-asserts its StyleSheet against this interface and falls back to a reasonable default color when it isn't implemented.
type Source ¶
type Source interface {
Node() *ASTNode
}
Source is the common ground between Block and Inline: something with a semantic identity in the compiled ASTNode 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 {
// contains filtered or unexported fields
}
func (*StackBlock) GetBlockLayout ¶
func (b *StackBlock) GetBlockLayout(ctx RenderingContext, 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 RenderingContext) 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() *ASTNode
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.
type StackBox ¶
type StackBox struct {
// contains filtered or unexported fields
}
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) 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, never on that top-level aggregate itself.
type StyleSheet ¶
type StyleSheet interface {
// Margins returns the margins for node's own tag.
Margins(node *ASTNode) Margins
// TextStyle returns node's own tag's contribution to TextStyle -
// combined with its ancestors' contributions by
// RenderingContext.ResolvedTextStyle, since e.g. Strong nested inside
// Emphasis needs both a bold and an italic contribution to survive.
TextStyle(node *ASTNode) PartialTextStyle
// Color returns node's own tag's contribution to the cascading text
// color, or nil if it has no opinion (inherits from an ancestor, or
// the document default at the root - see
// RenderingContext.ResolvedColor). Mirrors CSS's `color`, which
// inherits by default.
Color(node *ASTNode) color.Color
// BorderColor returns node's own single, non-inherited decoration
// color - a thematic break's rule, a blockquote's bar, a table's
// frame. Unlike Color, this is always resolved directly against one
// node, never by walking ancestry - mirrors CSS's border-color,
// which doesn't inherit.
BorderColor(node *ASTNode) color.Color
// BackgroundColor returns the color the document's viewport is filled
// with before anything else draws. Unlike every other method here,
// it takes no node: it isn't a property of any particular tag, just
// painted once, for the whole view, before any content-specific
// resolution happens - a node parameter would have nothing real to
// mean.
BackgroundColor() color.Color
// ViewMargins returns the space between the document's viewport edge
// and its content, on all four sides - the same shape as a tag's own
// Margins, but for the whole view rather than one node, the same way
// BackgroundColor is a whole-view counterpart to Color.
ViewMargins() Margins
// HighlightColor returns the text color for whatever's under
// RenderingContext.HighlightNode (e.g. a hovered link) - takes no
// node for the same reason BackgroundColor doesn't: it isn't a
// property of any one tag, it's a transient interaction state
// RenderingContext.ResolvedColor substitutes in ahead of the normal
// per-node cascade.
HighlightColor() color.Color
// StrikeThickness returns the thickness for a strikethrough line at
// node, or 0 if node isn't (nor is any ancestor) struck through -
// this is both "should this be struck" and "how thick", the same way
// TextStyle's Weight being font.WeightNormal means "not bold".
StrikeThickness(node *ASTNode) float64
ThematicBreakThickness(node *ASTNode) float64
BlockquoteGeometry(node *ASTNode) BlockquoteGeometry
TableGeometry(node *ASTNode) TableGeometry
// LineHeight returns the multiplier applied to a line's natural
// Ascent+Descent to get the vertical space reserved for it - CSS's
// line-height, as a unitless ratio (e.g. 1.2 means 120% of the font's
// own single-line height). A font's own metrics don't reliably encode
// comfortable reading spacing (many real fonts report a zero or
// near-zero line gap), so this is what actually separates wrapped
// lines within a paragraph - distinct from Margins, which only
// separates one block from the next. 1.0 means no extra space beyond
// the font's bare ascent/descent.
LineHeight(node *ASTNode) float64
}
StyleSheet resolves an ASTNode's semantic role to concrete appearance - margins, text styling, color, and a few dimensional constants. Every method takes the node, uniformly, even where nothing in whynot itself varies a value by node today (there's only one table's worth of column gap, for instance) - an implementation is free to ignore it, but the shape leaves room for one that doesn't want to. See DefaultStyleSheet for whynot's built-in appearance.
type SyntaxColors ¶ added in v0.3.0
type SyntaxColors struct {
Keyword color.Color
// Type is shared by a builtin primitive type and a declared
// custom type/class name - see TokenType's own doc comment.
Type color.Color
// Function is a function/method name - see TokenFunction's own doc
// comment.
Function color.Color
String color.Color
Number color.Color
Comment color.Color
}
SyntaxColors is the palette a Highlighter's classified tokens draw from (see TagCodeKeyword..TagCodeFunction) - grouped like BlockquoteGeometry/ TableGeometry/ScrollbarColors, since picking a new palette usually means reconsidering the whole set together, not one color at a time.
type TableBlock ¶
type TableBlock struct {
WithoutMargins
// 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 RenderingContext, width int) BlockLayout
func (*TableBlock) Node ¶
func (b *TableBlock) Node() *ASTNode
type TableBox ¶
type TableBox struct {
// contains filtered or unexported fields
}
TableBox draws a GFM table: a frame around the whole thing, a rule under the header row, a rule between each pair of columns, and each cell positioned at its resolved (column, row) offset. columnOffsets/ rowOffsets have one more entry than there are columns/rows - the last entry is the table's own right/bottom edge, so Bounds() doesn't need separate width/height fields, and the header rule's position is just rowOffsets[1] (see TableBlock.GetBlockLayout for why that boundary is exactly where the rule belongs). Each column rule is centered in the columnGap between adjacent columns' content, at columnOffsets[c] - columnGap/2.
func (*TableBox) HitTest ¶
HitTest finds which cell (row, col) p falls in via columnOffsets/ rowOffsets, and falls back to the table's own source whenever there isn't one to recurse into (the frame, a gap between cells/rows) or the cell itself declines (its content is narrower/shorter than the cell's allotted space).
func (*TableBox) PendingImages ¶
type TableGeometry ¶
type TableGeometry struct {
FrameThickness float64
ColumnGap float64
RowGap float64
HeaderGap float64
ColumnRuleThickness float64
}
TableGeometry is the dimensional constants a table's layout needs - grouped since TableBlock.GetBlockLayout always reads all five together.
type TaskCheckbox ¶ added in v0.3.0
type TaskCheckbox struct {
// contains filtered or unexported fields
}
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 ¶ added in v0.3.0
func (c *TaskCheckbox) GetInlineLayout(ctx RenderingContext, width int) InlineLayout
func (*TaskCheckbox) Node ¶ added in v0.3.0
func (c *TaskCheckbox) Node() *ASTNode
type TextBlock ¶
type TextBlock struct {
WithoutMargins
// contains filtered or unexported fields
}
func (*TextBlock) GetBlockLayout ¶
func (b *TextBlock) GetBlockLayout(ctx RenderingContext, 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 TextStyleField ¶
type TextStyleField uint8
TextStyleField identifies one field of TextStyle, so a PartialTextStyle can say which fields it actually sets - needed because TextStyle's own zero values (font.WeightNormal, font.StyleNormal, Proportional) are real, meaningful values, not "unset" sentinels.
const ( FieldSize TextStyleField = 1 << iota FieldStyle FieldWeight FieldFamily )
type ThematicBreakBlock ¶
type ThematicBreakBlock struct {
WithoutMargins
// contains filtered or unexported fields
}
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 RenderingContext, width int) BlockLayout
func (*ThematicBreakBlock) Node ¶
func (b *ThematicBreakBlock) Node() *ASTNode
type TokenClass ¶ added in v0.3.0
type TokenClass int
TokenClass is a small, whynot-level classification of a syntax token - deliberately much coarser than a real highlighting library's own token taxonomy (chroma alone has ~50 TokenTypes): StyleSheet only needs enough categories to assign a handful of distinct colors, not to reproduce a library's full type system.
const ( TokenPlain TokenClass = iota // no special color - inherits the block's own CodeBlockColor TokenKeyword // TokenType is a type name - a builtin primitive type and a declared // custom type/class name share this one class, so e.g. int and a // user-defined struct read as the same kind of thing. A Highlighter // isn't expected to recognize every usage of a type, only what it // can tell from syntax (typically its declaration). TokenType // TokenFunction is a function/method name - same expectation as // TokenType: recognize what's clear from syntax (a declaration, and // often a call), not necessarily every usage. TokenFunction TokenString TokenNumber TokenComment )
type View ¶
type View struct {
// contains filtered or unexported fields
}
View renders a parsed Markdown document onto a Canvas. It owns the layout cache (rebuilt only when width or scale change, not every frame), viewport culling, and scroll-position anchoring across resizes - the parts of rendering a reflowing document that are easy to get wrong and not specific to any one game, so a caller doesn't have to reimplement them.
A View doesn't read input itself: call Scroll with deltas from whatever input source is appropriate for the embedding game, and call Layout whenever the available width or the display scale changes (typically from the embedding game's own layout/resize callback).
func NewView ¶
func NewView(source []byte, faceSelector FaceSelector, opts ...ViewOption) *View
NewView parses source and returns a View ready to render it once Layout has been called at least once to establish a width.
func (*View) DocumentBounds ¶
DocumentBounds returns the document's estimated extent, origin at (0, 0): width is the last Layout width; height is the current best estimate of the total (see refreshSlotHeights), exact once every slot has been resolved. A caller scales the ratio between this and VisibleViewBounds to build its own scrollbar.
func (*View) Draw ¶
Draw renders the document onto dst with its top-left corner at (x, y), at the current scroll position. Content above the current cursor, and content outside dst's bounds, is never resolved or drawn - Draw's cost tracks what's visible, not the document's total size or how far into it the scroll position is.
Draw fills dst's whole bounds with the StyleSheet's background color first - a caller doesn't need its own clear/fill step (or to know the StyleSheet's background color itself) before calling Draw. The ViewMargins' top/bottom is already baked into the scroll position (see rebuild); only Left needs applying here.
func (*View) HitTest ¶
HitTest identifies what's at document position (x, y) - the same coordinate space Draw's (x, y) places content's origin into. hit is nil if (x, y) doesn't land on any content - past the end of the document, or in a margin/gap. Otherwise offset is where hit.Bounds() should be placed to get its absolute position in that same space, e.g. for drawing an outline around it.
y is resolved via normalizeCursor, the same cursor-relative walk Scroll/Layout use, so a click far from the current scroll position doesn't force-build every slot in between.
Known imprecision: normalizeCursor clamps a position above the very start of the document to the same value as the start itself, so clicking well above the rendered content resolves as if it landed on the first slot rather than missing. Not worth fixing since callers only ever pass points within their own rendered viewport.
func (*View) Hover ¶
Hover updates the currently-highlighted link, given the mouse position in the same coordinate space HitTest/Draw use - call every frame from the embedding game's own input handling. Highlighting only ever changes color, never layout, so at most two slots (the one left, the one entered) are surgically invalidated rather than triggering a full rebuild.
Hover also reports the link under (x, y), same as LinkAt, so a caller handling a click at the same position doesn't need a second HitTest.
func (*View) Layout ¶
Layout sets the pixel width and display scale to render at (DPI = scale * 72, matching main.go's convention), and now - elapsed time since rendering started, the embedder's own reference point (see RenderingContext.Time) - which it stores unconditionally, even when nothing else changed, since an animated image needs fresh time every call to actually animate. Cheap to call every frame otherwise: the layout tree only rebuilds when width or scale actually change.
height is the viewport's own height - not used for wrapping (only width affects that), only so pre-layout/prefetch below know where the visible area ends. Not stored - used transiently within this call.
func (*View) LinkAt ¶
LinkAt reports the destination URL of the link at document position (x, y) - the same coordinate space HitTest/Hover use. ok is false if (x, y) doesn't land on a link. Unlike Hover, this never changes the highlight - use it to query a position other than the current hover (e.g. from a test, or a separate input source).
func (*View) RestoreScrollPosition ¶
func (v *View) RestoreScrollPosition(p ScrollPosition)
RestoreScrollPosition restores a position captured earlier by ScrollPosition - only meaningful on the same View it was taken from.
func (*View) ScaledViewMargins ¶ added in v0.2.0
ScaledViewMargins returns the View's current effective margin between its viewport edge and its content, in real screen pixels (already multiplied by the zoom/DPI scale most recently passed to Layout) - the same value Draw and HitTest use internally to place content, exposed so an embedder positioning something else relative to the View (e.g. cmd/whynot's own scrollbar, drawn beside it rather than by it) can stay in exact agreement with it.
func (*View) Scroll ¶
Scroll adjusts the vertical scroll position by dy pixels: negative dy moves the cursor forward through the document, revealing later content ("scrolling down"); positive moves back toward the start. This matches ebiten.Wheel()'s dy passed straight through, so callers don't need to negate it.
func (*View) ScrollPosition ¶
func (v *View) ScrollPosition() ScrollPosition
ScrollPosition captures the View's current scroll position.
func (*View) ScrollToAnchor ¶
ScrollToAnchor scrolls to put the heading with the given anchor id (see ASTNode.ID) at the top of the viewport, e.g. after following a link with a URL fragment. ok is false, and the scroll position unchanged, if no heading has that id or nothing has been laid out yet (see Layout).
Only top-level headings are found - a heading nested inside a blockquote or list won't be. Cheap regardless of document size: a slot's own Node() is known without resolving its content into a box (see StackBox.boxAt), so this never lays out anything beyond what's already built.
func (*View) ScrollToRatio ¶
ScrollToRatio sets the scroll position to ratio (clamped to [0, 1]) through the document's current best-estimated height - e.g. for a caller implementing scrollbar-thumb dragging, where DocumentBounds/ VisibleViewBounds already give the inverse ratio back. Recomputes from the live estimate every call rather than a target captured once, so a jump into not-yet-resolved territory only ever corrects toward whatever ratio the caller asks for next, never drifts from it.
func (*View) SetStyleSheet ¶
func (v *View) SetStyleSheet(s StyleSheet)
SetStyleSheet swaps the View's StyleSheet and takes effect immediately - GetBlockLayout/GetInlineLayout resolve and bake in concrete style values (colors, margins collapsed to gaps, ...) when the layout tree is built, so without an explicit rebuild here the change wouldn't be visible until whatever next happened to resize the view. Rebuilding also re-anchors the scroll position by ratio through the current slot, the same way a resize does - a new StyleSheet can change content heights (different margins, font sizes, ...) just as reflowing at a new width can.
func (*View) Title ¶
Title returns the document's own title - the text of its first heading, at any level - or ok=false if it has none. A caller (e.g. for a window/tab title) decides its own fallback; a document with no heading at all is a normal, unremarkable case, not an error.
Only a top-level heading is found, the same limitation ScrollToAnchor has and for the same reason: one nested inside a blockquote or list isn't reachable this way.
func (*View) VisibleViewBounds ¶
VisibleViewBounds returns the sub-rectangle of DocumentBounds currently visible for a viewport of viewportSize (the same size passed to Draw's dst). The bottom edge is exactly top+viewportSize.Y, clamped to the document's total - deliberately not "wherever the last slot DrawFrom would draw happens to end": a slot's own bottom can overshoot the viewport by a lot (a large image or table), and which slot ends up last shifts discretely while scrolling, so pinning the result to it made the thumb's size visibly jump - a quantization artifact independent of estimation accuracy. The forward walk below still force-resolves the same slots DrawFrom would draw this frame, as a side effect: no laziness benefit in skipping it, since Draw resolves them anyway, and doing so keeps the document total as accurate as possible near the viewport.
type ViewOption ¶
type ViewOption func(*View)
ViewOption customizes a View at construction, via NewView's opts parameter.
func WithHighlighter ¶ added in v0.3.0
func WithHighlighter(h Highlighter) ViewOption
WithHighlighter sets the Highlighter used to color code blocks token-by-token - the NewView-level equivalent of Parse's own WithSyntaxHighlighter (named differently since it's a different option type, ViewOption vs ParseOption; Go has no function overloading). Unset, code blocks render in one flat color, same as before this option existed.
func WithImageSource ¶
func WithImageSource(s ImageSource) ViewOption
WithImageSource overrides the ImageSource NewView otherwise defaults to (FileImageSource) - e.g. for an embedder that wants images resolved relative to a document's own location, or fetched over http(s), the way cmd/whynot does. Wrapped in a cache automatically (see ImageCache), so resolving/fetching a given image only happens once for the life of the resulting View, however many times it's asked for (every layout rebuild - resize, zoom, theme change, even hovering a different link - asks again).
func WithStyleSheet ¶
func WithStyleSheet(s StyleSheet) ViewOption
WithStyleSheet overrides the StyleSheet NewView otherwise defaults to (NewDarkStyleSheet) - e.g. NewView(source, faceSelector, WithStyleSheet(NewLightStyleSheet())).
type WithoutMargins ¶
type WithoutMargins struct{}
WithoutMargins satisfies Block's Margins() with a zero value, for content that never gets margins of its own (a list item's head, a table cell's content).
func (WithoutMargins) Margins ¶
func (WithoutMargins) Margins(ctx RenderingContext) Margins
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
backends
|
|
|
giobackend
module
|
|
|
Package chromahighlight implements whynot.Highlighter on top of github.com/alecthomas/chroma/v2.
|
Package chromahighlight implements whynot.Highlighter on top of github.com/alecthomas/chroma/v2. |
|
cmd
|
|
|
whynot
command
|
|
|
Package ebitenrenderer implements whynot.Canvas on top of ebiten.
|
Package ebitenrenderer implements whynot.Canvas on top of ebiten. |
|
examples
|
|
|
chromahighlight
command
Command chromahighlight is a runnable example of whynot.WithHighlighter backed by chromahighlight.Highlighter (github.com/alecthomas/chroma/v2).
|
Command chromahighlight is a runnable example of whynot.WithHighlighter backed by chromahighlight.Highlighter (github.com/alecthomas/chroma/v2). |
|
customfont
command
Command customfont is a runnable example of whynot.CustomFontFaceSelector.
|
Command customfont is a runnable example of whynot.CustomFontFaceSelector. |
|
panel
command
Command panel is a runnable example of ebitenrenderer.Panel embedded in a larger game window: it fills the whole window with a solid color standing in for "other game content," then draws a Panel into an inset rectangle, proving Panel.Draw's SubImage clipping holds on all four edges - something cmd/whynot never has to demonstrate, since its View occupies basically the whole window.
|
Command panel is a runnable example of ebitenrenderer.Panel embedded in a larger game window: it fills the whole window with a solid color standing in for "other game content," then draws a Panel into an inset rectangle, proving Panel.Draw's SubImage clipping holds on all four edges - something cmd/whynot never has to demonstrate, since its View occupies basically the whole window. |
|
systemfont
command
Command systemfont is a runnable example of systemfont.SystemFontFaceSelector.
|
Command systemfont is a runnable example of systemfont.SystemFontFaceSelector. |
|
view
command
Command view is a runnable example of whynot.View wired up by hand - full control over input handling, at the cost of doing it yourself.
|
Command view is a runnable example of whynot.View wired up by hand - full control over input handling, at the cost of doing it yourself. |
|
wasm
command
Command wasm is a runnable example of whynot compiled to WebAssembly and run in a browser, via Ebitengine's own js/wasm backend - see README.md in this directory for how to build and serve it.
|
Command wasm is a runnable example of whynot compiled to WebAssembly and run in a browser, via Ebitengine's own js/wasm backend - see README.md in this directory for how to build and serve it. |
|
Package systemfont resolves fonts by name from whatever's installed on the host machine (e.g.
|
Package systemfont resolves fonts by name from whatever's installed on the host machine (e.g. |


