diff --git a/draw2dsvg/fileutil.go b/draw2dsvg/fileutil.go index ed28f03..2ade34b 100644 --- a/draw2dsvg/fileutil.go +++ b/draw2dsvg/fileutil.go @@ -1,9 +1,9 @@ package draw2dsvg import ( - "os" "encoding/xml" _ "errors" + "os" ) func SaveToSvgFile(filePath string, svg *Svg) error { @@ -19,4 +19,4 @@ func SaveToSvgFile(filePath string, svg *Svg) error { err = encoder.Encode(svg) return err -} \ No newline at end of file +} diff --git a/draw2dsvg/gc.go b/draw2dsvg/gc.go index 6e83aa4..b4a6dca 100644 --- a/draw2dsvg/gc.go +++ b/draw2dsvg/gc.go @@ -4,17 +4,17 @@ package draw2dsvg import ( + "fmt" + "github.com/golang/freetype/truetype" "github.com/llgcode/draw2d" "github.com/llgcode/draw2d/draw2dbase" + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" "image" "log" - "strings" "math" - "github.com/golang/freetype/truetype" - "golang.org/x/image/math/fixed" - "golang.org/x/image/font" "strconv" - "fmt" + "strings" ) type drawType int @@ -133,12 +133,16 @@ func (gc *GraphicContext) drawString(text string, drawType drawType, x, y float6 svgText.FontSize = gc.Current.FontSize svgText.X = x svgText.Y = y - svgText.FontFamily = "" // TODO set font + svgText.FontFamily = gc.Current.FontData.Name + + if gc.svg.fontMode == SvgFontMode { + gc.embedSvgFont(text) + } // link to group group.Texts = []*Text{&svgText} left, _, right, _ := gc.GetStringBounds(text) - return right-left + return right - left } // Creates new group from current context @@ -170,20 +174,59 @@ func (gc *GraphicContext) newGroup(drawType drawType) *Group { return &group } -/////////////////////////////////////// -// TODO implement following methods (or remove if not neccesary) +// Embed svg font definition to svg tree itself +func (gc *GraphicContext) embedSvgFont(text string) { + fontName := gc.Current.FontData.Name + gc.loadCurrentFont() -// SetFontData sets the current FontData -func (gc *GraphicContext) SetFontData(fontData draw2d.FontData) { + // find or create font Element + svgFont := (*Font)(nil) + for _, font := range gc.svg.Fonts { + if font.Name == fontName { + svgFont = font + break + } + } + if svgFont == nil { + // create new + svgFont = &Font{} + // and link + gc.svg.Fonts = append(gc.svg.Fonts, svgFont) + } + // fill with glyphs + + gc.Save() + defer gc.Restore() + gc.SetFontSize(2048) + defer gc.SetDPI(gc.GetDPI()) + gc.SetDPI(92) +filling: + for _, rune := range text { + for _, g := range svgFont.Glyphs { + if g.Rune == Rune(rune) { + continue filling + } + } + glyph := gc.glyphCache.Fetch(gc, gc.GetFontName(), rune) + // glyphCache.Load indirectly calls CreateStringPath for single rune string + + glypPath := glyph.Path.VerticalFlip() // svg font glyphs have oposite y axe + svgFont.Glyphs = append(svgFont.Glyphs, &Glyph{ + Rune: Rune(rune), + Desc: toSvgPathDesc(glypPath), + HorizAdvX: glyph.Width, + }) + } + + // set attrs + svgFont.Id = "font-" + strconv.Itoa(len(gc.svg.Fonts)) + svgFont.Name = fontName + + // TODO use css @font-face with id instead of this + svgFont.Face = &Face{Family: fontName, Units: 2048, HorizAdvX: 2048} } -// GetFontData gets the current FontData -func (gc *GraphicContext) GetFontData() draw2d.FontData { - return draw2d.FontData{} -} - - // NOTE following functions copied from dwra2d{img|gl} // TODO move them all to common draw2dbase? @@ -218,7 +261,6 @@ func (gc *GraphicContext) CreateStringPath(s string, x, y float64) (cursor float return x - startx } - // GetStringBounds returns the approximate pixel bounds of the string s at x, y. // The the left edge of the em square of the first character of s // and the baseline intersect at 0, 0 in the returned coordinates. @@ -311,7 +353,6 @@ func (gc *GraphicContext) SetFontSize(fontSize float64) { gc.recalc() } - /////////////////////////////////////// // TODO implement following methods (or remove if not neccesary) diff --git a/draw2dsvg/svg.go b/draw2dsvg/svg.go index baad46e..819832e 100644 --- a/draw2dsvg/svg.go +++ b/draw2dsvg/svg.go @@ -9,10 +9,22 @@ import ( /* svg elements */ +type FontMode int + +const ( + SysFontMode FontMode = 1 << iota + LinkFontMode + SvgFontMode + CssFontMode + PathFontMode +) + type Svg struct { - XMLName xml.Name `xml:"svg"` - Xmlns string `xml:"xmlns,attr"` - Groups []*Group `xml:"g"` + XMLName xml.Name `xml:"svg"` + Xmlns string `xml:"xmlns,attr"` + Fonts []*Font `xml:"defs>font"` + Groups []*Group `xml:"g"` + fontMode FontMode FillStroke } @@ -20,6 +32,7 @@ func NewSvg() *Svg { return &Svg{ Xmlns: "http://www.w3.org/2000/svg", FillStroke: FillStroke{Fill: "none", Stroke: "none"}, + fontMode: SvgFontMode, } } @@ -45,8 +58,41 @@ type Text struct { Style string `xml:"style,attr,omitempty"` } +type Font struct { + Identity + Face *Face `xml:"font-face"` + Glyphs []*Glyph `xml:"glyph"` +} + +type Face struct { + Family string `xml:"font-family,attr"` + Units int `xml:"units-per-em,attr"` + HorizAdvX float64 `xml:"horiz-adv-x,attr"` + // TODO add other attrs, like style, variant, weight... +} + +type Glyph struct { + Rune Rune `xml:"unicode,attr"` + Desc string `xml:"d,attr"` + HorizAdvX float64 `xml:"horiz-adv-x,attr"` +} + +type Rune rune + +func (r Rune) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { + return xml.Attr{ + Name: name, + Value: string(rune(r)), + }, nil +} + /* shared attrs */ +type Identity struct { + Id string `xml:"id,attr"` + Name string `xml:"name,attr"` +} + type Position struct { X float64 `xml:"x,attr,omitempty"` Y float64 `xml:"y,attr,omitempty"` diff --git a/draw2dsvg/xml_test.go b/draw2dsvg/xml_test.go index b0760e7..8a7d11e 100644 --- a/draw2dsvg/xml_test.go +++ b/draw2dsvg/xml_test.go @@ -31,6 +31,7 @@ func TestXml(t *testing.T) { }} expectedOut := ` + diff --git a/output/samples/geometry.png b/output/samples/geometry.png index b68bb32..b46ec89 100644 Binary files a/output/samples/geometry.png and b/output/samples/geometry.png differ diff --git a/path.go b/path.go index 04a6572..bd24c9b 100644 --- a/path.go +++ b/path.go @@ -190,3 +190,34 @@ func (p *Path) String() string { } return s } + +// Returns new Path with flipped y axes +func (path *Path) VerticalFlip() *Path { + p := path.Copy() + j := 0 + for _, cmd := range p.Components { + switch cmd { + case MoveToCmp, LineToCmp: + p.Points[j+1] = -p.Points[j+1] + j = j + 2 + case QuadCurveToCmp: + p.Points[j+1] = -p.Points[j+1] + p.Points[j+3] = -p.Points[j+3] + j = j + 4 + case CubicCurveToCmp: + p.Points[j+1] = -p.Points[j+1] + p.Points[j+3] = -p.Points[j+3] + p.Points[j+5] = -p.Points[j+5] + j = j + 6 + case ArcToCmp: + p.Points[j+1] = -p.Points[j+1] + p.Points[j+3] = -p.Points[j+3] + p.Points[j+4] = -p.Points[j+4] // start angle + p.Points[j+5] = -p.Points[j+5] // angle + j = j + 6 + case CloseCmp: + } + } + p.y = -p.y + return p +} diff --git a/samples/geometry/geometry.go b/samples/geometry/geometry.go index 90892f9..05d9bdb 100644 --- a/samples/geometry/geometry.go +++ b/samples/geometry/geometry.go @@ -189,6 +189,7 @@ func CubicCurve(gc draw2d.GraphicContext, x, y, width, height float64) { } // FillString draws a filled and stroked string. +// And filles/stroked path created from string. Which may have different - unselectable - output in non raster gc implementations. func FillString(gc draw2d.GraphicContext, x, y, width, height float64) { sx, sy := width/100, height/100 gc.Save() @@ -215,6 +216,14 @@ func FillString(gc draw2d.GraphicContext, x, y, width, height float64) { gc.SetStrokeColor(color.NRGBA{0x33, 0x33, 0xff, 0xff}) gc.SetLineWidth(height / 100) gc.StrokeString("Hug") + + gc.Translate(-(w + sx), sy*24) + w = gc.CreateStringPath("Hug", 0, 0) + gc.Fill() + gc.Translate(w+sx, 0) + gc.CreateStringPath("Hug", 0, 0) + path := gc.GetPath() + gc.Stroke((&path).VerticalFlip()) gc.Restore() }