forked from dmitmel/CCUpdaterUI
/
fontLayout.go
150 lines (142 loc) · 5.21 KB
/
fontLayout.go
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
150
package integration
import (
"github.com/20kdc/CCUpdaterUI/frenyard"
"image"
"golang.org/x/image/math/fixed"
)
// "The Annoyance" is stupid things like characters going behind their own start points.
// "Accounts for the Annoyance" is, essentially, awful workarounds HERE to keep the REST of the system sane.
// TextLayouterOptions contains the options for text layout.
type TextLayouterOptions struct {
Text TypeChunk
// SizeUnlimited should be used if an axis should be unbounded.
Limits frenyard.Vec2i
}
// TextLayouterResult contains the results from text layouting.
type TextLayouterResult struct {
Area frenyard.Area2i
Lines []TypeChunk
}
// Calculates the Area.
func (tlr *TextLayouterResult) fyCalcSize(xLimit int32) {
bounds := fixed.Rectangle26_6{}
dot := fixed.Point26_6{}
for _, v := range tlr.Lines {
_, lineBounds := v.FyCBounds(dot)
if bounds.Empty() {
bounds = lineBounds
} else {
bounds = bounds.Union(lineBounds)
}
dot = dot.Add(fixed.P(0, v.FyCHeight()))
}
tlr.Area = FontRectangleConverter(bounds)
// Accounts for the Annoyance {
if tlr.Area.X.Pos > -1 {
// This tries to keep the X.Pos reasonably constant, which stops the text shifting left/right.
tlr.Area.X.Size += tlr.Area.X.Pos + 1
tlr.Area.X.Pos = -1
}
tlr.Area.X.Size++
if tlr.Area.X.Size > xLimit {
// This is an "at any cost" solution to FORCE things to be met.
tlr.Area.X.Size = xLimit
}
// }
}
// Draw draws the laid-out text to a texture.
func (tlr *TextLayouterResult) Draw() frenyard.Texture {
img := image.NewNRGBA(image.Rect(0, 0, int(tlr.Area.X.Size), int(tlr.Area.Y.Size)))
dotFy := tlr.Area.Pos().Negate()
dot := fixed.P(int(dotFy.X), int(dotFy.Y))
for _, v := range tlr.Lines {
v.FyCDraw(img, dot)
dot = dot.Add(fixed.P(0, v.FyCHeight()))
}
return GoImageToTexture(img, []ColourTransform{})
}
func fyTextLayouterBreakerNormal(text TypeChunk, xLimit int32, wordwrap bool) TextLayouterResult {
// Accounts for the Annoyance {
xLimit--
// }
committedBuffer := TextLayouterResult{
Lines: []TypeChunk{},
}
lineBufferStart := 0
textCursor := 0
// End of line buffer is text cursor
lineBufferDot := fixed.Point26_6{}
lastSpaceComponent := -1
// Do be alerted! This retrieves runes, but slices use byte indexes.
carriageReturn := false
textLength := text.FyCComponentCount()
for textCursor < textLength {
breakStatus := text.FyCComponentBreakStatus(textCursor)
if breakStatus == TypeChunkComponentBreakStatusNewline {
// Newline, commit buffer
committedBuffer.Lines = append(committedBuffer.Lines, text.FyCSection(lineBufferStart, textCursor))
textCursor++
lineBufferStart = textCursor
lastSpaceComponent = -1
carriageReturn = true
continue
}
if carriageReturn {
lineBufferDot = fixed.Point26_6{}
advance, _ := text.FyCSection(lineBufferStart, textCursor).FyCBounds(lineBufferDot)
lineBufferDot = lineBufferDot.Add(advance)
}
// Insert character
advance := text.FyCComponentAdvance(textCursor, textCursor != lineBufferStart)
lineBufferDot = lineBufferDot.Add(fixed.Point26_6{X: advance})
if int32(lineBufferDot.X.Ceil()) >= xLimit {
// Check for cut position. Disabling wordwrap prevents these from ever being found.
if lastSpaceComponent >= 0 {
// Append the confirmed text.
committedBuffer.Lines = append(committedBuffer.Lines, text.FyCSection(lineBufferStart, lastSpaceComponent))
// Reset the line buffer to being at the " "
lineBufferStart = lastSpaceComponent
textCursor = lastSpaceComponent + 1
carriageReturn = true
lastSpaceComponent = -1
} else {
// Character break. Much simpler, doesn't even require interrupting the stream.
// If the character actually failed here we'd be doomed anyway, so place it now, too.
if lineBufferStart == textCursor - 1 && text.FyCComponentBreakStatus(lineBufferStart) == TypeChunkComponentBreakStatusSpace {
// The line buffer just contains a space.
// Draw this char and then CR immediately since it's clear there's pretty much no room.
committedBuffer.Lines = append(committedBuffer.Lines, text.FyCSection(textCursor, textCursor + 1))
textCursor++
lineBufferStart = textCursor
carriageReturn = true
// Line buffer is now empty.
} else {
committedBuffer.Lines = append(committedBuffer.Lines, text.FyCSection(lineBufferStart, textCursor))
// This includes the character in the new line buffer.
lineBufferStart = textCursor
textCursor++
carriageReturn = true
}
}
} else {
if breakStatus == TypeChunkComponentBreakStatusSpace && wordwrap {
// Successfully inserting space, let's note that
lastSpaceComponent = textCursor
}
textCursor++
}
}
if lineBufferStart != textCursor {
committedBuffer.Lines = append(committedBuffer.Lines, text.FyCSection(lineBufferStart, textCursor))
}
committedBuffer.fyCalcSize(xLimit)
return committedBuffer
}
// TheOneTextLayouterToRuleThemAll lays out text with wrapping limits and other such constraints.
func TheOneTextLayouterToRuleThemAll(opts TextLayouterOptions) TextLayouterResult {
brokenText := fyTextLayouterBreakerNormal(opts.Text, opts.Limits.X, true)
if brokenText.Area.Y.Size >= opts.Limits.Y {
brokenText = fyTextLayouterBreakerNormal(opts.Text, opts.Limits.X, false)
}
return brokenText
}