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, ``+"\n", src.nsAttrs, pageWidth, pageHeight, pageWidth*pt, pageHeight*pt) fmt.Fprintf(&b, `%s`+"\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, ``+"\n", cx, cy, circleDiameter/2*pt) fmt.Fprintf(&b, ``+"\n", cx-flagW/2*pt, cy-flagH/2*pt, flagW*pt, flagH*pt) if *stroke { fmt.Fprintf(&b, ``+"\n", cx-flagW/2*pt, cy-flagH/2*pt, flagW*pt, flagH*pt) } } } b.WriteString("\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("