scene/decoder.go

Functions Structs

Functions

func AntiAlias

AntiAlias reads the current SetAntiAlias command data.

Returns true if anti-aliasing is enabled, false if disabled.

Only valid when Tag() == TagSetAntiAlias.

func (d *Decoder) AntiAlias() bool {
	if d.drawIdx+1 > len(d.enc.drawData) {
		return true	// default: AA enabled
	}
	val := d.enc.drawData[d.drawIdx]
	d.drawIdx++
	return val != 0
}

func Brush

Brush reads the current Brush command data.

Returns the RGBA color values.

Only valid when Tag() == TagBrush.

func (d *Decoder) Brush() (r, g, b, a float32) {
	if d.pathIdx+4 > len(d.enc.pathData) {
		return 0, 0, 0, 1
	}
	r = d.enc.pathData[d.pathIdx]
	g = d.enc.pathData[d.pathIdx+1]
	b = d.enc.pathData[d.pathIdx+2]
	a = d.enc.pathData[d.pathIdx+3]
	d.pathIdx += 4
	return r, g, b, a
}

func CollectPath

CollectPath collects all path commands until EndPath into a new Path.

Returns the collected path. The decoder is advanced past the path data.

Returns nil if not currently at the start of a path.

func (d *Decoder) CollectPath() *Path {
	path := NewPath()

	for d.Next() {
		switch d.currentTag {
		case TagMoveTo:
			x, y := d.MoveTo()
			path.MoveTo(x, y)
		case TagLineTo:
			x, y := d.LineTo()
			path.LineTo(x, y)
		case TagQuadTo:
			cx, cy, x, y := d.QuadTo()
			path.QuadTo(cx, cy, x, y)
		case TagCubicTo:
			c1x, c1y, c2x, c2y, x, y := d.CubicTo()
			path.CubicTo(c1x, c1y, c2x, c2y, x, y)
		case TagClosePath:
			path.Close()
		case TagEndPath:
			return path
		}
	}

	return path
}

func CubicTo

CubicTo reads the current CubicTo command data.

Returns control point 1 (c1x, c1y), control point 2 (c2x, c2y), and destination (x, y).

Only valid when Tag() == TagCubicTo.

 

nolint:gocritic // tooManyResultsChecker: 6 results are natural for cubic curves (2 control points + endpoint)

func (d *Decoder) CubicTo() (c1x, c1y, c2x, c2y, x, y float32) {
	if d.pathIdx+6 > len(d.enc.pathData) {
		return 0, 0, 0, 0, 0, 0
	}
	c1x = d.enc.pathData[d.pathIdx]
	c1y = d.enc.pathData[d.pathIdx+1]
	c2x = d.enc.pathData[d.pathIdx+2]
	c2y = d.enc.pathData[d.pathIdx+3]
	x = d.enc.pathData[d.pathIdx+4]
	y = d.enc.pathData[d.pathIdx+5]
	d.pathIdx += 6
	return c1x, c1y, c2x, c2y, x, y
}

func Encoding

Encoding returns the encoding being decoded.

func (d *Decoder) Encoding() *Encoding {
	return d.enc
}

func Fill

Fill reads the current Fill command data.

Returns the brush and fill style.

Only valid when Tag() == TagFill.

func (d *Decoder) Fill() (brush Brush, style FillStyle) {
	if d.drawIdx+2 > len(d.enc.drawData) {
		return Brush{}, FillNonZero
	}

	brushIdx := d.enc.drawData[d.drawIdx]
	styleVal := d.enc.drawData[d.drawIdx+1]
	d.drawIdx += 2

	if int(brushIdx) < len(d.enc.brushes) {
		brush = d.enc.brushes[brushIdx]
	}
	style = FillStyle(styleVal)
	return brush, style
}

func FillRoundRect

FillRoundRect reads the current FillRoundRect command data.

Returns the brush, fill style, bounding rectangle, and corner radii.

Only valid when Tag() == TagFillRoundRect.

func (d *Decoder) FillRoundRect() (brush Brush, style FillStyle, rect Rect, rx, ry float32) {
	if d.drawIdx+2 > len(d.enc.drawData) {
		return Brush{}, FillNonZero, Rect{}, 0, 0
	}

	brushIdx := d.enc.drawData[d.drawIdx]
	styleVal := d.enc.drawData[d.drawIdx+1]
	d.drawIdx += 2

	if int(brushIdx) < len(d.enc.brushes) {
		brush = d.enc.brushes[brushIdx]
	}
	style = FillStyle(styleVal)

	if d.pathIdx+6 > len(d.enc.pathData) {
		return brush, style, Rect{}, 0, 0
	}

	rect = Rect{
		MinX:	d.enc.pathData[d.pathIdx],
		MinY:	d.enc.pathData[d.pathIdx+1],
		MaxX:	d.enc.pathData[d.pathIdx+2],
		MaxY:	d.enc.pathData[d.pathIdx+3],
	}
	rx = d.enc.pathData[d.pathIdx+4]
	ry = d.enc.pathData[d.pathIdx+5]
	d.pathIdx += 6

	return brush, style, rect, rx, ry
}

