|
|
@@ -17,21 +17,25 @@ import (
|
|
|
)
|
|
|
|
|
|
// Font represents a TrueType font face.
|
|
|
+// Attributes must be set prior to drawing.
|
|
|
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
|
|
|
+ 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
|
|
|
}
|
|
|
|
|
|
-// Font hinting types.
|
|
|
+// 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
|
|
|
@@ -39,17 +43,17 @@ const (
|
|
|
)
|
|
|
|
|
|
// NewFont creates and returns a new font object using the specified TrueType font file.
|
|
|
-func NewFont(fontfile string) (*Font, error) {
|
|
|
+func NewFont(ttfFile string) (*Font, error) {
|
|
|
|
|
|
// Reads font bytes
|
|
|
- fontBytes, err := ioutil.ReadFile(fontfile)
|
|
|
+ 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 data.
|
|
|
+// NewFontFromData creates and returns a new font object from the specified TTF data.
|
|
|
func NewFontFromData(fontData []byte) (*Font, error) {
|
|
|
|
|
|
// Parses the font data
|
|
|
@@ -60,123 +64,93 @@ func NewFontFromData(fontData []byte) (*Font, error) {
|
|
|
|
|
|
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
|
|
|
+
|
|
|
+ // 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.fontSize,
|
|
|
- DPI: f.fontDPI,
|
|
|
- Hinting: f.hinting,
|
|
|
+ Size: f.attrib.PointSize,
|
|
|
+ DPI: f.attrib.DPI,
|
|
|
+ Hinting: f.attrib.Hinting,
|
|
|
})
|
|
|
+
|
|
|
return f, nil
|
|
|
}
|
|
|
|
|
|
-// SetSize sets the size of the font.
|
|
|
-func (f *Font) SetSize(size float64) {
|
|
|
+// SetPointSize sets the point size of the font.
|
|
|
+func (f *Font) SetPointSize(size float64) {
|
|
|
|
|
|
- if size == f.fontSize {
|
|
|
+ if size == f.attrib.PointSize {
|
|
|
return
|
|
|
}
|
|
|
- f.fontSize = size
|
|
|
+ f.attrib.PointSize = 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.
|
|
|
+// SetDPI sets the resolution of the font in dots per inches (DPI).
|
|
|
func (f *Font) SetDPI(dpi float64) {
|
|
|
|
|
|
- if dpi == f.fontDPI {
|
|
|
+ if dpi == f.attrib.DPI {
|
|
|
return
|
|
|
}
|
|
|
- f.fontDPI = dpi
|
|
|
+ f.attrib.DPI = dpi
|
|
|
f.changed = true
|
|
|
}
|
|
|
|
|
|
-// DPI returns the current font DPI.
|
|
|
-func (f *Font) DPI() float64 {
|
|
|
-
|
|
|
- return f.fontDPI
|
|
|
-}
|
|
|
-
|
|
|
-// SetLineSpacing sets the spacing between lines.
|
|
|
+// SetLineSpacing sets the amount of spacing between lines (in terms of font height).
|
|
|
func (f *Font) SetLineSpacing(spacing float64) {
|
|
|
|
|
|
- f.lineSpacing = spacing
|
|
|
-}
|
|
|
-
|
|
|
-// LineSpacing returns the current spacing between lines.
|
|
|
-func (f *Font) LineSpacing() float64 {
|
|
|
-
|
|
|
- return f.lineSpacing
|
|
|
+ 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.hinting {
|
|
|
+ if hinting == f.attrib.Hinting {
|
|
|
return
|
|
|
}
|
|
|
- f.hinting = hinting
|
|
|
+ f.attrib.Hinting = hinting
|
|
|
f.changed = true
|
|
|
}
|
|
|
|
|
|
-// Hinting returns the current hinting type.
|
|
|
-func (f *Font) Hinting() font.Hinting {
|
|
|
+// SetFgColor sets the text color.
|
|
|
+func (f *Font) SetFgColor(color *math32.Color4) {
|
|
|
|
|
|
- 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) {
|
|
|
+// SetBgColor sets the background color.
|
|
|
+func (f *Font) SetBgColor(color *math32.Color4) {
|
|
|
|
|
|
- f.bgColor.FromColor(color, 1.0)
|
|
|
- f.bg = image.NewUniform(Color4RGBA(&f.fgColor))
|
|
|
+ f.bg = image.NewUniform(Color4RGBA(color))
|
|
|
}
|
|
|
|
|
|
-// SetBgColor4 sets the current background color of the font.
|
|
|
-func (f *Font) SetBgColor4(color *math32.Color4) {
|
|
|
+// 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.bgColor = *color
|
|
|
- f.bg = image.NewUniform(Color4RGBA(color))
|
|
|
+ f.fg = image.NewUniform(Color4RGBA(fg))
|
|
|
+ f.bg = image.NewUniform(Color4RGBA(&math32.Color4{fg.R, fg.G, fg.B, 0}))
|
|
|
}
|
|
|
|
|
|
-// BgColor4 returns the current background color.
|
|
|
-func (f *Font) BgColor4() math32.Color4 {
|
|
|
+// SetAttributes sets the font attributes.
|
|
|
+func (f *Font) SetAttributes(fa *FontAttributes) {
|
|
|
|
|
|
- return f.bgColor
|
|
|
+ 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.
|
|
|
@@ -184,17 +158,16 @@ func (f *Font) updateFace() {
|
|
|
|
|
|
if f.changed {
|
|
|
f.face = truetype.NewFace(f.ttf, &truetype.Options{
|
|
|
- Size: f.fontSize,
|
|
|
- DPI: f.fontDPI,
|
|
|
- Hinting: f.hinting,
|
|
|
+ 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).
|
|
|
+// 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
|
|
|
@@ -205,7 +178,7 @@ func (f *Font) MeasureText(text string) (int, int) {
|
|
|
var width, height int
|
|
|
metrics := f.face.Metrics()
|
|
|
lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
|
|
|
- lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight))
|
|
|
+ lineGap := int((f.attrib.LineSpacing - float64(1)) * float64(lineHeight))
|
|
|
|
|
|
lines := strings.Split(text, "\n")
|
|
|
for i, s := range lines {
|
|
|
@@ -222,14 +195,47 @@ func (f *Font) MeasureText(text string) (int, int) {
|
|
|
return width, height
|
|
|
}
|
|
|
|
|
|
-// Metrics returns the font metrics
|
|
|
+// Metrics returns the font metrics.
|
|
|
func (f *Font) Metrics() font.Metrics {
|
|
|
|
|
|
f.updateFace()
|
|
|
return f.face.Metrics()
|
|
|
}
|
|
|
|
|
|
-// Canvas is an image to draw text
|
|
|
+// 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
|
|
|
@@ -255,30 +261,14 @@ func NewCanvas(width, height int, bgColor *math32.Color4) *Canvas {
|
|
|
// 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
|
|
|
- }
|
|
|
- }
|
|
|
+ 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, line, col int) error {
|
|
|
|
|
|
// Creates drawer
|
|
|
@@ -289,7 +279,7 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) err
|
|
|
metrics := f.face.Metrics()
|
|
|
py := y + metrics.Ascent.Round()
|
|
|
lineHeight := (metrics.Ascent + metrics.Descent).Ceil()
|
|
|
- lineGap := int((f.lineSpacing - float64(1)) * float64(lineHeight))
|
|
|
+ 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)
|
|
|
@@ -298,11 +288,9 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) err
|
|
|
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)
|
|
|
+ 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)
|
|
|
}
|
|
|
@@ -313,7 +301,8 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) err
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // pt := freetype.Pt(font.marginX+x, font.marginY+y+int(font.ctx.PointToFixed(font.fontSize)>>6))
|
|
|
+ // 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)
|
|
|
@@ -327,25 +316,25 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) err
|
|
|
// return err
|
|
|
// }
|
|
|
// // Draw caret vertical line
|
|
|
- // caretH := int(font.fontSize) + 2
|
|
|
- // caretY := int(pt.Y>>6) - int(font.fontSize) + 2
|
|
|
+ // 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.fontSize * font.lineSpacing)
|
|
|
+ // pt.Y += font.ctx.PointToFixed(font.PointSize * font.LineSpacing)
|
|
|
// }
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// Color4RGBA converts a math32.Color4 to Go's color.RGBA
|
|
|
+// 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}
|
|
|
+ alpha := uint8(c.A * 0xFF)
|
|
|
+ return color.RGBA{red, green, blue, alpha}
|
|
|
}
|