Compare commits
21 Commits
89c23a8947
...
4c42b12cc1
Author | SHA1 | Date | |
---|---|---|---|
4c42b12cc1 | |||
6efe40efc2 | |||
17385c4c9a | |||
e38cac8e3b | |||
90b2e49664 | |||
2ae07af710 | |||
ce21b34f86 | |||
ff8f86e034 | |||
288a1fb9ef | |||
a91816df6c | |||
aa486fe660 | |||
cde84b8756 | |||
013b121d46 | |||
85c48461c7 | |||
0342e25456 | |||
f0adca5c37 | |||
56024caaf5 | |||
5171cbac16 | |||
300c28853d | |||
6fabfd9fd0 | |||
6b6e485aca |
5
draw.go
5
draw.go
@ -50,7 +50,10 @@ func Draw (destination draw.Image, setter *TypeSetter, offset fixed.Point26_6, c
|
||||
}
|
||||
|
||||
// DrawBounds draws the LayoutBounds, MinimumSize, and LayoutBoundsSpace of a
|
||||
// TypeSetter to the given image.
|
||||
// TypeSetter to the given image using these colors:
|
||||
// - Red: LayoutBounds
|
||||
// - Green: MinimumSize
|
||||
// - Blue: LayoutBoundsSpace
|
||||
func DrawBounds (destination draw.Image, setter *TypeSetter, offset fixed.Point26_6) {
|
||||
blue := color.RGBA { B: 255, A: 255 }
|
||||
red := color.RGBA { R: 255, A: 255 }
|
||||
|
@ -94,6 +94,8 @@ func main () {
|
||||
drawText(img, &setter, image.Rect(256, 0, 512, 256).Add(image.Pt(1536, 512)).Inset(4))
|
||||
setter.SetAlign(typeset.AlignEven, typeset.AlignEnd)
|
||||
drawText(img, &setter, image.Rect( 0, 256, 256, 512).Add(image.Pt(1536, 512)).Inset(4))
|
||||
setter.SetAlign(typeset.AlignEnd, typeset.AlignMiddle)
|
||||
drawText(img, &setter, image.Rect(256, 256, 512, 512).Add(image.Pt(1536, 512)).Inset(4))
|
||||
|
||||
file, err := os.Create("output.png")
|
||||
if err != nil { panic(err) }
|
||||
@ -156,7 +158,7 @@ the sea welcomes me.
|
||||
This is a very long line that will probably get cut off.`
|
||||
|
||||
const haikuAlt = `An ocean voyage.
|
||||
As waves break over the bow,
|
||||
As waves break over the bow,
|
||||
the sea welcomes me.
|
||||
|
||||
This is a short ending`
|
||||
|
94
flow.go
94
flow.go
@ -18,27 +18,51 @@ func reflow (
|
||||
metrics := face.Metrics()
|
||||
|
||||
var dot fixed.Point26_6
|
||||
lineStart := 0
|
||||
lineEnd := 0
|
||||
lastWord := 0
|
||||
lastToken := 0
|
||||
nLines := 0
|
||||
const notSeen = -1
|
||||
firstWord := notSeen
|
||||
lineStart := 0
|
||||
lineEnd := 0
|
||||
lastWord := 0
|
||||
lastNonLineBreak := notSeen
|
||||
nLines := 0
|
||||
firstLine := true
|
||||
|
||||
newline := func () {
|
||||
newline := func (wrapped bool) {
|
||||
// if the line isn't empty
|
||||
if lineStart != lineEnd {
|
||||
// align line
|
||||
alignLine (
|
||||
tokens[lineStart:lineEnd],
|
||||
size.X, xAlign, lineEnd == len(tokens))
|
||||
|
||||
|
||||
// calculate extents
|
||||
lastWordTok := tokens[lastWord]
|
||||
lastTokenTok := tokens[lastToken]
|
||||
lineMax := lastWordTok.position.X + lastWordTok.width
|
||||
lineMaxSpace := lastTokenTok.position.X + lastTokenTok.width
|
||||
if lineMax > minimumSize.X { minimumSize.X = lineMax }
|
||||
if lineMaxSpace > extentsSpace.Max.X { extentsSpace.Max.X = lineMaxSpace }
|
||||
var lineMin, lineMinSpace, lineMax, lineMaxSpace fixed.Int26_6
|
||||
|
||||
lineMinSpace = tokens[lineStart].position.X
|
||||
if firstWord == notSeen {
|
||||
lineMin = lineMinSpace
|
||||
} else {
|
||||
lineMin = tokens[firstWord].position.X
|
||||
}
|
||||
|
||||
lastWordTok := tokens[lastWord]
|
||||
if lastWordTok.kind == tokenKindWord {
|
||||
// the line had a word in it
|
||||
lineMax = lastWordTok.position.X + lastWordTok.width
|
||||
}
|
||||
if wrapped || lastNonLineBreak == notSeen {
|
||||
lineMaxSpace = lineMax
|
||||
} else {
|
||||
lastTokenTok := tokens[lastNonLineBreak]
|
||||
lineMaxSpace = lastTokenTok.position.X + lastTokenTok.width
|
||||
}
|
||||
// println(lineMax.String(), lineMaxSpace.String())
|
||||
|
||||
if lineMin < extents.Min.X || firstLine { extents.Min.X = lineMin }
|
||||
if lineMinSpace < extentsSpace.Min.X || firstLine { extentsSpace.Min.X = lineMinSpace }
|
||||
if lineMax > extents.Max.X { extents.Max.X = lineMax }
|
||||
if lineMaxSpace > extentsSpace.Max.X { extentsSpace.Max.X = lineMaxSpace }
|
||||
firstLine = false
|
||||
}
|
||||
|
||||
// update dot
|
||||
@ -46,8 +70,10 @@ func reflow (
|
||||
dot.X = 0
|
||||
|
||||
// update indices, counts
|
||||
lineStart = lineEnd
|
||||
lastWord = lineEnd
|
||||
lineStart = lineEnd
|
||||
lastWord = lineEnd
|
||||
lastNonLineBreak = notSeen
|
||||
firstWord = notSeen
|
||||
nLines ++
|
||||
}
|
||||
|
||||
@ -57,15 +83,20 @@ func reflow (
|
||||
for index, token := range tokens {
|
||||
lineEnd = index
|
||||
updateIndices := func () {
|
||||
lastToken = index
|
||||
if token.kind != tokenKindLineBreak {
|
||||
lastNonLineBreak = index
|
||||
}
|
||||
if token.kind == tokenKindWord {
|
||||
lastWord = index
|
||||
if firstWord == notSeen {
|
||||
firstWord = index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// demarcate lines
|
||||
if sawLineBreak {
|
||||
newline()
|
||||
newline(false)
|
||||
sawLineBreak = false
|
||||
}
|
||||
if token.kind == tokenKindLineBreak {
|
||||
@ -78,7 +109,7 @@ func reflow (
|
||||
token.kind == tokenKindWord &&
|
||||
dot.X + token.width > size.X
|
||||
if needWrap {
|
||||
newline()
|
||||
newline(true)
|
||||
}
|
||||
updateIndices()
|
||||
tokens[index].position = dot
|
||||
@ -86,23 +117,40 @@ func reflow (
|
||||
}
|
||||
}
|
||||
lineEnd ++ // make lineEnd equal to len(tokens)
|
||||
newline()
|
||||
newline(false)
|
||||
minimumSize.Y = metrics.Height * fixed.Int26_6(nLines) + metrics.Descent
|
||||
|
||||
// second, vertical alignment pass
|
||||
alignLinesVertically(tokens, size.Y, minimumSize.Y, yAlign)
|
||||
|
||||
// calculate extents
|
||||
extentsOffset := fixed.Point26_6 { Y: metrics.Ascent - tokens[0].position.Y }
|
||||
extents.Max.X = minimumSize.X
|
||||
extentsVerticalOffset := fixed.Point26_6 { Y: metrics.Ascent - tokens[0].position.Y }
|
||||
extents.Max.Y = dot.Y + metrics.Descent
|
||||
extentsSpace.Max.Y = dot.Y + metrics.Descent
|
||||
extents = extents.Sub(extentsOffset)
|
||||
extentsSpace = extentsSpace.Sub(extentsOffset)
|
||||
minimumSize.X = fixedRectDx(extents)
|
||||
minimumSize.Y = fixedRectDy(extents)
|
||||
extents = extents.Sub(extentsVerticalOffset)
|
||||
extentsSpace = extentsSpace.Sub(extentsVerticalOffset)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func fixedRectDx (rect fixed.Rectangle26_6) fixed.Int26_6 {
|
||||
return rect.Max.X - rect.Min.X
|
||||
}
|
||||
|
||||
func fixedRectDy (rect fixed.Rectangle26_6) fixed.Int26_6 {
|
||||
return rect.Max.Y - rect.Min.Y
|
||||
}
|
||||
|
||||
func calculateLineExtents (
|
||||
firstWord, firstToken, lastWord, lastToken token,
|
||||
) (
|
||||
lineMin, lineMinSpace, lineMax, lineMaxSpace fixed.Int26_6,
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
func alignLinesVertically (tokens []token, height, contentHeight fixed.Int26_6, align Align) {
|
||||
if len(tokens) == 0 { return }
|
||||
if align == AlignStart { return }
|
||||
|
38
flow_test.go
Normal file
38
flow_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
package typeset
|
||||
|
||||
import "testing"
|
||||
import "golang.org/x/image/math/fixed"
|
||||
import "golang.org/x/image/font/basicfont"
|
||||
|
||||
func BenchmarkFlowLipsumLeftTop (benchmark *testing.B) {
|
||||
benchmarkFlow(benchmark, false, AlignStart, AlignStart)
|
||||
}
|
||||
|
||||
func BenchmarkFlowLipsumWrapLeftTop (benchmark *testing.B) {
|
||||
benchmarkFlow(benchmark, true, AlignStart, AlignStart)
|
||||
}
|
||||
|
||||
func BenchmarkFlowLipsumCenterTop (benchmark *testing.B) {
|
||||
benchmarkFlow(benchmark, false, AlignMiddle, AlignStart)
|
||||
}
|
||||
|
||||
func BenchmarkFlowLipsumWrapCenterTop (benchmark *testing.B) {
|
||||
benchmarkFlow(benchmark, true, AlignMiddle, AlignStart)
|
||||
}
|
||||
|
||||
func BenchmarkFlowLipsumWrapJustifyTop (benchmark *testing.B) {
|
||||
benchmarkFlow(benchmark, true, AlignEven, AlignStart)
|
||||
}
|
||||
|
||||
func benchmarkFlow (benchmark *testing.B, wrap bool, xAlign, yAlign Align) {
|
||||
_, tokens := parseString(lipsumLt)
|
||||
benchmark.ReportAllocs()
|
||||
benchmark.ResetTimer()
|
||||
for i := 0; i < benchmark.N; i ++ {
|
||||
reflow (
|
||||
tokens,
|
||||
basicfont.Face7x13,
|
||||
fixed.P(256, 256),
|
||||
wrap, xAlign, yAlign)
|
||||
}
|
||||
}
|
38
parse.go
38
parse.go
@ -5,14 +5,25 @@ import "unicode"
|
||||
// TODO perhaps follow https://unicode.org/reports/tr14/
|
||||
|
||||
func parseString (text string) ([]runeLayout, []token) {
|
||||
// TODO find an optimal size for both of these to minimize allocs. will
|
||||
// require some testing.
|
||||
runes := make([]runeLayout, 0, len(text) * 2 / 3)
|
||||
tokens := make([]token, 0, len(text) / 4)
|
||||
// alloc initial rune slice
|
||||
runes := make([]runeLayout, 0, len(text) * 2 / 3)
|
||||
|
||||
// build the rune slice
|
||||
// we need to do this before parsing into tokens, because otherwise
|
||||
// a realloc will occur in the middle of it and the tokens at the start
|
||||
// will be referencing old memory
|
||||
for _, run := range text {
|
||||
runes = append(runes, runeLayout {
|
||||
run: run,
|
||||
})
|
||||
}
|
||||
|
||||
// alloc initial token slice
|
||||
tokens := make([]token, 0, len(runes) / 5)
|
||||
|
||||
var index int
|
||||
var startingIndex int
|
||||
var run rune
|
||||
var runl runeLayout
|
||||
var lastRune rune
|
||||
|
||||
var tok token
|
||||
@ -31,19 +42,16 @@ func parseString (text string) ([]runeLayout, []token) {
|
||||
}
|
||||
}
|
||||
|
||||
for index, run = range text {
|
||||
runes = append(runes, runeLayout {
|
||||
run: run,
|
||||
})
|
||||
|
||||
// parse tokens
|
||||
for index, runl = range runes {
|
||||
switch {
|
||||
case run == '\r':
|
||||
case runl.run == '\r':
|
||||
tokenBoundary()
|
||||
// we don't know the token type yet. if next rune is a
|
||||
// \n then this is a CRLF line break. if not, this is
|
||||
// just a word.
|
||||
|
||||
case run == '\n':
|
||||
case runl.run == '\n':
|
||||
if lastRune == '\r' {
|
||||
// continue the \r to make a CRLF line break
|
||||
tok.kind = tokenKindLineBreak
|
||||
@ -52,16 +60,16 @@ func parseString (text string) ([]runeLayout, []token) {
|
||||
tok.kind = tokenKindLineBreak
|
||||
}
|
||||
|
||||
case run == '\t':
|
||||
case runl.run == '\t':
|
||||
mustBeInToken(tokenKindTab)
|
||||
|
||||
case unicode.IsSpace(run):
|
||||
case unicode.IsSpace(runl.run):
|
||||
mustBeInToken(tokenKindSpace)
|
||||
|
||||
default:
|
||||
mustBeInToken(tokenKindWord)
|
||||
}
|
||||
lastRune = run
|
||||
lastRune = runl.run
|
||||
}
|
||||
index ++ // make index equal to len([]rune(text))
|
||||
|
||||
|
102
parse_test.go
102
parse_test.go
@ -121,6 +121,104 @@ func TestParseString (test *testing.T) {
|
||||
logTokens(test, correctTokens)
|
||||
test.FailNow()
|
||||
}
|
||||
// TODO: ensure runeLayout slices in the tokens reference the same
|
||||
// memory as the complete runes slice
|
||||
test.Logf("changing first rune from %c to x", runes[0].run)
|
||||
runes[0].run = 'x'
|
||||
test.Logf("first rune is now %c", runes[0].run)
|
||||
tokenRune := tokens[0].runes[0].run
|
||||
if tokenRune != 'x' {
|
||||
test.Fatalf (
|
||||
"tokens does not reference the same memory as runes after changing runes: %c, %c",
|
||||
runes[0].run, tokenRune)
|
||||
}
|
||||
runeIndex := 0
|
||||
for tokenIndex, token := range tokens {
|
||||
tokenRunePtr := &token.runes[0]
|
||||
runePtr := &runes[runeIndex]
|
||||
|
||||
if runePtr != tokenRunePtr {
|
||||
test.Fatalf (
|
||||
"tokens[%d] does not reference runes[%d]: %p, %p",
|
||||
tokenIndex, runeIndex, tokenRunePtr, runePtr)
|
||||
}
|
||||
|
||||
runeIndex += len(token.runes)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParseStringLatin (benchmark *testing.B) {
|
||||
benchmark.ReportAllocs()
|
||||
var rmeanLen, rmeanCap int
|
||||
var tmeanLen, tmeanCap int
|
||||
for i := 0; i < benchmark.N; i ++ {
|
||||
runes, tokens := parseString(lipsumLt)
|
||||
rmeanLen += len(runes)
|
||||
rmeanCap += cap(runes)
|
||||
tmeanLen += len(tokens)
|
||||
tmeanCap += cap(tokens)
|
||||
}
|
||||
rmeanLen /= benchmark.N
|
||||
rmeanCap /= benchmark.N
|
||||
tmeanLen /= benchmark.N
|
||||
tmeanCap /= benchmark.N
|
||||
benchmark.ReportMetric(float64(rmeanCap) / float64(rmeanLen), "rune-waste")
|
||||
benchmark.ReportMetric(float64(tmeanCap) / float64(tmeanLen), "token-waste")
|
||||
}
|
||||
|
||||
func BenchmarkParseStringChinese (benchmark *testing.B) {
|
||||
benchmark.ReportAllocs()
|
||||
var rmeanLen, rmeanCap int
|
||||
var tmeanLen, tmeanCap int
|
||||
for i := 0; i < benchmark.N; i ++ {
|
||||
runes, tokens := parseString(lipsumCn)
|
||||
rmeanLen += len(runes)
|
||||
rmeanCap += cap(runes)
|
||||
tmeanLen += len(tokens)
|
||||
tmeanCap += cap(tokens)
|
||||
}
|
||||
rmeanLen /= benchmark.N
|
||||
rmeanCap /= benchmark.N
|
||||
tmeanLen /= benchmark.N
|
||||
tmeanCap /= benchmark.N
|
||||
benchmark.ReportMetric(float64(rmeanCap) / float64(rmeanLen), "rune-waste")
|
||||
benchmark.ReportMetric(float64(tmeanCap) / float64(tmeanLen), "token-waste")
|
||||
}
|
||||
|
||||
const lipsumLt =
|
||||
`Voluptatem impedit id id facilis et. Sit eligendi aspernatur dicta vitae ipsa officia enim harum. Occaecati quod harum quos temporibus officiis provident enim neque. Odio totam ducimus commodi quis minima ea.
|
||||
|
||||
Ut delectus quis a rem consectetur laudantium hic sequi. Vel sunt neque nisi excepturi id sit id ut. Dolores expedita et odio. Quibusdam sed et quam nostrum. Sed perspiciatis voluptatibus et.
|
||||
|
||||
Omnis qui tempore corrupti alias ut repellendus est. A officiis molestias perspiciatis ut dolores nihil. Ut officiis hic quo aut aut dolorum. Modi at molestiae praesentium ea eveniet aut porro.
|
||||
|
||||
Similique facere cum amet nesciunt dolorem nemo. Rerum temporibus iure maiores. Facere quam nihil quia debitis nihil est officia aliquam. Magnam aut alias consectetur. Velit cumque eligendi assumenda magni ratione. Est dolorem modi a unde.
|
||||
|
||||
Illo reprehenderit est sunt quaerat cum nihil non. Quia nihil placeat qui ex hic molestiae eligendi. Asperiores optio et nobis et.`
|
||||
|
||||
const lipsumCn =
|
||||
`这很容易并且妨碍快乐。让我们很难为这些人选择上述生活的职责。他们被这样的事实蒙蔽了双眼:他们现在不提供办公室。我讨厌我们给小孩子们带来所有的好处。
|
||||
|
||||
作为被选中的人,他将跟随这里的赞美。或者除非他们被排除在外,否则他们不是。意想不到的痛苦和仇恨。但对某些人来说,以及我们自己。但让我们看看其中的乐趣和
|
||||
|
||||
当时所有腐败的人都必须被击退。办公室的麻烦被视为无痛。至于这里的服务无论是哪里还是让人心疼。但目前的麻烦将会发生或持续下去。
|
||||
|
||||
当没有人知道其中的痛苦时,也要做同样的事情。事物的时代确实更加伟大。无所作为,因为你不欠任何东西,这是一些责任。这将是伟大的或其他方面。无论他选择什么,他都必须非常小心。有一种痛从何而来。
|
||||
|
||||
他责怪他们在什么都没有的情况下才问。因为没有人喜欢从这里选择麻烦。对我们来说,这是一个更艰难的选择。
|
||||
|
||||
这很容易并且妨碍快乐。让我们很难为这些人选择上述生活的职责。他们被这样的事实蒙蔽了双眼:他们现在不提供办公室。我讨厌我们给小孩子们带来所有的好处。
|
||||
|
||||
作为被选中的人,他将跟随这里的赞美。或者除非他们被排除在外,否则他们不是。意想不到的痛苦和仇恨。但对某些人来说,以及我们自己。但让我们看看其中的乐趣和
|
||||
|
||||
当时所有腐败的人都必须被击退。办公室的麻烦被视为无痛。至于这里的服务无论是哪里还是让人心疼。但目前的麻烦将会发生或持续下去。
|
||||
|
||||
当没有人知道其中的痛苦时,也要做同样的事情。事物的时代确实更加伟大。无所作为,因为你不欠任何东西,这是一些责任。这将是伟大的或其他方面。无论他选择什么,他都必须非常小心。有一种痛从何而来。
|
||||
|
||||
他责怪他们在什么都没有的情况下才问。因为没有人喜欢从这里选择麻烦。对我们来说,这是一个更艰难的选择。
|
||||
|
||||
这很容易并且妨碍快乐。让我们很难为这些人选择上述生活的职责。他们被这样的事实蒙蔽了双眼:他们现在不提供办公室。我讨厌我们给小孩子们带来所有的好处。
|
||||
|
||||
作为被选中的人,他将跟随这里的赞美。或者除非他们被排除在外,否则他们不是。意想不到的痛苦和仇恨。但对某些人来说,以及我们自己。但让我们看看其中的乐趣和
|
||||
|
||||
当时所有腐败的人都必须被击退。办公室的麻烦被视为无痛。至于这里的服务无论是哪里还是让人心疼。但目前的麻烦将会发生或持续下去。
|
||||
|
||||
当没有人知道其中的痛苦时,也要做同样的事情。事物的`
|
||||
|
@ -47,6 +47,7 @@ func (tok token) String () string {
|
||||
tok.position.X, tok.position.Y, tok.width)
|
||||
}
|
||||
|
||||
// TODO: perhaps rename this to just "glyph"
|
||||
type runeLayout struct {
|
||||
x fixed.Int26_6
|
||||
run rune
|
||||
@ -88,13 +89,13 @@ type TypeSetter struct {
|
||||
layoutBoundsSpace fixed.Rectangle26_6
|
||||
}
|
||||
|
||||
// Runes returns an iterator for all runes in the TypeSetter, and thier positions.
|
||||
// Runes returns an iterator for all runes in the TypeSetter, and their positions.
|
||||
func (this *TypeSetter) Runes () RuneIter {
|
||||
this.needFlow()
|
||||
return func (yield func (fixed.Point26_6, rune) bool) {
|
||||
for _, token := range this.tokens {
|
||||
for _, runl := range token.runes {
|
||||
pos := token.position
|
||||
for _, tok := range this.tokens {
|
||||
for _, runl := range tok.runes {
|
||||
pos := tok.position
|
||||
pos.X += runl.x
|
||||
if !yield(pos, runl.run) { return }
|
||||
}
|
||||
@ -102,6 +103,27 @@ func (this *TypeSetter) Runes () RuneIter {
|
||||
}
|
||||
}
|
||||
|
||||
// RunesWithNull returns an iterator for all runes in the TypeSetter, plus an
|
||||
// additional null rune at the end. This is useful for calculating the positions
|
||||
// of things.
|
||||
func (this *TypeSetter) RunesWithNull () RuneIter {
|
||||
this.needFlow()
|
||||
return func (yield func (fixed.Point26_6, rune) bool) {
|
||||
var tok token
|
||||
for _, tok = range this.tokens {
|
||||
for _, runl := range tok.runes {
|
||||
pos := tok.position
|
||||
pos.X += runl.x
|
||||
if !yield(pos, runl.run) { return }
|
||||
}
|
||||
}
|
||||
|
||||
pos := tok.position
|
||||
pos.X += tok.width
|
||||
yield(pos, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Em returns the width of one emspace according to the typesetter's typeface,
|
||||
// which is the width of the capital letter 'M'.
|
||||
func (this *TypeSetter) Em () fixed.Int26_6 {
|
||||
@ -110,12 +132,9 @@ func (this *TypeSetter) Em () fixed.Int26_6 {
|
||||
return width
|
||||
}
|
||||
|
||||
// MinimumSize returns the minimum width and height needed to display text. If
|
||||
// wrapping is enabled, this method will return { X: Em(), Y: 0 }.
|
||||
func (this *TypeSetter) MinimumSize () fixed.Point26_6 {
|
||||
if this.wrap { return fixed.Point26_6{ X: this.Em(), Y: 0 } }
|
||||
this.needFlow()
|
||||
return this.minimumSize
|
||||
// Face returns the font face as set by SetFace.
|
||||
func (this *TypeSetter) Face () font.Face {
|
||||
return this.face
|
||||
}
|
||||
|
||||
// LayoutBounds returns the semantic bounding box of the text. The origin point
|
||||
@ -133,11 +152,19 @@ func (this *TypeSetter) LayoutBoundsSpace () fixed.Rectangle26_6 {
|
||||
return this.layoutBoundsSpace
|
||||
}
|
||||
|
||||
// MinimumSize returns the minimum width and height needed to display text. If
|
||||
// wrapping is enabled, this method will return { X: Em(), Y: 0 }.
|
||||
func (this *TypeSetter) MinimumSize () fixed.Point26_6 {
|
||||
if this.wrap { return fixed.Point26_6{ X: this.Em(), Y: 0 } }
|
||||
this.needFlow()
|
||||
return this.minimumSize
|
||||
}
|
||||
|
||||
// PositionAt returns the position of the rune at the specified index.
|
||||
func (this *TypeSetter) PositionAt (index int) fixed.Point26_6 {
|
||||
idx := 0
|
||||
var position fixed.Point26_6
|
||||
this.Runes()(func (pos fixed.Point26_6, run rune) bool {
|
||||
this.RunesWithNull()(func (pos fixed.Point26_6, run rune) bool {
|
||||
if index == idx {
|
||||
position = pos
|
||||
return false
|
||||
@ -148,11 +175,19 @@ func (this *TypeSetter) PositionAt (index int) fixed.Point26_6 {
|
||||
return position
|
||||
}
|
||||
|
||||
// SetText sets the text of the TypeSetter.
|
||||
func (this *TypeSetter) SetText (text string) {
|
||||
if this.text == text { return }
|
||||
this.text = text
|
||||
this.invalidate(validationLevelTokens)
|
||||
// SetAlign sets the horizontal and vertical alignment of the text.
|
||||
func (this *TypeSetter) SetAlign (x, y Align) {
|
||||
if this.xAlign == x && this.yAlign == y { return }
|
||||
this.xAlign = x
|
||||
this.yAlign = y
|
||||
this.invalidate(validationLevelFlow)
|
||||
}
|
||||
|
||||
// SetFace sets the font face the text will be laid out according to.
|
||||
func (this *TypeSetter) SetFace (face font.Face) {
|
||||
if this.face == face { return }
|
||||
this.face = face
|
||||
this.invalidate(validationLevelMeasurement)
|
||||
}
|
||||
|
||||
// SetSize sets the width and height of the TypeSetter.
|
||||
@ -162,6 +197,13 @@ func (this *TypeSetter) SetSize (size fixed.Point26_6) {
|
||||
this.invalidate(validationLevelFlow)
|
||||
}
|
||||
|
||||
// SetText sets the text of the TypeSetter.
|
||||
func (this *TypeSetter) SetText (text string) {
|
||||
if this.text == text { return }
|
||||
this.text = text
|
||||
this.invalidate(validationLevelTokens)
|
||||
}
|
||||
|
||||
// SetWrap sets whether the text will wrap to the width specified by SetSize.
|
||||
func (this *TypeSetter) SetWrap (wrap bool) {
|
||||
if this.wrap == wrap { return }
|
||||
@ -169,26 +211,6 @@ func (this *TypeSetter) SetWrap (wrap bool) {
|
||||
this.invalidate(validationLevelFlow)
|
||||
}
|
||||
|
||||
// SetAlign sets the horizontal and vertical alignment of the text.
|
||||
func (this *TypeSetter) SetAlign (x, y Align) {
|
||||
if this.xAlign == x && this.yAlign == y { return }
|
||||
this.xAlign = x
|
||||
this.yAlign = y
|
||||
this.invalidate(validationLevelFlow)
|
||||
}
|
||||
|
||||
// Face returns the font face as set by SetFace.
|
||||
func (this *TypeSetter) Face () font.Face {
|
||||
return this.face
|
||||
}
|
||||
|
||||
// SetFace sets the font face the text will be laid out according to.
|
||||
func (this *TypeSetter) SetFace (face font.Face) {
|
||||
if this.face == face { return }
|
||||
this.face = face
|
||||
this.invalidate(validationLevelMeasurement)
|
||||
}
|
||||
|
||||
func (this *TypeSetter) needTokens () {
|
||||
if this.valid(validationLevelTokens) { return }
|
||||
this.runes, this.tokens = parseString(this.text)
|
||||
|
Loading…
Reference in New Issue
Block a user