Experimental embed svg font functionality
This commit is contained in:
parent
c1e5edea41
commit
1b49270d08
7 changed files with 152 additions and 24 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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
31
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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue