Add incomplete reflow stage
This commit is contained in:
parent
37554dd719
commit
9c7732c95b
191
flow.go
Normal file
191
flow.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user