100 lines
2.6 KiB
Go
100 lines
2.6 KiB
Go
package caption
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"syscall"
|
|
|
|
"github.com/golang/freetype/truetype"
|
|
"golang.org/x/image/draw"
|
|
"golang.org/x/image/font"
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
type Line struct {
|
|
// collection of all text segments
|
|
Data []Segment `json:"data"`
|
|
// applies to all segments; avoid direct assignment and use SetFont instead
|
|
Font []byte `json:"font"`
|
|
// applies to all segments; avoid direct assignment and use SetFont instead
|
|
Options *truetype.Options `json:"options"`
|
|
|
|
// contains reference to parsed font, populated once per instance
|
|
face font.Face
|
|
// whether to fail if pixels are drawn out-of-bounds
|
|
bounds bool
|
|
}
|
|
|
|
// SetFont clears line font cache and sets the new font.
|
|
func (l *Line) SetFont(v []byte, opts *truetype.Options) { l.face = nil; l.Font = v; l.Options = opts }
|
|
|
|
// SetBoundsCheck changes whether the frame's bounding box is enforced.
|
|
func (l *Line) SetBoundsCheck(v bool) { l.bounds = v }
|
|
|
|
type Segment struct {
|
|
Value string `json:"value"`
|
|
Color color.Color `json:"color"`
|
|
}
|
|
|
|
// Render draws the current contents of Line on [draw.Image].
|
|
func (l *Line) Render(ctx *Context, frame draw.Image, x, y int) error {
|
|
face := l.face
|
|
if face == nil {
|
|
if len(l.Font) != 0 {
|
|
// parse & cache Line font
|
|
if err := l.cacheFont(l.Font); err != nil {
|
|
return err
|
|
}
|
|
return l.Render(ctx, frame, x, y)
|
|
}
|
|
|
|
// fall back to default font
|
|
|
|
if l.Options != nil { // global default: Options is not zero
|
|
// cache global default without clobbering Line font
|
|
if err := l.cacheFont(defaultFont); err != nil {
|
|
return err // unreachable
|
|
}
|
|
return l.Render(ctx, frame, x, y)
|
|
}
|
|
|
|
// fast path: Font and Options are zero
|
|
if err := ctx.parseFont(); err != nil {
|
|
return err
|
|
}
|
|
face = ctx.defaultFont
|
|
}
|
|
|
|
outOfBounds := false
|
|
bounds := frame.Bounds()
|
|
boundsFixed := fixed.R(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y)
|
|
|
|
drawer := &font.Drawer{Dst: frame, Src: new(image.Uniform), Face: face, Dot: fixed.Point26_6{X: fixed.I(x), Y: fixed.I(y)}}
|
|
for _, seg := range l.Data {
|
|
drawer.Src.(*image.Uniform).C = seg.Color
|
|
|
|
// optional bounds check to reduce overhead
|
|
if l.bounds && !outOfBounds {
|
|
b, _ := drawer.BoundString(seg.Value)
|
|
outOfBounds = !b.In(boundsFixed)
|
|
}
|
|
|
|
drawer.DrawString(seg.Value)
|
|
}
|
|
|
|
if outOfBounds {
|
|
return syscall.EDOM
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// cacheFont parses truetype font data v and stores the result in Line.
|
|
// The cache is invalidated by SetFont.
|
|
func (l *Line) cacheFont(v []byte) error {
|
|
f, err := truetype.Parse(v)
|
|
if err == nil {
|
|
l.face = truetype.NewFace(f, l.Options)
|
|
}
|
|
return err
|
|
}
|