func HasMore

HasMore returns true if there are more commands to decode.

func (d *Decoder) HasMore() bool {
	return d.enc != nil && d.tagIdx < len(d.enc.tags)
}

func Image

Image reads the current Image command data.

Returns the image index and transform.

Only valid when Tag() == TagImage.

func (d *Decoder) Image() (imageIndex uint32, transform Affine) {
	if d.drawIdx+1 > len(d.enc.drawData) {
		return 0, IdentityAffine()
	}

	imageIndex = d.enc.drawData[d.drawIdx]
	d.drawIdx++

	if d.transIdx < len(d.enc.transforms) {
		transform = d.enc.transforms[d.transIdx]
		d.transIdx++
	} else {
		transform = IdentityAffine()
	}
	return imageIndex, transform
}

func LineTo

LineTo reads the current LineTo command data.

Returns the destination point (x, y).

Only valid when Tag() == TagLineTo.

func (d *Decoder) LineTo() (x, y float32) {
	if d.pathIdx+2 > len(d.enc.pathData) {
		return 0, 0
	}
	x = d.enc.pathData[d.pathIdx]
	y = d.enc.pathData[d.pathIdx+1]
	d.pathIdx += 2
	return x, y
}

func MoveTo

MoveTo reads the current MoveTo command data.

Returns the destination point (x, y).

Only valid when Tag() == TagMoveTo.

func (d *Decoder) MoveTo() (x, y float32) {
	if d.pathIdx+2 > len(d.enc.pathData) {
		return 0, 0
	}
	x = d.enc.pathData[d.pathIdx]
	y = d.enc.pathData[d.pathIdx+1]
	d.pathIdx += 2
	return x, y
}

func NewDecoder

NewDecoder creates a new decoder for the given encoding.

Returns nil if encoding is nil.

func NewDecoder(enc *Encoding) *Decoder {
	if enc == nil {
		return nil
	}
	return &Decoder{
		enc: enc,
	}
}

func Next

Next advances to the next command in the stream.

Returns true if there is another command, false when iteration is complete.

After calling Next, use Tag() to get the current command type,

then call the appropriate method (MoveTo, LineTo, Fill, etc.) to get the data.

func (d *Decoder) Next() bool {
	if d.enc == nil || d.tagIdx >= len(d.enc.tags) {
		return false
	}

	d.currentTag = d.enc.tags[d.tagIdx]
	d.tagIdx++
	return true
}

func Peek

Peek returns the next tag without advancing the decoder.

Returns 0 if at end of stream.

func (d *Decoder) Peek() Tag {
	if d.tagIdx >= len(d.enc.tags) {
		return 0
	}
	return d.enc.tags[d.tagIdx]
}

func Position

Position returns the current position in the tag stream.

func (d *Decoder) Position() int {
	return d.tagIdx
}

func PushLayer

PushLayer reads the current PushLayer command data.

Returns the blend mode and alpha value.

Only valid when Tag() == TagPushLayer.

func (d *Decoder) PushLayer() (blend BlendMode, alpha float32) {
	if d.drawIdx+2 > len(d.enc.drawData) {
		return BlendNormal, 1.0
	}

	blendVal := d.enc.drawData[d.drawIdx]
	alphaBits := d.enc.drawData[d.drawIdx+1]
	d.drawIdx += 2

	blend = BlendMode(blendVal)
	alpha = math.Float32frombits(alphaBits)
	return blend, alpha
}

func QuadTo

QuadTo reads the current QuadTo command data.

Returns the control point (cx, cy) and destination point (x, y).

Only valid when Tag() == TagQuadTo.

func (d *Decoder) QuadTo() (cx, cy, x, y float32) {
	if d.pathIdx+4 > len(d.enc.pathData) {
		return 0, 0, 0, 0
	}
	cx = d.enc.pathData[d.pathIdx]
	cy = d.enc.pathData[d.pathIdx+1]
	x = d.enc.pathData[d.pathIdx+2]
	y = d.enc.pathData[d.pathIdx+3]
	d.pathIdx += 4
	return cx, cy, x, y
}

func Reset

Reset resets the decoder to the beginning of the encoding.

This allows reusing the decoder for multiple passes.

func (d *Decoder) Reset(enc *Encoding) {
	d.enc = enc
	d.tagIdx = 0
	d.pathIdx = 0
	d.drawIdx = 0
	d.transIdx = 0
	d.textIdx = 0
	d.currentTag = 0
}

func SkipPath

SkipPath advances past all path commands until EndPath is found.

This is useful for quickly skipping paths that are outside the clip region.

