// Copyright 2016 The G3N Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package text import ( "github.com/g3n/engine/math32" "github.com/golang/freetype/truetype" "golang.org/x/image/font" "golang.org/x/image/math/fixed" "image" "image/color" "image/draw" "io/ioutil" "strings" ) // Font represents a TrueType font face. type Font struct { ttf *truetype.Font face font.Face fgColor math32.Color4 bgColor math32.Color4 fontSize float64 fontDPI float64 lineSpacing float64 // [0,1] relative to font height fg *image.Uniform bg *image.Uniform hinting font.Hinting changed bool } // Font hinting types. const ( HintingNone = font.HintingNone HintingVertical = font.HintingVertical HintingFull = font.HintingFull ) // NewFont creates and returns a new font object using the specified TrueType font file. func NewFont(fontfile string) (*Font, error) { // Reads font bytes fontBytes, err := ioutil.ReadFile(fontfile) if err != nil { return nil, err } return NewFontFromData(fontBytes) } // NewFontFromData creates and returns a new font object from the specified data. func NewFontFromData(fontData []byte) (*Font, error) { // Parses the font data ttf, err := truetype.Parse(fontData) if err != nil { return nil, err } f := new(Font) f.ttf = ttf f.fontSize = 12 f.fontDPI = 72 f.lineSpacing = 1.0 f.hinting = font.HintingNone f.SetFgColor4(&math32.Color4{0, 0, 0, 1}) f.SetBgColor4(&math32.Color4{1, 1, 1, 0}) f.changed = false // Creates font face f.face = truetype.NewFace(f.ttf, &truetype.Options{ Size: f.fontSize, DPI: f.fontDPI, Hinting: f.hinting, }) return f, nil } // SetSize sets the size of the font. func (f *Font) SetSize(size float64) { if size == f.fontSize { return } f.fontSize = size f.changed = true } // Size returns the current font size. func (f *Font) Size() float64 { return f.fontSize } // SetDPI sets the current DPI of the font. func (f *Font) SetDPI(dpi float64) { if dpi == f.fontDPI { return } f.fontDPI = dpi f.changed = true } // DPI returns the current font DPI. func (f *Font) DPI() float64 { return f.fontDPI } // SetLineSpacing sets the spacing between lines. func (f *Font) SetLineSpacing(spacing float64) { f.lineSpacing = spacing } // LineSpacing returns the current spacing between lines. func (f *Font) LineSpacing() float64 { return f.lineSpacing } // SetHinting sets the hinting type. func (f *Font) SetHinting(hinting font.Hinting) { if hinting == f.hinting { return } f.hinting = hinting f.changed = true } // Hinting returns the current hinting type. func (f *Font) Hinting() font.Hinting { return f.hinting } // SetFgColor sets the current foreground color of the font. // The alpha value is set to 1 (opaque). func (f *Font) SetFgColor(color *math32.Color) { f.fgColor.FromColor(color, 1.0) f.fg = image.NewUniform(Color4RGBA(&f.fgColor)) } // SetFgColor4 sets the current foreground color of the font. func (f *Font) SetFgColor4(color *math32.Color4) { f.fgColor = *color f.fg = image.NewUniform(Color4RGBA(color)) } // FgColor4 returns the current foreground color. func (f *Font) FgColor4() math32.Color4 { return f.fgColor } // SetBgColor sets the current foreground color of the font. // The alpha value is set to 1 (opaque). func (f *Font) SetBgColor(color *math32.Color) { f.bgColor.FromColor(color, 1.0) f.bg = image.NewUniform(Color4RGBA(&f.fgColor)) } // SetBgColor4 sets the current background color of the font. func (f *Font) SetBgColor4(color *math32.Color4) { f.bgColor = *color f.bg = image.NewUniform(Color4RGBA(color)) } // BgColor4 returns the current background color. func (f *Font) BgColor4() math32.Color4 { return f.bgColor } // updateFace updates the font face if parameters have changed. func (f *Font) updateFace() { if f.changed { f.face = truetype.NewFace(f.ttf, &truetype.Options{ Size: f.fontSize, DPI: f.fontDPI, Hinting: f.hinting, }) f.changed = false } } // MeasureText returns the minimum width and height in pixels // necessary for an image to contain the specified text. // The supplied text string can contain line break escape sequences (\n). func (f *Font) MeasureText(text string) (int, int) { // Create font drawer f.updateFace() d := &font.Drawer{Dst: nil, Src: f.fg, Face: f.face} // Draw text var width, height int metrics := f.face.Metrics() lineHeight := (metrics.Ascent + metrics.Descent).Ceil() lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight)) lines := strings.Split(text, "\n") for i, s := range lines { d.Dot = fixed.P(0, height) lineWidth := d.MeasureString(s).Ceil() if lineWidth > width { width = lineWidth } height += lineHeight if i > 1 { height += lineGap } } return width, height } // Metrics returns the font metrics func (f *Font) Metrics() font.Metrics { f.updateFace() return f.face.Metrics() } // Canvas is an image to draw text type Canvas struct { RGBA *image.RGBA bgColor *image.Uniform } // NewCanvas creates and returns a pointer to a new canvas with the // specified width and height in pixels and background color func NewCanvas(width, height int, bgColor *math32.Color4) *Canvas { c := new(Canvas) c.RGBA = image.NewRGBA(image.Rect(0, 0, width, height)) // Creates the image.Uniform for the background color c.bgColor = image.NewUniform(Color4RGBA(bgColor)) // Draw image draw.Draw(c.RGBA, c.RGBA.Bounds(), c.bgColor, image.ZP, draw.Src) return c } // DrawText draws text at the specified position (in pixels) // of this canvas, using the specified font. // The supplied text string can contain line break escape sequences (\n). func (c Canvas) DrawText(x, y int, text string, f *Font) { // Creates drawer f.updateFace() d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face} // Draw text metrics := f.face.Metrics() py := y + metrics.Ascent.Round() lineHeight := (metrics.Ascent + metrics.Descent).Ceil() lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight)) lines := strings.Split(text, "\n") for i, s := range lines { d.Dot = fixed.P(x, py) d.DrawString(s) py += lineHeight if i > 1 { py += lineGap } } } // DrawTextCaret draws text at the specified position (in pixels) // of this canvas, using the specified font, and also a caret at // the specified line and column. // The supplied text string can contain line break escape sequences (\n). func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) error { // Creates drawer f.updateFace() d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face} // Draw text metrics := f.face.Metrics() py := y + metrics.Ascent.Round() lineHeight := (metrics.Ascent + metrics.Descent).Ceil() lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight)) lines := strings.Split(text, "\n") for l, s := range lines { d.Dot = fixed.P(x, py) d.DrawString(s) // Checks for caret position if l == line && col <= StrCount(s) { width, _ := f.MeasureText(StrPrefix(s, col)) // Draw caret vertical line caretH := int(f.fontSize) + 2 //caretY := int(pt.Y>>6) - int(f.fontSize) + 2 caretY := int(d.Dot.Y>>6) - int(f.fontSize) + 2 fgCol := f.FgColor4() color := Color4RGBA(&fgCol) for j := caretY; j < caretY+caretH; j++ { c.RGBA.Set(x+width, j, color) } } py += lineHeight if l > 1 { py += lineGap } } // pt := freetype.Pt(font.marginX+x, font.marginY+y+int(font.ctx.PointToFixed(font.fontSize)>>6)) // for l, s := range lines { // // Draw string // _, err := font.ctx.DrawString(s, pt) // if err != nil { // return err // } // // Checks for caret position // if l == line && col <= StrCount(s) { // width, _, err := font.MeasureText(StrPrefix(s, col)) // if err != nil { // return err // } // // Draw caret vertical line // caretH := int(font.fontSize) + 2 // caretY := int(pt.Y>>6) - int(font.fontSize) + 2 // color := Color4RGBA(&math32.Color4{0, 0, 0, 1}) // for j := caretY; j < caretY+caretH; j++ { // c.RGBA.Set(x+width, j, color) // } // } // // Increment y coordinate // pt.Y += font.ctx.PointToFixed(font.fontSize * font.lineSpacing) // } return nil } // Color4RGBA converts a math32.Color4 to Go's color.RGBA func Color4RGBA(c *math32.Color4) color.RGBA { red := uint8(c.R * 0xFF) green := uint8(c.G * 0xFF) blue := uint8(c.B * 0xFF) al := uint8(c.A * 0xFF) return color.RGBA{red, green, blue, al} }