Experimental embed svg font functionality

This commit is contained in:
Drahoslav 2018-01-07 17:57:38 +01:00
parent c1e5edea41
commit 1b49270d08
7 changed files with 152 additions and 24 deletions

View file

@ -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
}
}

View file

@ -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)

View file

@ -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"`

View file

@ -31,6 +31,7 @@ func TestXml(t *testing.T) {
}}
expectedOut := `<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="none">
<defs></defs>
<g>
<g></g>
<g></g>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 24 KiB

31
path.go
View file

@ -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
}

View file

@ -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()
}