func (d *Decoder) SkipPath() {
	for d.Next() {
		switch d.currentTag {
		case TagMoveTo:
			d.pathIdx += 2
		case TagLineTo:
			d.pathIdx += 2
		case TagQuadTo:
			d.pathIdx += 4
		case TagCubicTo:
			d.pathIdx += 6
		case TagEndPath:
			return
		}
	}
}

func Stroke

Stroke reads the current Stroke command data.

Returns the brush and stroke style.

Only valid when Tag() == TagStroke.

func (d *Decoder) Stroke() (brush Brush, style *StrokeStyle) {
	if d.drawIdx+5 > len(d.enc.drawData) {
		return Brush{}, DefaultStrokeStyle()
	}

	brushIdx := d.enc.drawData[d.drawIdx]
	widthBits := d.enc.drawData[d.drawIdx+1]
	miterBits := d.enc.drawData[d.drawIdx+2]
	capVal := d.enc.drawData[d.drawIdx+3]
	joinVal := d.enc.drawData[d.drawIdx+4]
	d.drawIdx += 5

	if int(brushIdx) < len(d.enc.brushes) {
		brush = d.enc.brushes[brushIdx]
	}

	style = &StrokeStyle{
		Width:		math.Float32frombits(widthBits),
		MiterLimit:	math.Float32frombits(miterBits),
		Cap:		LineCap(capVal),
		Join:		LineJoin(joinVal),
	}
	return brush, style
}

func Tag

Tag returns the current command tag.

Call this after Next() returns true to determine which data method to call.

func (d *Decoder) Tag() Tag {
	return d.currentTag
}

func Text

Text reads the current TagText command data.

Returns the glyph run header, glyph entries, original text string, and the brush.

Only valid when Tag() == TagText.

func (d *Decoder) Text() (run GlyphRunData, glyphs []GlyphEntry, str string, brush Brush) {
	td := d.enc.textData
	if d.textIdx+glyphRunDataSize > len(td) {
		return GlyphRunData{}, nil, "", Brush{}
	}

	off := d.textIdx
	run.FontSourceID = binary.LittleEndian.Uint64(td[off:])
	off += 8
	run.FontSize = math.Float32frombits(binary.LittleEndian.Uint32(td[off:]))
	off += 4
	run.GlyphCount = binary.LittleEndian.Uint16(td[off:])
	off += 2
	run.Flags = TextFlags(binary.LittleEndian.Uint16(td[off:]))
	off += 2
	run.OriginX = math.Float32frombits(binary.LittleEndian.Uint32(td[off:]))
	off += 4
	run.OriginY = math.Float32frombits(binary.LittleEndian.Uint32(td[off:]))
	off += 4
	run.BrushIndex = binary.LittleEndian.Uint32(td[off:])
	off += 4
	run.TextLen = binary.LittleEndian.Uint16(td[off:])
	off += 2

	n := int(run.GlyphCount)
	if off+n*glyphEntrySize > len(td) {
		d.textIdx = off
		return run, nil, "", Brush{}
	}

	glyphs = make([]GlyphEntry, n)
	for i := range n {
		glyphs[i].GlyphID = text.GlyphID(binary.LittleEndian.Uint16(td[off:]))
		off += 2
		glyphs[i].X = math.Float32frombits(binary.LittleEndian.Uint32(td[off:]))
		off += 4
		glyphs[i].Y = math.Float32frombits(binary.LittleEndian.Uint32(td[off:]))
		off += 4
	}

	textLen := int(run.TextLen)
	if off+textLen <= len(td) {
		str = string(td[off : off+textLen])
		off += textLen
	}
	d.textIdx = off

	if int(run.BrushIndex) < len(d.enc.brushes) {
		brush = d.enc.brushes[run.BrushIndex]
	}
	return run, glyphs, str, brush
}

func Transform

Transform reads the current Transform command data.

Returns the affine transformation matrix.

Only valid when Tag() == TagTransform.

func (d *Decoder) Transform() Affine {
	if d.transIdx >= len(d.enc.transforms) {
		return IdentityAffine()
	}
	t := d.enc.transforms[d.transIdx]
	d.transIdx++
	return t
}

Structs

type Decoder struct

Decoder provides sequential decoding of an Encoding's command stream.

It tracks position indices across all data streams (tags, paths, draws, transforms)

and provides methods to read each command type's associated data.

 

The decoder is designed for efficient rendering playback, supporting both

full traversal and selective decoding for tile-based rendering.

 

Example usage:

 

dec := NewDecoder(encoding)

for dec.Next() {

switch dec.Tag() {

case TagMoveTo:

x, y := dec.MoveTo()

// handle move

case TagLineTo:

x, y := dec.LineTo()

// handle line

case TagFill:

brush, style := dec.Fill()

// handle fill

}

}

type Decoder struct {
	enc	*Encoding

	// Stream position indices
	tagIdx		int
	pathIdx		int
	drawIdx		int
	transIdx	int
	textIdx		int

	// Current tag being processed
	currentTag	Tag
}