Experimental embed svg font functionality
This commit is contained in:
parent
c1e5edea41
commit
1b49270d08
|
@ -1,9 +1,9 @@
|
||||||
package draw2dsvg
|
package draw2dsvg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
_ "errors"
|
_ "errors"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SaveToSvgFile(filePath string, svg *Svg) error {
|
func SaveToSvgFile(filePath string, svg *Svg) error {
|
||||||
|
@ -19,4 +19,4 @@ func SaveToSvgFile(filePath string, svg *Svg) error {
|
||||||
err = encoder.Encode(svg)
|
err = encoder.Encode(svg)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,17 @@
|
||||||
package draw2dsvg
|
package draw2dsvg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/golang/freetype/truetype"
|
||||||
"github.com/llgcode/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"github.com/llgcode/draw2d/draw2dbase"
|
"github.com/llgcode/draw2d/draw2dbase"
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
"image"
|
"image"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
|
||||||
"math"
|
"math"
|
||||||
"github.com/golang/freetype/truetype"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"fmt"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type drawType int
|
type drawType int
|
||||||
|
@ -133,12 +133,16 @@ func (gc *GraphicContext) drawString(text string, drawType drawType, x, y float6
|
||||||
svgText.FontSize = gc.Current.FontSize
|
svgText.FontSize = gc.Current.FontSize
|
||||||
svgText.X = x
|
svgText.X = x
|
||||||
svgText.Y = y
|
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
|
// link to group
|
||||||
group.Texts = []*Text{&svgText}
|
group.Texts = []*Text{&svgText}
|
||||||
left, _, right, _ := gc.GetStringBounds(text)
|
left, _, right, _ := gc.GetStringBounds(text)
|
||||||
return right-left
|
return right - left
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates new group from current context
|
// Creates new group from current context
|
||||||
|
@ -170,20 +174,59 @@ func (gc *GraphicContext) newGroup(drawType drawType) *Group {
|
||||||
return &group
|
return &group
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////
|
// Embed svg font definition to svg tree itself
|
||||||
// TODO implement following methods (or remove if not neccesary)
|
func (gc *GraphicContext) embedSvgFont(text string) {
|
||||||
|
fontName := gc.Current.FontData.Name
|
||||||
|
gc.loadCurrentFont()
|
||||||
|
|
||||||
// SetFontData sets the current FontData
|
// find or create font Element
|
||||||
func (gc *GraphicContext) SetFontData(fontData draw2d.FontData) {
|
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}
|
// NOTE following functions copied from dwra2d{img|gl}
|
||||||
// TODO move them all to common draw2dbase?
|
// 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
|
return x - startx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// GetStringBounds returns the approximate pixel bounds of the string s at x, y.
|
// 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
|
// 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.
|
// and the baseline intersect at 0, 0 in the returned coordinates.
|
||||||
|
@ -311,7 +353,6 @@ func (gc *GraphicContext) SetFontSize(fontSize float64) {
|
||||||
gc.recalc()
|
gc.recalc()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
// TODO implement following methods (or remove if not neccesary)
|
// TODO implement following methods (or remove if not neccesary)
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,22 @@ import (
|
||||||
|
|
||||||
/* svg elements */
|
/* svg elements */
|
||||||
|
|
||||||
|
type FontMode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
SysFontMode FontMode = 1 << iota
|
||||||
|
LinkFontMode
|
||||||
|
SvgFontMode
|
||||||
|
CssFontMode
|
||||||
|
PathFontMode
|
||||||
|
)
|
||||||
|
|
||||||
type Svg struct {
|
type Svg struct {
|
||||||
XMLName xml.Name `xml:"svg"`
|
XMLName xml.Name `xml:"svg"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
Groups []*Group `xml:"g"`
|
Fonts []*Font `xml:"defs>font"`
|
||||||
|
Groups []*Group `xml:"g"`
|
||||||
|
fontMode FontMode
|
||||||
FillStroke
|
FillStroke
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +32,7 @@ func NewSvg() *Svg {
|
||||||
return &Svg{
|
return &Svg{
|
||||||
Xmlns: "http://www.w3.org/2000/svg",
|
Xmlns: "http://www.w3.org/2000/svg",
|
||||||
FillStroke: FillStroke{Fill: "none", Stroke: "none"},
|
FillStroke: FillStroke{Fill: "none", Stroke: "none"},
|
||||||
|
fontMode: SvgFontMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +58,41 @@ type Text struct {
|
||||||
Style string `xml:"style,attr,omitempty"`
|
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 */
|
/* shared attrs */
|
||||||
|
|
||||||
|
type Identity struct {
|
||||||
|
Id string `xml:"id,attr"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
type Position struct {
|
type Position struct {
|
||||||
X float64 `xml:"x,attr,omitempty"`
|
X float64 `xml:"x,attr,omitempty"`
|
||||||
Y float64 `xml:"y,attr,omitempty"`
|
Y float64 `xml:"y,attr,omitempty"`
|
||||||
|
|
|
@ -31,6 +31,7 @@ func TestXml(t *testing.T) {
|
||||||
}}
|
}}
|
||||||
|
|
||||||
expectedOut := `<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="none">
|
expectedOut := `<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="none">
|
||||||
|
<defs></defs>
|
||||||
<g>
|
<g>
|
||||||
<g></g>
|
<g></g>
|
||||||
<g></g>
|
<g></g>
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 24 KiB |
31
path.go
31
path.go
|
@ -190,3 +190,34 @@ func (p *Path) String() string {
|
||||||
}
|
}
|
||||||
return s
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -189,6 +189,7 @@ func CubicCurve(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillString draws a filled and stroked string.
|
// 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) {
|
func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||||
sx, sy := width/100, height/100
|
sx, sy := width/100, height/100
|
||||||
gc.Save()
|
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.SetStrokeColor(color.NRGBA{0x33, 0x33, 0xff, 0xff})
|
||||||
gc.SetLineWidth(height / 100)
|
gc.SetLineWidth(height / 100)
|
||||||
gc.StrokeString("Hug")
|
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()
|
gc.Restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue