diff --git a/font.go b/font.go index 731e1e5..547cde6 100644 --- a/font.go +++ b/font.go @@ -5,7 +5,6 @@ import ( "image/color" "io/ioutil" "math" - "os/exec" "reflect" "sync" @@ -118,10 +117,11 @@ var sysfontFinder = struct { func FindLocalFont(name string, style FontStyle) string { // TODO: use style to match font // try with fc-match first - filename, err := exec.Command("fc-match", "--format=%{file}", name).Output() + // TODO: this causes a panic on Linux "panic: requested font style not found" + /*filename, err := exec.Command("fc-match", "--format=%{file}", name).Output() if err == nil { return string(filename) - } + }*/ // then use known font directories sysfontFinder.m.Lock() diff --git a/svg.go b/svg.go index 177e223..1c68be3 100644 --- a/svg.go +++ b/svg.go @@ -26,6 +26,11 @@ type svgState struct { strokeMiterLimit float64 textX float64 textY float64 + refX float64 + refY float64 + markerStart svgMarker + markerEnd svgMarker + markerMid svgMarker textAnchor string fontFamily string fontSize float64 @@ -38,12 +43,25 @@ var svgDefaultState = svgState{ fontSize: 16.0, // in px } +type svgCanvas struct { + c *Canvas + ctx *Context + width, height, diagonal float64 +} + +type svgMarker struct { + c *Canvas + refX, refY float64 +} + type svgParser struct { - z *parse.Input + z *parse.Input + err error + + canvasStack []svgCanvas c *Canvas ctx *Context width, height, diagonal float64 - err error stateStack []svgState state svgState @@ -52,9 +70,18 @@ type svgParser struct { activeDef svgDef defs map[string]svgDef fonts map[string]*FontFamily + + markers map[string]svgMarker } -func (svg *svgParser) init(width, height float64, viewbox [4]float64) { +func (svg *svgParser) pushCanvas(width, height float64, viewbox [4]float64) { + if len(svg.canvasStack) == 0 { + svg.state = svgDefaultState + svg.defs = map[string]svgDef{} + svg.fonts = map[string]*FontFamily{} + svg.markers = map[string]svgMarker{} + } + svg.c = New(width, height) svg.ctx = NewContext(svg.c) svg.ctx.SetCoordSystem(CartesianIV) @@ -64,9 +91,33 @@ func (svg *svgParser) init(width, height float64, viewbox [4]float64) { svg.ctx.SetCoordView(m) } svg.ctx.SetStrokeJoiner(MiterJoiner{BevelJoin, svgDefaultState.strokeMiterLimit}) - svg.state = svgDefaultState - svg.defs = map[string]svgDef{} - svg.fonts = map[string]*FontFamily{} + svg.width = width * 96.0 / 25.4 + svg.height = height * 96.0 / 25.4 + svg.diagonal = math.Sqrt((svg.width*svg.width + svg.height*svg.height) / 2.0) + + c := svgCanvas{ + c: svg.c, + ctx: svg.ctx, + width: svg.width, + height: svg.height, + diagonal: svg.diagonal, + } + + svg.canvasStack = append(svg.canvasStack, c) +} + +func (svg *svgParser) popCanvas() { + if len(svg.canvasStack) <= 1 { + return + } + + svg.canvasStack = svg.canvasStack[:len(svg.canvasStack)-1] + c := svg.canvasStack[len(svg.canvasStack)-1] + svg.c = c.c + svg.ctx = c.ctx + svg.width = svg.width + svg.height = svg.height + svg.diagonal = svg.diagonal } func (svg *svgParser) push() { @@ -159,9 +210,13 @@ func (svg *svgParser) parseColorComponent(v string) uint8 { } func (svg *svgParser) parsePaint(v string) Paint { + if v == "none" { + return Paint{} + } + if strings.HasPrefix(v, "url(") && strings.HasSuffix(v, ")") { - if 8 < len(v) && v[5] == '#' { - id := v[6 : len(v)-2] + if 6 < len(v) && v[4] == '#' { + id := v[5 : len(v)-1] svg.activeDef = svg.defs[id] return Paint{Color: Black} } @@ -344,91 +399,64 @@ func (svg *svgParser) skipToEndTag(l *xml.Lexer, tt xml.TokenType) xml.TokenType return tt } -func (svg *svgParser) parseDefs(l *xml.Lexer) { - tt, data := l.Next() +func (svg *svgParser) parseLinearGradient(attrs map[string]string, l *xml.Lexer) { + if _, ok := attrs["x2"]; !ok { + attrs["x2"] = "100%" + } + x1p := strings.HasSuffix(attrs["x1"], "%") + y1p := strings.HasSuffix(attrs["y1"], "%") + x2p := strings.HasSuffix(attrs["x2"], "%") + y2p := strings.HasSuffix(attrs["y2"], "%") + x1 := svg.parseDimension(attrs["x1"], 1.0) + x2 := svg.parseDimension(attrs["x2"], 1.0) + y1 := svg.parseDimension(attrs["y1"], 1.0) + y2 := svg.parseDimension(attrs["y2"], 1.0) + + stops := Stops{} for { - if tt == xml.EndTagToken { + _, tag, stopattrs := svg.parseSingleTag(l) + if tag != "stop" { break - } else if tt == xml.StartTagToken { - tag := string(data[1:]) - var attrs map[string]string - tt, _, attrs = svg.parseAttributes(l) - id := attrs["id"] - if id == "" { - tt = svg.skipToEndTag(l, tt) - continue - } - - switch tag { - case "linearGradient": - if tt != xml.StartTagCloseToken { - break - } - - if _, ok := attrs["x2"]; !ok { - attrs["x2"] = "100%" - } - x1p := strings.HasSuffix(attrs["x1"], "%") - y1p := strings.HasSuffix(attrs["y1"], "%") - x2p := strings.HasSuffix(attrs["x2"], "%") - y2p := strings.HasSuffix(attrs["y2"], "%") - x1 := svg.parseDimension(attrs["x1"], 1.0) - x2 := svg.parseDimension(attrs["x2"], 1.0) - y1 := svg.parseDimension(attrs["y1"], 1.0) - y2 := svg.parseDimension(attrs["y2"], 1.0) - - stops := Stops{} - for { - tt, tag, attrs = svg.parseSingleTag(l) - if tag != "stop" { - break - } - offset := svg.parseNumber(attrs["offset"]) - stopColor := svg.parseColor(attrs["stop-color"]) - if v, ok := attrs["stop-opacity"]; ok { - stopOpacity := svg.parseNumber(v) - stopColor.R = uint8(float64(stopColor.R) / float64(stopColor.A) * stopOpacity * 255.0) - stopColor.G = uint8(float64(stopColor.G) / float64(stopColor.A) * stopOpacity * 255.0) - stopColor.B = uint8(float64(stopColor.B) / float64(stopColor.A) * stopOpacity * 255.0) - stopColor.A = uint8(stopOpacity * 255.0) - } - stops.Add(offset, stopColor) - } - svg.defs[id] = func(rect Rect) interface{} { - x1t, y1t, x2t, y2t := x1, y1, x2, y2 - if x1p { - x1t = (rect.X + rect.W*x1t) * 25.4 / 96.0 - } - if y1p { - y1t = (rect.Y + rect.H*y1t) * 25.4 / 96.0 - } - if x2p { - x2t = (rect.X + rect.W*x2t) * 25.4 / 96.0 - } - if y2p { - y2t = (rect.Y + rect.H*y2t) * 25.4 / 96.0 - } - linearGradient := NewLinearGradient(Point{x1t, y1t}, Point{x2t, y2t}) - linearGradient.Stops = stops - return linearGradient - } - tt, data = l.Next() - default: - tt = svg.skipToEndTag(l, tt) - } - } else { - tt, data = l.Next() } + offset := svg.parseNumber(stopattrs["offset"]) + stopColor := svg.parseColor(stopattrs["stop-color"]) + if v, ok := stopattrs["stop-opacity"]; ok { + stopOpacity := svg.parseNumber(v) + stopColor.R = uint8(float64(stopColor.R) / float64(stopColor.A) * stopOpacity * 255.0) + stopColor.G = uint8(float64(stopColor.G) / float64(stopColor.A) * stopOpacity * 255.0) + stopColor.B = uint8(float64(stopColor.B) / float64(stopColor.A) * stopOpacity * 255.0) + stopColor.A = uint8(stopOpacity * 255.0) + } + stops.Add(offset, stopColor) + } + svg.defs[attrs["id"]] = func(rect Rect) interface{} { + x1t, y1t, x2t, y2t := x1, y1, x2, y2 + if x1p { + x1t = (rect.X + rect.W*x1t) * 25.4 / 96.0 + } + if y1p { + y1t = (rect.Y + rect.H*y1t) * 25.4 / 96.0 + } + if x2p { + x2t = (rect.X + rect.W*x2t) * 25.4 / 96.0 + } + if y2p { + y2t = (rect.Y + rect.H*y2t) * 25.4 / 96.0 + } + linearGradient := NewLinearGradient(Point{x1t, y1t}, Point{x2t, y2t}) + linearGradient.Stops = stops + return linearGradient } } func (svg *svgParser) parseStyle(b []byte) { p := css.NewParser(parse.NewInputBytes(b), false) + qualifiedselectors := [][]cssSelector{} for { gt, _, _ := p.Next() if gt == css.ErrorGrammar { break - } else if gt == css.BeginRulesetGrammar { + } else if gt == css.BeginRulesetGrammar || gt == css.QualifiedRuleGrammar { selectors := []cssSelector{cssSelector{}} node := cssSelectorNode{op: ' '} vals := p.Values() @@ -463,23 +491,32 @@ func (svg *svgParser) parseStyle(b []byte) { } selectors[len(selectors)-1] = append(selectors[len(selectors)-1], node) - props := []cssProperty{} - for { - gt, _, data := p.Next() - if gt != css.DeclarationGrammar { - break + qualifiedselectors = append(qualifiedselectors, selectors) + + if gt == css.BeginRulesetGrammar { + props := []cssProperty{} + for { + gt, _, data := p.Next() + if gt != css.DeclarationGrammar { + break + } + + val := strings.Builder{} + for _, t := range p.Values() { + val.Write(t.Data) + } + props = append(props, cssProperty{string(data), val.String()}) } - val := strings.Builder{} - for _, t := range p.Values() { - val.Write(t.Data) + for _, qs := range qualifiedselectors { + svg.cssRules = append(svg.cssRules, cssRule{ + selectors: qs, + props: props, + }) } - props = append(props, cssProperty{string(data), val.String()}) + + qualifiedselectors = [][]cssSelector{} } - svg.cssRules = append(svg.cssRules, cssRule{ - selectors: selectors, - props: props, - }) } } } @@ -502,6 +539,14 @@ func (svg *svgParser) parseStyleAttribute(style string) []cssProperty { return props } +func (svg *svgParser) parseId(ref string) string { + if strings.HasPrefix(ref, "url(") && ref[len(ref)-1] == ')' { + return ref[5 : len(ref)-1] + } + + return "" +} + func (svg *svgParser) setStyling(elems []elem, props []cssProperty) { // apply CSS from