package card
import (
"strings"
"unicode"
"unicode/utf8"
)
// breakable reports whether a line may break before r when it follows prev:
// between any two CJK characters, except before closing punctuation and
// small kana, which must not start a line.
func breakable(prev, r rune) bool {
if strings.ContainsRune("、。,.・ー」』))!!??ぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮ", r) {
return false
}
return isWide(prev) || isWide(r)
}
func isWide(r rune) bool {
return unicode.In(r, unicode.Han, unicode.Hiragana, unicode.Katakana) ||
(r >= 0x3000 && r <= 0x303f) || (r >= 0xff00 && r <= 0xffef)
}
// wrap breaks s into lines no wider than max (logical px) at spaces and CJK
// boundaries, splitting overlong words anywhere.
func (l *layout) wrap(s string, size, max float64) []string {
var lines []string
for s != "" {
n := l.fit(s, size, max)
lines = append(lines, strings.TrimRight(s[:n], " "))
s = strings.TrimLeft(s[n:], " ")
}
return lines
}
// fit returns the byte length of the longest prefix of s that fits in max and
// ends at a break opportunity; at least one rune.
func (l *layout) fit(s string, size, max float64) int {
f := l.face(size)
var w float64
lastBreak := 0
prev := rune(0)
for i, r := range s {
if i > 0 && (r == ' ' || prev == ' ' || prev == '/' || breakable(prev, r)) {
lastBreak = i
}
w += l.dev(f.glyph(r, 0).adv)
if w > max && i > 0 {
if lastBreak > 0 {
return lastBreak
}
return i
}
prev = r
}
return len(s)
}
// ellipsize trims s with "…" so it fits in max.
func (l *layout) ellipsize(s string, size, max float64) string {
if l.width(s, size) <= max {
return s
}
ell := l.width("…", size)
n := l.fit(s, size, max-ell)
for n > 0 && l.width(s[:n], size)+ell > max {
_, sz := utf8.DecodeLastRuneInString(s[:n])
n -= sz
}
return strings.TrimRight(s[:n], " ") + "…"
}