|
@@ -13,11 +13,10 @@ import (
|
|
|
"image/color"
|
|
"image/color"
|
|
|
"image/draw"
|
|
"image/draw"
|
|
|
"io/ioutil"
|
|
"io/ioutil"
|
|
|
- "math"
|
|
|
|
|
"strings"
|
|
"strings"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-// Font represents a TrueType font
|
|
|
|
|
|
|
+// Font represents a TrueType font face.
|
|
|
type Font struct {
|
|
type Font struct {
|
|
|
ttf *truetype.Font
|
|
ttf *truetype.Font
|
|
|
face font.Face
|
|
face font.Face
|
|
@@ -25,22 +24,21 @@ type Font struct {
|
|
|
bgColor math32.Color4
|
|
bgColor math32.Color4
|
|
|
fontSize float64
|
|
fontSize float64
|
|
|
fontDPI float64
|
|
fontDPI float64
|
|
|
- lineSpacing float64
|
|
|
|
|
|
|
+ lineSpacing float64 // [0,1] relative to font height
|
|
|
fg *image.Uniform
|
|
fg *image.Uniform
|
|
|
bg *image.Uniform
|
|
bg *image.Uniform
|
|
|
hinting font.Hinting
|
|
hinting font.Hinting
|
|
|
changed bool
|
|
changed bool
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Font hinting types
|
|
|
|
|
|
|
+// Font hinting types.
|
|
|
const (
|
|
const (
|
|
|
HintingNone = font.HintingNone
|
|
HintingNone = font.HintingNone
|
|
|
HintingVertical = font.HintingVertical
|
|
HintingVertical = font.HintingVertical
|
|
|
HintingFull = font.HintingFull
|
|
HintingFull = font.HintingFull
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
-// NewFont creates and returns a new font object using the specified
|
|
|
|
|
-// truetype font file
|
|
|
|
|
|
|
+// NewFont creates and returns a new font object using the specified TrueType font file.
|
|
|
func NewFont(fontfile string) (*Font, error) {
|
|
func NewFont(fontfile string) (*Font, error) {
|
|
|
|
|
|
|
|
// Reads font bytes
|
|
// Reads font bytes
|
|
@@ -51,18 +49,17 @@ func NewFont(fontfile string) (*Font, error) {
|
|
|
return NewFontFromData(fontBytes)
|
|
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 data.
|
|
|
func NewFontFromData(fontData []byte) (*Font, error) {
|
|
func NewFontFromData(fontData []byte) (*Font, error) {
|
|
|
|
|
|
|
|
// Parses the font data
|
|
// Parses the font data
|
|
|
- ff, err := truetype.Parse(fontData)
|
|
|
|
|
|
|
+ ttf, err := truetype.Parse(fontData)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
f := new(Font)
|
|
f := new(Font)
|
|
|
- f.ttf = ff
|
|
|
|
|
|
|
+ f.ttf = ttf
|
|
|
f.fontSize = 12
|
|
f.fontSize = 12
|
|
|
f.fontDPI = 72
|
|
f.fontDPI = 72
|
|
|
f.lineSpacing = 1.0
|
|
f.lineSpacing = 1.0
|
|
@@ -80,27 +77,51 @@ func NewFontFromData(fontData []byte) (*Font, error) {
|
|
|
return f, nil
|
|
return f, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// SetDPI sets the current DPI for the font
|
|
|
|
|
-func (f *Font) SetDPI(dpi float64) {
|
|
|
|
|
|
|
+// SetSize sets the size of the font.
|
|
|
|
|
+func (f *Font) SetSize(size float64) {
|
|
|
|
|
|
|
|
- if dpi == f.fontDPI {
|
|
|
|
|
|
|
+ if size == f.fontSize {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- f.fontDPI = dpi
|
|
|
|
|
|
|
+ f.fontSize = size
|
|
|
f.changed = true
|
|
f.changed = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// SetSize sets the size of the font
|
|
|
|
|
-func (f *Font) SetSize(size float64) {
|
|
|
|
|
|
|
+// Size returns the current font size.
|
|
|
|
|
+func (f *Font) Size() float64 {
|
|
|
|
|
|
|
|
- if size == f.fontSize {
|
|
|
|
|
|
|
+ return f.fontSize
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// SetDPI sets the current DPI of the font.
|
|
|
|
|
+func (f *Font) SetDPI(dpi float64) {
|
|
|
|
|
+
|
|
|
|
|
+ if dpi == f.fontDPI {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- f.fontSize = size
|
|
|
|
|
|
|
+ f.fontDPI = dpi
|
|
|
f.changed = true
|
|
f.changed = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// SetHinting sets the hinting type
|
|
|
|
|
|
|
+// 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) {
|
|
func (f *Font) SetHinting(hinting font.Hinting) {
|
|
|
|
|
|
|
|
if hinting == f.hinting {
|
|
if hinting == f.hinting {
|
|
@@ -110,96 +131,94 @@ func (f *Font) SetHinting(hinting font.Hinting) {
|
|
|
f.changed = true
|
|
f.changed = true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Size returns the current font size
|
|
|
|
|
-func (f *Font) Size() float64 {
|
|
|
|
|
|
|
+// Hinting returns the current hinting type.
|
|
|
|
|
+func (f *Font) Hinting() font.Hinting {
|
|
|
|
|
|
|
|
- return f.fontSize
|
|
|
|
|
|
|
+ return f.hinting
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// DPI returns the current font DPI
|
|
|
|
|
-func (f *Font) DPI() float64 {
|
|
|
|
|
-
|
|
|
|
|
- return f.fontDPI
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// SetFgColor sets the current foreground color of the font
|
|
|
|
|
-// The alpha value is set to 1 (opaque)
|
|
|
|
|
|
|
+// SetFgColor sets the current foreground color of the font.
|
|
|
|
|
+// The alpha value is set to 1 (opaque).
|
|
|
func (f *Font) SetFgColor(color *math32.Color) {
|
|
func (f *Font) SetFgColor(color *math32.Color) {
|
|
|
|
|
|
|
|
- f.fgColor.R = color.R
|
|
|
|
|
- f.fgColor.G = color.G
|
|
|
|
|
- f.fgColor.B = color.B
|
|
|
|
|
- f.fgColor.A = 1.0
|
|
|
|
|
|
|
+ f.fgColor.FromColor(color, 1.0)
|
|
|
f.fg = image.NewUniform(Color4RGBA(&f.fgColor))
|
|
f.fg = image.NewUniform(Color4RGBA(&f.fgColor))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// SetFgColor4 sets the current foreground color of the font
|
|
|
|
|
|
|
+// SetFgColor4 sets the current foreground color of the font.
|
|
|
func (f *Font) SetFgColor4(color *math32.Color4) {
|
|
func (f *Font) SetFgColor4(color *math32.Color4) {
|
|
|
|
|
|
|
|
f.fgColor = *color
|
|
f.fgColor = *color
|
|
|
f.fg = image.NewUniform(Color4RGBA(color))
|
|
f.fg = image.NewUniform(Color4RGBA(color))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// FgColor4 returns the current foreground color
|
|
|
|
|
|
|
+// FgColor4 returns the current foreground color.
|
|
|
func (f *Font) FgColor4() math32.Color4 {
|
|
func (f *Font) FgColor4() math32.Color4 {
|
|
|
|
|
|
|
|
return f.fgColor
|
|
return f.fgColor
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// SetBgColor sets the current foreground color of the font
|
|
|
|
|
-// The alpha value is set to 1 (opaque)
|
|
|
|
|
|
|
+// SetBgColor sets the current foreground color of the font.
|
|
|
|
|
+// The alpha value is set to 1 (opaque).
|
|
|
func (f *Font) SetBgColor(color *math32.Color) {
|
|
func (f *Font) SetBgColor(color *math32.Color) {
|
|
|
|
|
|
|
|
- f.bgColor.R = color.R
|
|
|
|
|
- f.bgColor.G = color.G
|
|
|
|
|
- f.bgColor.B = color.B
|
|
|
|
|
- f.bgColor.A = 1.0
|
|
|
|
|
|
|
+ f.bgColor.FromColor(color, 1.0)
|
|
|
f.bg = image.NewUniform(Color4RGBA(&f.fgColor))
|
|
f.bg = image.NewUniform(Color4RGBA(&f.fgColor))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// SetBgColor4 sets the current background color of the font
|
|
|
|
|
|
|
+// SetBgColor4 sets the current background color of the font.
|
|
|
func (f *Font) SetBgColor4(color *math32.Color4) {
|
|
func (f *Font) SetBgColor4(color *math32.Color4) {
|
|
|
|
|
|
|
|
f.bgColor = *color
|
|
f.bgColor = *color
|
|
|
f.bg = image.NewUniform(Color4RGBA(color))
|
|
f.bg = image.NewUniform(Color4RGBA(color))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// BgColor4 returns the current background color
|
|
|
|
|
|
|
+// BgColor4 returns the current background color.
|
|
|
func (f *Font) BgColor4() math32.Color4 {
|
|
func (f *Font) BgColor4() math32.Color4 {
|
|
|
|
|
|
|
|
return f.bgColor
|
|
return f.bgColor
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// SetLineSpacing sets the spacing between lines
|
|
|
|
|
-func (f *Font) SetLineSpacing(spacing float64) {
|
|
|
|
|
|
|
+// updateFace updates the font face if parameters have changed.
|
|
|
|
|
+func (f *Font) updateFace() {
|
|
|
|
|
|
|
|
- f.lineSpacing = spacing
|
|
|
|
|
|
|
+ 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 maximum width and height in pixels
|
|
|
|
|
|
|
+// MeasureText returns the minimum width and height in pixels
|
|
|
// necessary for an image to contain the specified text.
|
|
// necessary for an image to contain the specified text.
|
|
|
// The supplied text string can contain line break escape sequences (\n).
|
|
// The supplied text string can contain line break escape sequences (\n).
|
|
|
func (f *Font) MeasureText(text string) (int, int) {
|
|
func (f *Font) MeasureText(text string) (int, int) {
|
|
|
|
|
|
|
|
- // Creates drawer
|
|
|
|
|
|
|
+ // Create font drawer
|
|
|
f.updateFace()
|
|
f.updateFace()
|
|
|
d := &font.Drawer{Dst: nil, Src: f.fg, Face: f.face}
|
|
d := &font.Drawer{Dst: nil, Src: f.fg, Face: f.face}
|
|
|
|
|
|
|
|
// Draw text
|
|
// Draw text
|
|
|
- width := 0
|
|
|
|
|
- py := int(math.Ceil(f.fontSize * f.fontDPI / 72))
|
|
|
|
|
- dy := int(math.Ceil(f.fontSize * f.lineSpacing * f.fontDPI / 72))
|
|
|
|
|
|
|
+ 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")
|
|
lines := strings.Split(text, "\n")
|
|
|
- for _, s := range lines {
|
|
|
|
|
- d.Dot = fixed.P(0, py)
|
|
|
|
|
- lfixed := d.MeasureString(s)
|
|
|
|
|
- lw := int(lfixed >> 6)
|
|
|
|
|
- if lw > width {
|
|
|
|
|
- width = lw
|
|
|
|
|
|
|
+ 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
|
|
|
}
|
|
}
|
|
|
- py += dy
|
|
|
|
|
}
|
|
}
|
|
|
- height := py - dy/2
|
|
|
|
|
return width, height
|
|
return width, height
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -216,19 +235,6 @@ type Canvas struct {
|
|
|
bgColor *image.Uniform
|
|
bgColor *image.Uniform
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Update font face if font parameters 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
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
// NewCanvas creates and returns a pointer to a new canvas with the
|
|
// NewCanvas creates and returns a pointer to a new canvas with the
|
|
|
// specified width and height in pixels and background color
|
|
// specified width and height in pixels and background color
|
|
|
func NewCanvas(width, height int, bgColor *math32.Color4) *Canvas {
|
|
func NewCanvas(width, height int, bgColor *math32.Color4) *Canvas {
|
|
@@ -254,13 +260,18 @@ func (c Canvas) DrawText(x, y int, text string, f *Font) {
|
|
|
d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face}
|
|
d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face}
|
|
|
|
|
|
|
|
// Draw text
|
|
// Draw text
|
|
|
- py := y + int(math.Ceil(f.fontSize*f.fontDPI/72))
|
|
|
|
|
- dy := int(math.Ceil(f.fontSize * f.lineSpacing * f.fontDPI / 72))
|
|
|
|
|
|
|
+ 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")
|
|
lines := strings.Split(text, "\n")
|
|
|
- for _, s := range lines {
|
|
|
|
|
|
|
+ for i, s := range lines {
|
|
|
d.Dot = fixed.P(x, py)
|
|
d.Dot = fixed.P(x, py)
|
|
|
d.DrawString(s)
|
|
d.DrawString(s)
|
|
|
- py += dy
|
|
|
|
|
|
|
+ py += lineHeight
|
|
|
|
|
+ if i > 1 {
|
|
|
|
|
+ py += lineGap
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -274,8 +285,11 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) err
|
|
|
f.updateFace()
|
|
f.updateFace()
|
|
|
d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face}
|
|
d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face}
|
|
|
|
|
|
|
|
- py := y + int(math.Ceil(f.fontSize*f.fontDPI/72))
|
|
|
|
|
- dy := int(math.Ceil(f.fontSize * f.lineSpacing * f.fontDPI / 72))
|
|
|
|
|
|
|
+ // 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")
|
|
lines := strings.Split(text, "\n")
|
|
|
for l, s := range lines {
|
|
for l, s := range lines {
|
|
|
d.Dot = fixed.P(x, py)
|
|
d.Dot = fixed.P(x, py)
|
|
@@ -287,13 +301,18 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, line, col int) err
|
|
|
caretH := int(f.fontSize) + 2
|
|
caretH := int(f.fontSize) + 2
|
|
|
//caretY := int(pt.Y>>6) - int(f.fontSize) + 2
|
|
//caretY := int(pt.Y>>6) - int(f.fontSize) + 2
|
|
|
caretY := int(d.Dot.Y>>6) - int(f.fontSize) + 2
|
|
caretY := int(d.Dot.Y>>6) - int(f.fontSize) + 2
|
|
|
- color := Color4RGBA(&math32.Color4{0, 0, 0, 1})
|
|
|
|
|
|
|
+ fgCol := f.FgColor4()
|
|
|
|
|
+ color := Color4RGBA(&fgCol)
|
|
|
for j := caretY; j < caretY+caretH; j++ {
|
|
for j := caretY; j < caretY+caretH; j++ {
|
|
|
c.RGBA.Set(x+width, j, color)
|
|
c.RGBA.Set(x+width, j, color)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- py += dy
|
|
|
|
|
|
|
+ py += lineHeight
|
|
|
|
|
+ if l > 1 {
|
|
|
|
|
+ py += lineGap
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
// pt := freetype.Pt(font.marginX+x, font.marginY+y+int(font.ctx.PointToFixed(font.fontSize)>>6))
|
|
// pt := freetype.Pt(font.marginX+x, font.marginY+y+int(font.ctx.PointToFixed(font.fontSize)>>6))
|
|
|
// for l, s := range lines {
|
|
// for l, s := range lines {
|
|
|
// // Draw string
|
|
// // Draw string
|