PLS312 flag label sheet generator
This commit is contained in:
@@ -0,0 +1 @@
|
||||
out/
|
||||
@@ -0,0 +1,145 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
pt = 72.0
|
||||
pageWidth = 8.5
|
||||
pageHeight = 11.0
|
||||
cols = 7
|
||||
rows = 9
|
||||
pitch = 1.125
|
||||
labelDiameter = 1.0
|
||||
leftMargin = 0.375
|
||||
topMargin = 0.5
|
||||
circleDiameter = 0.875
|
||||
flagDiagonal = 0.8
|
||||
)
|
||||
|
||||
var rootOnlyAttrs = map[string]bool{
|
||||
"width": true, "height": true, "x": true, "y": true, "viewBox": true,
|
||||
"preserveAspectRatio": true, "version": true, "baseProfile": true,
|
||||
}
|
||||
|
||||
type flagSVG struct {
|
||||
viewBox [4]float64
|
||||
nsAttrs string
|
||||
gAttrs string
|
||||
inner string
|
||||
}
|
||||
|
||||
func main() {
|
||||
stroke := flag.Bool("stroke", false, "draw a thin black stroke around each flag")
|
||||
flag.Parse()
|
||||
if flag.NArg() != 2 {
|
||||
log.Fatal("usage: go run . [--stroke] flag.svg out.pdf")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(flag.Arg(0))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
src := parseFlag(data)
|
||||
|
||||
aspect := src.viewBox[2] / src.viewBox[3]
|
||||
flagH := flagDiagonal / math.Sqrt(1+aspect*aspect)
|
||||
flagW := aspect * flagH
|
||||
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"%s width="%gin" height="%gin" viewBox="0 0 %g %g">`+"\n", src.nsAttrs, pageWidth, pageHeight, pageWidth*pt, pageHeight*pt)
|
||||
fmt.Fprintf(&b, `<defs><symbol id="labels-flag" viewBox="%g %g %g %g" preserveAspectRatio="none"><g%s>%s</g></symbol></defs>`+"\n", src.viewBox[0], src.viewBox[1], src.viewBox[2], src.viewBox[3], src.gAttrs, src.inner)
|
||||
for r := 0; r < rows; r++ {
|
||||
for c := 0; c < cols; c++ {
|
||||
cx := (leftMargin + labelDiameter/2 + float64(c)*pitch) * pt
|
||||
cy := (topMargin + labelDiameter/2 + float64(r)*pitch) * pt
|
||||
fmt.Fprintf(&b, `<circle cx="%g" cy="%g" r="%g" fill="none" stroke="black" stroke-width="0.5" stroke-dasharray="2 1.5"/>`+"\n", cx, cy, circleDiameter/2*pt)
|
||||
fmt.Fprintf(&b, `<use xlink:href="#labels-flag" x="%g" y="%g" width="%g" height="%g"/>`+"\n", cx-flagW/2*pt, cy-flagH/2*pt, flagW*pt, flagH*pt)
|
||||
if *stroke {
|
||||
fmt.Fprintf(&b, `<rect x="%g" y="%g" width="%g" height="%g" fill="none" stroke="black" stroke-width="0.25"/>`+"\n", cx-flagW/2*pt, cy-flagH/2*pt, flagW*pt, flagH*pt)
|
||||
}
|
||||
}
|
||||
}
|
||||
b.WriteString("</svg>\n")
|
||||
|
||||
cmd := exec.Command("rsvg-convert", "--format", "pdf", "--output", flag.Arg(1))
|
||||
cmd.Stdin = strings.NewReader(b.String())
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func parseFlag(data []byte) flagSVG {
|
||||
dec := xml.NewDecoder(bytes.NewReader(data))
|
||||
for {
|
||||
tok, err := dec.Token()
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] no svg root element: %v", err)
|
||||
}
|
||||
start, ok := tok.(xml.StartElement)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
f := flagSVG{}
|
||||
attrs := map[string]string{}
|
||||
for _, a := range start.Attr {
|
||||
switch {
|
||||
case a.Name.Space == "xmlns" && a.Name.Local != "xlink":
|
||||
f.nsAttrs += fmt.Sprintf(` xmlns:%s="%s"`, a.Name.Local, escape(a.Value))
|
||||
case a.Name.Space == "" && a.Name.Local != "xmlns" && !rootOnlyAttrs[a.Name.Local]:
|
||||
f.gAttrs += fmt.Sprintf(` %s="%s"`, a.Name.Local, escape(a.Value))
|
||||
}
|
||||
attrs[a.Name.Local] = a.Value
|
||||
}
|
||||
|
||||
if vb, ok := attrs["viewBox"]; ok {
|
||||
fields := strings.FieldsFunc(vb, func(r rune) bool { return r == ' ' || r == ',' })
|
||||
if len(fields) != 4 {
|
||||
log.Fatalf("[ERROR] bad viewBox %q", vb)
|
||||
}
|
||||
for i, s := range fields {
|
||||
f.viewBox[i] = parseLength(s)
|
||||
}
|
||||
} else {
|
||||
f.viewBox = [4]float64{0, 0, parseLength(attrs["width"]), parseLength(attrs["height"])}
|
||||
}
|
||||
if f.viewBox[2] <= 0 || f.viewBox[3] <= 0 {
|
||||
log.Fatalf("[ERROR] bad svg size %v", f.viewBox)
|
||||
}
|
||||
|
||||
innerStart := int(dec.InputOffset())
|
||||
innerEnd := bytes.LastIndex(data, []byte("</"))
|
||||
if innerEnd < innerStart {
|
||||
log.Fatal("[ERROR] svg root element has no closing tag")
|
||||
}
|
||||
f.inner = string(data[innerStart:innerEnd])
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
func parseLength(s string) float64 {
|
||||
v, err := strconv.ParseFloat(strings.TrimRight(strings.TrimSpace(s), "abcdefghijklmnopqrstuvwxyz%"), 64)
|
||||
if err != nil {
|
||||
log.Fatalf("[ERROR] bad svg dimension %q", s)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func escape(s string) string {
|
||||
var b strings.Builder
|
||||
xml.EscapeText(&b, []byte(s))
|
||||
return b.String()
|
||||
}
|
||||
Reference in New Issue
Block a user