Add incomplete reflow stage

This commit is contained in:
Sasha Koshka 2024-09-10 15:52:18 -04:00
parent 37554dd719
commit 9c7732c95b

191
flow.go Normal file
View File

@ -0,0 +1,191 @@
package typeset
import "golang.org/x/image/font"
import "golang.org/x/image/math/fixed"
// TODO perhaps follow https://unicode.org/reports/tr14/
func reflow (
tokens []token,
face font.Face, size fixed.Point26_6,
wrap bool, xAlign, yAlign Align,
) (
extents, extentsSpace fixed.Rectangle26_6,
minimumSize fixed.Point26_6,
) {
if len(tokens) == 0 { return }
metrics := face.Metrics()
var dot fixed.Point26_6
lineStart := 0
lineEnd := 0
lastWord := 0
lastToken := 0
nLines := 0
newline := func () {
// 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 }
}
// update dot
dot.Y += metrics.Height
dot.X = 0
// update indices, counts
lineStart = lineEnd
lastWord = lineEnd
nLines ++
}
// for each line, arrange and align while calculating effective
// bounds/extents
sawLineBreak := false
for index, token := range tokens {
lineEnd = index
updateIndices := func () {
lastToken = index
if token.kind == tokenKindWord {
lastWord = index
}
}
// demarcate lines
if sawLineBreak {
newline()
sawLineBreak = false
}
if token.kind == tokenKindLineBreak {
updateIndices()
tokens[index].position = dot
sawLineBreak = true
} else {
needWrap :=
wrap &&
token.kind == tokenKindWord &&
dot.X + token.width > size.X
if needWrap {
newline()
}
updateIndices()
tokens[index].position = dot
dot.X += token.width
}
}
lineEnd ++ // make lineEnd equal to len(tokens)
newline()
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
extents.Max.Y = dot.Y + metrics.Descent
extentsSpace.Max.Y = dot.Y + metrics.Descent
extents = extents.Sub(extentsOffset)
extentsSpace = extentsSpace.Sub(extentsOffset)
return
}
func alignLinesVertically (tokens []token, height, contentHeight fixed.Int26_6, align Align) {
if len(tokens) == 0 { return }
if align == AlignStart { return }
var topOffset fixed.Int26_6
switch align {
case AlignMiddle: topOffset = (height - contentHeight) / 2
case AlignEnd, AlignEven: topOffset = height - contentHeight
}
for index := range tokens {
tokens[index].position.Y += topOffset
}
}
func alignLine (tokens []token, width fixed.Int26_6, align Align, atEnd bool) {
if len(tokens) == 0 { return }
if align == AlignStart { return }
if align == AlignEven {
alignLineJustify(tokens, width, atEnd)
return
}
var leftOffset fixed.Int26_6
contentWidth := lineContentWidth(tokens)
switch align {
case AlignMiddle: leftOffset = (width - contentWidth) / 2
case AlignEnd: leftOffset = width - contentWidth
}
for index := range tokens {
tokens[index].position.X += leftOffset
}
}
func alignLineJustify (tokens []token, width fixed.Int26_6, atEnd bool) {
cantJustify :=
len(tokens) < 2 ||
atEnd ||
tokens[len(tokens) - 1].kind == tokenKindLineBreak
if cantJustify {
alignLine(tokens, width, AlignStart, atEnd)
return
}
contentWidth, wordCount := lineContentWordWidth(tokens)
spaceCount := wordCount - 1
if spaceCount == 0 { return }
spacePerWord := (width - contentWidth) / fixed.Int26_6(spaceCount)
var x fixed.Int26_6
for index, token := range tokens {
if token.kind == tokenKindWord {
tokens[index].position.X = x
x += spacePerWord + token.width
} else {
tokens[index].position.X = x
}
}
}
func lineContentWordWidth (tokens []token) (fixed.Int26_6, int) {
var width fixed.Int26_6
var count int
for _, token := range tokens {
if token.kind == tokenKindWord {
width += token.width
count ++
}
}
return width, count
}
func lineContentWidth (tokens []token) fixed.Int26_6 {
var width, spaceWidth fixed.Int26_6
for _, token := range tokens {
if token.kind == tokenKindWord {
width += spaceWidth + token.width
spaceWidth = 0
} else {
spaceWidth = token.width
}
}
return width
}