1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
package text
import (
"fmt"
"strconv"
"strings"
"unicode/utf8"
)
// Align denotes how text is to be aligned horizontally.
type Align int
// Align enumerations
const (
AlignDefault Align = iota // same as AlignLeft
AlignLeft // "left "
AlignCenter // " center "
AlignJustify // "justify it"
AlignRight // " right"
AlignAuto // AlignRight for numbers, AlignLeft for the rest
)
// Apply aligns the text as directed. For ex.:
// - AlignDefault.Apply("Jon Snow", 12) returns "Jon Snow "
// - AlignLeft.Apply("Jon Snow", 12) returns "Jon Snow "
// - AlignCenter.Apply("Jon Snow", 12) returns " Jon Snow "
// - AlignJustify.Apply("Jon Snow", 12) returns "Jon Snow"
// - AlignRight.Apply("Jon Snow", 12) returns " Jon Snow"
// - AlignAuto.Apply("Jon Snow", 12) returns "Jon Snow "
func (a Align) Apply(text string, maxLength int) string {
aComputed := a
if aComputed == AlignAuto {
_, err := strconv.ParseFloat(text, 64)
if err == nil { // was able to parse a number out of the string
aComputed = AlignRight
} else {
aComputed = AlignLeft
}
}
text = aComputed.trimString(text)
sLen := utf8.RuneCountInString(text)
sLenWoE := RuneWidthWithoutEscSequences(text)
numEscChars := sLen - sLenWoE
// now, align the text
switch aComputed {
case AlignDefault, AlignLeft:
return fmt.Sprintf("%-"+strconv.Itoa(maxLength+numEscChars)+"s", text)
case AlignCenter:
if sLenWoE < maxLength {
// left pad with half the number of spaces needed before using %text
return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s",
text+strings.Repeat(" ", (maxLength-sLenWoE)/2))
}
case AlignJustify:
return justifyText(text, sLenWoE, maxLength)
}
return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s", text)
}
// HTMLProperty returns the equivalent HTML horizontal-align tag property.
func (a Align) HTMLProperty() string {
switch a {
case AlignLeft:
return "align=\"left\""
case AlignCenter:
return "align=\"center\""
case AlignJustify:
return "align=\"justify\""
case AlignRight:
return "align=\"right\""
default:
return ""
}
}
// MarkdownProperty returns the equivalent Markdown horizontal-align separator.
func (a Align) MarkdownProperty() string {
switch a {
case AlignLeft:
return ":--- "
case AlignCenter:
return ":---:"
case AlignRight:
return " ---:"
default:
return " --- "
}
}
func (a Align) trimString(text string) string {
switch a {
case AlignDefault, AlignLeft:
if strings.HasSuffix(text, " ") {
return strings.TrimRight(text, " ")
}
case AlignRight:
if strings.HasPrefix(text, " ") {
return strings.TrimLeft(text, " ")
}
default:
if strings.HasPrefix(text, " ") || strings.HasSuffix(text, " ") {
return strings.Trim(text, " ")
}
}
return text
}
func justifyText(text string, textLength int, maxLength int) string {
// split the text into individual words
words := Filter(strings.Split(text, " "), func(item string) bool {
return item != ""
})
// empty string implies result is just spaces for maxLength
if len(words) == 0 {
return strings.Repeat(" ", maxLength)
}
// get the number of spaces to insert into the text
numSpacesNeeded := maxLength - textLength + strings.Count(text, " ")
numSpacesNeededBetweenWords := 0
if len(words) > 1 {
numSpacesNeededBetweenWords = numSpacesNeeded / (len(words) - 1)
}
// create the output string word by word with spaces in between
var outText strings.Builder
outText.Grow(maxLength)
for idx, word := range words {
if idx > 0 {
// insert spaces only after the first word
if idx == len(words)-1 {
// insert all the remaining space before the last word
outText.WriteString(strings.Repeat(" ", numSpacesNeeded))
numSpacesNeeded = 0
} else {
// insert the determined number of spaces between each word
outText.WriteString(strings.Repeat(" ", numSpacesNeededBetweenWords))
// and reduce the number of spaces needed after this
numSpacesNeeded -= numSpacesNeededBetweenWords
}
}
outText.WriteString(word)
if idx == len(words)-1 && numSpacesNeeded > 0 {
outText.WriteString(strings.Repeat(" ", numSpacesNeeded))
}
}
return outText.String()
}
|