154 lines
4.5 KiB
Go
154 lines
4.5 KiB
Go
|
package chart
|
||
|
|
||
|
import "strings"
|
||
|
|
||
|
// TextHorizontalAlign is an enum for the horizontal alignment options.
|
||
|
type textHorizontalAlign int
|
||
|
|
||
|
const (
|
||
|
// TextHorizontalAlignUnset is the unset state for text horizontal alignment.
|
||
|
TextHorizontalAlignUnset textHorizontalAlign = 0
|
||
|
// TextHorizontalAlignLeft aligns a string horizontally so that it's left ligature starts at horizontal pixel 0.
|
||
|
TextHorizontalAlignLeft textHorizontalAlign = 1
|
||
|
// TextHorizontalAlignCenter left aligns a string horizontally so that there are equal pixels
|
||
|
// to the left and to the right of a string within a box.
|
||
|
TextHorizontalAlignCenter textHorizontalAlign = 2
|
||
|
// TextHorizontalAlignRight right aligns a string horizontally so that the right ligature ends at the right-most pixel
|
||
|
// of a box.
|
||
|
TextHorizontalAlignRight textHorizontalAlign = 3
|
||
|
)
|
||
|
|
||
|
// TextWrap is an enum for the word wrap options.
|
||
|
type textWrap int
|
||
|
|
||
|
const (
|
||
|
// TextWrapUnset is the unset state for text wrap options.
|
||
|
TextWrapUnset textWrap = 0
|
||
|
// TextWrapNone will spill text past horizontal boundaries.
|
||
|
TextWrapNone textWrap = 1
|
||
|
// TextWrapWord will split a string on words (i.e. spaces) to fit within a horizontal boundary.
|
||
|
TextWrapWord textWrap = 2
|
||
|
// TextWrapRune will split a string on a rune (i.e. utf-8 codepage) to fit within a horizontal boundary.
|
||
|
TextWrapRune textWrap = 3
|
||
|
)
|
||
|
|
||
|
// TextVerticalAlign is an enum for the vertical alignment options.
|
||
|
type textVerticalAlign int
|
||
|
|
||
|
const (
|
||
|
// TextVerticalAlignUnset is the unset state for vertical alignment options.
|
||
|
TextVerticalAlignUnset textVerticalAlign = 0
|
||
|
// TextVerticalAlignBaseline aligns text according to the "baseline" of the string, or where a normal ascender begins.
|
||
|
TextVerticalAlignBaseline textVerticalAlign = 1
|
||
|
// TextVerticalAlignBottom aligns the text according to the lowers pixel of any of the ligatures (ex. g or q both extend below the baseline).
|
||
|
TextVerticalAlignBottom textVerticalAlign = 2
|
||
|
// TextVerticalAlignMiddle aligns the text so that there is an equal amount of space above and below the top and bottom of the ligatures.
|
||
|
TextVerticalAlignMiddle textVerticalAlign = 3
|
||
|
// TextVerticalAlignMiddleBaseline aligns the text veritcally so that there is an equal number of pixels above and below the baseline of the string.
|
||
|
TextVerticalAlignMiddleBaseline textVerticalAlign = 4
|
||
|
// TextVerticalAlignTop alignts the text so that the top of the ligatures are at y-pixel 0 in the container.
|
||
|
TextVerticalAlignTop textVerticalAlign = 5
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// Text contains utilities for text.
|
||
|
Text = &text{}
|
||
|
)
|
||
|
|
||
|
// TextStyle encapsulates text style options.
|
||
|
type TextStyle struct {
|
||
|
HorizontalAlign textHorizontalAlign
|
||
|
VerticalAlign textVerticalAlign
|
||
|
Wrap textWrap
|
||
|
}
|
||
|
|
||
|
type text struct{}
|
||
|
|
||
|
func (t text) WrapFit(r Renderer, value string, width int, style Style, wrapOption textWrap) []string {
|
||
|
valueBox := r.MeasureText(value)
|
||
|
if valueBox.Width() > width {
|
||
|
switch wrapOption {
|
||
|
case TextWrapRune:
|
||
|
return t.WrapFitRune(r, value, width, style)
|
||
|
case TextWrapWord:
|
||
|
return t.WrapFitWord(r, value, width, style)
|
||
|
}
|
||
|
}
|
||
|
return []string{value}
|
||
|
}
|
||
|
|
||
|
func (t text) WrapFitWord(r Renderer, value string, width int, style Style) []string {
|
||
|
style.WriteToRenderer(r)
|
||
|
|
||
|
var output []string
|
||
|
var line string
|
||
|
var word string
|
||
|
|
||
|
var textBox Box
|
||
|
|
||
|
for _, c := range value {
|
||
|
if c == rune('\n') { // commit the line to output
|
||
|
output = append(output, t.Trim(line+word))
|
||
|
line = ""
|
||
|
word = ""
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
textBox = r.MeasureText(line + word + string(c))
|
||
|
|
||
|
if textBox.Width() >= width {
|
||
|
output = append(output, t.Trim(line))
|
||
|
line = word
|
||
|
word = string(c)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if c == rune(' ') || c == rune('\t') {
|
||
|
line = line + word + string(c)
|
||
|
word = ""
|
||
|
continue
|
||
|
}
|
||
|
word = word + string(c)
|
||
|
}
|
||
|
|
||
|
return append(output, t.Trim(line+word))
|
||
|
}
|
||
|
|
||
|
func (t text) WrapFitRune(r Renderer, value string, width int, style Style) []string {
|
||
|
style.WriteToRenderer(r)
|
||
|
|
||
|
var output []string
|
||
|
var line string
|
||
|
var textBox Box
|
||
|
for _, c := range value {
|
||
|
if c == rune('\n') {
|
||
|
output = append(output, line)
|
||
|
line = ""
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
textBox = r.MeasureText(line + string(c))
|
||
|
|
||
|
if textBox.Width() >= width {
|
||
|
output = append(output, line)
|
||
|
line = string(c)
|
||
|
continue
|
||
|
}
|
||
|
line = line + string(c)
|
||
|
}
|
||
|
return t.appendLast(output, line)
|
||
|
}
|
||
|
|
||
|
func (t text) Trim(value string) string {
|
||
|
return strings.Trim(value, " \t\n\r")
|
||
|
}
|
||
|
|
||
|
func (t text) appendLast(lines []string, text string) []string {
|
||
|
if len(lines) == 0 {
|
||
|
return []string{text}
|
||
|
}
|
||
|
lastLine := lines[len(lines)-1]
|
||
|
lines[len(lines)-1] = lastLine + text
|
||
|
return lines
|
||
|
}
|