stb-truetype-go/sfnt.go¶
:material-github: View on GitHub · 784 lines · live source, included at build time
package truetype
import (
"encoding/binary"
"errors"
"fmt"
"image"
"math"
"sort"
)
// This file implements a real (pure-Go) TrueType rasterizer: it parses the sfnt
// table directory, maps runes to glyph IDs via cmap, decodes glyf outlines
// (simple and composite), flattens the quadratic Béziers, and scan-fills the
// contours to an anti-aliased grayscale bitmap using the nonzero winding rule.
//
// Scope: glyf-based TrueType fonts. CFF/OpenType (OTTO) outlines are not
// supported. Hinting is ignored (not needed for rasterization quality at UI
// sizes). Composite glyphs are supported for the common XY-offset form.
const ssaa = 4 // supersampling factor per axis for anti-aliasing
type tableRec struct {
offset uint32
length uint32
}
// i16 reads a big-endian int16. The int16() conversion reinterprets two bytes
// as signed — it is not a lossy numeric narrowing.
//
//nolint:gosec // G115: deliberate 2-byte reinterpretation as signed
func i16(b []byte) int16 { return int16(binary.BigEndian.Uint16(b)) }
// parseSFNT parses the table directory and the tables the rasterizer needs.
func (f *Font) parseSFNT() error {
d := f.rawData
if len(d) < 12 {
return errors.New("truetype: data too short for sfnt header")
}
switch binary.BigEndian.Uint32(d) {
case 0x00010000, 0x74727565: // TrueType ("\x00\x01\x00\x00" or "true")
case 0x4F54544F: // "OTTO"
return errors.New("truetype: OpenType/CFF fonts are not supported (no glyf table)")
default:
return fmt.Errorf("truetype: unrecognized sfnt version 0x%08x", binary.BigEndian.Uint32(d))
}
numTables := int(binary.BigEndian.Uint16(d[4:]))
f.tables = make(map[string]tableRec, numTables)
for i := 0; i < numTables; i++ {
off := 12 + i*16
if off+16 > len(d) {
return errors.New("truetype: truncated table directory")
}
f.tables[string(d[off:off+4])] = tableRec{
offset: binary.BigEndian.Uint32(d[off+8:]),
length: binary.BigEndian.Uint32(d[off+12:]),
}
}
for _, step := range []func() error{f.parseHead, f.parseMaxp, f.parseHhea, f.parseLoca, f.parseCmap} {
if err := step(); err != nil {
return err
}
}
if _, ok := f.tableData("glyf"); !ok {
return errors.New("truetype: missing or invalid glyf table")
}
return nil
}
// tableData returns the bytes of a table, bounds-checked against the file.
func (f *Font) tableData(tag string) ([]byte, bool) {
rec, ok := f.tables[tag]
if !ok {
return nil, false
}
start, end := int(rec.offset), int(rec.offset)+int(rec.length)
if start < 0 || end < start || end > len(f.rawData) {
return nil, false
}
return f.rawData[start:end], true
}
func (f *Font) parseHead() error {
d, ok := f.tableData("head")
if !ok || len(d) < 54 {
return errors.New("truetype: bad head table")
}
f.unitsPerEm = binary.BigEndian.Uint16(d[18:])
if f.unitsPerEm == 0 {
f.unitsPerEm = 1000
}
f.indexToLoc = i16(d[50:])
return nil
}
func (f *Font) parseMaxp() error {
d, ok := f.tableData("maxp")
if !ok || len(d) < 6 {
return errors.New("truetype: bad maxp table")
}
f.numGlyphs = binary.BigEndian.Uint16(d[4:])
return nil
}
func (f *Font) parseHhea() error {
d, ok := f.tableData("hhea")
if !ok || len(d) < 36 {
return errors.New("truetype: bad hhea table")
}
f.numHMetrics = binary.BigEndian.Uint16(d[34:])
return nil
}
func (f *Font) parseLoca() error {
d, ok := f.tableData("loca")
if !ok {
return errors.New("truetype: bad loca table")
}
n := int(f.numGlyphs) + 1
f.loca = make([]uint32, n)
if f.indexToLoc == 0 { // short format: uint16 offsets, doubled
if len(d) < n*2 {
return errors.New("truetype: loca too short (short format)")
}
for i := 0; i < n; i++ {
f.loca[i] = uint32(binary.BigEndian.Uint16(d[i*2:])) * 2
}
return nil
}
if len(d) < n*4 { // long format: uint32 offsets
return errors.New("truetype: loca too short (long format)")
}
for i := 0; i < n; i++ {
f.loca[i] = binary.BigEndian.Uint32(d[i*4:])
}
return nil
}
// parseCmap selects the best Unicode subtable and stores its bytes.
func (f *Font) parseCmap() error {
d, ok := f.tableData("cmap")
if !ok || len(d) < 4 {
return errors.New("truetype: bad cmap table")
}
numSub := int(binary.BigEndian.Uint16(d[2:]))
bestOff, bestScore := -1, -1
for i := 0; i < numSub; i++ {
rec := 4 + i*8
if rec+8 > len(d) {
break
}
subOff := int(binary.BigEndian.Uint32(d[rec+4:]))
score := cmapScore(binary.BigEndian.Uint16(d[rec:]), binary.BigEndian.Uint16(d[rec+2:]))
if score > bestScore && subOff > 0 && subOff < len(d) {
bestScore, bestOff = score, subOff
}
}
if bestOff < 0 {
return errors.New("truetype: no usable cmap subtable")
}
f.cmapData = d[bestOff:]
return nil
}
// cmapScore ranks cmap subtables, preferring Unicode/Windows tables.
func cmapScore(platformID, encodingID uint16) int {
switch {
case platformID == 3 && encodingID == 10:
return 5
case platformID == 0 && (encodingID == 4 || encodingID == 6):
return 5
case platformID == 3 && encodingID == 1:
return 4
case platformID == 0:
return 3
default:
return 1
}
}
// glyphIndex maps a rune to a glyph ID using the selected cmap subtable.
func (f *Font) glyphIndex(r rune) uint16 {
d := f.cmapData
if len(d) < 2 {
return 0
}
switch binary.BigEndian.Uint16(d) {
case 0:
return cmapFormat0(d, r)
case 4:
return cmapFormat4(d, r)
case 6:
return cmapFormat6(d, r)
case 12:
return cmapFormat12(d, r)
}
return 0
}
func cmapFormat0(d []byte, r rune) uint16 {
if r < 0 || r > 255 || len(d) < 6+256 {
return 0
}
return uint16(d[6+int(r)])
}
func cmapFormat4(d []byte, r rune) uint16 {
if r < 0 || r > 0xFFFF || len(d) < 14 {
return 0
}
c := uint16(r)
segX2 := int(binary.BigEndian.Uint16(d[6:]))
endOff := 14
startOff := endOff + segX2 + 2 // +2 for reservedPad
deltaOff := startOff + segX2
rangeOff := deltaOff + segX2
if rangeOff+segX2 > len(d) {
return 0
}
for i := 0; i < segX2/2; i++ {
if c > binary.BigEndian.Uint16(d[endOff+i*2:]) {
continue
}
start := binary.BigEndian.Uint16(d[startOff+i*2:])
if c < start {
return 0
}
idDelta := binary.BigEndian.Uint16(d[deltaOff+i*2:])
idRange := binary.BigEndian.Uint16(d[rangeOff+i*2:])
if idRange == 0 {
return c + idDelta
}
gidx := rangeOff + i*2 + int(idRange) + int(c-start)*2
if gidx+2 > len(d) {
return 0
}
g := binary.BigEndian.Uint16(d[gidx:])
if g == 0 {
return 0
}
return g + idDelta
}
return 0
}
func cmapFormat6(d []byte, r rune) uint16 {
if len(d) < 10 || r < 0 || r > 0xFFFF {
return 0
}
first := binary.BigEndian.Uint16(d[6:])
count := binary.BigEndian.Uint16(d[8:])
c := uint16(r)
if c < first || c >= first+count {
return 0
}
idx := 10 + int(c-first)*2
if idx+2 > len(d) {
return 0
}
return binary.BigEndian.Uint16(d[idx:])
}
func cmapFormat12(d []byte, r rune) uint16 {
if len(d) < 16 {
return 0
}
nGroups := binary.BigEndian.Uint32(d[12:])
for i := uint32(0); i < nGroups; i++ {
g := 16 + int(i)*12
if g+12 > len(d) {
return 0
}
startChar := binary.BigEndian.Uint32(d[g:])
endChar := binary.BigEndian.Uint32(d[g+4:])
if uint32(r) >= startChar && uint32(r) <= endChar {
gid := binary.BigEndian.Uint32(d[g+8:]) + (uint32(r) - startChar)
return uint16(gid) //nolint:gosec // G115: glyph IDs are 16-bit by definition
}
}
return 0
}
// glyphPoint is a contour point in font units (y-up).
type glyphPoint struct {
x, y float64
onCurve bool
}
// glyphBudget bounds total work for one top-level glyph. The depth cap alone
// does not stop a malicious composite with high fan-out (K children per level,
// 8 levels ≈ K^8 invocations from a tiny file — a billion-laughs amplification),
// so a shared counter caps total components visited and total points produced.
type glyphBudget struct {
components int // remaining glyph invocations (call-tree nodes)
points int // remaining total contour points
}
const (
maxGlyphComponents = 4096 // total component invocations per top-level glyph
maxGlyphPoints = 1 << 20 // total contour points per top-level glyph
)
// glyphContours returns the contours of a glyph in font units. Empty glyphs
// (e.g. space) return nil, nil. b bounds the total composite expansion.
func (f *Font) glyphContours(gid uint16, depth int, b *glyphBudget) ([][]glyphPoint, error) {
if depth > 8 {
return nil, errors.New("truetype: composite glyph nesting too deep")
}
if b.components--; b.components < 0 {
return nil, errors.New("truetype: composite glyph component budget exceeded")
}
if int(gid)+1 >= len(f.loca) {
return nil, nil
}
start, end := f.loca[gid], f.loca[gid+1]
if start >= end {
return nil, nil // empty glyph
}
glyf, ok := f.tableData("glyf")
if !ok || int(end) > len(glyf) || start > end {
return nil, errors.New("truetype: glyph data out of range")
}
g := glyf[start:end]
if len(g) < 10 {
return nil, nil
}
numContours := i16(g)
if numContours < 0 {
return f.compositeContours(g, depth, b)
}
contours, err := parseSimpleGlyph(g, int(numContours))
if err != nil {
return nil, err
}
for _, c := range contours {
b.points -= len(c)
}
if b.points < 0 {
return nil, errors.New("truetype: composite glyph point budget exceeded")
}
return contours, nil
}
func parseSimpleGlyph(g []byte, numContours int) ([][]glyphPoint, error) {
pos := 10 // skip numContours(2) + bounding box(8)
endPts := make([]int, numContours)
for i := 0; i < numContours; i++ {
if pos+2 > len(g) {
return nil, errors.New("truetype: bad endPtsOfContours")
}
endPts[i] = int(binary.BigEndian.Uint16(g[pos:]))
pos += 2
}
numPts := 0
if numContours > 0 {
numPts = endPts[numContours-1] + 1
}
if numPts <= 0 || numPts > 20000 {
return nil, errors.New("truetype: implausible point count")
}
if pos+2 > len(g) {
return nil, errors.New("truetype: bad instructionLength")
}
pos += 2 + int(binary.BigEndian.Uint16(g[pos:])) // skip hinting instructions
if pos > len(g) {
return nil, errors.New("truetype: instructions overrun")
}
flags, err := readGlyphFlags(g, &pos, numPts)
if err != nil {
return nil, err
}
xs, err := readGlyphCoords(g, &pos, flags, 0x02, 0x10) // X_SHORT, X_SAME_OR_POSITIVE
if err != nil {
return nil, err
}
ys, err := readGlyphCoords(g, &pos, flags, 0x04, 0x20) // Y_SHORT, Y_SAME_OR_POSITIVE
if err != nil {
return nil, err
}
return splitContours(flags, xs, ys, endPts, numPts), nil
}
// readGlyphFlags reads the per-point flag array, expanding REPEAT_FLAG runs.
func readGlyphFlags(g []byte, pos *int, numPts int) ([]byte, error) {
flags := make([]byte, numPts)
for i := 0; i < numPts; {
if *pos >= len(g) {
return nil, errors.New("truetype: flags overrun")
}
fl := g[*pos]
*pos++
flags[i] = fl
i++
if fl&0x08 == 0 { // not REPEAT_FLAG
continue
}
if *pos >= len(g) {
return nil, errors.New("truetype: flag repeat overrun")
}
rep := int(g[*pos])
*pos++
for j := 0; j < rep && i < numPts; j++ {
flags[i] = fl
i++
}
}
return flags, nil
}
// readGlyphCoords decodes one delta-encoded coordinate axis (x or y) selected by
// the short/same flag masks.
func readGlyphCoords(g []byte, pos *int, flags []byte, shortMask, sameMask byte) ([]int, error) {
coords := make([]int, len(flags))
v := 0
for i, fl := range flags {
switch {
case fl&shortMask != 0: // 1-byte unsigned magnitude; sameMask is the sign
if *pos >= len(g) {
return nil, errors.New("truetype: coordinate overrun")
}
d := int(g[*pos])
*pos++
if fl&sameMask == 0 {
d = -d
}
v += d
case fl&sameMask == 0: // 2-byte signed delta
if *pos+2 > len(g) {
return nil, errors.New("truetype: coordinate overrun")
}
v += int(i16(g[*pos:]))
*pos += 2
}
coords[i] = v
}
return coords, nil
}
func splitContours(flags []byte, xs, ys, endPts []int, numPts int) [][]glyphPoint {
contours := make([][]glyphPoint, len(endPts))
p := 0
for c := range endPts {
var pts []glyphPoint
for ; p <= endPts[c] && p < numPts; p++ {
pts = append(pts, glyphPoint{x: float64(xs[p]), y: float64(ys[p]), onCurve: flags[p]&0x01 != 0})
}
contours[c] = pts
}
return contours
}
func f2dot14(b []byte) float64 { return float64(i16(b)) / 16384.0 }
// compositeContours assembles a composite glyph from its components (the common
// ARGS_ARE_XY_VALUES form, with optional scale / 2x2 transform).
func (f *Font) compositeContours(g []byte, depth int, b *glyphBudget) ([][]glyphPoint, error) {
var all [][]glyphPoint
pos := 10
for pos+4 <= len(g) {
flags := binary.BigEndian.Uint16(g[pos:])
compGID := binary.BigEndian.Uint16(g[pos+2:])
pos += 4
tf, ok := readComponentTransform(g, &pos, flags)
if !ok {
break
}
var err error
if all, err = f.appendComponent(all, compGID, depth, tf, b); err != nil {
return nil, err
}
if flags&0x0020 == 0 { // no MORE_COMPONENTS
break
}
}
return all, nil
}
// componentTransform is a 2x2 matrix plus translation applied to a sub-glyph.
type componentTransform struct{ a, b, c, d, dx, dy float64 }
func readComponentTransform(g []byte, pos *int, flags uint16) (componentTransform, bool) {
tf := componentTransform{a: 1, d: 1}
if flags&0x0001 != 0 { // ARG_1_AND_2_ARE_WORDS
if *pos+4 > len(g) {
return tf, false
}
tf.dx, tf.dy = float64(i16(g[*pos:])), float64(i16(g[*pos+2:]))
*pos += 4
} else {
if *pos+2 > len(g) {
return tf, false
}
tf.dx, tf.dy = float64(int8(g[*pos])), float64(int8(g[*pos+1]))
*pos += 2
}
switch {
case flags&0x0008 != 0: // WE_HAVE_A_SCALE
if *pos+2 > len(g) {
return tf, false
}
tf.a, tf.d = f2dot14(g[*pos:]), f2dot14(g[*pos:])
*pos += 2
case flags&0x0040 != 0: // WE_HAVE_AN_X_AND_Y_SCALE
if *pos+4 > len(g) {
return tf, false
}
tf.a, tf.d = f2dot14(g[*pos:]), f2dot14(g[*pos+2:])
*pos += 4
case flags&0x0080 != 0: // WE_HAVE_A_TWO_BY_TWO
if *pos+8 > len(g) {
return tf, false
}
tf.a, tf.b = f2dot14(g[*pos:]), f2dot14(g[*pos+2:])
tf.c, tf.d = f2dot14(g[*pos+4:]), f2dot14(g[*pos+6:])
*pos += 8
}
return tf, true
}
func (f *Font) appendComponent(
all [][]glyphPoint, gid uint16, depth int, tf componentTransform, b *glyphBudget,
) ([][]glyphPoint, error) {
sub, err := f.glyphContours(gid, depth+1, b)
if err != nil {
return nil, err
}
for _, ct := range sub {
np := make([]glyphPoint, len(ct))
for i, pt := range ct {
np[i] = glyphPoint{
x: tf.a*pt.x + tf.c*pt.y + tf.dx,
y: tf.b*pt.x + tf.d*pt.y + tf.dy,
onCurve: pt.onCurve,
}
}
all = append(all, np)
}
return all, nil
}
// advanceWidth returns the glyph's advance in font units.
func (f *Font) advanceWidth(gid uint16) int {
d, ok := f.tableData("hmtx")
if !ok || f.numHMetrics == 0 {
return int(f.unitsPerEm)
}
i := int(gid)
if i >= int(f.numHMetrics) {
i = int(f.numHMetrics) - 1
}
if i*4+2 > len(d) {
return int(f.unitsPerEm)
}
return int(binary.BigEndian.Uint16(d[i*4:]))
}
// fpoint is a 2D point in floating-point coordinates.
type fpoint struct{ x, y float64 }
// flattenContour converts a TrueType contour (on/off-curve points, with implied
// on-curve midpoints between consecutive off-curve points) into a polygon.
func flattenContour(pts []glyphPoint) []fpoint {
n := len(pts)
if n == 0 {
return nil
}
seq := withImpliedPoints(pts)
startIdx := -1
for i := range seq {
if seq[i].onCurve {
startIdx = i
break
}
}
if startIdx == -1 {
return nil // degenerate: no on-curve point
}
rot := make([]glyphPoint, 0, len(seq)+1)
rot = append(rot, seq[startIdx:]...)
rot = append(rot, seq[:startIdx]...)
rot = append(rot, rot[0]) // close the loop
out := []fpoint{{rot[0].x, rot[0].y}}
for i := 1; i < len(rot); {
if rot[i].onCurve {
out = append(out, fpoint{rot[i].x, rot[i].y})
i++
continue
}
ctrl := rot[i]
end := rot[(i+1)%len(rot)]
flattenQuad(&out, out[len(out)-1], fpoint{ctrl.x, ctrl.y}, fpoint{end.x, end.y})
i += 2
}
return out
}
// withImpliedPoints inserts the implied on-curve midpoint between any two
// consecutive off-curve points.
func withImpliedPoints(pts []glyphPoint) []glyphPoint {
n := len(pts)
seq := make([]glyphPoint, 0, n*2)
for i := 0; i < n; i++ {
p := pts[i]
seq = append(seq, p)
nxt := pts[(i+1)%n]
if !p.onCurve && !nxt.onCurve {
seq = append(seq, glyphPoint{x: (p.x + nxt.x) / 2, y: (p.y + nxt.y) / 2, onCurve: true})
}
}
return seq
}
// flattenQuad appends a flattened quadratic Bézier (p0 already in out) to out.
func flattenQuad(out *[]fpoint, p0, p1, p2 fpoint) {
const steps = 10
for s := 1; s <= steps; s++ {
t := float64(s) / steps
mt := 1 - t
*out = append(*out, fpoint{
x: mt*mt*p0.x + 2*mt*t*p1.x + t*t*p2.x,
y: mt*mt*p0.y + 2*mt*t*p1.y + t*t*p2.y,
})
}
}
// gEdge is a polygon edge in supersampled device space.
type gEdge struct{ x0, y0, x1, y1 float64 }
// xCrossing is an edge's intersection with a scanline plus its winding direction.
type xCrossing struct {
x float64
dir int
}
// rasterizeGlyph renders the glyph for r at the given pixel size to an
// anti-aliased grayscale bitmap (0 = transparent, 255 = full coverage).
func rasterizeGlyph(f *Font, r rune, size float64) (*image.Gray, GlyphMetrics, error) {
if f == nil || f.tables == nil {
return nil, GlyphMetrics{}, errors.New("truetype: font not parsed")
}
if size <= 0 {
return nil, GlyphMetrics{}, errors.New("truetype: size must be positive")
}
gid := f.glyphIndex(r)
budget := glyphBudget{components: maxGlyphComponents, points: maxGlyphPoints}
contours, err := f.glyphContours(gid, 0, &budget)
if err != nil {
return nil, GlyphMetrics{}, fmt.Errorf("truetype: decode glyph %q: %w", r, err)
}
scale := size / float64(f.unitsPerEm)
metrics := GlyphMetrics{
AdvanceWidth: int(math.Round(float64(f.advanceWidth(gid)) * scale)),
Scale: scale,
}
polys, minX, minY, maxX, maxY := glyphPolygons(contours)
if len(polys) == 0 { // empty glyph (space, etc.)
return image.NewGray(image.Rect(0, 0, 1, 1)), metrics, nil
}
px0 := int(math.Floor(minX * scale))
px1 := int(math.Ceil(maxX * scale))
py0 := int(math.Floor(-maxY * scale)) // device y is down; -maxY is the top
py1 := int(math.Ceil(-minY * scale))
w, h := px1-px0, py1-py0
if w <= 0 || h <= 0 {
return image.NewGray(image.Rect(0, 0, 1, 1)), metrics, nil
}
if w > 4096 || h > 4096 {
return nil, metrics, errors.New("truetype: rasterized glyph too large")
}
metrics.BearingX, metrics.BearingY = px0, -py0
coverage := fillCoverage(buildEdges(polys, scale, px0, py0), w, h)
img := image.NewGray(image.Rect(0, 0, w, h))
const maxCov = ssaa * ssaa
for i, c := range coverage {
if v := c * 255 / maxCov; v >= 255 {
img.Pix[i] = 255
} else {
img.Pix[i] = uint8(v)
}
}
return img, metrics, nil
}
// glyphPolygons flattens each contour and returns the polygons plus the overall
// bounding box in font units.
func glyphPolygons(contours [][]glyphPoint) (polys [][]fpoint, minX, minY, maxX, maxY float64) {
minX, minY = math.Inf(1), math.Inf(1)
maxX, maxY = math.Inf(-1), math.Inf(-1)
for _, ct := range contours {
poly := flattenContour(ct)
if len(poly) < 2 {
continue
}
for _, p := range poly {
minX, maxX = math.Min(minX, p.x), math.Max(maxX, p.x)
minY, maxY = math.Min(minY, p.y), math.Max(maxY, p.y)
}
polys = append(polys, poly)
}
return polys, minX, minY, maxX, maxY
}
// buildEdges converts polygons to edges in supersampled device space (y-down).
func buildEdges(polys [][]fpoint, scale float64, px0, py0 int) []gEdge {
toDev := func(p fpoint) (float64, float64) {
return (p.x*scale - float64(px0)) * ssaa, (-p.y*scale - float64(py0)) * ssaa
}
var edges []gEdge
for _, poly := range polys {
for i := 0; i < len(poly); i++ {
ax, ay := toDev(poly[i])
bx, by := toDev(poly[(i+1)%len(poly)])
edges = append(edges, gEdge{ax, ay, bx, by})
}
}
return edges
}
// fillCoverage scan-fills the edges with the nonzero winding rule at
// supersampled resolution, returning per-output-pixel coverage counts.
func fillCoverage(edges []gEdge, w, h int) []uint32 {
sw, sh := w*ssaa, h*ssaa
coverage := make([]uint32, w*h)
xs := make([]xCrossing, 0, len(edges))
for sy := 0; sy < sh; sy++ {
xs = scanlineCrossings(edges, float64(sy)+0.5, xs[:0])
if len(xs) < 2 {
continue
}
sort.Slice(xs, func(i, j int) bool { return xs[i].x < xs[j].x })
accumulateSpans(coverage, xs, sy/ssaa, w, sw)
}
return coverage
}
// scanlineCrossings collects the x-intersections of edges with the horizontal
// line y = yc (appending into the provided slice).
func scanlineCrossings(edges []gEdge, yc float64, xs []xCrossing) []xCrossing {
for _, e := range edges {
lo, hi, dir := e.y0, e.y1, 1
if lo > hi {
lo, hi, dir = hi, lo, -1
}
if yc < lo || yc >= hi {
continue
}
t := (yc - e.y0) / (e.y1 - e.y0)
xs = append(xs, xCrossing{e.x0 + t*(e.x1-e.x0), dir})
}
return xs
}
// accumulateSpans fills the inside spans (nonzero winding) of one supersampled
// scanline into the coverage buffer.
func accumulateSpans(coverage []uint32, xs []xCrossing, py, w, sw int) {
winding := 0
rowBase := py * w
for i := 0; i+1 < len(xs); i++ {
winding += xs[i].dir
if winding == 0 {
continue
}
lo := int(math.Ceil(xs[i].x - 0.5))
hi := int(math.Floor(xs[i+1].x - 0.5))
if lo < 0 {
lo = 0
}
if hi >= sw {
hi = sw - 1
}
for sx := lo; sx <= hi; sx++ {
coverage[rowBase+sx/ssaa]++
}
}
}