Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
4ac4348abb |
47 changed files with 150 additions and 2096 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -21,5 +21,3 @@ _test*
|
||||||
**/core*[0-9]
|
**/core*[0-9]
|
||||||
.private
|
.private
|
||||||
|
|
||||||
go.sum
|
|
||||||
|
|
||||||
|
|
2
AUTHORS
2
AUTHORS
|
@ -1,4 +1,2 @@
|
||||||
Laurent Le Goff
|
Laurent Le Goff
|
||||||
Stani Michiels, gmail:stani.be
|
Stani Michiels, gmail:stani.be
|
||||||
Drahoslav Bednář
|
|
||||||
Sebastien Binet
|
|
||||||
|
|
|
@ -57,8 +57,7 @@ func main() {
|
||||||
gc.SetLineWidth(5)
|
gc.SetLineWidth(5)
|
||||||
|
|
||||||
// Draw a closed shape
|
// Draw a closed shape
|
||||||
gc.BeginPath() // Initialize a new path
|
gc.MoveTo(10, 10) // should always be called first for a new path
|
||||||
gc.MoveTo(10, 10) // Move to a position to start the new path
|
|
||||||
gc.LineTo(100, 50)
|
gc.LineTo(100, 50)
|
||||||
gc.QuadCurveTo(100, 10, 10, 10)
|
gc.QuadCurveTo(100, 10, 10, 10)
|
||||||
gc.Close()
|
gc.Close()
|
||||||
|
|
16
draw2d.go
16
draw2d.go
|
@ -128,14 +128,6 @@ const (
|
||||||
SquareCap
|
SquareCap
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cap LineCap) String() string {
|
|
||||||
return map[LineCap]string{
|
|
||||||
RoundCap: "round",
|
|
||||||
ButtCap: "cap",
|
|
||||||
SquareCap: "square",
|
|
||||||
}[cap]
|
|
||||||
}
|
|
||||||
|
|
||||||
// LineJoin is the style of segments joint
|
// LineJoin is the style of segments joint
|
||||||
type LineJoin int
|
type LineJoin int
|
||||||
|
|
||||||
|
@ -148,14 +140,6 @@ const (
|
||||||
MiterJoin
|
MiterJoin
|
||||||
)
|
)
|
||||||
|
|
||||||
func (join LineJoin) String() string {
|
|
||||||
return map[LineJoin]string{
|
|
||||||
RoundJoin: "round",
|
|
||||||
BevelJoin: "bevel",
|
|
||||||
MiterJoin: "miter",
|
|
||||||
}[join]
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrokeStyle keeps stroke style attributes
|
// StrokeStyle keeps stroke style attributes
|
||||||
// that is used by the Stroke method of a Drawer
|
// that is used by the Stroke method of a Drawer
|
||||||
type StrokeStyle struct {
|
type StrokeStyle struct {
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
package draw2dbase
|
package draw2dbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,7 +17,6 @@ const (
|
||||||
|
|
||||||
// SubdivideCubic a Bezier cubic curve in 2 equivalents Bezier cubic curves.
|
// SubdivideCubic a Bezier cubic curve in 2 equivalents Bezier cubic curves.
|
||||||
// c1 and c2 parameters are the resulting curves
|
// c1 and c2 parameters are the resulting curves
|
||||||
// length of c, c1 and c2 must be 8 otherwise it panics.
|
|
||||||
func SubdivideCubic(c, c1, c2 []float64) {
|
func SubdivideCubic(c, c1, c2 []float64) {
|
||||||
// First point of c is the first point of c1
|
// First point of c is the first point of c1
|
||||||
c1[0], c1[1] = c[0], c[1]
|
c1[0], c1[1] = c[0], c[1]
|
||||||
|
@ -50,10 +48,7 @@ func SubdivideCubic(c, c1, c2 []float64) {
|
||||||
|
|
||||||
// TraceCubic generate lines subdividing the cubic curve using a Liner
|
// TraceCubic generate lines subdividing the cubic curve using a Liner
|
||||||
// flattening_threshold helps determines the flattening expectation of the curve
|
// flattening_threshold helps determines the flattening expectation of the curve
|
||||||
func TraceCubic(t Liner, cubic []float64, flatteningThreshold float64) error {
|
func TraceCubic(t Liner, cubic []float64, flatteningThreshold float64) {
|
||||||
if len(cubic) < 8 {
|
|
||||||
return errors.New("cubic length must be >= 8")
|
|
||||||
}
|
|
||||||
// Allocation curves
|
// Allocation curves
|
||||||
var curves [CurveRecursionLimit * 8]float64
|
var curves [CurveRecursionLimit * 8]float64
|
||||||
copy(curves[0:8], cubic[0:8])
|
copy(curves[0:8], cubic[0:8])
|
||||||
|
@ -65,7 +60,7 @@ func TraceCubic(t Liner, cubic []float64, flatteningThreshold float64) error {
|
||||||
var dx, dy, d2, d3 float64
|
var dx, dy, d2, d3 float64
|
||||||
|
|
||||||
for i >= 0 {
|
for i >= 0 {
|
||||||
c = curves[i:]
|
c = curves[i*8:]
|
||||||
dx = c[6] - c[0]
|
dx = c[6] - c[0]
|
||||||
dy = c[7] - c[1]
|
dy = c[7] - c[1]
|
||||||
|
|
||||||
|
@ -73,16 +68,15 @@ func TraceCubic(t Liner, cubic []float64, flatteningThreshold float64) error {
|
||||||
d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx)
|
d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx)
|
||||||
|
|
||||||
// if it's flat then trace a line
|
// if it's flat then trace a line
|
||||||
if (d2+d3)*(d2+d3) <= flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-8 {
|
if (d2+d3)*(d2+d3) < flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||||
t.LineTo(c[6], c[7])
|
t.LineTo(c[6], c[7])
|
||||||
i -= 8
|
i--
|
||||||
} else {
|
} else {
|
||||||
// second half of bezier go lower onto the stack
|
// second half of bezier go lower onto the stack
|
||||||
SubdivideCubic(c, curves[i+8:], curves[i:])
|
SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:])
|
||||||
i += 8
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quad
|
// Quad
|
||||||
|
@ -90,7 +84,6 @@ func TraceCubic(t Liner, cubic []float64, flatteningThreshold float64) error {
|
||||||
|
|
||||||
// SubdivideQuad a Bezier quad curve in 2 equivalents Bezier quad curves.
|
// SubdivideQuad a Bezier quad curve in 2 equivalents Bezier quad curves.
|
||||||
// c1 and c2 parameters are the resulting curves
|
// c1 and c2 parameters are the resulting curves
|
||||||
// length of c, c1 and c2 must be 6 otherwise it panics.
|
|
||||||
func SubdivideQuad(c, c1, c2 []float64) {
|
func SubdivideQuad(c, c1, c2 []float64) {
|
||||||
// First point of c is the first point of c1
|
// First point of c is the first point of c1
|
||||||
c1[0], c1[1] = c[0], c[1]
|
c1[0], c1[1] = c[0], c[1]
|
||||||
|
@ -110,10 +103,7 @@ func SubdivideQuad(c, c1, c2 []float64) {
|
||||||
|
|
||||||
// TraceQuad generate lines subdividing the curve using a Liner
|
// TraceQuad generate lines subdividing the curve using a Liner
|
||||||
// flattening_threshold helps determines the flattening expectation of the curve
|
// flattening_threshold helps determines the flattening expectation of the curve
|
||||||
func TraceQuad(t Liner, quad []float64, flatteningThreshold float64) error {
|
func TraceQuad(t Liner, quad []float64, flatteningThreshold float64) {
|
||||||
if len(quad) < 6 {
|
|
||||||
return errors.New("quad length must be >= 6")
|
|
||||||
}
|
|
||||||
// Allocates curves stack
|
// Allocates curves stack
|
||||||
var curves [CurveRecursionLimit * 6]float64
|
var curves [CurveRecursionLimit * 6]float64
|
||||||
copy(curves[0:6], quad[0:6])
|
copy(curves[0:6], quad[0:6])
|
||||||
|
@ -123,23 +113,22 @@ func TraceQuad(t Liner, quad []float64, flatteningThreshold float64) error {
|
||||||
var dx, dy, d float64
|
var dx, dy, d float64
|
||||||
|
|
||||||
for i >= 0 {
|
for i >= 0 {
|
||||||
c = curves[i:]
|
c = curves[i*6:]
|
||||||
dx = c[4] - c[0]
|
dx = c[4] - c[0]
|
||||||
dy = c[5] - c[1]
|
dy = c[5] - c[1]
|
||||||
|
|
||||||
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
|
d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx))
|
||||||
|
|
||||||
// if it's flat then trace a line
|
// if it's flat then trace a line
|
||||||
if (d*d) <= flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-6 {
|
if (d*d) < flatteningThreshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||||
t.LineTo(c[4], c[5])
|
t.LineTo(c[4], c[5])
|
||||||
i -= 6
|
i--
|
||||||
} else {
|
} else {
|
||||||
// second half of bezier go lower onto the stack
|
// second half of bezier go lower onto the stack
|
||||||
SubdivideQuad(c, curves[i+6:], curves[i:])
|
SubdivideQuad(c, curves[(i+1)*6:], curves[i*6:])
|
||||||
i += 6
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceArc trace an arc using a Liner
|
// TraceArc trace an arc using a Liner
|
||||||
|
|
|
@ -101,32 +101,6 @@ func TestQuadCurve(t *testing.T) {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQuadCurveCombinedPoint(t *testing.T) {
|
|
||||||
var p1 SegmentedPath
|
|
||||||
TraceQuad(&p1, []float64{0, 0, 0, 0, 0, 0}, flatteningThreshold)
|
|
||||||
if len(p1.Points) != 2 {
|
|
||||||
t.Error("It must have one point for this curve", len(p1.Points))
|
|
||||||
}
|
|
||||||
var p2 SegmentedPath
|
|
||||||
TraceQuad(&p2, []float64{0, 0, 100, 100, 0, 0}, flatteningThreshold)
|
|
||||||
if len(p2.Points) != 2 {
|
|
||||||
t.Error("It must have one point for this curve", len(p2.Points))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCubicCurveCombinedPoint(t *testing.T) {
|
|
||||||
var p1 SegmentedPath
|
|
||||||
TraceCubic(&p1, []float64{0, 0, 0, 0, 0, 0, 0, 0}, flatteningThreshold)
|
|
||||||
if len(p1.Points) != 2 {
|
|
||||||
t.Error("It must have one point for this curve", len(p1.Points))
|
|
||||||
}
|
|
||||||
var p2 SegmentedPath
|
|
||||||
TraceCubic(&p2, []float64{0, 0, 100, 100, 200, 200, 0, 0}, flatteningThreshold)
|
|
||||||
if len(p2.Points) != 2 {
|
|
||||||
t.Error("It must have one point for this curve", len(p2.Points))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkCubicCurve(b *testing.B) {
|
func BenchmarkCubicCurve(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for i := 0; i < len(testsCubicFloat64); i += 8 {
|
for i := 0; i < len(testsCubicFloat64); i += 8 {
|
||||||
|
@ -158,19 +132,3 @@ func SaveToPngFile(filePath string, m image.Image) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOutOfRangeTraceCurve(t *testing.T) {
|
|
||||||
c := []float64{
|
|
||||||
100, 100, 200, 100, 100, 200,
|
|
||||||
}
|
|
||||||
var p SegmentedPath
|
|
||||||
TraceCubic(&p, c, flatteningThreshold)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutOfRangeTraceQuad(t *testing.T) {
|
|
||||||
c := []float64{
|
|
||||||
100, 100, 200, 100,
|
|
||||||
}
|
|
||||||
var p SegmentedPath
|
|
||||||
TraceQuad(&p, c, flatteningThreshold)
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
package draw2dbase
|
package draw2dbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Liner receive segment definition
|
// Liner receive segment definition
|
||||||
|
@ -19,7 +19,7 @@ type Flattener interface {
|
||||||
MoveTo(x, y float64)
|
MoveTo(x, y float64)
|
||||||
// LineTo Draw a line from the current position to the point (x, y)
|
// LineTo Draw a line from the current position to the point (x, y)
|
||||||
LineTo(x, y float64)
|
LineTo(x, y float64)
|
||||||
// LineJoin use Round, Bevel or miter to join points
|
// LineJoin add the most recent starting point to close the path to create a polygon
|
||||||
LineJoin()
|
LineJoin()
|
||||||
// Close add the most recent starting point to close the path to create a polygon
|
// Close add the most recent starting point to close the path to create a polygon
|
||||||
Close()
|
Close()
|
||||||
|
|
|
@ -4,13 +4,12 @@
|
||||||
package draw2dbase
|
package draw2dbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal}
|
var DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal}
|
||||||
|
@ -41,12 +40,6 @@ type ContextStack struct {
|
||||||
Previous *ContextStack
|
Previous *ContextStack
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFontName gets the current FontData with fontSize as a string
|
|
||||||
func (cs *ContextStack) GetFontName() string {
|
|
||||||
fontData := cs.FontData
|
|
||||||
return fmt.Sprintf("%s:%d:%d:%9.2f", fontData.Name, fontData.Family, fontData.Style, cs.FontSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Graphic context from an image
|
* Create a new Graphic context from an image
|
||||||
*/
|
*/
|
||||||
|
@ -139,10 +132,6 @@ func (gc *StackGraphicContext) BeginPath() {
|
||||||
gc.Current.Path.Clear()
|
gc.Current.Path.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) GetPath() draw2d.Path {
|
|
||||||
return *gc.Current.Path.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *StackGraphicContext) IsEmpty() bool {
|
func (gc *StackGraphicContext) IsEmpty() bool {
|
||||||
return gc.Current.Path.IsEmpty()
|
return gc.Current.Path.IsEmpty()
|
||||||
}
|
}
|
||||||
|
@ -202,7 +191,3 @@ func (gc *StackGraphicContext) Restore() {
|
||||||
oldContext.Previous = nil
|
oldContext.Previous = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *StackGraphicContext) GetFontName() string {
|
|
||||||
return gc.Current.GetFontName()
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ package draw2dbase
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LineStroker struct {
|
type LineStroker struct {
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
package draw2dbase
|
|
||||||
|
|
||||||
import "git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
|
|
||||||
// GlyphCache manage a cache of glyphs
|
|
||||||
type GlyphCache interface {
|
|
||||||
// Fetch fetches a glyph from the cache, storing with Render first if it doesn't already exist
|
|
||||||
Fetch(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph
|
|
||||||
}
|
|
||||||
|
|
||||||
// GlyphCacheImp manage a map of glyphs without sync mecanism, not thread safe
|
|
||||||
type GlyphCacheImp struct {
|
|
||||||
glyphs map[string]map[rune]*Glyph
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGlyphCache initializes a GlyphCache
|
|
||||||
func NewGlyphCache() *GlyphCacheImp {
|
|
||||||
glyphs := make(map[string]map[rune]*Glyph)
|
|
||||||
return &GlyphCacheImp{
|
|
||||||
glyphs: glyphs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch fetches a glyph from the cache, calling renderGlyph first if it doesn't already exist
|
|
||||||
func (glyphCache *GlyphCacheImp) Fetch(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph {
|
|
||||||
if glyphCache.glyphs[fontName] == nil {
|
|
||||||
glyphCache.glyphs[fontName] = make(map[rune]*Glyph, 60)
|
|
||||||
}
|
|
||||||
if glyphCache.glyphs[fontName][chr] == nil {
|
|
||||||
glyphCache.glyphs[fontName][chr] = renderGlyph(gc, fontName, chr)
|
|
||||||
}
|
|
||||||
return glyphCache.glyphs[fontName][chr].Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
// renderGlyph renders a glyph then caches and returns it
|
|
||||||
func renderGlyph(gc draw2d.GraphicContext, fontName string, chr rune) *Glyph {
|
|
||||||
gc.Save()
|
|
||||||
defer gc.Restore()
|
|
||||||
gc.BeginPath()
|
|
||||||
width := gc.CreateStringPath(string(chr), 0, 0)
|
|
||||||
path := gc.GetPath()
|
|
||||||
return &Glyph{
|
|
||||||
Path: &path,
|
|
||||||
Width: width,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Glyph represents a rune which has been converted to a Path and width
|
|
||||||
type Glyph struct {
|
|
||||||
// path represents a glyph, it is always at (0, 0)
|
|
||||||
Path *draw2d.Path
|
|
||||||
// Width of the glyph
|
|
||||||
Width float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy Returns a copy of a Glyph
|
|
||||||
func (g *Glyph) Copy() *Glyph {
|
|
||||||
return &Glyph{
|
|
||||||
Path: g.Path.Copy(),
|
|
||||||
Width: g.Width,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill copies a glyph from the cache, and fills it
|
|
||||||
func (g *Glyph) Fill(gc draw2d.GraphicContext, x, y float64) float64 {
|
|
||||||
gc.Save()
|
|
||||||
gc.BeginPath()
|
|
||||||
gc.Translate(x, y)
|
|
||||||
gc.Fill(g.Path)
|
|
||||||
gc.Restore()
|
|
||||||
return g.Width
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stroke fetches a glyph from the cache, and strokes it
|
|
||||||
func (g *Glyph) Stroke(gc draw2d.GraphicContext, x, y float64) float64 {
|
|
||||||
gc.Save()
|
|
||||||
gc.BeginPath()
|
|
||||||
gc.Translate(x, y)
|
|
||||||
gc.Stroke(g.Path)
|
|
||||||
gc.Restore()
|
|
||||||
return g.Width
|
|
||||||
}
|
|
196
draw2dgl/gc.go
196
draw2dgl/gc.go
|
@ -4,19 +4,13 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dbase"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
|
|
||||||
"github.com/go-gl/gl/v2.1/gl"
|
"github.com/go-gl/gl/v2.1/gl"
|
||||||
"github.com/golang/freetype/raster"
|
"github.com/golang/freetype/raster"
|
||||||
"github.com/golang/freetype/truetype"
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d/draw2dbase"
|
||||||
"golang.org/x/image/font"
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -124,10 +118,6 @@ type GraphicContext struct {
|
||||||
painter *Painter
|
painter *Painter
|
||||||
fillRasterizer *raster.Rasterizer
|
fillRasterizer *raster.Rasterizer
|
||||||
strokeRasterizer *raster.Rasterizer
|
strokeRasterizer *raster.Rasterizer
|
||||||
FontCache draw2d.FontCache
|
|
||||||
glyphCache draw2dbase.GlyphCache
|
|
||||||
glyphBuf *truetype.GlyphBuf
|
|
||||||
DPI int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGraphicContext creates a new Graphic context from an image.
|
// NewGraphicContext creates a new Graphic context from an image.
|
||||||
|
@ -137,200 +127,54 @@ func NewGraphicContext(width, height int) *GraphicContext {
|
||||||
NewPainter(),
|
NewPainter(),
|
||||||
raster.NewRasterizer(width, height),
|
raster.NewRasterizer(width, height),
|
||||||
raster.NewRasterizer(width, height),
|
raster.NewRasterizer(width, height),
|
||||||
draw2d.GetGlobalFontCache(),
|
|
||||||
draw2dbase.NewGlyphCache(),
|
|
||||||
&truetype.GlyphBuf{},
|
|
||||||
92,
|
|
||||||
}
|
}
|
||||||
return gc
|
return gc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
|
||||||
font, err := gc.FontCache.Load(gc.Current.FontData)
|
|
||||||
if err != nil {
|
|
||||||
font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
|
|
||||||
}
|
|
||||||
if font != nil {
|
|
||||||
gc.SetFont(font)
|
|
||||||
gc.SetFontSize(gc.Current.FontSize)
|
|
||||||
}
|
|
||||||
return font, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
|
|
||||||
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e0 := 0
|
|
||||||
for _, e1 := range gc.glyphBuf.Ends {
|
|
||||||
DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
|
|
||||||
e0 = e1
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
|
||||||
// The text is placed so that the left edge of the em square of the first character of s
|
|
||||||
// and the baseline intersect at x, y. The majority of the affected pixels will be
|
|
||||||
// above and to the right of the point, but some may be below or to the left.
|
|
||||||
// For example, drawing a string that starts with a 'J' in an italic font may
|
|
||||||
// affect pixels below and left of the point.
|
|
||||||
func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
|
func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 {
|
||||||
f, err := gc.loadCurrentFont()
|
panic("not implemented")
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
startx := x
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
for _, rune := range s {
|
|
||||||
index := f.Index(rune)
|
|
||||||
if hasPrev {
|
|
||||||
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
|
||||||
}
|
|
||||||
err := gc.drawGlyph(index, x, y)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return startx - x
|
|
||||||
}
|
|
||||||
x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
|
|
||||||
prev, hasPrev = index, true
|
|
||||||
}
|
|
||||||
return x - startx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillString draws the text at point (0, 0)
|
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
|
||||||
func (gc *GraphicContext) FillString(text string) (width float64) {
|
panic("not implemented")
|
||||||
return gc.FillStringAt(text, 0, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillStringAt draws the text at the specified point (x, y)
|
|
||||||
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) {
|
|
||||||
f, err := gc.loadCurrentFont()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
startx := x
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
fontName := gc.GetFontName()
|
|
||||||
for _, r := range text {
|
|
||||||
index := f.Index(r)
|
|
||||||
if hasPrev {
|
|
||||||
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
|
||||||
}
|
|
||||||
glyph := gc.glyphCache.Fetch(gc, fontName, r)
|
|
||||||
x += glyph.Fill(gc, x, y)
|
|
||||||
prev, hasPrev = index, true
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
// Therefore the top and left coordinates may well be negative.
|
|
||||||
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
|
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
|
||||||
f, err := gc.loadCurrentFont()
|
panic("not implemented")
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 0, 0, 0, 0
|
|
||||||
}
|
|
||||||
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
|
|
||||||
cursor := 0.0
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
for _, rune := range s {
|
|
||||||
index := f.Index(rune)
|
|
||||||
if hasPrev {
|
|
||||||
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
|
||||||
}
|
|
||||||
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 0, 0, 0, 0
|
|
||||||
}
|
|
||||||
e0 := 0
|
|
||||||
for _, e1 := range gc.glyphBuf.Ends {
|
|
||||||
ps := gc.glyphBuf.Points[e0:e1]
|
|
||||||
for _, p := range ps {
|
|
||||||
x, y := pointToF64Point(p)
|
|
||||||
top = math.Min(top, y)
|
|
||||||
bottom = math.Max(bottom, y)
|
|
||||||
left = math.Min(left, x+cursor)
|
|
||||||
right = math.Max(right, x+cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
|
|
||||||
prev, hasPrev = index, true
|
|
||||||
}
|
|
||||||
return left, top, right, bottom
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokeString draws the contour of the text at point (0, 0)
|
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
|
||||||
func (gc *GraphicContext) StrokeString(text string) (width float64) {
|
|
||||||
return gc.StrokeStringAt(text, 0, 0)
|
return gc.StrokeStringAt(text, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokeStringAt draws the contour of the text at point (x, y)
|
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
|
||||||
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float64) {
|
width := gc.CreateStringPath(text, x, y)
|
||||||
f, err := gc.loadCurrentFont()
|
gc.Stroke()
|
||||||
if err != nil {
|
return width
|
||||||
log.Println(err)
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
startx := x
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
fontName := gc.GetFontName()
|
|
||||||
for _, r := range text {
|
|
||||||
index := f.Index(r)
|
|
||||||
if hasPrev {
|
|
||||||
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
|
||||||
}
|
|
||||||
glyph := gc.glyphCache.Fetch(gc, fontName, r)
|
|
||||||
x += glyph.Stroke(gc, x, y)
|
|
||||||
prev, hasPrev = index, true
|
|
||||||
}
|
|
||||||
return x - startx
|
|
||||||
}
|
|
||||||
|
|
||||||
// recalc recalculates scale and bounds values from the font size, screen
|
|
||||||
// resolution and font metrics, and invalidates the glyph cache.
|
|
||||||
func (gc *GraphicContext) recalc() {
|
|
||||||
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) SetDPI(dpi int) {
|
func (gc *GraphicContext) SetDPI(dpi int) {
|
||||||
gc.DPI = dpi
|
|
||||||
gc.recalc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFont sets the font used to draw text.
|
|
||||||
func (gc *GraphicContext) SetFont(font *truetype.Font) {
|
|
||||||
gc.Current.Font = font
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFontSize sets the font size in points (as in ``a 12 point font'').
|
|
||||||
func (gc *GraphicContext) SetFontSize(fontSize float64) {
|
|
||||||
gc.Current.FontSize = fontSize
|
|
||||||
gc.recalc()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) GetDPI() int {
|
func (gc *GraphicContext) GetDPI() int {
|
||||||
return gc.DPI
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
|
||||||
func (gc *GraphicContext) Clear() {
|
func (gc *GraphicContext) Clear() {
|
||||||
panic("not implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
|
||||||
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
||||||
panic("not implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO
|
|
||||||
func (gc *GraphicContext) DrawImage(img image.Image) {
|
func (gc *GraphicContext) DrawImage(img image.Image) {
|
||||||
panic("not implemented")
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *GraphicContext) FillString(text string) (cursor float64) {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
|
func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) {
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
package draw2dgl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
|
||||||
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DrawContour draws the given closed contour at the given sub-pixel offset.
|
|
||||||
func DrawContour(path draw2d.PathBuilder, ps []truetype.Point, dx, dy float64) {
|
|
||||||
if len(ps) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
startX, startY := pointToF64Point(ps[0])
|
|
||||||
path.MoveTo(startX+dx, startY+dy)
|
|
||||||
q0X, q0Y, on0 := startX, startY, true
|
|
||||||
for _, p := range ps[1:] {
|
|
||||||
qX, qY := pointToF64Point(p)
|
|
||||||
on := p.Flags&0x01 != 0
|
|
||||||
if on {
|
|
||||||
if on0 {
|
|
||||||
path.LineTo(qX+dx, qY+dy)
|
|
||||||
} else {
|
|
||||||
path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if on0 {
|
|
||||||
// No-op.
|
|
||||||
} else {
|
|
||||||
midX := (q0X + qX) / 2
|
|
||||||
midY := (q0Y + qY) / 2
|
|
||||||
path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q0X, q0Y, on0 = qX, qY, on
|
|
||||||
}
|
|
||||||
// Close the curve.
|
|
||||||
if on0 {
|
|
||||||
path.LineTo(startX+dx, startY+dy)
|
|
||||||
} else {
|
|
||||||
path.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pointToF64Point(p truetype.Point) (x, y float64) {
|
|
||||||
return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fUnitsToFloat64(x fixed.Int26_6) float64 {
|
|
||||||
scaled := x << 2
|
|
||||||
return float64(scaled/256) + float64(scaled%256)/256.0
|
|
||||||
}
|
|
||||||
|
|
||||||
// FontExtents contains font metric information.
|
|
||||||
type FontExtents struct {
|
|
||||||
// Ascent is the distance that the text
|
|
||||||
// extends above the baseline.
|
|
||||||
Ascent float64
|
|
||||||
|
|
||||||
// Descent is the distance that the text
|
|
||||||
// extends below the baseline. The descent
|
|
||||||
// is given as a negative value.
|
|
||||||
Descent float64
|
|
||||||
|
|
||||||
// Height is the distance from the lowest
|
|
||||||
// descending point to the highest ascending
|
|
||||||
// point.
|
|
||||||
Height float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extents returns the FontExtents for a font.
|
|
||||||
// TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
|
|
||||||
func Extents(font *truetype.Font, size float64) FontExtents {
|
|
||||||
bounds := font.Bounds(fixed.Int26_6(font.FUnitsPerEm()))
|
|
||||||
scale := size / float64(font.FUnitsPerEm())
|
|
||||||
return FontExtents{
|
|
||||||
Ascent: float64(bounds.Max.Y) * scale,
|
|
||||||
Descent: float64(bounds.Min.Y) * scale,
|
|
||||||
Height: float64(bounds.Max.Y-bounds.Min.Y) * scale,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,190 +0,0 @@
|
||||||
package draw2dimg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
|
||||||
"golang.org/x/image/font/gofont/goregular"
|
|
||||||
)
|
|
||||||
|
|
||||||
// font generated from icomoon.io and converted to go byte slice
|
|
||||||
// contains only two glyphs
|
|
||||||
// \u2716 - which should look like a cross
|
|
||||||
// \u25cb - which should look like an empty circle
|
|
||||||
var icoTTF = []byte{
|
|
||||||
0, 1, 0, 0, 0, 12, 0, 128, 0, 3, 0, 64, 71, 83, 85, 66, 219, 7, 221, 185,
|
|
||||||
0, 0, 0, 204, 0, 0, 0, 188, 79, 83, 47, 50, 175, 17, 51, 150, 0, 0, 1, 136,
|
|
||||||
0, 0, 0, 96, 99, 109, 97, 112, 37, 204, 43, 67, 0, 0, 1, 232, 0, 0, 0, 148,
|
|
||||||
103, 97, 115, 112, 0, 0, 0, 16, 0, 0, 2, 124, 0, 0, 0, 8, 103, 108, 121, 102,
|
|
||||||
163, 112, 233, 32, 0, 0, 2, 132, 0, 0, 3, 64, 104, 101, 97, 100, 15, 49, 194, 135,
|
|
||||||
0, 0, 5, 196, 0, 0, 0, 54, 104, 104, 101, 97, 7, 194, 3, 217, 0, 0, 5, 252,
|
|
||||||
0, 0, 0, 36, 104, 109, 116, 120, 14, 0, 0, 2, 0, 0, 6, 32, 0, 0, 0, 96,
|
|
||||||
108, 111, 99, 97, 6, 168, 5, 226, 0, 0, 6, 128, 0, 0, 0, 50, 109, 97, 120, 112,
|
|
||||||
0, 27, 0, 86, 0, 0, 6, 180, 0, 0, 0, 32, 110, 97, 109, 101, 108, 36, 213, 69,
|
|
||||||
0, 0, 6, 212, 0, 0, 1, 170, 112, 111, 115, 116, 0, 3, 0, 0, 0, 0, 8, 128,
|
|
||||||
0, 0, 0, 32, 0, 1, 0, 0, 0, 10, 0, 30, 0, 44, 0, 1, 108, 97, 116, 110,
|
|
||||||
0, 8, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 108, 105, 103, 97,
|
|
||||||
0, 8, 0, 0, 0, 1, 0, 0, 0, 1, 0, 4, 0, 4, 0, 0, 0, 1, 0, 10,
|
|
||||||
0, 0, 0, 1, 0, 12, 0, 3, 0, 22, 0, 54, 0, 120, 0, 1, 0, 3, 0, 8,
|
|
||||||
0, 17, 0, 23, 0, 2, 0, 6, 0, 18, 0, 22, 0, 5, 0, 17, 0, 16, 0, 18,
|
|
||||||
0, 18, 0, 22, 0, 6, 0, 6, 0, 15, 0, 8, 0, 10, 0, 14, 0, 2, 0, 6,
|
|
||||||
0, 38, 0, 21, 0, 15, 0, 6, 0, 9, 0, 12, 0, 16, 0, 4, 0, 20, 0, 15,
|
|
||||||
0, 8, 0, 11, 0, 10, 0, 8, 0, 13, 0, 10, 0, 9, 0, 21, 0, 13, 0, 6,
|
|
||||||
0, 9, 0, 12, 0, 16, 0, 4, 0, 7, 0, 20, 0, 19, 0, 19, 0, 16, 0, 15,
|
|
||||||
0, 5, 0, 1, 0, 4, 0, 22, 0, 2, 0, 23, 0, 3, 3, 85, 1, 144, 0, 5,
|
|
||||||
0, 0, 2, 153, 2, 204, 0, 0, 0, 143, 2, 153, 2, 204, 0, 0, 1, 235, 0, 51,
|
|
||||||
1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
|
||||||
160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 39, 23,
|
|
||||||
3, 192, 255, 192, 0, 64, 3, 192, 0, 64, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 28,
|
|
||||||
0, 1, 0, 3, 0, 0, 0, 28, 0, 3, 0, 1, 0, 0, 0, 28, 0, 4, 0, 120,
|
|
||||||
0, 0, 0, 26, 0, 16, 0, 3, 0, 10, 0, 1, 0, 32, 0, 45, 0, 51, 0, 101,
|
|
||||||
0, 105, 0, 108, 0, 111, 0, 117, 37, 203, 39, 23, 255, 253, 255, 255, 0, 0, 0, 0,
|
|
||||||
0, 32, 0, 45, 0, 51, 0, 97, 0, 104, 0, 107, 0, 110, 0, 114, 37, 203, 39, 22,
|
|
||||||
255, 253, 255, 255, 0, 1, 255, 227, 255, 215, 255, 210, 255, 165, 255, 163, 255, 162, 255, 161,
|
|
||||||
255, 159, 218, 74, 217, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
|
|
||||||
255, 255, 0, 15, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57,
|
|
||||||
1, 0, 0, 0, 0, 2, 0, 0, 255, 192, 4, 0, 3, 192, 0, 27, 0, 55, 0, 0,
|
|
||||||
1, 34, 7, 14, 1, 7, 6, 21, 20, 23, 30, 1, 23, 22, 51, 50, 55, 62, 1, 55,
|
|
||||||
54, 53, 52, 39, 46, 1, 39, 38, 3, 34, 39, 46, 1, 39, 38, 53, 52, 55, 62, 1,
|
|
||||||
55, 54, 51, 50, 23, 30, 1, 23, 22, 21, 20, 7, 14, 1, 7, 6, 2, 0, 106, 93,
|
|
||||||
94, 139, 40, 40, 40, 40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 40, 40, 139, 94,
|
|
||||||
93, 106, 80, 69, 70, 105, 30, 30, 30, 30, 105, 70, 69, 80, 80, 69, 70, 105, 30, 30,
|
|
||||||
30, 30, 105, 70, 69, 3, 192, 40, 40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 40,
|
|
||||||
40, 139, 94, 93, 106, 106, 93, 94, 139, 40, 40, 252, 128, 30, 30, 105, 70, 69, 80, 80,
|
|
||||||
69, 70, 105, 30, 30, 30, 30, 105, 70, 69, 80, 80, 69, 70, 105, 30, 30, 0, 0, 0,
|
|
||||||
0, 1, 0, 2, 255, 194, 3, 254, 3, 190, 0, 83, 0, 0, 37, 56, 1, 49, 9, 1,
|
|
||||||
56, 1, 49, 62, 1, 55, 54, 38, 47, 1, 46, 1, 7, 14, 1, 7, 56, 1, 49, 9,
|
|
||||||
1, 56, 1, 49, 46, 1, 39, 38, 6, 15, 1, 14, 1, 23, 30, 1, 23, 56, 1, 49,
|
|
||||||
9, 1, 56, 1, 49, 14, 1, 7, 6, 22, 31, 1, 30, 1, 55, 62, 1, 55, 56, 1,
|
|
||||||
49, 9, 1, 56, 1, 49, 30, 1, 23, 22, 54, 63, 1, 62, 1, 39, 46, 1, 3, 247,
|
|
||||||
254, 201, 1, 55, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 2, 254, 201, 254,
|
|
||||||
201, 2, 6, 3, 9, 18, 7, 147, 7, 3, 3, 1, 4, 2, 1, 55, 254, 201, 2, 4,
|
|
||||||
1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 2, 1, 55, 1, 55, 2, 6, 3, 9, 18,
|
|
||||||
7, 147, 7, 3, 3, 1, 4, 137, 1, 55, 1, 55, 2, 6, 3, 9, 18, 7, 147, 7,
|
|
||||||
3, 3, 1, 4, 2, 254, 201, 1, 55, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3,
|
|
||||||
6, 2, 254, 201, 254, 201, 2, 6, 3, 9, 18, 7, 147, 7, 3, 3, 1, 4, 2, 1,
|
|
||||||
55, 254, 201, 2, 4, 1, 3, 3, 7, 147, 7, 18, 9, 3, 6, 0, 0, 1, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 55, 57, 1, 0, 0, 0, 0, 1, 0, 0,
|
|
||||||
0, 1, 0, 0, 32, 120, 21, 165, 95, 15, 60, 245, 0, 11, 4, 0, 0, 0, 0, 0,
|
|
||||||
214, 9, 63, 5, 0, 0, 0, 0, 214, 9, 63, 5, 0, 0, 255, 192, 4, 0, 3, 192,
|
|
||||||
0, 0, 0, 8, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 3, 192, 255, 192,
|
|
||||||
0, 0, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 2,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60,
|
|
||||||
0, 70, 0, 80, 0, 90, 0, 100, 0, 110, 0, 120, 0, 130, 0, 140, 0, 150, 0, 160,
|
|
||||||
0, 170, 0, 180, 0, 190, 0, 200, 1, 32, 1, 150, 1, 160, 0, 0, 0, 1, 0, 0,
|
|
||||||
0, 24, 0, 84, 0, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 174, 0, 1, 0, 0, 0, 0,
|
|
||||||
0, 1, 0, 10, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 0, 7, 0, 123, 0, 1,
|
|
||||||
0, 0, 0, 0, 0, 3, 0, 10, 0, 63, 0, 1, 0, 0, 0, 0, 0, 4, 0, 10,
|
|
||||||
0, 144, 0, 1, 0, 0, 0, 0, 0, 5, 0, 11, 0, 30, 0, 1, 0, 0, 0, 0,
|
|
||||||
0, 6, 0, 10, 0, 93, 0, 1, 0, 0, 0, 0, 0, 10, 0, 26, 0, 174, 0, 3,
|
|
||||||
0, 1, 4, 9, 0, 1, 0, 20, 0, 10, 0, 3, 0, 1, 4, 9, 0, 2, 0, 14,
|
|
||||||
0, 130, 0, 3, 0, 1, 4, 9, 0, 3, 0, 20, 0, 73, 0, 3, 0, 1, 4, 9,
|
|
||||||
0, 4, 0, 20, 0, 154, 0, 3, 0, 1, 4, 9, 0, 5, 0, 22, 0, 41, 0, 3,
|
|
||||||
0, 1, 4, 9, 0, 6, 0, 20, 0, 103, 0, 3, 0, 1, 4, 9, 0, 10, 0, 52,
|
|
||||||
0, 200, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99, 0, 111, 0, 45,
|
|
||||||
0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 86, 101, 114, 115, 105, 111, 110, 32,
|
|
||||||
49, 46, 48, 0, 86, 0, 101, 0, 114, 0, 115, 0, 105, 0, 111, 0, 110, 0, 32, 0,
|
|
||||||
49, 0, 46, 0, 48, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99, 0,
|
|
||||||
111, 0, 45, 0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 105, 99, 111, 45, 112,
|
|
||||||
101, 110, 101, 103, 111, 0, 105, 0, 99, 0, 111, 0, 45, 0, 112, 0, 101, 0, 110, 0,
|
|
||||||
101, 0, 103, 0, 111, 82, 101, 103, 117, 108, 97, 114, 0, 82, 0, 101, 0, 103, 0, 117,
|
|
||||||
0, 108, 0, 97, 0, 114, 105, 99, 111, 45, 112, 101, 110, 101, 103, 111, 0, 105, 0, 99,
|
|
||||||
0, 111, 0, 45, 0, 112, 0, 101, 0, 110, 0, 101, 0, 103, 0, 111, 70, 111, 110, 116,
|
|
||||||
32, 103, 101, 110, 101, 114, 97, 116, 101, 100, 32, 98, 121, 32, 73, 99, 111, 77, 111, 111,
|
|
||||||
110, 46, 0, 70, 0, 111, 0, 110, 0, 116, 0, 32, 0, 103, 0, 101, 0, 110, 0, 101,
|
|
||||||
0, 114, 0, 97, 0, 116, 0, 101, 0, 100, 0, 32, 0, 98, 0, 121, 0, 32, 0, 73,
|
|
||||||
0, 99, 0, 111, 0, 77, 0, 111, 0, 111, 0, 110, 0, 46, 0, 0, 0, 3, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
type customFontCache map[string]*truetype.Font
|
|
||||||
|
|
||||||
func (fc customFontCache) Store(fd draw2d.FontData, font *truetype.Font) {
|
|
||||||
fc[fd.Name] = font
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fc customFontCache) Load(fd draw2d.FontData) (*truetype.Font, error) {
|
|
||||||
font, stored := fc[fd.Name]
|
|
||||||
if !stored {
|
|
||||||
return nil, fmt.Errorf("font %s is not stored in font cache", fd.Name)
|
|
||||||
}
|
|
||||||
return font, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initFontCache() { // init font cache
|
|
||||||
fontCache := customFontCache{}
|
|
||||||
// add gofont to cache
|
|
||||||
gofont, err := truetype.Parse(goregular.TTF)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fontCache.Store(draw2d.FontData{Name: "goregular"}, gofont)
|
|
||||||
// add icofont to cache
|
|
||||||
icofont, err := truetype.Parse(icoTTF)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fontCache.Store(draw2d.FontData{Name: "ico"}, icofont)
|
|
||||||
|
|
||||||
draw2d.SetFontCache(fontCache)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCurveIndexOutOfRange(t *testing.T) {
|
|
||||||
|
|
||||||
initFontCache()
|
|
||||||
|
|
||||||
// Initialize the graphic context on an RGBA image
|
|
||||||
dest := image.NewRGBA(image.Rect(0, 0, 512, 512))
|
|
||||||
gc := NewGraphicContext(dest)
|
|
||||||
|
|
||||||
// background
|
|
||||||
gc.SetFillColor(color.RGBA{0xef, 0xef, 0xef, 0xff})
|
|
||||||
draw2dkit.Rectangle(gc, 0, 0, 512, 512)
|
|
||||||
gc.Fill()
|
|
||||||
|
|
||||||
// text
|
|
||||||
gc.SetFontSize(20)
|
|
||||||
gc.SetFillColor(color.RGBA{0x10, 0x10, 0x10, 0xff})
|
|
||||||
gc.SetFontData(draw2d.FontData{Name: "goregular"})
|
|
||||||
|
|
||||||
// gc.FillStringAt("Hello", 128, 120) // this works well
|
|
||||||
|
|
||||||
gc.SetFontData(draw2d.FontData{Name: "ico"})
|
|
||||||
gc.FillStringAt("\u25cb", 128, 150) // this also works
|
|
||||||
gc.FillStringAt("\u2716", 128, 170) // Works now
|
|
||||||
|
|
||||||
SaveToPngFile("_test_hello.png", dest)
|
|
||||||
}
|
|
|
@ -4,17 +4,17 @@
|
||||||
package draw2dimg
|
package draw2dimg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dbase"
|
"github.com/llgcode/draw2d/draw2dbase"
|
||||||
"git.fromouter.space/crunchy-rocks/emoji"
|
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/raster"
|
"github.com/golang/freetype/raster"
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
|
|
||||||
"golang.org/x/image/draw"
|
"golang.org/x/image/draw"
|
||||||
"golang.org/x/image/font"
|
"golang.org/x/image/font"
|
||||||
|
@ -35,11 +35,8 @@ type GraphicContext struct {
|
||||||
painter Painter
|
painter Painter
|
||||||
fillRasterizer *raster.Rasterizer
|
fillRasterizer *raster.Rasterizer
|
||||||
strokeRasterizer *raster.Rasterizer
|
strokeRasterizer *raster.Rasterizer
|
||||||
FontCache draw2d.FontCache
|
|
||||||
glyphCache draw2dbase.GlyphCache
|
|
||||||
glyphBuf *truetype.GlyphBuf
|
glyphBuf *truetype.GlyphBuf
|
||||||
DPI int
|
DPI int
|
||||||
Emojis emoji.Table
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageFilter defines the type of filter to use
|
// ImageFilter defines the type of filter to use
|
||||||
|
@ -56,6 +53,7 @@ const (
|
||||||
|
|
||||||
// NewGraphicContext creates a new Graphic context from an image.
|
// NewGraphicContext creates a new Graphic context from an image.
|
||||||
func NewGraphicContext(img draw.Image) *GraphicContext {
|
func NewGraphicContext(img draw.Image) *GraphicContext {
|
||||||
|
|
||||||
var painter Painter
|
var painter Painter
|
||||||
switch selectImage := img.(type) {
|
switch selectImage := img.(type) {
|
||||||
case *image.RGBA:
|
case *image.RGBA:
|
||||||
|
@ -76,11 +74,8 @@ func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicConte
|
||||||
painter,
|
painter,
|
||||||
raster.NewRasterizer(width, height),
|
raster.NewRasterizer(width, height),
|
||||||
raster.NewRasterizer(width, height),
|
raster.NewRasterizer(width, height),
|
||||||
draw2d.GetGlobalFontCache(),
|
|
||||||
draw2dbase.NewGlyphCache(),
|
|
||||||
&truetype.GlyphBuf{},
|
&truetype.GlyphBuf{},
|
||||||
dpi,
|
dpi,
|
||||||
make(emoji.Table),
|
|
||||||
}
|
}
|
||||||
return gc
|
return gc
|
||||||
}
|
}
|
||||||
|
@ -113,7 +108,7 @@ func DrawImage(src image.Image, dest draw.Image, tr draw2d.Matrix, op draw.Op, f
|
||||||
case BicubicFilter:
|
case BicubicFilter:
|
||||||
transformer = draw.CatmullRom
|
transformer = draw.CatmullRom
|
||||||
}
|
}
|
||||||
transformer.Transform(dest, f64.Aff3{tr[0], tr[1], tr[4], tr[2], tr[3], tr[5]}, src, src.Bounds(), op, nil)
|
transformer.Transform(dest, f64.Aff3{tr[0], tr[1], tr[4], tr[2], tr[3], tr[5]}, src, src.Bounds(), draw.Over, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawImage draws the raster image in the current canvas
|
// DrawImage draws the raster image in the current canvas
|
||||||
|
@ -122,98 +117,40 @@ func (gc *GraphicContext) DrawImage(img image.Image) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillString draws the text at point (0, 0)
|
// FillString draws the text at point (0, 0)
|
||||||
func (gc *GraphicContext) FillString(text string) (width float64) {
|
func (gc *GraphicContext) FillString(text string) (cursor float64) {
|
||||||
return gc.FillStringAt(text, 0, 0)
|
return gc.FillStringAt(text, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const emojiSpacing = 10
|
|
||||||
const emojiScale = 110
|
|
||||||
|
|
||||||
// FillStringAt draws the text at the specified point (x, y)
|
// FillStringAt draws the text at the specified point (x, y)
|
||||||
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) {
|
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
|
||||||
f, err := gc.loadCurrentFont()
|
width := gc.CreateStringPath(text, x, y)
|
||||||
if err != nil {
|
gc.Fill()
|
||||||
log.Println(err)
|
return width
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
startx := x
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
fontName := gc.GetFontName()
|
|
||||||
for fragment := range gc.Emojis.Iterate(text) {
|
|
||||||
if fragment.IsEmoji {
|
|
||||||
img, err := LoadFromPngFile(fragment.Emoji.Path)
|
|
||||||
if err == nil {
|
|
||||||
gc.Save()
|
|
||||||
scale := gc.GetFontSize() / 100
|
|
||||||
gc.Translate(x+scale*emojiSpacing, y-scale*emojiScale)
|
|
||||||
gc.Scale(scale, scale)
|
|
||||||
gc.DrawImage(img)
|
|
||||||
gc.Restore()
|
|
||||||
x += scale*float64(img.Bounds().Size().X) + scale*emojiSpacing*2
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
index := f.Index(fragment.Rune)
|
|
||||||
if hasPrev {
|
|
||||||
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
|
||||||
}
|
|
||||||
glyph := gc.glyphCache.Fetch(gc, fontName, fragment.Rune)
|
|
||||||
x += glyph.Fill(gc, x, y)
|
|
||||||
prev, hasPrev = index, true
|
|
||||||
}
|
|
||||||
return x - startx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokeString draws the contour of the text at point (0, 0)
|
// StrokeString draws the contour of the text at point (0, 0)
|
||||||
func (gc *GraphicContext) StrokeString(text string) (width float64) {
|
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
|
||||||
return gc.StrokeStringAt(text, 0, 0)
|
return gc.StrokeStringAt(text, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StrokeStringAt draws the contour of the text at point (x, y)
|
// StrokeStringAt draws the contour of the text at point (x, y)
|
||||||
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float64) {
|
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
|
||||||
f, err := gc.loadCurrentFont()
|
width := gc.CreateStringPath(text, x, y)
|
||||||
if err != nil {
|
gc.Stroke()
|
||||||
log.Println(err)
|
return width
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
startx := x
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
fontName := gc.GetFontName()
|
|
||||||
for fragment := range gc.Emojis.Iterate(text) {
|
|
||||||
if fragment.IsEmoji {
|
|
||||||
img, err := LoadFromPngFile(fragment.Emoji.Path)
|
|
||||||
if err == nil {
|
|
||||||
gc.Save()
|
|
||||||
scale := gc.GetFontSize() / 100
|
|
||||||
gc.Translate(x+scale*emojiSpacing, y-scale*emojiScale)
|
|
||||||
gc.Scale(scale, scale)
|
|
||||||
gc.DrawImage(img)
|
|
||||||
gc.Restore()
|
|
||||||
x += scale*float64(img.Bounds().Size().X) + scale*emojiSpacing*2
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
index := f.Index(fragment.Rune)
|
|
||||||
if hasPrev {
|
|
||||||
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
|
||||||
}
|
|
||||||
glyph := gc.glyphCache.Fetch(gc, fontName, fragment.Rune)
|
|
||||||
x += glyph.Stroke(gc, x, y)
|
|
||||||
prev, hasPrev = index, true
|
|
||||||
}
|
|
||||||
return x - startx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
||||||
font, err := gc.FontCache.Load(gc.Current.FontData)
|
font := draw2d.GetFont(gc.Current.FontData)
|
||||||
if err != nil {
|
if font == nil {
|
||||||
font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
|
font = draw2d.GetFont(draw2dbase.DefaultFontData)
|
||||||
|
}
|
||||||
|
if font == nil {
|
||||||
|
return nil, errors.New("No font set, and no default font available.")
|
||||||
}
|
}
|
||||||
if font != nil {
|
|
||||||
gc.SetFont(font)
|
gc.SetFont(font)
|
||||||
gc.SetFontSize(gc.Current.FontSize)
|
gc.SetFontSize(gc.Current.FontSize)
|
||||||
}
|
return font, nil
|
||||||
return font, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// p is a truetype.Point measured in FUnits and positive Y going upwards.
|
// p is a truetype.Point measured in FUnits and positive Y going upwards.
|
||||||
|
@ -275,39 +212,8 @@ func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom fl
|
||||||
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
|
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
|
||||||
cursor := 0.0
|
cursor := 0.0
|
||||||
prev, hasPrev := truetype.Index(0), false
|
prev, hasPrev := truetype.Index(0), false
|
||||||
// Get sample letter for approximated emoji calculations
|
for _, rune := range s {
|
||||||
const letter = 'M'
|
index := f.Index(rune)
|
||||||
mindex := f.Index(letter)
|
|
||||||
mtop, mleft, mheight, mwidth := 10e6, 10e6, -10e6, -10e6
|
|
||||||
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), mindex, font.HintingNone); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 0, 0, 0, 0
|
|
||||||
}
|
|
||||||
e0 := 0
|
|
||||||
for _, e1 := range gc.glyphBuf.Ends {
|
|
||||||
ps := gc.glyphBuf.Points[e0:e1]
|
|
||||||
for _, p := range ps {
|
|
||||||
x, y := pointToF64Point(p)
|
|
||||||
mtop = math.Min(mtop, y)
|
|
||||||
mheight = math.Max(mheight, y)
|
|
||||||
mleft = math.Min(mleft, x)
|
|
||||||
mwidth = math.Max(mwidth, x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mtop *= 1.2
|
|
||||||
mwidth *= 1.55
|
|
||||||
mheight += math.Abs(mtop-mheight) * 0.2
|
|
||||||
// Actually iterate through the string
|
|
||||||
for fragment := range gc.Emojis.Iterate(s) {
|
|
||||||
if fragment.IsEmoji {
|
|
||||||
cursor += mwidth
|
|
||||||
top = math.Min(top, mtop)
|
|
||||||
bottom = math.Max(bottom, mheight)
|
|
||||||
left = math.Min(left, mleft)
|
|
||||||
right = math.Max(right, cursor)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
index := f.Index(fragment.Rune)
|
|
||||||
if hasPrev {
|
if hasPrev {
|
||||||
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
package draw2dimg
|
package draw2dimg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/raster"
|
"github.com/golang/freetype/raster"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package draw2dimg
|
package draw2dimg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/golang/freetype/truetype"
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
"github.com/llgcode/draw2d"
|
||||||
|
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
"github.com/golang/freetype/truetype"
|
||||||
|
|
||||||
"github.com/jung-kurt/gofpdf"
|
"github.com/jung-kurt/gofpdf"
|
||||||
"github.com/llgcode/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
|
|
|
@ -1,176 +0,0 @@
|
||||||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 16/12/2017 by Drahoslav Bednářpackage draw2dsvg
|
|
||||||
|
|
||||||
package draw2dsvg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/png"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
)
|
|
||||||
|
|
||||||
func toSvgRGBA(c color.Color) string {
|
|
||||||
r, g, b, a := c.RGBA()
|
|
||||||
r, g, b, a = r>>8, g>>8, b>>8, a>>8
|
|
||||||
if a == 255 {
|
|
||||||
return optiSprintf("#%02X%02X%02X", r, g, b)
|
|
||||||
}
|
|
||||||
return optiSprintf("rgba(%v,%v,%v,%f)", r, g, b, float64(a)/255)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toSvgLength(l float64) string {
|
|
||||||
if math.IsInf(l, 1) {
|
|
||||||
return "100%"
|
|
||||||
}
|
|
||||||
return optiSprintf("%f", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toSvgArray(nums []float64) string {
|
|
||||||
arr := make([]string, len(nums))
|
|
||||||
for i, num := range nums {
|
|
||||||
arr[i] = optiSprintf("%f", num)
|
|
||||||
}
|
|
||||||
return strings.Join(arr, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func toSvgFillRule(rule draw2d.FillRule) string {
|
|
||||||
return map[draw2d.FillRule]string{
|
|
||||||
draw2d.FillRuleEvenOdd: "evenodd",
|
|
||||||
draw2d.FillRuleWinding: "nonzero",
|
|
||||||
}[rule]
|
|
||||||
}
|
|
||||||
|
|
||||||
func toSvgPathDesc(p *draw2d.Path) string {
|
|
||||||
parts := make([]string, len(p.Components))
|
|
||||||
ps := p.Points
|
|
||||||
for i, cmp := range p.Components {
|
|
||||||
switch cmp {
|
|
||||||
case draw2d.MoveToCmp:
|
|
||||||
parts[i] = optiSprintf("M %f,%f", ps[0], ps[1])
|
|
||||||
ps = ps[2:]
|
|
||||||
case draw2d.LineToCmp:
|
|
||||||
parts[i] = optiSprintf("L %f,%f", ps[0], ps[1])
|
|
||||||
ps = ps[2:]
|
|
||||||
case draw2d.QuadCurveToCmp:
|
|
||||||
parts[i] = optiSprintf("Q %f,%f %f,%f", ps[0], ps[1], ps[2], ps[3])
|
|
||||||
ps = ps[4:]
|
|
||||||
case draw2d.CubicCurveToCmp:
|
|
||||||
parts[i] = optiSprintf("C %f,%f %f,%f %f,%f", ps[0], ps[1], ps[2], ps[3], ps[4], ps[5])
|
|
||||||
ps = ps[6:]
|
|
||||||
case draw2d.ArcToCmp:
|
|
||||||
cx, cy := ps[0], ps[1] // center
|
|
||||||
rx, ry := ps[2], ps[3] // radii
|
|
||||||
fi := ps[4] + ps[5] // startAngle + angle
|
|
||||||
|
|
||||||
// compute endpoint
|
|
||||||
sinfi, cosfi := math.Sincos(fi)
|
|
||||||
nom := math.Hypot(ry*cosfi, rx*sinfi)
|
|
||||||
x := cx + (rx*ry*cosfi)/nom
|
|
||||||
y := cy + (rx*ry*sinfi)/nom
|
|
||||||
|
|
||||||
// compute large and sweep flags
|
|
||||||
large := 0
|
|
||||||
sweep := 0
|
|
||||||
if math.Abs(ps[5]) > math.Pi {
|
|
||||||
large = 1
|
|
||||||
}
|
|
||||||
if !math.Signbit(ps[5]) {
|
|
||||||
sweep = 1
|
|
||||||
}
|
|
||||||
// dirty hack to ensure whole arc is drawn
|
|
||||||
// if start point equals end point
|
|
||||||
if sweep == 1 {
|
|
||||||
x += 0.01 * sinfi
|
|
||||||
y += 0.01 * -cosfi
|
|
||||||
} else {
|
|
||||||
x += 0.01 * sinfi
|
|
||||||
y += 0.01 * cosfi
|
|
||||||
}
|
|
||||||
|
|
||||||
// rx ry x-axis-rotation large-arc-flag sweep-flag x y
|
|
||||||
parts[i] = optiSprintf("A %f %f %v %v %v %F %F",
|
|
||||||
rx, ry, 0, large, sweep, x, y,
|
|
||||||
)
|
|
||||||
ps = ps[6:]
|
|
||||||
case draw2d.CloseCmp:
|
|
||||||
parts[i] = "Z"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(parts, " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func toSvgTransform(mat draw2d.Matrix) string {
|
|
||||||
if mat.IsIdentity() {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if mat.IsTranslation() {
|
|
||||||
x, y := mat.GetTranslation()
|
|
||||||
return optiSprintf("translate(%f,%f)", x, y)
|
|
||||||
}
|
|
||||||
return optiSprintf("matrix(%f,%f,%f,%f,%f,%f)",
|
|
||||||
mat[0], mat[1], mat[2], mat[3], mat[4], mat[5],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func imageToSvgHref(image image.Image) string {
|
|
||||||
out := "data:image/png;base64,"
|
|
||||||
pngBuf := &bytes.Buffer{}
|
|
||||||
png.Encode(pngBuf, image)
|
|
||||||
out += base64.RawStdEncoding.EncodeToString(pngBuf.Bytes())
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the same thing as fmt.Sprintf
|
|
||||||
// except it uses the optimal precition for floats: (0-3) for f and (0-6) for F
|
|
||||||
// eg.:
|
|
||||||
// optiSprintf("%f", 3.0) => fmt.Sprintf("%.0f", 3.0)
|
|
||||||
// optiSprintf("%f", 3.33) => fmt.Sprintf("%.2f", 3.33)
|
|
||||||
// optiSprintf("%f", 3.3001) => fmt.Sprintf("%.1f", 3.3001)
|
|
||||||
// optiSprintf("%f", 3.333333333333333) => fmt.Sprintf("%.3f", 3.333333333333333)
|
|
||||||
// optiSprintf("%F", 3.333333333333333) => fmt.Sprintf("%.6f", 3.333333333333333)
|
|
||||||
func optiSprintf(format string, a ...interface{}) string {
|
|
||||||
chunks := strings.Split(format, "%")
|
|
||||||
newChunks := make([]string, len(chunks))
|
|
||||||
for i, chunk := range chunks {
|
|
||||||
if i != 0 {
|
|
||||||
verb := chunk[0]
|
|
||||||
if verb == 'f' || verb == 'F' {
|
|
||||||
num := a[i-1].(float64)
|
|
||||||
p := strconv.Itoa(getPrec(num, verb == 'F'))
|
|
||||||
chunk = strings.Replace(chunk, string(verb), "."+p+"f", 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newChunks[i] = chunk
|
|
||||||
}
|
|
||||||
format = strings.Join(newChunks, "%")
|
|
||||||
return fmt.Sprintf(format, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO needs test, since it is not quiet right
|
|
||||||
func getPrec(num float64, better bool) int {
|
|
||||||
max := 3
|
|
||||||
eps := 0.0005
|
|
||||||
if better {
|
|
||||||
max = 6
|
|
||||||
eps = 0.0000005
|
|
||||||
}
|
|
||||||
prec := 0
|
|
||||||
for math.Mod(num, 1) > eps {
|
|
||||||
num *= 10
|
|
||||||
eps *= 10
|
|
||||||
prec++
|
|
||||||
}
|
|
||||||
|
|
||||||
if max < prec {
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
return prec
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 16/12/2017 by Drahoslav Bednář
|
|
||||||
|
|
||||||
// Package draw2svg provides a graphic context that can draw
|
|
||||||
// vector graphics and text on svg file.
|
|
||||||
//
|
|
||||||
// Quick Start
|
|
||||||
// The following Go code geneartes a simple drawing and saves it
|
|
||||||
// to a svg document:
|
|
||||||
// TODO
|
|
||||||
package draw2dsvg
|
|
|
@ -1,22 +0,0 @@
|
||||||
package draw2dsvg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
_ "errors"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SaveToSvgFile(filePath string, svg *Svg) error {
|
|
||||||
f, err := os.Create(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
f.Write([]byte(xml.Header))
|
|
||||||
encoder := xml.NewEncoder(f)
|
|
||||||
encoder.Indent("", "\t")
|
|
||||||
err = encoder.Encode(svg)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
403
draw2dsvg/gc.go
403
draw2dsvg/gc.go
|
@ -1,403 +0,0 @@
|
||||||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 16/12/2017 by Drahoslav Bednář
|
|
||||||
|
|
||||||
package draw2dsvg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dbase"
|
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
type drawType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
filled drawType = 1 << iota
|
|
||||||
stroked
|
|
||||||
)
|
|
||||||
|
|
||||||
// GraphicContext implements the draw2d.GraphicContext interface
|
|
||||||
// It provides draw2d with a svg backend
|
|
||||||
type GraphicContext struct {
|
|
||||||
*draw2dbase.StackGraphicContext
|
|
||||||
FontCache draw2d.FontCache
|
|
||||||
glyphCache draw2dbase.GlyphCache
|
|
||||||
glyphBuf *truetype.GlyphBuf
|
|
||||||
svg *Svg
|
|
||||||
DPI int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGraphicContext(svg *Svg) *GraphicContext {
|
|
||||||
gc := &GraphicContext{
|
|
||||||
draw2dbase.NewStackGraphicContext(),
|
|
||||||
draw2d.GetGlobalFontCache(),
|
|
||||||
draw2dbase.NewGlyphCache(),
|
|
||||||
&truetype.GlyphBuf{},
|
|
||||||
svg,
|
|
||||||
92,
|
|
||||||
}
|
|
||||||
return gc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear fills the current canvas with a default transparent color
|
|
||||||
func (gc *GraphicContext) Clear() {
|
|
||||||
gc.svg.Groups = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stroke strokes the paths with the color specified by SetStrokeColor
|
|
||||||
func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) {
|
|
||||||
gc.drawPaths(stroked, paths...)
|
|
||||||
gc.Current.Path.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill fills the paths with the color specified by SetFillColor
|
|
||||||
func (gc *GraphicContext) Fill(paths ...*draw2d.Path) {
|
|
||||||
gc.drawPaths(filled, paths...)
|
|
||||||
gc.Current.Path.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FillStroke first fills the paths and than strokes them
|
|
||||||
func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) {
|
|
||||||
gc.drawPaths(filled|stroked, paths...)
|
|
||||||
gc.Current.Path.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FillString draws the text at point (0, 0)
|
|
||||||
func (gc *GraphicContext) FillString(text string) (cursor float64) {
|
|
||||||
return gc.FillStringAt(text, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FillStringAt draws the text at the specified point (x, y)
|
|
||||||
func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) {
|
|
||||||
return gc.drawString(text, filled, x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrokeString draws the contour of the text at point (0, 0)
|
|
||||||
func (gc *GraphicContext) StrokeString(text string) (cursor float64) {
|
|
||||||
return gc.StrokeStringAt(text, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrokeStringAt draws the contour of the text at point (x, y)
|
|
||||||
func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) {
|
|
||||||
return gc.drawString(text, stroked, x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the context and push it to the context stack
|
|
||||||
func (gc *GraphicContext) Save() {
|
|
||||||
gc.StackGraphicContext.Save()
|
|
||||||
// TODO use common transformation group for multiple elements
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore remove the current context and restore the last one
|
|
||||||
func (gc *GraphicContext) Restore() {
|
|
||||||
gc.StackGraphicContext.Restore()
|
|
||||||
// TODO use common transformation group for multiple elements
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *GraphicContext) SetDPI(dpi int) {
|
|
||||||
gc.DPI = dpi
|
|
||||||
gc.recalc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *GraphicContext) GetDPI() int {
|
|
||||||
return gc.DPI
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFont sets the font used to draw text.
|
|
||||||
func (gc *GraphicContext) SetFont(font *truetype.Font) {
|
|
||||||
gc.Current.Font = font
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFontSize sets the font size in points (as in “a 12 point font”).
|
|
||||||
func (gc *GraphicContext) SetFontSize(fontSize float64) {
|
|
||||||
gc.Current.FontSize = fontSize
|
|
||||||
gc.recalc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawImage draws the raster image in the current canvas
|
|
||||||
func (gc *GraphicContext) DrawImage(image image.Image) {
|
|
||||||
bounds := image.Bounds()
|
|
||||||
|
|
||||||
svgImage := &Image{Href: imageToSvgHref(image)}
|
|
||||||
svgImage.X = float64(bounds.Min.X)
|
|
||||||
svgImage.Y = float64(bounds.Min.Y)
|
|
||||||
svgImage.Width = toSvgLength(float64(bounds.Max.X - bounds.Min.X))
|
|
||||||
svgImage.Height = toSvgLength(float64(bounds.Max.Y - bounds.Min.Y))
|
|
||||||
gc.newGroup(0).Image = svgImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearRect fills the specified rectangle with a default transparent color
|
|
||||||
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
|
||||||
mask := gc.newMask(x1, y1, x2-x1, y2-y1)
|
|
||||||
|
|
||||||
newGroup := &Group{
|
|
||||||
Groups: gc.svg.Groups,
|
|
||||||
Mask: "url(#" + mask.Id + ")",
|
|
||||||
}
|
|
||||||
|
|
||||||
// replace groups with new masked group
|
|
||||||
gc.svg.Groups = []*Group{newGroup}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE following two functions and soe other further below copied from dwra2d{img|gl}
|
|
||||||
// TODO move them all to common draw2dbase?
|
|
||||||
|
|
||||||
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
|
||||||
// The text is placed so that the left edge of the em square of the first character of s
|
|
||||||
// and the baseline intersect at x, y. The majority of the affected pixels will be
|
|
||||||
// above and to the right of the point, but some may be below or to the left.
|
|
||||||
// For example, drawing a string that starts with a 'J' in an italic font may
|
|
||||||
// affect pixels below and left of the point.
|
|
||||||
func (gc *GraphicContext) CreateStringPath(s string, x, y float64) (cursor float64) {
|
|
||||||
f, err := gc.loadCurrentFont()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
startx := x
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
for _, rune := range s {
|
|
||||||
index := f.Index(rune)
|
|
||||||
if hasPrev {
|
|
||||||
x += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
|
||||||
}
|
|
||||||
err := gc.drawGlyph(index, x, y)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return startx - x
|
|
||||||
}
|
|
||||||
x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
|
|
||||||
prev, hasPrev = index, true
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
// Therefore the top and left coordinates may well be negative.
|
|
||||||
func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) {
|
|
||||||
f, err := gc.loadCurrentFont()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 0, 0, 0, 0
|
|
||||||
}
|
|
||||||
if gc.Current.Scale == 0 {
|
|
||||||
panic("zero scale")
|
|
||||||
}
|
|
||||||
top, left, bottom, right = 10e6, 10e6, -10e6, -10e6
|
|
||||||
cursor := 0.0
|
|
||||||
prev, hasPrev := truetype.Index(0), false
|
|
||||||
for _, rune := range s {
|
|
||||||
index := f.Index(rune)
|
|
||||||
if hasPrev {
|
|
||||||
cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))
|
|
||||||
}
|
|
||||||
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, font.HintingNone); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return 0, 0, 0, 0
|
|
||||||
}
|
|
||||||
e0 := 0
|
|
||||||
for _, e1 := range gc.glyphBuf.Ends {
|
|
||||||
ps := gc.glyphBuf.Points[e0:e1]
|
|
||||||
for _, p := range ps {
|
|
||||||
x, y := pointToF64Point(p)
|
|
||||||
top = math.Min(top, y)
|
|
||||||
bottom = math.Max(bottom, y)
|
|
||||||
left = math.Min(left, x+cursor)
|
|
||||||
right = math.Max(right, x+cursor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cursor += fUnitsToFloat64(f.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)
|
|
||||||
prev, hasPrev = index, true
|
|
||||||
}
|
|
||||||
return left, top, right, bottom
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////
|
|
||||||
// private funcitons
|
|
||||||
|
|
||||||
func (gc *GraphicContext) drawPaths(drawType drawType, paths ...*draw2d.Path) {
|
|
||||||
// create elements
|
|
||||||
svgPath := Path{}
|
|
||||||
group := gc.newGroup(drawType)
|
|
||||||
|
|
||||||
// set attrs to path element
|
|
||||||
paths = append(paths, gc.Current.Path)
|
|
||||||
svgPathsDesc := make([]string, len(paths))
|
|
||||||
// multiple pathes has to be joined to single svg path description
|
|
||||||
// because fill-rule wont work for whole group as excepted
|
|
||||||
for i, path := range paths {
|
|
||||||
svgPathsDesc[i] = toSvgPathDesc(path)
|
|
||||||
}
|
|
||||||
svgPath.Desc = strings.Join(svgPathsDesc, " ")
|
|
||||||
|
|
||||||
// attach to group
|
|
||||||
group.Paths = []*Path{&svgPath}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add text element to svg and returns its expected width
|
|
||||||
func (gc *GraphicContext) drawString(text string, drawType drawType, x, y float64) float64 {
|
|
||||||
switch gc.svg.FontMode {
|
|
||||||
case PathFontMode:
|
|
||||||
w := gc.CreateStringPath(text, x, y)
|
|
||||||
gc.drawPaths(drawType)
|
|
||||||
gc.Current.Path.Clear()
|
|
||||||
return w
|
|
||||||
case SvgFontMode:
|
|
||||||
gc.embedSvgFont(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create elements
|
|
||||||
svgText := Text{}
|
|
||||||
group := gc.newGroup(drawType)
|
|
||||||
|
|
||||||
// set attrs to text element
|
|
||||||
svgText.Text = text
|
|
||||||
svgText.FontSize = gc.Current.FontSize
|
|
||||||
svgText.X = x
|
|
||||||
svgText.Y = y
|
|
||||||
svgText.FontFamily = gc.Current.FontData.Name
|
|
||||||
|
|
||||||
// attach to group
|
|
||||||
group.Texts = []*Text{&svgText}
|
|
||||||
left, _, right, _ := gc.GetStringBounds(text)
|
|
||||||
return right - left
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates new group from current context
|
|
||||||
// attach it to svg and return
|
|
||||||
func (gc *GraphicContext) newGroup(drawType drawType) *Group {
|
|
||||||
group := Group{}
|
|
||||||
// set attrs to group
|
|
||||||
if drawType&stroked == stroked {
|
|
||||||
group.Stroke = toSvgRGBA(gc.Current.StrokeColor)
|
|
||||||
group.StrokeWidth = toSvgLength(gc.Current.LineWidth)
|
|
||||||
group.StrokeLinecap = gc.Current.Cap.String()
|
|
||||||
group.StrokeLinejoin = gc.Current.Join.String()
|
|
||||||
if len(gc.Current.Dash) > 0 {
|
|
||||||
group.StrokeDasharray = toSvgArray(gc.Current.Dash)
|
|
||||||
group.StrokeDashoffset = toSvgLength(gc.Current.DashOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if drawType&filled == filled {
|
|
||||||
group.Fill = toSvgRGBA(gc.Current.FillColor)
|
|
||||||
group.FillRule = toSvgFillRule(gc.Current.FillRule)
|
|
||||||
}
|
|
||||||
|
|
||||||
group.Transform = toSvgTransform(gc.Current.Tr)
|
|
||||||
|
|
||||||
// attach
|
|
||||||
gc.svg.Groups = append(gc.svg.Groups, &group)
|
|
||||||
|
|
||||||
return &group
|
|
||||||
}
|
|
||||||
|
|
||||||
// creates new mask attached to svg
|
|
||||||
func (gc *GraphicContext) newMask(x, y, width, height int) *Mask {
|
|
||||||
mask := &Mask{}
|
|
||||||
mask.X = float64(x)
|
|
||||||
mask.Y = float64(y)
|
|
||||||
mask.Width = toSvgLength(float64(width))
|
|
||||||
mask.Height = toSvgLength(float64(height))
|
|
||||||
|
|
||||||
// attach mask
|
|
||||||
gc.svg.Masks = append(gc.svg.Masks, mask)
|
|
||||||
mask.Id = "mask-" + strconv.Itoa(len(gc.svg.Masks))
|
|
||||||
return mask
|
|
||||||
}
|
|
||||||
|
|
||||||
// Embed svg font definition to svg tree itself
|
|
||||||
// Or update existing if already exists for curent font data
|
|
||||||
func (gc *GraphicContext) embedSvgFont(text string) *Font {
|
|
||||||
fontName := gc.Current.FontData.Name
|
|
||||||
gc.loadCurrentFont()
|
|
||||||
|
|
||||||
// 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 attach
|
|
||||||
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}
|
|
||||||
return svgFont
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
|
||||||
font, err := gc.FontCache.Load(gc.Current.FontData)
|
|
||||||
if err != nil {
|
|
||||||
font, err = gc.FontCache.Load(draw2dbase.DefaultFontData)
|
|
||||||
}
|
|
||||||
if font != nil {
|
|
||||||
gc.SetFont(font)
|
|
||||||
gc.SetFontSize(gc.Current.FontSize)
|
|
||||||
}
|
|
||||||
return font, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error {
|
|
||||||
if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e0 := 0
|
|
||||||
for _, e1 := range gc.glyphBuf.Ends {
|
|
||||||
DrawContour(gc, gc.glyphBuf.Points[e0:e1], dx, dy)
|
|
||||||
e0 = e1
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// recalc recalculates scale and bounds values from the font size, screen
|
|
||||||
// resolution and font metrics, and invalidates the glyph cache.
|
|
||||||
func (gc *GraphicContext) recalc() {
|
|
||||||
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 26/06/2015 by Stani Michiels
|
|
||||||
// See also test_test.go
|
|
||||||
|
|
||||||
package draw2dsvg_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/android"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/frameimage"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/geometry"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher2"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/helloworld"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/line"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/linecapjoin"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/postscript"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSampleAndroid(t *testing.T) {
|
|
||||||
test(t, android.Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: FillString: w (width) is incorrect
|
|
||||||
func TestSampleGeometry(t *testing.T) {
|
|
||||||
// Set the global folder for searching fonts
|
|
||||||
// The pdf backend needs for every ttf file its corresponding
|
|
||||||
// json/.z file which is generated by gofpdf/makefont.
|
|
||||||
draw2d.SetFontFolder("../resource/font")
|
|
||||||
test(t, geometry.Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSampleGopher(t *testing.T) {
|
|
||||||
test(t, gopher.Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSampleGopher2(t *testing.T) {
|
|
||||||
test(t, gopher2.Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSampleHelloWorld(t *testing.T) {
|
|
||||||
// Set the global folder for searching fonts
|
|
||||||
// The pdf backend needs for every ttf file its corresponding
|
|
||||||
// json/.z file which is generated by gofpdf/makefont.
|
|
||||||
draw2d.SetFontFolder("../resource/font")
|
|
||||||
test(t, helloworld.Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSampleFrameImage(t *testing.T) {
|
|
||||||
test(t, frameimage.Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSampleLine(t *testing.T) {
|
|
||||||
test(t, line.Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSampleLineCap(t *testing.T) {
|
|
||||||
test(t, linecapjoin.Main)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSamplePostscript(t *testing.T) {
|
|
||||||
test(t, postscript.Main)
|
|
||||||
}
|
|
172
draw2dsvg/svg.go
172
draw2dsvg/svg.go
|
@ -1,172 +0,0 @@
|
||||||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 16/12/2017 by Drahoslav Bednář
|
|
||||||
|
|
||||||
package draw2dsvg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* svg elements */
|
|
||||||
|
|
||||||
type FontMode int
|
|
||||||
|
|
||||||
// Modes of font handling in svg
|
|
||||||
const (
|
|
||||||
// Does nothing special
|
|
||||||
// Makes sense only for common system fonts
|
|
||||||
SysFontMode FontMode = 1 << iota
|
|
||||||
|
|
||||||
// Links font files in css def
|
|
||||||
// Requires distribution of font files with outputed svg
|
|
||||||
LinkFontMode // TODO implement
|
|
||||||
|
|
||||||
// Embeds glyphs definition in svg file itself in svg font format
|
|
||||||
// Has poor browser support
|
|
||||||
SvgFontMode
|
|
||||||
|
|
||||||
// Embeds font definiton in svg file itself in woff format as part of css def
|
|
||||||
CssFontMode // TODO implement
|
|
||||||
|
|
||||||
// Converts texts to paths
|
|
||||||
PathFontMode
|
|
||||||
)
|
|
||||||
|
|
||||||
type Svg struct {
|
|
||||||
XMLName xml.Name `xml:"svg"`
|
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Fonts []*Font `xml:"defs>font"`
|
|
||||||
Masks []*Mask `xml:"defs>mask"`
|
|
||||||
Groups []*Group `xml:"g"`
|
|
||||||
FontMode FontMode `xml:"-"`
|
|
||||||
FillStroke
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSvg() *Svg {
|
|
||||||
return &Svg{
|
|
||||||
Xmlns: "http://www.w3.org/2000/svg",
|
|
||||||
FillStroke: FillStroke{Fill: "none", Stroke: "none"},
|
|
||||||
FontMode: PathFontMode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Group struct {
|
|
||||||
FillStroke
|
|
||||||
Transform string `xml:"transform,attr,omitempty"`
|
|
||||||
Groups []*Group `xml:"g"`
|
|
||||||
Paths []*Path `xml:"path"`
|
|
||||||
Texts []*Text `xml:"text"`
|
|
||||||
Image *Image `xml:"image"`
|
|
||||||
Mask string `xml:"mask,attr,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Path struct {
|
|
||||||
FillStroke
|
|
||||||
Desc string `xml:"d,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Text struct {
|
|
||||||
FillStroke
|
|
||||||
Position
|
|
||||||
FontSize float64 `xml:"font-size,attr,omitempty"`
|
|
||||||
FontFamily string `xml:"font-family,attr,omitempty"`
|
|
||||||
Text string `xml:",innerxml"`
|
|
||||||
Style string `xml:"style,attr,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Image struct {
|
|
||||||
Position
|
|
||||||
Dimension
|
|
||||||
Href string `xml:"href,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mask struct {
|
|
||||||
Identity
|
|
||||||
Position
|
|
||||||
Dimension
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rect struct {
|
|
||||||
Position
|
|
||||||
Dimension
|
|
||||||
FillStroke
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Mask) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
||||||
bigRect := Rect{}
|
|
||||||
bigRect.X, bigRect.Y = 0, 0
|
|
||||||
bigRect.Width, bigRect.Height = "100%", "100%"
|
|
||||||
bigRect.Fill = "#fff"
|
|
||||||
rect := Rect{}
|
|
||||||
rect.X, rect.Y = m.X, m.Y
|
|
||||||
rect.Width, rect.Height = m.Width, m.Height
|
|
||||||
rect.Fill = "#000"
|
|
||||||
|
|
||||||
return e.EncodeElement(struct {
|
|
||||||
XMLName xml.Name `xml:"mask"`
|
|
||||||
Rects [2]Rect `xml:"rect"`
|
|
||||||
Id string `xml:"id,attr"`
|
|
||||||
}{
|
|
||||||
Rects: [2]Rect{bigRect, rect},
|
|
||||||
Id: m.Id,
|
|
||||||
}, start)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* font related elements */
|
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Dimension struct {
|
|
||||||
Width string `xml:"width,attr"`
|
|
||||||
Height string `xml:"height,attr"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FillStroke struct {
|
|
||||||
Fill string `xml:"fill,attr,omitempty"`
|
|
||||||
FillRule string `xml:"fill-rule,attr,omitempty"`
|
|
||||||
|
|
||||||
Stroke string `xml:"stroke,attr,omitempty"`
|
|
||||||
StrokeWidth string `xml:"stroke-width,attr,omitempty"`
|
|
||||||
StrokeLinecap string `xml:"stroke-linecap,attr,omitempty"`
|
|
||||||
StrokeLinejoin string `xml:"stroke-linejoin,attr,omitempty"`
|
|
||||||
StrokeDasharray string `xml:"stroke-dasharray,attr,omitempty"`
|
|
||||||
StrokeDashoffset string `xml:"stroke-dashoffset,attr,omitempty"`
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 16/12/2017 by Drahoslav Bednář
|
|
||||||
|
|
||||||
// Package draw2dsvg_test gives test coverage with the command:
|
|
||||||
// go test -cover ./... | grep -v "no test"
|
|
||||||
// (It should be run from its parent draw2d directory.)
|
|
||||||
package draw2dsvg_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dsvg"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sample func(gc draw2d.GraphicContext, ext string) (string, error)
|
|
||||||
|
|
||||||
func test(t *testing.T, draw sample) {
|
|
||||||
// Initialize the graphic context on an pdf document
|
|
||||||
dest := draw2dsvg.NewSvg()
|
|
||||||
gc := draw2dsvg.NewGraphicContext(dest)
|
|
||||||
// Draw sample
|
|
||||||
output, err := draw(gc, "svg")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Drawing %q failed: %v", output, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = draw2dsvg.SaveToSvgFile(output, dest)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Saving %q failed: %v", output, err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,83 +0,0 @@
|
||||||
// NOTE that this is identical copy of draw2dgl/text.go and draw2dimg/text.go
|
|
||||||
package draw2dsvg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DrawContour draws the given closed contour at the given sub-pixel offset.
|
|
||||||
func DrawContour(path draw2d.PathBuilder, ps []truetype.Point, dx, dy float64) {
|
|
||||||
if len(ps) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
startX, startY := pointToF64Point(ps[0])
|
|
||||||
|
|
||||||
path.MoveTo(startX+dx, startY+dy)
|
|
||||||
q0X, q0Y, on0 := startX, startY, true
|
|
||||||
for _, p := range ps[1:] {
|
|
||||||
qX, qY := pointToF64Point(p)
|
|
||||||
on := p.Flags&0x01 != 0
|
|
||||||
if on {
|
|
||||||
if on0 {
|
|
||||||
path.LineTo(qX+dx, qY+dy)
|
|
||||||
} else {
|
|
||||||
path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if on0 {
|
|
||||||
// No-op.
|
|
||||||
} else {
|
|
||||||
midX := (q0X + qX) / 2
|
|
||||||
midY := (q0Y + qY) / 2
|
|
||||||
path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
q0X, q0Y, on0 = qX, qY, on
|
|
||||||
}
|
|
||||||
// Close the curve.
|
|
||||||
if on0 {
|
|
||||||
path.LineTo(startX+dx, startY+dy)
|
|
||||||
} else {
|
|
||||||
path.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pointToF64Point(p truetype.Point) (x, y float64) {
|
|
||||||
return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fUnitsToFloat64(x fixed.Int26_6) float64 {
|
|
||||||
scaled := x << 2
|
|
||||||
return float64(scaled/256) + float64(scaled%256)/256.0
|
|
||||||
}
|
|
||||||
|
|
||||||
// FontExtents contains font metric information.
|
|
||||||
type FontExtents struct {
|
|
||||||
// Ascent is the distance that the text
|
|
||||||
// extends above the baseline.
|
|
||||||
Ascent float64
|
|
||||||
|
|
||||||
// Descent is the distance that the text
|
|
||||||
// extends below the baseline. The descent
|
|
||||||
// is given as a negative value.
|
|
||||||
Descent float64
|
|
||||||
|
|
||||||
// Height is the distance from the lowest
|
|
||||||
// descending point to the highest ascending
|
|
||||||
// point.
|
|
||||||
Height float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extents returns the FontExtents for a font.
|
|
||||||
// TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro
|
|
||||||
func Extents(font *truetype.Font, size float64) FontExtents {
|
|
||||||
bounds := font.Bounds(fixed.Int26_6(font.FUnitsPerEm()))
|
|
||||||
scale := size / float64(font.FUnitsPerEm())
|
|
||||||
return FontExtents{
|
|
||||||
Ascent: float64(bounds.Max.Y) * scale,
|
|
||||||
Descent: float64(bounds.Min.Y) * scale,
|
|
||||||
Height: float64(bounds.Max.Y-bounds.Min.Y) * scale,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
// Copyright 2015 The draw2d Authors. All rights reserved.
|
|
||||||
// created: 16/12/2017 by Drahoslav Bednář
|
|
||||||
|
|
||||||
// Package draw2dsvg_test gives test coverage with the command:
|
|
||||||
// go test -cover ./... | grep -v "no test"
|
|
||||||
// (It should be run from its parent draw2d directory.)
|
|
||||||
package draw2dsvg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test basic encoding of svg/xml elements
|
|
||||||
func TestXml(t *testing.T) {
|
|
||||||
|
|
||||||
svg := NewSvg()
|
|
||||||
svg.Groups = []*Group{&Group{
|
|
||||||
Groups: []*Group{
|
|
||||||
&Group{}, // nested groups
|
|
||||||
&Group{},
|
|
||||||
},
|
|
||||||
Texts: []*Text{
|
|
||||||
&Text{Text: "Hello"}, // text
|
|
||||||
&Text{Text: "world", Style: "opacity: 0.5"}, // text with style
|
|
||||||
},
|
|
||||||
Paths: []*Path{
|
|
||||||
&Path{Desc: "M100,200 C100,100 250,100 250,200 S400,300 400,200"}, // simple path
|
|
||||||
&Path{}, // empty path
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
|
|
||||||
expectedOut := `<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="none">
|
|
||||||
<defs></defs>
|
|
||||||
<g>
|
|
||||||
<g></g>
|
|
||||||
<g></g>
|
|
||||||
<path d="M100,200 C100,100 250,100 250,200 S400,300 400,200"></path>
|
|
||||||
<path d=""></path>
|
|
||||||
<text>Hello</text>
|
|
||||||
<text style="opacity: 0.5">world</text>
|
|
||||||
</g>
|
|
||||||
</svg>`
|
|
||||||
|
|
||||||
out, err := xml.MarshalIndent(svg, "", " ")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
if string(out) != expectedOut {
|
|
||||||
t.Errorf("svg output is not as expected\n"+
|
|
||||||
"got:\n%s\n\n"+
|
|
||||||
"want:\n%s\n",
|
|
||||||
string(out),
|
|
||||||
expectedOut,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
177
font.go
177
font.go
|
@ -6,11 +6,16 @@ package draw2d
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"sync"
|
"github.com/golang/freetype/truetype"
|
||||||
|
)
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/freetype/truetype"
|
var (
|
||||||
|
fontFolder = "../resource/font/"
|
||||||
|
fonts = make(map[string]*truetype.Font)
|
||||||
|
fontNamer FontFileNamer = FontFileName
|
||||||
)
|
)
|
||||||
|
|
||||||
// FontStyle defines bold and italic styles for the font
|
// FontStyle defines bold and italic styles for the font
|
||||||
|
@ -64,167 +69,41 @@ func FontFileName(fontData FontData) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterFont(fontData FontData, font *truetype.Font) {
|
func RegisterFont(fontData FontData, font *truetype.Font) {
|
||||||
fontCache.Store(fontData, font)
|
fonts[fontNamer(fontData)] = font
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFont(fontData FontData) (font *truetype.Font) {
|
func GetFont(fontData FontData) *truetype.Font {
|
||||||
var err error
|
fontFileName := fontNamer(fontData)
|
||||||
|
font := fonts[fontFileName]
|
||||||
if font, err = fontCache.Load(fontData); err != nil {
|
if font != nil {
|
||||||
log.Println(err)
|
return font
|
||||||
}
|
}
|
||||||
|
fonts[fontFileName] = loadFont(fontFileName)
|
||||||
return
|
return fonts[fontFileName]
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFontFolder() string {
|
func GetFontFolder() string {
|
||||||
return defaultFonts.folder
|
return fontFolder
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetFontFolder(folder string) {
|
func SetFontFolder(folder string) {
|
||||||
defaultFonts.setFolder(filepath.Clean(folder))
|
fontFolder = filepath.Clean(folder)
|
||||||
}
|
|
||||||
|
|
||||||
func GetGlobalFontCache() FontCache {
|
|
||||||
return fontCache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetFontNamer(fn FontFileNamer) {
|
func SetFontNamer(fn FontFileNamer) {
|
||||||
defaultFonts.setNamer(fn)
|
fontNamer = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Types implementing this interface can be passed to SetFontCache to change the
|
func loadFont(fontFileName string) *truetype.Font {
|
||||||
// way fonts are being stored and retrieved.
|
fontBytes, err := ioutil.ReadFile(path.Join(fontFolder, fontFileName))
|
||||||
type FontCache interface {
|
if err != nil {
|
||||||
// Loads a truetype font represented by the FontData object passed as
|
log.Println(err)
|
||||||
// argument.
|
return nil
|
||||||
// The method returns an error if the font could not be loaded, either
|
|
||||||
// because it didn't exist or the resource it was loaded from was corrupted.
|
|
||||||
Load(FontData) (*truetype.Font, error)
|
|
||||||
|
|
||||||
// Sets the truetype font that will be returned by Load when given the font
|
|
||||||
// data passed as first argument.
|
|
||||||
Store(FontData, *truetype.Font)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changes the font cache backend used by the package. After calling this
|
|
||||||
// functionSetFontFolder and SetFontNamer will not affect anymore how fonts are
|
|
||||||
// loaded.
|
|
||||||
// To restore the default font cache, call this function passing nil as argument.
|
|
||||||
func SetFontCache(cache FontCache) {
|
|
||||||
if cache == nil {
|
|
||||||
fontCache = defaultFonts
|
|
||||||
} else {
|
|
||||||
fontCache = cache
|
|
||||||
}
|
}
|
||||||
}
|
font, err := truetype.Parse(fontBytes)
|
||||||
|
if err != nil {
|
||||||
// FolderFontCache can Load font from folder
|
log.Println(err)
|
||||||
type FolderFontCache struct {
|
return nil
|
||||||
fonts map[string]*truetype.Font
|
|
||||||
folder string
|
|
||||||
namer FontFileNamer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFolderFontCache creates FolderFontCache
|
|
||||||
func NewFolderFontCache(folder string) *FolderFontCache {
|
|
||||||
return &FolderFontCache{
|
|
||||||
fonts: make(map[string]*truetype.Font),
|
|
||||||
folder: folder,
|
|
||||||
namer: FontFileName,
|
|
||||||
}
|
}
|
||||||
|
return font
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load a font from cache if exists otherwise it will load the font from file
|
|
||||||
func (cache *FolderFontCache) Load(fontData FontData) (font *truetype.Font, err error) {
|
|
||||||
if font = cache.fonts[cache.namer(fontData)]; font != nil {
|
|
||||||
return font, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var data []byte
|
|
||||||
var file = cache.namer(fontData)
|
|
||||||
|
|
||||||
if data, err = ioutil.ReadFile(filepath.Join(cache.folder, file)); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if font, err = truetype.Parse(data); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.fonts[file] = font
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store a font to this cache
|
|
||||||
func (cache *FolderFontCache) Store(fontData FontData, font *truetype.Font) {
|
|
||||||
cache.fonts[cache.namer(fontData)] = font
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncFolderFontCache can Load font from folder
|
|
||||||
type SyncFolderFontCache struct {
|
|
||||||
sync.RWMutex
|
|
||||||
fonts map[string]*truetype.Font
|
|
||||||
folder string
|
|
||||||
namer FontFileNamer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSyncFolderFontCache creates SyncFolderFontCache
|
|
||||||
func NewSyncFolderFontCache(folder string) *SyncFolderFontCache {
|
|
||||||
return &SyncFolderFontCache{
|
|
||||||
fonts: make(map[string]*truetype.Font),
|
|
||||||
folder: folder,
|
|
||||||
namer: FontFileName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *SyncFolderFontCache) setFolder(folder string) {
|
|
||||||
cache.Lock()
|
|
||||||
cache.folder = folder
|
|
||||||
cache.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *SyncFolderFontCache) setNamer(namer FontFileNamer) {
|
|
||||||
cache.Lock()
|
|
||||||
cache.namer = namer
|
|
||||||
cache.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load a font from cache if exists otherwise it will load the font from file
|
|
||||||
func (cache *SyncFolderFontCache) Load(fontData FontData) (font *truetype.Font, err error) {
|
|
||||||
cache.RLock()
|
|
||||||
font = cache.fonts[cache.namer(fontData)]
|
|
||||||
cache.RUnlock()
|
|
||||||
|
|
||||||
if font != nil {
|
|
||||||
return font, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var data []byte
|
|
||||||
var file = cache.namer(fontData)
|
|
||||||
|
|
||||||
if data, err = ioutil.ReadFile(filepath.Join(cache.folder, file)); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if font, err = truetype.Parse(data); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cache.Lock()
|
|
||||||
cache.fonts[file] = font
|
|
||||||
cache.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store a font to this cache
|
|
||||||
func (cache *SyncFolderFontCache) Store(fontData FontData, font *truetype.Font) {
|
|
||||||
cache.Lock()
|
|
||||||
cache.fonts[cache.namer(fontData)] = font
|
|
||||||
cache.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultFonts = NewSyncFolderFontCache("../resource/font")
|
|
||||||
|
|
||||||
fontCache FontCache = defaultFonts
|
|
||||||
)
|
|
||||||
|
|
7
gc.go
7
gc.go
|
@ -14,8 +14,6 @@ type GraphicContext interface {
|
||||||
PathBuilder
|
PathBuilder
|
||||||
// BeginPath creates a new path
|
// BeginPath creates a new path
|
||||||
BeginPath()
|
BeginPath()
|
||||||
// GetPath copies the current path, then returns it
|
|
||||||
GetPath() Path
|
|
||||||
// GetMatrixTransform returns the current transformation matrix
|
// GetMatrixTransform returns the current transformation matrix
|
||||||
GetMatrixTransform() Matrix
|
GetMatrixTransform() Matrix
|
||||||
// SetMatrixTransform sets the current transformation matrix
|
// SetMatrixTransform sets the current transformation matrix
|
||||||
|
@ -50,17 +48,12 @@ type GraphicContext interface {
|
||||||
SetFontData(fontData FontData)
|
SetFontData(fontData FontData)
|
||||||
// GetFontData gets the current FontData
|
// GetFontData gets the current FontData
|
||||||
GetFontData() FontData
|
GetFontData() FontData
|
||||||
// GetFontName gets the current FontData as a string
|
|
||||||
GetFontName() string
|
|
||||||
// DrawImage draws the raster image in the current canvas
|
// DrawImage draws the raster image in the current canvas
|
||||||
DrawImage(image image.Image)
|
DrawImage(image image.Image)
|
||||||
// Save the context and push it to the context stack
|
|
||||||
Save()
|
Save()
|
||||||
// Restore remove the current context and restore the last one
|
|
||||||
Restore()
|
Restore()
|
||||||
// Clear fills the current canvas with a default transparent color
|
// Clear fills the current canvas with a default transparent color
|
||||||
Clear()
|
Clear()
|
||||||
// ClearRect fills the specified rectangle with a default transparent color
|
|
||||||
ClearRect(x1, y1, x2, y2 int)
|
ClearRect(x1, y1, x2, y2 int)
|
||||||
// SetDPI sets the current DPI
|
// SetDPI sets the current DPI
|
||||||
SetDPI(dpi int)
|
SetDPI(dpi int)
|
||||||
|
|
13
go.mod
13
go.mod
|
@ -1,13 +0,0 @@
|
||||||
module git.fromouter.space/crunchy-rocks/draw2d
|
|
||||||
|
|
||||||
require (
|
|
||||||
git.fromouter.space/crunchy-rocks/emoji v0.0.0-20181116142102-2188aadaf093
|
|
||||||
git.fromouter.space/crunchy-rocks/freetype v0.0.0-20181116104610-3115318f2577
|
|
||||||
github.com/go-gl/gl v0.0.0-20180407155706-68e253793080
|
|
||||||
github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326
|
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
|
||||||
github.com/jung-kurt/gofpdf v1.0.0
|
|
||||||
github.com/llgcode/draw2d v0.0.0-20180825133448-f52c8a71aff0
|
|
||||||
github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb
|
|
||||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81
|
|
||||||
)
|
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 41 KiB |
46
path.go
46
path.go
|
@ -76,10 +76,9 @@ func (p *Path) MoveTo(x, y float64) {
|
||||||
// LineTo adds a line to the current path
|
// LineTo adds a line to the current path
|
||||||
func (p *Path) LineTo(x, y float64) {
|
func (p *Path) LineTo(x, y float64) {
|
||||||
if len(p.Components) == 0 { //special case when no move has been done
|
if len(p.Components) == 0 { //special case when no move has been done
|
||||||
p.MoveTo(x, y)
|
p.MoveTo(0, 0)
|
||||||
} else {
|
|
||||||
p.appendToPath(LineToCmp, x, y)
|
|
||||||
}
|
}
|
||||||
|
p.appendToPath(LineToCmp, x, y)
|
||||||
p.x = x
|
p.x = x
|
||||||
p.y = y
|
p.y = y
|
||||||
}
|
}
|
||||||
|
@ -87,10 +86,9 @@ func (p *Path) LineTo(x, y float64) {
|
||||||
// QuadCurveTo adds a quadratic bezier curve to the current path
|
// QuadCurveTo adds a quadratic bezier curve to the current path
|
||||||
func (p *Path) QuadCurveTo(cx, cy, x, y float64) {
|
func (p *Path) QuadCurveTo(cx, cy, x, y float64) {
|
||||||
if len(p.Components) == 0 { //special case when no move has been done
|
if len(p.Components) == 0 { //special case when no move has been done
|
||||||
p.MoveTo(x, y)
|
p.MoveTo(0, 0)
|
||||||
} else {
|
|
||||||
p.appendToPath(QuadCurveToCmp, cx, cy, x, y)
|
|
||||||
}
|
}
|
||||||
|
p.appendToPath(QuadCurveToCmp, cx, cy, x, y)
|
||||||
p.x = x
|
p.x = x
|
||||||
p.y = y
|
p.y = y
|
||||||
}
|
}
|
||||||
|
@ -98,10 +96,9 @@ func (p *Path) QuadCurveTo(cx, cy, x, y float64) {
|
||||||
// CubicCurveTo adds a cubic bezier curve to the current path
|
// CubicCurveTo adds a cubic bezier curve to the current path
|
||||||
func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
|
func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) {
|
||||||
if len(p.Components) == 0 { //special case when no move has been done
|
if len(p.Components) == 0 { //special case when no move has been done
|
||||||
p.MoveTo(x, y)
|
p.MoveTo(0, 0)
|
||||||
} else {
|
|
||||||
p.appendToPath(CubicCurveToCmp, cx1, cy1, cx2, cy2, x, y)
|
|
||||||
}
|
}
|
||||||
|
p.appendToPath(CubicCurveToCmp, cx1, cy1, cx2, cy2, x, y)
|
||||||
p.x = x
|
p.x = x
|
||||||
p.y = y
|
p.y = y
|
||||||
}
|
}
|
||||||
|
@ -190,34 +187,3 @@ 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
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
"github.com/llgcode/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws a droid and returns the filename. This should only be
|
// Main draws a droid and returns the filename. This should only be
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"image/png"
|
"image/png"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dpdf"
|
"github.com/llgcode/draw2d/draw2dpdf"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/android"
|
"github.com/llgcode/draw2d/samples/android"
|
||||||
|
|
||||||
"appengine"
|
"appengine"
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,10 +7,10 @@ package frameimage
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
|
"github.com/llgcode/draw2d/draw2dimg"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
"github.com/llgcode/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws the image frame and returns the filename.
|
// Main draws the image frame and returns the filename.
|
||||||
|
|
|
@ -9,10 +9,10 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
"github.com/llgcode/draw2d/samples"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher2"
|
"github.com/llgcode/draw2d/samples/gopher2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws geometry and returns the filename. This should only be
|
// Main draws geometry and returns the filename. This should only be
|
||||||
|
@ -189,7 +189,6 @@ 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()
|
||||||
|
@ -203,8 +202,7 @@ func FillString(gc draw2d.GraphicContext, x, y, width, height float64) {
|
||||||
gc.SetFontData(draw2d.FontData{
|
gc.SetFontData(draw2d.FontData{
|
||||||
Name: "luxi",
|
Name: "luxi",
|
||||||
Family: draw2d.FontFamilyMono,
|
Family: draw2d.FontFamilyMono,
|
||||||
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic,
|
Style: draw2d.FontStyleBold | draw2d.FontStyleItalic})
|
||||||
})
|
|
||||||
w := gc.FillString("Hug")
|
w := gc.FillString("Hug")
|
||||||
gc.Translate(w+sx, 0)
|
gc.Translate(w+sx, 0)
|
||||||
left, top, right, bottom := gc.GetStringBounds("cou")
|
left, top, right, bottom := gc.GetStringBounds("cou")
|
||||||
|
@ -216,14 +214,6 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@ package gopher
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
"github.com/llgcode/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws a left hand and ear of a gopher. Afterwards it returns
|
// Main draws a left hand and ear of a gopher. Afterwards it returns
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
"github.com/llgcode/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws a rotated face of the gopher. Afterwards it returns
|
// Main draws a rotated face of the gopher. Afterwards it returns
|
||||||
|
|
|
@ -9,9 +9,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
"github.com/llgcode/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws "Hello World" and returns the filename. This should only be
|
// Main draws "Hello World" and returns the filename. This should only be
|
||||||
|
|
|
@ -6,11 +6,11 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dgl"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
|
||||||
"github.com/go-gl/gl/v2.1/gl"
|
"github.com/go-gl/gl/v2.1/gl"
|
||||||
"github.com/go-gl/glfw/v3.1/glfw"
|
"github.com/go-gl/glfw/v3.1/glfw"
|
||||||
|
"github.com/llgcode/draw2d"
|
||||||
|
"github.com/llgcode/draw2d/draw2dgl"
|
||||||
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -7,9 +7,9 @@ package line
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
"github.com/llgcode/draw2d/draw2dkit"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
"github.com/llgcode/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws vertically spaced lines and returns the filename.
|
// Main draws vertically spaced lines and returns the filename.
|
||||||
|
|
|
@ -7,8 +7,8 @@ package linecapjoin
|
||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
"github.com/llgcode/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws the different line caps and joins.
|
// Main draws the different line caps and joins.
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
|
|
||||||
"github.com/llgcode/ps"
|
"github.com/llgcode/ps"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples"
|
"github.com/llgcode/draw2d/samples"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Main draws the tiger
|
// Main draws the tiger
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dgl"
|
|
||||||
"github.com/go-gl/gl/v2.1/gl"
|
"github.com/go-gl/gl/v2.1/gl"
|
||||||
"github.com/go-gl/glfw/v3.1/glfw"
|
"github.com/go-gl/glfw/v3.1/glfw"
|
||||||
|
"github.com/llgcode/draw2d/draw2dgl"
|
||||||
"github.com/llgcode/ps"
|
"github.com/llgcode/ps"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import "fmt"
|
||||||
// Resource returns a resource filename for testing.
|
// Resource returns a resource filename for testing.
|
||||||
func Resource(folder, filename, ext string) string {
|
func Resource(folder, filename, ext string) string {
|
||||||
var root string
|
var root string
|
||||||
if ext == "pdf" || ext == "svg" {
|
if ext == "pdf" {
|
||||||
root = "../"
|
root = "../"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%sresource/%s/%s", root, folder, filename)
|
return fmt.Sprintf("%sresource/%s/%s", root, folder, filename)
|
||||||
|
@ -17,7 +17,7 @@ func Resource(folder, filename, ext string) string {
|
||||||
// Output returns the output filename for testing.
|
// Output returns the output filename for testing.
|
||||||
func Output(name, ext string) string {
|
func Output(name, ext string) string {
|
||||||
var root string
|
var root string
|
||||||
if ext == "pdf" || ext == "svg" {
|
if ext == "pdf" {
|
||||||
root = "../"
|
root = "../"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%soutput/samples/%s.%s", root, name, ext)
|
return fmt.Sprintf("%soutput/samples/%s.%s", root, name, ext)
|
||||||
|
|
|
@ -5,16 +5,16 @@ package draw2d_test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
"github.com/llgcode/draw2d"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/android"
|
"github.com/llgcode/draw2d/samples/android"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/frameimage"
|
"github.com/llgcode/draw2d/samples/frameimage"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/geometry"
|
"github.com/llgcode/draw2d/samples/geometry"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher"
|
"github.com/llgcode/draw2d/samples/gopher"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/gopher2"
|
"github.com/llgcode/draw2d/samples/gopher2"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/helloworld"
|
"github.com/llgcode/draw2d/samples/helloworld"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/line"
|
"github.com/llgcode/draw2d/samples/line"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/linecapjoin"
|
"github.com/llgcode/draw2d/samples/linecapjoin"
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/samples/postscript"
|
"github.com/llgcode/draw2d/samples/postscript"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSampleAndroid(t *testing.T) {
|
func TestSampleAndroid(t *testing.T) {
|
||||||
|
|
46
sync_test.go
46
sync_test.go
|
@ -1,46 +0,0 @@
|
||||||
// go test -race -test.v sync_test.go
|
|
||||||
|
|
||||||
package draw2d_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dimg"
|
|
||||||
"git.fromouter.space/crunchy-rocks/draw2d/draw2dkit"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSync(t *testing.T) {
|
|
||||||
ch := make(chan int)
|
|
||||||
limit := 2
|
|
||||||
for i := 0; i < limit; i++ {
|
|
||||||
go Draw(i, ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < limit; i++ {
|
|
||||||
counter := <-ch
|
|
||||||
t.Logf("Goroutine %d returned\n", counter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Draw(i int, ch chan<- int) {
|
|
||||||
draw2d.SetFontFolder("./resource/font")
|
|
||||||
// Draw a rounded rectangle using default colors
|
|
||||||
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
|
|
||||||
gc := draw2dimg.NewGraphicContext(dest)
|
|
||||||
|
|
||||||
draw2dkit.RoundedRectangle(gc, 5, 5, 135, 95, 10, 10)
|
|
||||||
gc.FillStroke()
|
|
||||||
|
|
||||||
// Set the fill text color to black
|
|
||||||
gc.SetFillColor(image.Black)
|
|
||||||
gc.SetFontSize(14)
|
|
||||||
|
|
||||||
// Display Hello World dimensions
|
|
||||||
x1, y1, x2, y2 := gc.GetStringBounds("Hello world")
|
|
||||||
gc.FillStringAt(fmt.Sprintf("%.2f %.2f %.2f %.2f", x1, y1, x2, y2), 0, 0)
|
|
||||||
|
|
||||||
ch <- i
|
|
||||||
}
|
|
Loading…
Reference in a new issue