mirror of
https://github.com/mineracks/seedhammer-v1-companion.git
synced 2026-06-26 22:01:05 +10:00
Twelve packages lifted from seedhammer/seedhammer @ v1.3.0
(commit 2f071c1d8f23eb7fd39b15fc0acb8874113f801e):
address/ — bitcoin address parsing
backup/ — v1 plate dimensions + UR-coded multi-plate backup
bc/ — Blockchain Commons: ur, fountain, bytewords, urtypes,
xoshiro256 (5 subpkgs)
bip32/ — HD-key derivation
bip39/ — mnemonic seed phrases + 2048-word wordlist
engrave/ — text/QR → MoveTo/LineTo command stream conversion
seedqr/ — SeedQR / CompactSeedQR encoders
image/ — paletted, rgb565, alpha4, ninepatch image formats
nonstandard/ — bitcoin descriptor + script parsing
font/ — bitmap + vector font runtime
font/{comfortaa,poppins,constant,bitmap,vector}/ — actual fonts
driver/mjolnir/ — MarkingWay USB-serial engraver driver
Plus an earlier-aside backup_test.go restored (its deps are now lifted).
Import paths globally rewritten seedhammer.com → mineracks namespace
via single sed pass; verified no orphan refs remain. go.mod adopts
upstream's full dep set plus the replace-directive for the patched
kortschak/qr fork.
go build ./... clean (all 27 packages)
go test ./... clean (12 packages with tests, all passing)
NOT lifted in this commit:
- driver/{wshat,drm,libcamera} (hardware-specific GPIO/LCD/camera —
will be platform-v1/-shaped abstractions instead)
- gui/ (depends on the above; lifts in Phase 2)
- cmd/{controller,...} (Pi binary entrypoints — not needed for the
companion repo)
- zbar/ (QR scanner — needs libcamera)
Next:
- Write the SH1E reference encoder/decoder in engrave/wire/sh1e/
- Lift Gangleri42's cmd/webnfc/ shell + retune to v1 plates
- First buildable composer WASM with a working preview
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
222 lines
6.0 KiB
Go
222 lines
6.0 KiB
Go
//go:build ignore
|
|
|
|
// generator converts an OpenType file into a bitmap font.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"go/format"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"golang.org/x/image/font"
|
|
"golang.org/x/image/font/opentype"
|
|
"golang.org/x/image/math/fixed"
|
|
|
|
"github.com/mineracks/seedhammer-v1-companion/font/bitmap"
|
|
simage "github.com/mineracks/seedhammer-v1-companion/image"
|
|
"github.com/mineracks/seedhammer-v1-companion/image/alpha4"
|
|
)
|
|
|
|
var (
|
|
packageName = flag.String("package", "main", "package name")
|
|
ppem = flag.Int("ppem", 16, "pixels per em")
|
|
alphabet = flag.String("alphabet", "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", "alphabet to generate")
|
|
)
|
|
|
|
type Face struct {
|
|
Metrics font.Metrics
|
|
// Index maps an ASCII rune to its glyph.
|
|
Index [unicode.MaxASCII]bitmap.Glyph
|
|
Kerns []bitmap.Kern
|
|
Pixels []byte
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if flag.NArg() != 2 {
|
|
fmt.Fprintf(os.Stderr, "usage: convert infile outfile\n")
|
|
os.Exit(1)
|
|
}
|
|
|
|
infile := flag.Arg(0)
|
|
ttf, err := os.ReadFile(infile)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
conv, err := parse(ttf, *ppem)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to parse %q: %v\n", infile, err)
|
|
os.Exit(1)
|
|
}
|
|
if err := generate(filepath.Base(infile), conv); err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to generate %q: %v\n", infile, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func generate(fname string, conv *Face) error {
|
|
outname := flag.Arg(1)
|
|
var output bytes.Buffer
|
|
ext := filepath.Ext(fname)
|
|
fname = fname[:len(fname)-len(ext)]
|
|
datafile := fmt.Sprintf("%s%d.bin", outname, *ppem)
|
|
first, n := utf8.DecodeRuneInString(outname)
|
|
name := string(unicode.ToTitle(first)) + outname[n:]
|
|
|
|
fmt.Fprintf(&output, "// Code generated by font/bitmap/convert.go; DO NOT EDIT.\n\npackage %s\n\n", *packageName)
|
|
fmt.Fprintf(&output, "import (\n")
|
|
fmt.Fprintf(&output, " _ \"embed\"\n")
|
|
fmt.Fprintf(&output, " \"unsafe\"\n")
|
|
fmt.Fprintf(&output, " \"github.com/mineracks/seedhammer-v1-companion/font/bitmap\"\n")
|
|
fmt.Fprintf(&output, ")\n\n")
|
|
fmt.Fprintf(&output, "var %[1]s%[2]d = bitmap.NewFace(unsafe.Slice(unsafe.StringData(data%[3]s%[2]d), len(data%[3]s%[2]d)))\n", name, *ppem, outname)
|
|
fmt.Fprintf(&output, "//go:embed %s\n", datafile)
|
|
fmt.Fprintf(&output, "var data%s%d string\n", outname, *ppem)
|
|
|
|
var data []byte
|
|
bo := binary.LittleEndian
|
|
data = bo.AppendUint32(data, uint32(conv.Metrics.Ascent))
|
|
data = bo.AppendUint32(data, uint32(conv.Metrics.Descent))
|
|
data = bo.AppendUint32(data, uint32(conv.Metrics.Height))
|
|
nkerns := uint16(len(conv.Kerns))
|
|
if int(nkerns) != len(conv.Kerns) {
|
|
return errors.New("kern table overflows uint16")
|
|
}
|
|
pixelStart := bitmap.OffKerns + len(conv.Kerns)*bitmap.KernElemSize
|
|
for _, g := range conv.Index {
|
|
data = bo.AppendUint32(data, uint32(g.Advance))
|
|
imgOff := int(g.ImageOff) + pixelStart
|
|
off16 := uint16(imgOff)
|
|
if int(off16) != imgOff {
|
|
return errors.New("pixel offset overflows uint16")
|
|
}
|
|
data = bo.AppendUint16(data, off16)
|
|
data = append(data, uint8(g.Rect.MinX), uint8(g.Rect.MinY), uint8(g.Rect.MaxX), uint8(g.Rect.MaxY))
|
|
}
|
|
data = bo.AppendUint16(data, nkerns)
|
|
for _, k := range conv.Kerns {
|
|
data = append(data, k.R1, k.R2)
|
|
data = bo.AppendUint32(data, uint32(k.Kern))
|
|
}
|
|
if len(data) != pixelStart {
|
|
panic("pixel start offset miscalculated")
|
|
}
|
|
data = append(data, conv.Pixels...)
|
|
formatted, err := format.Source(output.Bytes())
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to format output: %v\n", err)
|
|
os.Exit(2)
|
|
}
|
|
gofile := fmt.Sprintf("%s%d.go", outname, *ppem)
|
|
if err := os.WriteFile(gofile, formatted, 0o600); err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(datafile, data, 0o600)
|
|
}
|
|
|
|
func face(ttf []byte, ppem int) (font.Face, error) {
|
|
f, err := opentype.Parse(ttf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
face, err := opentype.NewFace(f, &opentype.FaceOptions{
|
|
Size: float64(ppem),
|
|
DPI: 72, // Size is in pixels.
|
|
Hinting: font.HintingFull,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return face, nil
|
|
}
|
|
|
|
func parse(ttf []byte, ppem int) (*Face, error) {
|
|
f, err := face(ttf, ppem)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m := f.Metrics()
|
|
face := &Face{
|
|
Metrics: font.Metrics{
|
|
Ascent: m.Ascent,
|
|
Descent: m.Descent,
|
|
Height: m.Height,
|
|
},
|
|
}
|
|
// Add whitespaces to alphabet.
|
|
var b strings.Builder
|
|
for _, r := range *alphabet {
|
|
i := uint8(r)
|
|
if rune(i) != r {
|
|
return nil, errors.New("alphabet overflows uint8")
|
|
}
|
|
b.WriteByte(i)
|
|
}
|
|
for i := uint8(0); i < unicode.MaxASCII; i++ {
|
|
if unicode.IsSpace(rune(i)) {
|
|
b.WriteByte(i)
|
|
}
|
|
}
|
|
alph := b.String()
|
|
for i := range face.Index {
|
|
r := rune(i)
|
|
off := uint16(len(face.Pixels))
|
|
if int(off) != len(face.Pixels) {
|
|
return nil, errors.New("pixel offset overflows uint16")
|
|
}
|
|
face.Index[r].ImageOff = off
|
|
if strings.IndexRune(alph, r) == -1 {
|
|
continue
|
|
}
|
|
// Brute force n² pairs to construct kerning table. It works
|
|
// as long as alphabets are small.
|
|
for _, r2 := range alph {
|
|
if strings.IndexRune(alph, r2) == -1 {
|
|
continue
|
|
}
|
|
k := f.Kern(r, r2)
|
|
if k == 0 {
|
|
continue
|
|
}
|
|
face.Kerns = append(face.Kerns, bitmap.Kern{R1: uint8(r), R2: uint8(r2), Kern: k})
|
|
}
|
|
dr, mask, maskp, adv, ok := f.Glyph(fixed.P(0, 0), r)
|
|
if !ok {
|
|
continue
|
|
}
|
|
alpha, ok := mask.(*image.Alpha)
|
|
if !ok {
|
|
return nil, fmt.Errorf("bitmap image type %T is not supported", mask)
|
|
}
|
|
alpha.Rect = dr
|
|
rcrop := simage.Crop(alpha)
|
|
crop := alpha4.New(alpha4.Rectangle{
|
|
MinX: int8(rcrop.Min.X),
|
|
MinY: int8(rcrop.Min.Y),
|
|
MaxX: int8(rcrop.Max.X),
|
|
MaxY: int8(rcrop.Max.Y),
|
|
})
|
|
if crop.Bounds() != rcrop {
|
|
return nil, errors.New("glyph bounds overflows int8")
|
|
}
|
|
draw.DrawMask(crop, crop.Bounds(), image.NewUniform(color.Black), image.Point{}, alpha, maskp.Add(crop.Bounds().Min), draw.Src)
|
|
face.Index[r].Advance = adv
|
|
face.Index[r].Rect = crop.Rect
|
|
face.Pixels = append(face.Pixels, crop.Pix...)
|
|
}
|
|
return face, nil
|
|
}
|