diff --git a/draw2dbase/stack_gc.go b/draw2dbase/stack_gc.go index b78665c..a92aabd 100644 --- a/draw2dbase/stack_gc.go +++ b/draw2dbase/stack_gc.go @@ -4,21 +4,13 @@ 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} - type StackGraphicContext struct { Current *ContextStack } @@ -41,8 +33,6 @@ 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 } @@ -63,8 +53,6 @@ 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 } @@ -121,16 +109,31 @@ 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 { return gc.Current.FontSize } +func (gc *StackGraphicContext) SetScale(scale float64) { + gc.Current.Scale = scale +} + +func (gc *StackGraphicContext) GetScale() float64 { + return gc.Current.Scale +} + +// SetFont sets the font used to draw text +func (gc *StackGraphicContext) SetFont(font *truetype.Font) { + gc.Current.Font = font +} + +func (gc *StackGraphicContext) GetFont() *truetype.Font { + return gc.Current.Font +} + func (gc *StackGraphicContext) SetFontData(fontData draw2d.FontData) { gc.Current.FontData = fontData } @@ -190,8 +193,6 @@ 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 @@ -204,117 +205,3 @@ 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/draw2dbase/text.go b/draw2dbase/text.go index 01e13ca..2a5f04e 100644 --- a/draw2dbase/text.go +++ b/draw2dbase/text.go @@ -1,12 +1,127 @@ package draw2dbase import ( + "errors" + "log" + "math" + "github.com/golang/freetype/truetype" "github.com/llgcode/draw2d" + + "golang.org/x/image/font" "golang.org/x/image/math/fixed" ) +var ( + glyphBuf = &truetype.GlyphBuf{} + DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal} +) + + +func loadCurrentFont(gc draw2d.GraphicContext) (*truetype.Font, error) { + font := draw2d.GetFont(gc.GetFontData()) + 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.GetFontSize()) + recalc(gc) + return font, nil +} + +func drawGlyph(gc draw2d.GraphicContext, glyph truetype.Index, dx, dy float64) error { + if err := glyphBuf.Load(gc.GetFont(), fixed.Int26_6(gc.GetScale()), glyph, font.HintingNone); err != nil { + return err + } + e0 := 0 + for _, e1 := range glyphBuf.Ends { + DrawContour(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 CreateStringPath(gc draw2d.GraphicContext, s string, x, y float64) float64 { + f, err := loadCurrentFont(gc) + if err != nil { + log.Println(err) + return 0.0 + } + startx := x + prev, hasPrev := truetype.Index(0), false + scale := gc.GetScale() + for _, rune := range s { + index := f.Index(rune) + if hasPrev { + x += fUnitsToFloat64(f.Kern(fixed.Int26_6(scale), prev, index)) + } + err := drawGlyph(gc, index, x, y) + if err != nil { + log.Println(err) + return startx - x + } + x += fUnitsToFloat64(f.HMetric(fixed.Int26_6(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 GetStringBounds(gc draw2d.GraphicContext, s string) (left, top, right, bottom float64) { + f, err := loadCurrentFont(gc) + 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 + scale := gc.GetScale() + for _, rune := range s { + index := f.Index(rune) + if hasPrev { + cursor += fUnitsToFloat64(f.Kern(fixed.Int26_6(scale), prev, index)) + } + if err := glyphBuf.Load(gc.GetFont(), fixed.Int26_6(scale), index, font.HintingNone); err != nil { + log.Println(err) + return 0, 0, 0, 0 + } + e0 := 0 + for _, e1 := range glyphBuf.Ends { + ps := 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.GetScale()), 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 recalc(gc draw2d.GraphicContext) { + gc.SetScale(gc.GetFontSize() * float64(gc.GetDPI()) * (64.0 / 72.0)) +} + // 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 { diff --git a/draw2dgl/gc.go b/draw2dgl/gc.go index 19d6d6e..aab3cb0 100644 --- a/draw2dgl/gc.go +++ b/draw2dgl/gc.go @@ -118,6 +118,7 @@ type GraphicContext struct { painter *Painter fillRasterizer *raster.Rasterizer strokeRasterizer *raster.Rasterizer + DPI int } // NewGraphicContext creates a new Graphic context from an image. @@ -127,10 +128,33 @@ func NewGraphicContext(width, height int) *GraphicContext { NewPainter(), raster.NewRasterizer(width, height), raster.NewRasterizer(width, height), + 92, } return gc } +// 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 { + return draw2dbase.CreateStringPath(gc, s, x, y) +} + +// GetStringBounds returns the approximate pixel bounds of the string s at x, y. +// The the left edge of the em square of the first character of s +// 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) { + return draw2dbase.GetStringBounds(gc, s) +} + +func (gc *GraphicContext) FillString(text string) (cursor float64) { + return gc.FillStringAt(text, 0, 0) +} + func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) { width = gc.CreateStringPath(text, x, y) gc.Fill() @@ -147,6 +171,19 @@ func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float return width } +func (gc *GraphicContext) SetDPI(dpi int) { + gc.DPI = dpi +} + +func (gc *GraphicContext) GetDPI() int { + return gc.DPI +} + +// SetFontSize sets the font size in points (as in ``a 12 point font''). +func (gc *GraphicContext) SetFontSize(fontSize float64) { + gc.Current.FontSize = fontSize +} + //TODO func (gc *GraphicContext) Clear() { panic("not implemented") @@ -162,10 +199,6 @@ func (gc *GraphicContext) DrawImage(img image.Image) { panic("not implemented") } -func (gc *GraphicContext) FillString(text string) (cursor float64) { - return gc.FillStringAt(text, 0, 0) -} - func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) { gc.painter.SetColor(color) rasterizer.Rasterize(gc.painter) diff --git a/draw2dimg/ftgc.go b/draw2dimg/ftgc.go index e5cc9e2..470adbc 100644 --- a/draw2dimg/ftgc.go +++ b/draw2dimg/ftgc.go @@ -28,6 +28,7 @@ type GraphicContext struct { painter Painter fillRasterizer *raster.Rasterizer strokeRasterizer *raster.Rasterizer + DPI int } // ImageFilter defines the type of filter to use @@ -44,7 +45,6 @@ const ( // NewGraphicContext creates a new Graphic context from an image. func NewGraphicContext(img draw.Image) *GraphicContext { - var painter Painter switch selectImage := img.(type) { case *image.RGBA: @@ -57,6 +57,7 @@ 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 { + dpi := 92 width, height := img.Bounds().Dx(), img.Bounds().Dy() gc := &GraphicContext{ draw2dbase.NewStackGraphicContext(), @@ -64,6 +65,7 @@ func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicConte painter, raster.NewRasterizer(width, height), raster.NewRasterizer(width, height), + dpi, } return gc } @@ -99,6 +101,24 @@ func (gc *GraphicContext) DrawImage(img image.Image) { DrawImage(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter) } +// 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 { + return draw2dbase.CreateStringPath(gc, s, x, y) +} + +// GetStringBounds returns the approximate pixel bounds of the string s at x, y. +// The the left edge of the em square of the first character of s +// 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) { + return draw2dbase.GetStringBounds(gc, s) +} + // FillString draws the text at point (0, 0) func (gc *GraphicContext) FillString(text string) (cursor float64) { return gc.FillStringAt(text, 0, 0) @@ -123,6 +143,19 @@ func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor floa return width } +func (gc *GraphicContext) SetDPI(dpi int) { + gc.DPI = dpi +} + +func (gc *GraphicContext) GetDPI() int { + return gc.DPI +} + +// SetFontSize sets the font size in points (as in ``a 12 point font''). +func (gc *GraphicContext) SetFontSize(fontSize float64) { + gc.Current.FontSize = fontSize +} + func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) { gc.painter.SetColor(color) rasterizer.Rasterize(gc.painter) diff --git a/gc.go b/gc.go index 6266c16..82144a7 100644 --- a/gc.go +++ b/gc.go @@ -6,6 +6,7 @@ package draw2d import ( "image" "image/color" + "github.com/golang/freetype/truetype" ) // GraphicContext describes the interface for the various backends (images, pdf, opengl, ...) @@ -44,6 +45,14 @@ type GraphicContext interface { SetFontSize(fontSize float64) // GetFontSize gets the current font size GetFontSize() float64 + // SetScale sets the current font scale + SetScale(scale float64) + // GetScale gets the current font scale + GetScale() float64 + // SetFont sets the current truetype.Font + SetFont(font *truetype.Font) + // GetFont gets the current truetype.Font + GetFont() *truetype.Font // SetFontData sets the current FontData SetFontData(fontData FontData) // GetFontData gets the current FontData