diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..cd93100 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,69 @@ +name: Go + +on: + push: + branches: [ "master", "main" ] + pull_request: + branches: [ "master", "main" ] + +permissions: + contents: write + pull-requests: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + + - name: Install dependencies + run: go mod download + + - name: Run go fix + run: go fix ./... + + - name: Run go fmt + run: go fmt ./... + + - name: Generate screenshot + run: go run cmd/example/main.go + + - name: Check for changes + id: git-check + run: | + git diff --exit-code || echo "changes=true" >> $GITHUB_OUTPUT + + - name: Create Pull Request + if: steps.git-check.outputs.changes == 'true' + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: Apply go fix, fmt and update screenshot + title: Apply go fix, fmt and update screenshot + body: | + This PR applies changes from `go fix`, `go fmt`, and updates the screenshot. + Auto-generated by GitHub Actions. + branch: auto-fix-fmt-screenshot + delete-branch: true + + - name: Fail if changes detected + if: steps.git-check.outputs.changes == 'true' + run: | + echo "Changes detected. PR created." + exit 1 + + - name: Run go vet + run: go vet ./... + + - name: Run go test + run: go test -v ./... + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest diff --git a/cmd/example/main.go b/cmd/example/main.go index 7f8c276..cec6fb7 100644 --- a/cmd/example/main.go +++ b/cmd/example/main.go @@ -64,7 +64,7 @@ func (r *ImageRenderer) Draw(verts []fontstash.Vertex) { c := v0.Color col := color.RGBA{ - R: uint8(c), // R is LSB in C code usually for LE? + R: uint8(c), // R is LSB in C code usually for LE? G: uint8(c >> 8), B: uint8(c >> 16), A: uint8(c >> 24), diff --git a/fontstash/fontstash.go b/fontstash/fontstash.go index 1ffd782..efaf5f5 100644 --- a/fontstash/fontstash.go +++ b/fontstash/fontstash.go @@ -117,16 +117,16 @@ const ( // Internal limits and defaults const ( - maxStates = 20 - maxBlur = 20 - minFontSize = 2 - blurPadding = 2 - initAtlasNodes = 256 - initFonts = 4 - maxVertices = 1024 - whiteRectSize = 2 - sizeScale = 10.0 - vertsPerQuad = 6 + maxStates = 20 + maxBlur = 20 + minFontSize = 2 + blurPadding = 2 + initAtlasNodes = 256 + initFonts = 4 + maxVertices = 1024 + whiteRectSize = 2 + sizeScale = 10.0 + vertsPerQuad = 6 ) // Common errors @@ -253,128 +253,142 @@ func hashInt(a int) int { } func (fs *FontStash) getGlyph(f *Font, codepoint rune, isize, iblur int16) (*Glyph, error) { - if isize < minFontSize { return nil, nil } - if iblur > maxBlur { iblur = maxBlur } - pad := int(iblur) + blurPadding - - h := hashInt(int(codepoint)) & (len(f.Lut) - 1) - i := f.Lut[h] - for i != -1 { - g := &f.Glyphs[i] - if g.Codepoint == codepoint && g.Size == isize && g.Blur == iblur { - return g, nil - } - i = g.Next - } - - // Create glyph - gIndex := fs.getGlyphIndex(f, codepoint) - renderFont := f - if gIndex == 0 { - for _, fb := range f.Fallbacks { - fallbackFont := fs.Fonts[fb] - fallbackIndex := fs.getGlyphIndex(fallbackFont, codepoint) - if fallbackIndex != 0 { - gIndex = fallbackIndex - renderFont = fallbackFont - break - } - } - } - - size := float64(isize) / sizeScale - - // Get glyph metrics and bitmap - face, err := opentype.NewFace(renderFont.sfnt, &opentype.FaceOptions{ - Size: size, - DPI: 72, - Hinting: font.HintingFull, - }) - if err != nil { return nil, err } - defer face.Close() - - // Bounds check skipped - _, advance, ok := face.GlyphBounds(codepoint) - if !ok { - // Continue but with empty bounds/image? - // Fallthrough - } - - dr, mask, _, _, ok := face.Glyph(fixed.P(0, 0), codepoint) - if !ok { - // Fallthrough - } - - gw := dr.Dx() + pad*2 - gh := dr.Dy() + pad*2 - - // Find free spot - gx, gy, ok := fs.Atlas.addRect(gw, gh) - if !ok { - // Atlas full - if fs.Params.ErrorCallback != nil { - fs.Params.ErrorCallback(ErrAtlasFull) - } - // Try again? The C code calls handler and tries again. - // User might resize in callback. - gx, gy, ok = fs.Atlas.addRect(gw, gh) - if !ok { - return nil, ErrAtlasFull - } - } - - // Init glyph - glyph := Glyph{ - Codepoint: codepoint, - Size: isize, - Blur: iblur, - Index: gIndex, - X0: int16(gx), - Y0: int16(gy), - X1: int16(gx + gw), - Y1: int16(gy + gh), - XAdv: int16(int32(advance) * sizeScale / 64), - XOff: int16(dr.Min.X - pad), - YOff: int16(dr.Min.Y - pad), - } - - // Copy bitmap to texture - dst := fs.TexData - width := fs.Params.Width - - if mask != nil { - b := mask.Bounds() - for y := 0; y < b.Dy(); y++ { - for x := 0; x < b.Dx(); x++ { - _, _, _, a := mask.At(x + b.Min.X, y + b.Min.Y).RGBA() - val := uint8(a >> 8) - - targetX := gx + pad + x - targetY := gy + pad + y - if targetX < width && targetY < fs.Params.Height { - dst[targetY * width + targetX] = val - } - } - } - } - - // Blur if needed - if iblur > 0 { - fs.blur(gx, gy, gw, gh, width, int(iblur)) - } - - // Update dirty rect - if gx < fs.Dirty.Min.X { fs.Dirty.Min.X = gx } - if gy < fs.Dirty.Min.Y { fs.Dirty.Min.Y = gy } - if gx+gw > fs.Dirty.Max.X { fs.Dirty.Max.X = gx+gw } - if gy+gh > fs.Dirty.Max.Y { fs.Dirty.Max.Y = gy+gh } - - // Add to cache - f.Glyphs = append(f.Glyphs, glyph) - f.Glyphs[len(f.Glyphs)-1].Next = f.Lut[h] - f.Lut[h] = len(f.Glyphs) - 1 - - return &f.Glyphs[len(f.Glyphs)-1], nil + if isize < minFontSize { + return nil, nil + } + if iblur > maxBlur { + iblur = maxBlur + } + pad := int(iblur) + blurPadding + + h := hashInt(int(codepoint)) & (len(f.Lut) - 1) + i := f.Lut[h] + for i != -1 { + g := &f.Glyphs[i] + if g.Codepoint == codepoint && g.Size == isize && g.Blur == iblur { + return g, nil + } + i = g.Next + } + + // Create glyph + gIndex := fs.getGlyphIndex(f, codepoint) + renderFont := f + if gIndex == 0 { + for _, fb := range f.Fallbacks { + fallbackFont := fs.Fonts[fb] + fallbackIndex := fs.getGlyphIndex(fallbackFont, codepoint) + if fallbackIndex != 0 { + gIndex = fallbackIndex + renderFont = fallbackFont + break + } + } + } + + size := float64(isize) / sizeScale + + // Get glyph metrics and bitmap + face, err := opentype.NewFace(renderFont.sfnt, &opentype.FaceOptions{ + Size: size, + DPI: 72, + Hinting: font.HintingFull, + }) + if err != nil { + return nil, err + } + defer face.Close() + + // Bounds check skipped + _, advance, ok := face.GlyphBounds(codepoint) + if !ok { + // Continue but with empty bounds/image? + // Fallthrough + } + + dr, mask, _, _, ok := face.Glyph(fixed.P(0, 0), codepoint) + if !ok { + // Fallthrough + } + + gw := dr.Dx() + pad*2 + gh := dr.Dy() + pad*2 + + // Find free spot + gx, gy, ok := fs.Atlas.addRect(gw, gh) + if !ok { + // Atlas full + if fs.Params.ErrorCallback != nil { + fs.Params.ErrorCallback(ErrAtlasFull) + } + // Try again? The C code calls handler and tries again. + // User might resize in callback. + gx, gy, ok = fs.Atlas.addRect(gw, gh) + if !ok { + return nil, ErrAtlasFull + } + } + + // Init glyph + glyph := Glyph{ + Codepoint: codepoint, + Size: isize, + Blur: iblur, + Index: gIndex, + X0: int16(gx), + Y0: int16(gy), + X1: int16(gx + gw), + Y1: int16(gy + gh), + XAdv: int16(int32(advance) * sizeScale / 64), + XOff: int16(dr.Min.X - pad), + YOff: int16(dr.Min.Y - pad), + } + + // Copy bitmap to texture + dst := fs.TexData + width := fs.Params.Width + + if mask != nil { + b := mask.Bounds() + for y := 0; y < b.Dy(); y++ { + for x := 0; x < b.Dx(); x++ { + _, _, _, a := mask.At(x+b.Min.X, y+b.Min.Y).RGBA() + val := uint8(a >> 8) + + targetX := gx + pad + x + targetY := gy + pad + y + if targetX < width && targetY < fs.Params.Height { + dst[targetY*width+targetX] = val + } + } + } + } + + // Blur if needed + if iblur > 0 { + fs.blur(gx, gy, gw, gh, width, int(iblur)) + } + + // Update dirty rect + if gx < fs.Dirty.Min.X { + fs.Dirty.Min.X = gx + } + if gy < fs.Dirty.Min.Y { + fs.Dirty.Min.Y = gy + } + if gx+gw > fs.Dirty.Max.X { + fs.Dirty.Max.X = gx + gw + } + if gy+gh > fs.Dirty.Max.Y { + fs.Dirty.Max.Y = gy + gh + } + + // Add to cache + f.Glyphs = append(f.Glyphs, glyph) + f.Glyphs[len(f.Glyphs)-1].Next = f.Lut[h] + f.Lut[h] = len(f.Glyphs) - 1 + + return &f.Glyphs[len(f.Glyphs)-1], nil } func (fs *FontStash) blur(x, y, w, h, stride, blur int) {