internal/site/mark.go
Ref: Size: 4.4 KiB History
// mark.go composes eitri's mark — "[E]" — from a font's own glyphs and emits
// it as SVG twice: padded into a square box for the browser tab, and tight to
// its ink for inline use in the page nav. One grid feeds both, and the same
// grid draws the link-preview card, so the mark cannot drift between them.
package site
import (
"fmt"
"strings"
)
// markRunes spell the mark. markKern overrides the font's own advance: at
// tab size the natural spacing leaves the brackets adrift from the E.
const (
markRunes = "[E]"
markKern = 1
// faviconBox is the tab icon's edge in grid units — 16, the size the
// icon is drawn at, so every unit is exactly one device pixel.
faviconBox = 16
)
// Mark colours: ink on light, and its inverse where the reader's browser
// asks for dark. The page follows the same pair.
const (
inkLight = "#1a1a1a"
inkDark = "#ececec"
)
// markBitmap composes the mark: each glyph trimmed to its ink, kept on the
// font's baseline, joined by markKern blank columns.
func markBitmap(f *bdfFont) ([][]bool, error) {
type placed struct {
pix [][]bool
top, bottom int // rows above the baseline of the ink's edges
}
var glyphs []placed
for _, r := range markRunes {
g, ok := f.glyphs[r]
if !ok {
return nil, fmt.Errorf("font %q has no glyph for %q", f.name, r)
}
pix, last := trimInk(g.pix)
if pix == nil {
return nil, fmt.Errorf("font %q draws %q blank", f.name, r)
}
// The ink box's bottom row sits at yoff; trimming blank rows off the
// bottom lifts that edge by however many were dropped.
bottom := g.yoff + (g.h - 1 - last)
glyphs = append(glyphs, placed{pix: pix, top: bottom + len(pix) - 1, bottom: bottom})
}
top, bottom := glyphs[0].top, glyphs[0].bottom
for _, g := range glyphs[1:] {
if g.top > top {
top = g.top
}
if g.bottom < bottom {
bottom = g.bottom
}
}
height := top - bottom + 1
out := make([][]bool, height)
for i, g := range glyphs {
if i > 0 {
for y := range out {
out[y] = append(out[y], make([]bool, markKern)...)
}
}
w := len(g.pix[0])
lead := top - g.top // blank rows above this glyph's ink
for y := 0; y < height; y++ {
row := y - lead
if row >= 0 && row < len(g.pix) {
out[y] = append(out[y], g.pix[row]...)
} else {
out[y] = append(out[y], make([]bool, w)...)
}
}
}
return out, nil
}
// trimInk drops blank rows and columns, returning the ink and the index of
// the last inked row of the input (nil if the glyph is blank).
func trimInk(pix [][]bool) (ink [][]bool, last int) {
first, last := -1, -1
left, right := -1, -1
for y, row := range pix {
for x, on := range row {
if !on {
continue
}
if first < 0 {
first = y
}
last = y
if left < 0 || x < left {
left = x
}
if x > right {
right = x
}
}
}
if first < 0 {
return nil, -1
}
ink = make([][]bool, 0, last-first+1)
for _, row := range pix[first : last+1] {
ink = append(ink, row[left:right+1])
}
return ink, last
}
// bitmapRuns collapses ink into horizontal runs — one SVG rect or one filled
// rectangle each, instead of one per pixel.
func bitmapRuns(pix [][]bool) []textRun {
var out []textRun
for y, row := range pix {
for x := 0; x < len(row); {
if !row[x] {
x++
continue
}
k := x
for k < len(row) && row[k] {
k++
}
out = append(out, textRun{X: x, Y: y, W: k - x})
x = k
}
}
return out
}
// faviconSVG centres the mark in a square box of box units. The box is the
// icon's pixel size, so one grid unit renders as one device pixel and the
// bitmap stays crisp instead of being scaled onto half-pixels.
func faviconSVG(pix [][]bool, box int) (string, error) {
h, w := len(pix), len(pix[0])
if w > box || h > box {
return "", fmt.Errorf("mark is %dx%d, does not fit a %d box", w, h, box)
}
ox, oy := (box-w)/2, (box-h)/2
var b strings.Builder
fmt.Fprintf(&b, `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %d %d" shape-rendering="crispEdges">`+"\n", box, box)
fmt.Fprintf(&b, " <style>\n rect { fill: %s; }\n @media (prefers-color-scheme: dark) { rect { fill: %s; } }\n </style>\n", inkLight, inkDark)
writeRects(&b, bitmapRuns(pix), ox, oy)
b.WriteString("</svg>\n")
return b.String(), nil
}
func writeRects(b *strings.Builder, runs []textRun, ox, oy int) {
for _, r := range runs {
fmt.Fprintf(b, ` <rect x="%d" y="%d" width="%d" height="1"/>`+"\n", ox+r.X, oy+r.Y, r.W)
}
}