diff --git a/draw2dbase/stack_gc.go b/draw2dbase/stack_gc.go index 12723c7..b78665c 100644 --- a/draw2dbase/stack_gc.go +++ b/draw2dbase/stack_gc.go @@ -4,12 +4,17 @@ package draw2dbase import ( + "errors" "image" "image/color" + "log" + "math" "github.com/llgcode/draw2d" "github.com/golang/freetype/truetype" + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" ) var DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal} @@ -36,6 +41,8 @@ type ContextStack struct { // fontSize and dpi are used to calculate scale. scale is the number of // 26.6 fixed point units in 1 em. Scale float64 + glyphBuf *truetype.GlyphBuf + DPI int Previous *ContextStack } @@ -56,6 +63,8 @@ func NewStackGraphicContext() *StackGraphicContext { gc.Current.Join = draw2d.RoundJoin gc.Current.FontSize = 10 gc.Current.FontData = DefaultFontData + gc.Current.glyphBuf = &truetype.GlyphBuf{} + gc.Current.DPI = 92 return gc } @@ -112,8 +121,10 @@ func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) { gc.Current.DashOffset = dashOffset } +// SetFontSize sets the font size in points (as in ``a 12 point font''). func (gc *StackGraphicContext) SetFontSize(fontSize float64) { gc.Current.FontSize = fontSize + gc.recalc() } func (gc *StackGraphicContext) GetFontSize() float64 { @@ -179,6 +190,8 @@ func (gc *StackGraphicContext) Save() { context.Path = gc.Current.Path.Copy() context.Font = gc.Current.Font context.Scale = gc.Current.Scale + context.glyphBuf = gc.Current.glyphBuf + context.DPI = gc.Current.DPI copy(context.Tr[:], gc.Current.Tr[:]) context.Previous = gc.Current gc.Current = context @@ -191,3 +204,117 @@ func (gc *StackGraphicContext) Restore() { oldContext.Previous = nil } } + +func (gc *StackGraphicContext) loadCurrentFont() (*truetype.Font, error) { + font := draw2d.GetFont(gc.Current.FontData) + if font == nil { + font = draw2d.GetFont(DefaultFontData) + } + if font == nil { + return nil, errors.New("No font set, and no default font available.") + } + gc.SetFont(font) + gc.SetFontSize(gc.Current.FontSize) + return font, nil +} + +func (gc *StackGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error { + if err := gc.Current.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, font.HintingNone); err != nil { + return err + } + e0 := 0 + for _, e1 := range gc.Current.glyphBuf.Ends { + DrawContour(gc, gc.Current.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 *StackGraphicContext) CreateStringPath(s string, x, y float64) 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 *StackGraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) { + f, err := gc.loadCurrentFont() + 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.Current.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.Current.glyphBuf.Ends { + ps := gc.Current.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 +} + +// recalc recalculates scale and bounds values from the font size, screen +// resolution and font metrics, and invalidates the glyph cache. +func (gc *StackGraphicContext) recalc() { + gc.Current.Scale = gc.Current.FontSize * float64(gc.Current.DPI) * (64.0 / 72.0) +} + +func (gc *StackGraphicContext) SetDPI(dpi int) { + gc.Current.DPI = dpi + gc.recalc() +} + +// SetFont sets the font used to draw text. +func (gc *StackGraphicContext) SetFont(font *truetype.Font) { + gc.Current.Font = font +} + +func (gc *StackGraphicContext) GetDPI() int { + return gc.Current.DPI +} diff --git a/draw2dgl/text.go b/draw2dbase/text.go similarity index 99% rename from draw2dgl/text.go rename to draw2dbase/text.go index 79cd8c8..01e13ca 100644 --- a/draw2dgl/text.go +++ b/draw2dbase/text.go @@ -1,4 +1,4 @@ -package draw2dgl +package draw2dbase import ( "github.com/golang/freetype/truetype" diff --git a/draw2dgl/gc.go b/draw2dgl/gc.go index 582eabf..19d6d6e 100644 --- a/draw2dgl/gc.go +++ b/draw2dgl/gc.go @@ -1,23 +1,16 @@ package draw2dgl import ( - "errors" "image" "image/color" "image/draw" - "log" - "math" "runtime" "github.com/go-gl/gl/v2.1/gl" "github.com/golang/freetype/raster" - "github.com/golang/freetype/truetype" "github.com/llgcode/draw2d" "github.com/llgcode/draw2d/draw2dbase" "github.com/llgcode/draw2d/draw2dimg" - - "golang.org/x/image/font" - "golang.org/x/image/math/fixed" ) func init() { @@ -125,8 +118,6 @@ type GraphicContext struct { painter *Painter fillRasterizer *raster.Rasterizer strokeRasterizer *raster.Rasterizer - glyphBuf *truetype.GlyphBuf - DPI int } // NewGraphicContext creates a new Graphic context from an image. @@ -136,112 +127,16 @@ func NewGraphicContext(width, height int) *GraphicContext { NewPainter(), raster.NewRasterizer(width, height), raster.NewRasterizer(width, height), - &truetype.GlyphBuf{}, - 92, } return gc } -func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) { - font := draw2d.GetFont(gc.Current.FontData) - if font == nil { - font = draw2d.GetFont(draw2dbase.DefaultFontData) - } - if font == nil { - return nil, errors.New("No font set, and no default font available.") - } - gc.SetFont(font) - gc.SetFontSize(gc.Current.FontSize) - return font, nil -} - -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 { - 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 -} - func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) { width = gc.CreateStringPath(text, x, y) gc.Fill() return width } -// 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 - } - 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 -} - func (gc *GraphicContext) StrokeString(text string) (width float64) { return gc.StrokeStringAt(text, 0, 0) } @@ -252,32 +147,6 @@ func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float return width } -// 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) { - 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 { - return gc.DPI -} - //TODO func (gc *GraphicContext) Clear() { panic("not implemented") diff --git a/draw2dimg/ftgc.go b/draw2dimg/ftgc.go index 8bedf91..e5cc9e2 100644 --- a/draw2dimg/ftgc.go +++ b/draw2dimg/ftgc.go @@ -4,22 +4,15 @@ package draw2dimg import ( - "errors" "image" "image/color" - "log" - "math" - "github.com/llgcode/draw2d" "github.com/llgcode/draw2d/draw2dbase" "github.com/golang/freetype/raster" - "github.com/golang/freetype/truetype" "golang.org/x/image/draw" - "golang.org/x/image/font" "golang.org/x/image/math/f64" - "golang.org/x/image/math/fixed" ) // Painter implements the freetype raster.Painter and has a SetColor method like the RGBAPainter @@ -35,8 +28,6 @@ type GraphicContext struct { painter Painter fillRasterizer *raster.Rasterizer strokeRasterizer *raster.Rasterizer - glyphBuf *truetype.GlyphBuf - DPI int } // ImageFilter defines the type of filter to use @@ -67,24 +58,16 @@ func NewGraphicContext(img draw.Image) *GraphicContext { // NewGraphicContextWithPainter creates a new Graphic context from an image and a Painter (see Freetype-go) func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicContext { width, height := img.Bounds().Dx(), img.Bounds().Dy() - dpi := 92 gc := &GraphicContext{ draw2dbase.NewStackGraphicContext(), img, painter, raster.NewRasterizer(width, height), raster.NewRasterizer(width, height), - &truetype.GlyphBuf{}, - dpi, } return gc } -// GetDPI returns the resolution of the Image GraphicContext -func (gc *GraphicContext) GetDPI() int { - return gc.DPI -} - // Clear fills the current canvas with a default transparent color func (gc *GraphicContext) Clear() { width, height := gc.img.Bounds().Dx(), gc.img.Bounds().Dy() @@ -140,127 +123,6 @@ func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor floa return width } -func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) { - font := draw2d.GetFont(gc.Current.FontData) - if font == nil { - font = draw2d.GetFont(draw2dbase.DefaultFontData) - } - if font == nil { - return nil, errors.New("No font set, and no default font available.") - } - gc.SetFont(font) - gc.SetFontSize(gc.Current.FontSize) - return font, nil -} - -// p is a truetype.Point measured in FUnits and positive Y going upwards. -// The returned value is the same thing measured in floating point and positive Y -// going downwards. - -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 { - 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 - } - 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 -} - -// 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) -} - -// SetDPI sets the screen resolution in dots per inch. -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) paint(rasterizer *raster.Rasterizer, color color.Color) { gc.painter.SetColor(color) rasterizer.Rasterize(gc.painter) diff --git a/draw2dimg/text.go b/draw2dimg/text.go deleted file mode 100644 index 2fe2493..0000000 --- a/draw2dimg/text.go +++ /dev/null @@ -1,82 +0,0 @@ -package draw2dimg - -import ( - "github.com/golang/freetype/truetype" - "github.com/llgcode/draw2d" - - "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, - } -}