Skip to content

Instantly share code, notes, and snippets.

@ajstarks
Last active August 29, 2015 14:05
Show Gist options
  • Save ajstarks/78f5d063571700da6d3a to your computer and use it in GitHub Desktop.
Save ajstarks/78f5d063571700da6d3a to your computer and use it in GitHub Desktop.
// pbox -- porportional box
package main
import (
"encoding/xml"
"fmt"
"github.com/ajstarks/svgo"
"io"
"os"
)
var canvas = svg.New(os.Stdout)
// <pbox width="1024" height="768" inset="1">
// <section x="0" y="0" w="33.3" h="66.7" bgcolor="purple" tcolor="white"/>
// <section x="33.3" y="0" w="33.3" h="33.3" bgcolor="green" tcolor="white"/>
// <section x="33.3" y="33.3" w="33.3" h="33.3" bgcolor="orange" tcolor="white"/>
// <section x="33.3" y="66.7" w="33.3" h="33.3" bgcolor="green" tcolor="white"/>
// <section x="0" y="66.7" w="33.3" h="33.3" bgcolor="orange" tcolor="white"/>
// <section x="66.7" y="0" w="33.3" h="33.3" bgcolor="green" tcolor="white"/>
// <section x="66.7" y="33.3" w="33.3" h="66.7" bgcolor="red" tcolor="white"/>
// </pbox>
// data defintion
type Pbox struct {
Width int `xml:"width,attr"`
Height int `xml:"height,attr"`
Inset int `xml:"inset,attr"`
Tsize int `xml:"tsize,attr"`
Tunit string `xml:"tunit,attr"`
Tcolor string `xml:"tcolor,attr"`
Font string `xml:"font,attr"`
Bgcolor string `xml:"bgcolor,attr"`
Sections []slist `xml:"section"`
}
type section struct {
dx, dy, dw, dh int
X float64 `xml:"x,attr"`
Y float64 `xml:"y,attr"`
W float64 `xml:"w,attr"`
H float64 `xml:"h,attr"`
Tsize float64 `xml:"tsize,attr"`
Id string `xml:"id,attr"`
Label string `xml:"label,attr"`
Bgcolor string `xml:"bgcolor,attr"`
Tcolor string `xml:"tcolor,attr"`
Tstyle string `xml:"tstyle,attr"`
Align string `xml:"align,attr"`
Href string `xml:"href,attr"`
Content string `xml:",chardata"`
}
type slist struct {
section
Item []titem `xml:"item"`
}
type titem struct {
Start float64 `xml:"start,attr"`
End float64 `xml:"end,attr"`
Content string `xml:",chardata"`
}
// SVG document
type SVG struct {
Width int `xml:"width,attr"`
Height int `xml:"height,attr"`
Doc string `xml:",innerxml"`
}
const (
defunit = "px"
globalstyle = "font-family:%s;font-size:%d%s;fill:%s"
stextstyle = "baseline-shift:-33%%;text-anchor:middle;font-size:%f%%;fill:%s"
)
// dopbox processes a pbox file
func dopbox(location string) {
var f *os.File
var err error
if len(location) > 0 {
f, err = os.Open(location)
} else {
f = os.Stdin
}
if err == nil {
readpbox(f)
f.Close()
} else {
fmt.Fprintf(os.Stderr, "%v\n", err)
}
}
// readpbox reads the pbox specification
func readpbox(r io.Reader) {
var p Pbox
if err := xml.NewDecoder(r).Decode(&p); err == nil {
drawpbox(p)
} else {
fmt.Fprintf(os.Stderr, "Unable to parse components (%v)\n", err)
}
}
// drawpbox draws the pbox, given the unmarshalled data
func drawpbox(p Pbox) {
if p.Bgcolor == "" {
p.Bgcolor = "black"
}
if p.Tcolor == "" {
p.Tcolor = "black"
}
if p.Tunit == "" {
p.Tunit = defunit
}
canvas.Start(p.Width, p.Height)
canvas.Rect(0, 0, p.Width, p.Height, "fill:"+p.Bgcolor)
canvas.Gstyle(fmt.Sprintf(globalstyle, p.Font, p.Tsize, p.Tunit, p.Tcolor))
for _, s := range p.Sections {
// fmt.Fprintf(os.Stderr, "Section %d\n%v\n", i, s)
s.display(p.Width, p.Height, p.Inset, p.Tsize, p.Bgcolor, p.Tunit, s.Item, true)
}
canvas.Gend()
canvas.End()
}
// display draws a section, given its rectangular dimensions, expressed as float percentages
// the section struct is altered with the actual computed pixels
func (s *section) display(width, height, inset, tsize int, bgcolor, unit string, items []titem, showlabel bool) {
//fmt.Fprintf(os.Stderr, "\nIn Display items=%v\n", items)
s.dx = pct(width, s.X)
s.dy = pct(height, s.Y)
s.dw = pct(width, s.W)
s.dh = pct(height, s.H)
fs := 12
if s.Bgcolor == "" {
s.Bgcolor = bgcolor
}
canvas.Rect(s.dx+inset, s.dy+inset, s.dw-(inset*2), s.dh-(inset*2), "fill:"+s.Bgcolor)
var tfs int
if len(s.Content) > 0 {
var pfs float64
var align string
if s.Tsize == 0 {
s.Tsize = 100
pfs = 100
} else {
pfs = s.Tsize
}
if s.Align == "" {
align = "middle"
} else {
align = s.Align
}
tfs = pct(tsize, pfs)
s.textblock(tfs, inset, align, unit)
}
if len(items) > 0 {
list := []string{}
for _, x := range items {
list = append(list, x.Content)
}
textrect(s.dx+tfs, s.dy+tfs, s.dw-10, tfs, list, "lightgray", s.Tcolor)
// markerlist(s.dx+tfs, s.dy+tfs, markerglyph, list, tfs, tfs+4, s.Tcolor, "start")
}
if showlabel && len(s.Label) > 0 {
// vtext(s.dx+inset, s.dy+inset, 90, s.Label, stextstyle+s.Tcolor)
canvas.Text(s.dx+fs, (s.dy+s.dh)-10, s.Label, stextstyle+s.Tcolor)
}
}
// textblock draws a block of text within a section, wordwrapped
func (s *section) textblock(size, inset int, align, unit string) {
var x int
tfmt := "baseline-shift:-33%"
if align == "middle" {
x = s.dx + inset + (s.dw-inset*2)/2
tfmt = "baseline-shift:-33%"
} else {
x = s.dx + inset + size
}
var wraplen int
wraplen = size + ((size * 2) / 3)
if wraplen < 20 {
wraplen = 20
}
words := wordwrap(s.Content, wraplen, 1)
canvas.Gstyle(fmt.Sprintf("font-style:%s;fill:%s;text-anchor:%s;font-size:%.2f%%", s.Tstyle, s.Tcolor, align, s.Tsize))
y := s.dy + inset + ((s.dh - (inset * 2)) / (len(words) + 1))
spacing := size + size/2
for i := 0; i < len(words); i++ {
canvas.Text(x, y, words[i], tfmt)
y += spacing
}
canvas.Gend()
}
var markerglyph = string(0x25b6)
// docontent fills in the content into a rectangular area:
// either a reference to content, or a plain or annontated list
func (sl slist) docontent(size, inset int) {
s := sl.section
spacing := size + 4
if len(s.Href) > 0 {
subpic(s.dx, s.dy, s.Href)
} else {
list := []string{}
for _, x := range sl.Item {
list = append(list, x.Content)
}
markerlist(s.dx+inset, s.dy+inset, markerglyph, list, size, spacing, s.Tcolor, "start")
pmarker(sl, inset, spacing)
}
}
// pmarker shows a percentage marker
func pmarker(s slist, inset, spacing int) {
var bx, bl, x int
h := 15
w := pct(s.section.dw, 20) // 120
y := s.section.dy + spacing
for _, m := range s.Item {
if m.Start >= 0 && m.End > 0 {
x = (s.section.dx + s.section.dw) - w - 10
bx = int((m.Start / 100) * float64(w))
bl = int((m.End / 100) * float64(w))
canvas.Rect(x, y, w, h, "fill:lightgray")
canvas.Roundrect(x+bx+2, y+2, bl-bx-4, h-4, 2, 2, "fill:rgb(128,0,0)")
}
y += spacing
}
}
// markerlist makes a list headed by a marker character. Long lines are word-wrapped
func markerlist(x, y int, marker string, list []string, size, spacing int, fill, align string) {
canvas.Gstyle(fmt.Sprintf("fill:%s;font-size:%dpx", fill, size))
indent := size + 2
for _, s := range list {
wlist := wordwrap(s, size*3, 1)
canvas.Text(x, y, marker, "fill:"+fill)
canvas.Text(x+indent, y, wlist[0])
if len(wlist) > 1 {
for wi := 1; wi < len(wlist); wi++ {
y += spacing
canvas.Text(x+indent, y, wlist[wi])
}
}
y += spacing
}
canvas.Gend()
}
func textrect(x, y, w, h int, text []string, rcolor, tcolor string) {
tx := x + 10
ty := y + 10
canvas.Textlines(x+tx, y+ty, text, 12, 14, "fill:"+tcolor, "start")
for i := 0; i < len(text); i++ {
canvas.Rect(x+tx, y, w, h, "stroke:red;fill:"+rcolor)
y += 16
}
}
func textwidth(c uint8, px int) int {
return px
}
// wordrap word-wraps a single string into a string slice, whose members are no longer than
// the specified number of characters
func wordwrap(s string, maxWidth, px int) []string {
a := []string{}
w := 0
i := 0
rs := 0
if maxWidth < 10 {
return a
}
for i < len(s) {
c := s[i]
w++ // w += textwidth(c, px)
if c == ' ' {
rs = i
}
if w > maxWidth {
sub := s[0:rs]
if len(sub) > 0 && sub[0] == ' ' {
sub = sub[1:len(sub)]
}
a = append(a, sub)
s = s[rs:len(s)]
i = 0
w = 0
} else {
i++
}
}
if len(s) > 0 && s[0] == ' ' {
s = s[1:len(s)]
}
a = append(a, s)
return a
}
// subpic places a SVG file at a coordinate
func subpic(x, y int, picname string) {
var s SVG
f, err := os.Open(picname)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return
}
defer f.Close()
if err := xml.NewDecoder(f).Decode(&s); err != nil {
fmt.Fprintf(os.Stderr, "Unable to parse (%v)\n", err)
return
}
canvas.Group(fmt.Sprintf(`clip-path="url(#%s)"`, picname), fmt.Sprintf(`transform="translate(%d,%d)"`, x, y))
canvas.ClipPath(fmt.Sprintf(`id="%s"`, picname))
canvas.Rect(0, 0, s.Width, s.Height)
canvas.ClipEnd()
io.WriteString(canvas.Writer, s.Doc)
canvas.Gend()
}
// vtext draws on an angle
func vtext(x, y int, angle float64, s string, style ...string) {
canvas.TranslateRotate(x, y, angle)
canvas.Text(0, 0, s, style...)
canvas.Gend()
}
// compute the pixels given a percentage and width
func pct(n int, p float64) int {
x := (p / 100) * float64(n)
i := int(x)
if x-float64(i) > 0.5 {
return i + 1
}
return i
}
// make a pbox for every file specified on the command line
// there are no flags
func main() {
if len(os.Args) > 1 {
for i := 1; i < len(os.Args); i++ {
dopbox(os.Args[i])
}
} else {
dopbox("")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment