From 105a963210d9bf206bb92aa4141a52c4d49e5842 Mon Sep 17 00:00:00 2001 From: redstarcoder Date: Sat, 15 Oct 2016 10:01:38 -0400 Subject: [PATCH] Copied font functions from draw2dimg to draw2dgl cursor -> width for certain functions for clarity Some panics added, some TODOs added --- draw2dgl/gc.go | 151 +++++++++++++++++++++++++++++++++++++++++++---- draw2dgl/text.go | 83 ++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 13 deletions(-) create mode 100644 draw2dgl/text.go diff --git a/draw2dgl/gc.go b/draw2dgl/gc.go index 6532838..4202914 100644 --- a/draw2dgl/gc.go +++ b/draw2dgl/gc.go @@ -1,16 +1,23 @@ 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() { @@ -118,6 +125,8 @@ 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. @@ -127,54 +136,170 @@ func NewGraphicContext(width, height int) *GraphicContext { NewPainter(), raster.NewRasterizer(width, height), raster.NewRasterizer(width, height), + &truetype.GlyphBuf{}, + 92, } return gc } +//FIXME copied from ../draw2dimg +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 +} + +//FIXME copied from ../draw2dimg +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 +} + +//FIXME copied from ../draw2dimg +// 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 { - panic("not implemented") + 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) (cursor float64) { - panic("not implemented") +func (gc *GraphicContext) FillStringAt(text string, x, y float64) (width float64) { + width = gc.CreateStringPath(text, x, y) + gc.Fill() + return width } +//FIXME copied from ../draw2dimg +// 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) { - panic("not implemented") + 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) (cursor float64) { +func (gc *GraphicContext) StrokeString(text string) (width float64) { return gc.StrokeStringAt(text, 0, 0) } -func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) { - width := gc.CreateStringPath(text, x, y) +func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (width float64) { + width = gc.CreateStringPath(text, x, y) gc.Stroke() return width } -func (gc *GraphicContext) SetDPI(dpi int) { +//FIXME copied from ../draw2dimg +// 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 -1 + return gc.DPI } +//TODO func (gc *GraphicContext) Clear() { - + panic("not implemented") } +//TODO func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) { - + panic("not implemented") } +//TODO func (gc *GraphicContext) DrawImage(img image.Image) { - + panic("not implemented") } func (gc *GraphicContext) FillString(text string) (cursor float64) { - return 0 + return gc.FillStringAt(text, 0, 0) } func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) { diff --git a/draw2dgl/text.go b/draw2dgl/text.go new file mode 100644 index 0000000..52c3902 --- /dev/null +++ b/draw2dgl/text.go @@ -0,0 +1,83 @@ +package draw2dgl +//Copied from ../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, + } +}