| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- // 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 (
- "image"
- "image/color"
- "image/draw"
- "io/ioutil"
- "strings"
- "github.com/g3n/engine/math32"
- "github.com/golang/freetype/truetype"
- "golang.org/x/image/font"
- "golang.org/x/image/math/fixed"
- )
- // Font represents a TrueType font face.
- // Attributes must be set prior to drawing.
- type Font struct {
- ttf *truetype.Font // The TrueType font
- face font.Face // The font face
- attrib FontAttributes // Internal attribute cache
- fg *image.Uniform // Text color cache
- bg *image.Uniform // Background color cache
- changed bool // Whether attributes have changed and the font face needs to be recreated
- }
- // FontAttributes contains tunable attributes of a font.
- type FontAttributes struct {
- PointSize float64 // Point size of the font
- DPI float64 // Resolution of the font in dots per inch
- LineSpacing float64 // Spacing between lines (in terms of font height)
- Hinting font.Hinting // Font hinting
- }
- // 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(ttfFile string) (*Font, error) {
- // Reads font bytes
- fontBytes, err := ioutil.ReadFile(ttfFile)
- if err != nil {
- return nil, err
- }
- return NewFontFromData(fontBytes)
- }
- // NewFontFromData creates and returns a new font object from the specified TTF 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
- // Initialize with default values
- f.attrib = FontAttributes{}
- f.attrib.PointSize = 12
- f.attrib.DPI = 72
- f.attrib.LineSpacing = 1.0
- f.attrib.Hinting = font.HintingNone
- f.SetColor(&math32.Color4{0, 0, 0, 1})
- // Create font face
- f.face = truetype.NewFace(f.ttf, &truetype.Options{
- Size: f.attrib.PointSize,
- DPI: f.attrib.DPI,
- Hinting: f.attrib.Hinting,
- })
- return f, nil
- }
- // SetPointSize sets the point size of the font.
- func (f *Font) SetPointSize(size float64) {
- if size == f.attrib.PointSize {
- return
- }
- f.attrib.PointSize = size
- f.changed = true
- }
- // SetDPI sets the resolution of the font in dots per inches (DPI).
- func (f *Font) SetDPI(dpi float64) {
- if dpi == f.attrib.DPI {
- return
- }
- f.attrib.DPI = dpi
- f.changed = true
- }
- // SetLineSpacing sets the amount of spacing between lines (in terms of font height).
- func (f *Font) SetLineSpacing(spacing float64) {
- if spacing == f.attrib.LineSpacing {
- return
- }
- f.attrib.LineSpacing = spacing
- f.changed = true
- }
- // SetHinting sets the hinting type.
- func (f *Font) SetHinting(hinting font.Hinting) {
- if hinting == f.attrib.Hinting {
- return
- }
- f.attrib.Hinting = hinting
- f.changed = true
- }
- // SetFgColor sets the text color.
- func (f *Font) SetFgColor(color *math32.Color4) {
- f.fg = image.NewUniform(Color4RGBA(color))
- }
- // SetBgColor sets the background color.
- func (f *Font) SetBgColor(color *math32.Color4) {
- f.bg = image.NewUniform(Color4RGBA(color))
- }
- // SetColor sets the text color to the specified value and makes the background color transparent.
- // Note that for perfect transparency in the anti-aliased region it's important that the RGB components
- // of the text and background colors match. This method handles that for the user.
- func (f *Font) SetColor(fg *math32.Color4) {
- f.fg = image.NewUniform(Color4RGBA(fg))
- f.bg = image.NewUniform(Color4RGBA(&math32.Color4{fg.R, fg.G, fg.B, 0}))
- }
- // SetAttributes sets the font attributes.
- func (f *Font) SetAttributes(fa *FontAttributes) {
- f.SetPointSize(fa.PointSize)
- f.SetDPI(fa.DPI)
- f.SetLineSpacing(fa.LineSpacing)
- f.SetHinting(fa.Hinting)
- }
- // 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.attrib.PointSize,
- DPI: f.attrib.DPI,
- Hinting: f.attrib.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.attrib.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()
- }
- // DrawText draws the specified text on a new, tightly fitting image, and returns a pointer to the image.
- func (f *Font) DrawText(text string) *image.RGBA {
- width, height := f.MeasureText(text)
- img := image.NewRGBA(image.Rect(0, 0, width, height))
- draw.Draw(img, img.Bounds(), f.bg, image.ZP, draw.Src)
- f.DrawTextOnImage(text, 0, 0, img)
- return img
- }
- // DrawTextOnImage draws the specified text on the specified image at the specified coordinates.
- func (f *Font) DrawTextOnImage(text string, x, y int, dst *image.RGBA) {
- f.updateFace()
- d := &font.Drawer{Dst: dst, 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.attrib.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
- }
- }
- }
- // Canvas is an image to draw on.
- 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) {
- f.DrawTextOnImage(text, x, y, c.RGBA)
- }
- // 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).
- // TODO Implement caret as a gui.Panel in gui.Edit
- func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, drawCaret bool, line, col, selStart, selEnd 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.attrib.LineSpacing - float64(1)) * float64(lineHeight))
- lines := strings.Split(text, "\n")
- for l, s := range lines {
- d.Dot = fixed.P(x, py)
- if selStart != selEnd && l == line && selEnd <= StrCount(s) {
- width, _ := f.MeasureText(StrPrefix(s, selStart))
- widthEnd, _ := f.MeasureText(StrPrefix(s, selEnd))
- // Draw selection caret
- // TODO This will not work when the selection spans multiple lines
- // Currently there is no multiline edit text
- // Once there is, this needs to change
- caretH := int(f.attrib.PointSize) + 2
- caretY := int(d.Dot.Y>>6) - int(f.attrib.PointSize) + 2
- color := Color4RGBA(&math32.Color4{0, 0, 1, 0.5}) // Hardcoded to blue, alpha 50%
- for w := width; w < widthEnd; w++ {
- for j := caretY; j < caretY+caretH; j++ {
- c.RGBA.Set(x+w, j, color)
- }
- }
- }
- d.DrawString(s)
- // Checks for caret position
- if drawCaret && l == line && col <= StrCount(s) {
- width, _ := f.MeasureText(StrPrefix(s, col))
- // Draw caret vertical line
- caretH := int(f.attrib.PointSize) + 2
- caretY := int(d.Dot.Y>>6) - int(f.attrib.PointSize) + 2
- color := Color4RGBA(&math32.Color4{0, 0, 0, 1}) // Hardcoded to black
- for j := caretY; j < caretY+caretH; j++ {
- c.RGBA.Set(x+width, j, color)
- }
- }
- py += lineHeight
- if l > 1 {
- py += lineGap
- }
- }
- // TODO remove ?
- // pt := freetype.Pt(font.marginX+x, font.marginY+y+int(font.ctx.PointToFixed(font.attrib.PointSize)>>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.PointSize) + 2
- // caretY := int(pt.Y>>6) - int(font.PointSize) + 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.PointSize * 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)
- alpha := uint8(c.A * 0xFF)
- return color.RGBA{red, green, blue, alpha}
- }
|