4 Commits

2 changed files with 69 additions and 47 deletions

View File

@@ -111,7 +111,7 @@ type LineLayout struct {
// wrap is set to true, this function will stop processing words once maxWidth
// is crossed. The word which would have crossed over the limit will not be
// processed.
func DoLine (text []rune, face font.Face, wrap bool, maxWidth fixed.Int26_6) (line LineLayout, remaining []rune) {
func DoLine (text []rune, face font.Face, wrap bool, width fixed.Int26_6) (line LineLayout, remaining []rune) {
remaining = text
x := fixed.Int26_6(0)
lastWord := WordLayout { }
@@ -122,9 +122,9 @@ func DoLine (text []rune, face font.Face, wrap bool, maxWidth fixed.Int26_6) (li
word.X = x
x += word.Width
// if we have gone over the maximum width, stop processing
// if we have gone over the preferred width, stop processing
// words (if wrap is enabled)
if !isFirstWord && wrap && x > maxWidth {
if !isFirstWord && wrap && x > width {
break
}
@@ -149,12 +149,27 @@ func DoLine (text []rune, face font.Face, wrap bool, maxWidth fixed.Int26_6) (li
}
// set the width of the line's content.
line.Width = maxWidth
line.ContentWidth = lastWord.X + lastWord.Width
line.SpaceAfter = lastWord.SpaceAfter
if wrap {
line.Width = width
} else {
line.Width = line.ContentWidth
}
line.SpaceAfter = lastWord.SpaceAfter
return
}
// Length returns the amount of runes within the line, including the trailing
// line break if it exists.
func (line *LineLayout) Length () int {
lineSize := 0
for _, word := range line.Words {
lineSize += len(word.Runes)
}
if line.BreakAfter { lineSize ++ }
return lineSize
}
// Align aligns the text in the line according to the specified alignment
// method.
func (line *LineLayout) Align (align Align) {

View File

@@ -30,28 +30,19 @@ func (setter *TypeSetter) needLayout () {
setter.layoutClean = true
setter.alignClean = false
// we need to have a font and some text to do anything
setter.lines = nil
setter.layoutBounds = image.Rectangle { }
setter.layoutBoundsSpace = image.Rectangle { }
setter.minWidth = 0
if len(setter.text) == 0 { return }
if setter.face == nil { return }
horizontalExtent := fixed.Int26_6(0)
horizontalExtentSpace := fixed.Int26_6(0)
metrics := setter.face.Metrics()
remaining := setter.text
y := fixed.Int26_6(0)
for len(remaining) > 0 {
// process one line
line, remainingFromLine := DoLine (
remaining, setter.face, setter.wrap,
fixed.I(setter.maxWidth))
remaining = remainingFromLine
// add the line
metrics := setter.face.Metrics()
remaining := setter.text
y := fixed.Int26_6(0)
addLine := func (line LineLayout) {
line.Y = y
y += metrics.Height
if line.ContentWidth > horizontalExtent {
@@ -63,10 +54,27 @@ func (setter *TypeSetter) needLayout () {
}
setter.lines = append(setter.lines, line)
}
setter.minWidth = horizontalExtentSpace
setter.layoutBounds.Max.X = setter.maxWidth
setter.layoutBoundsSpace.Max.X = setter.maxWidth
// process every line until there are no more remaining runes
for len(remaining) > 0 {
line, remainingFromLine := DoLine (
remaining, setter.face, setter.wrap,
fixed.I(setter.maxWidth))
remaining = remainingFromLine
addLine(line)
}
// if there were no lines processed or the last line has a break after
// it, add a blank line at the end
needBlankLine :=
len(setter.lines) == 0 ||
setter.lines[len(setter.lines) - 1].BreakAfter
if needBlankLine { addLine(LineLayout { }) }
// calculate layout boundaries
setter.minWidth = horizontalExtentSpace
setter.layoutBounds.Max.X = horizontalExtent.Round()
setter.layoutBoundsSpace.Max.X = horizontalExtentSpace.Round()
y -= metrics.Height
if setter.maxHeight == 0 {
@@ -91,8 +99,11 @@ func (setter *TypeSetter) needAlignedLayout () {
for index := range setter.lines {
align := setter.align
if index == len(setter.lines) - 1 && align == AlignJustify {
align = AlignLeft
if align == AlignJustify {
except :=
index == len(setter.lines) - 1 ||
setter.lines[index].BreakAfter
if except { align = AlignLeft }
}
setter.lines[index].Align(align)
}
@@ -127,7 +138,10 @@ func (setter *TypeSetter) SetFace (face font.Face) {
setter.face = face
}
// SetMaxWidth sets the maximum width of the typesetter.
// TODO rename to SetWidth and SetHeight
// SetWidth sets the width of the typesetter. Text will still be able
// to overflow outside of this width if wrapping is disabled.
func (setter *TypeSetter) SetMaxWidth (width int) {
if setter.maxWidth == width { return }
setter.layoutClean = false
@@ -135,9 +149,9 @@ func (setter *TypeSetter) SetMaxWidth (width int) {
setter.maxWidth = width
}
// SetMaxHeight sets the maximum height of the typesetter. If the maximum height
// is greater than zero, no lines will be laid out past that point. If the
// maximum height is zero, the text's maximum height will not be constrained.
// SetHeight sets the height of the typesetter. If the height is greater than
// zero, no lines will be laid out past it. If the height is zero, the text's
// maximum height will not be constrained.
func (setter *TypeSetter) SetMaxHeight (heignt int) {
if setter.maxHeight == heignt { return }
setter.layoutClean = false
@@ -159,14 +173,13 @@ func (setter *TypeSetter) LineHeight () fixed.Int26_6 {
return setter.face.Metrics().Height
}
// MaxWidth returns the maximum width of the typesetter as set by SetMaxWidth.
func (setter *TypeSetter) MaxWidth () int {
// Width returns the height of the typesetter as set by SetWidth.
func (setter *TypeSetter) Width () int {
return setter.maxWidth
}
// MaxHeight returns the maximum height of the typesetter as set by
// SetMaxHeight.
func (setter *TypeSetter) MaxHeight () int {
// Height returns the height of the typesetter as set by SetHeight.
func (setter *TypeSetter) Height () int {
return setter.maxHeight
}
@@ -238,27 +251,21 @@ func (setter *TypeSetter) AtPosition (position fixed.Point26_6) (index int) {
// find the first line who's bottom bound is greater than position.Y. if
// we haven't found it, then dont set the line variable (defaults to the
// last line)
metrics := setter.face.Metrics()
line := setter.lines[len(setter.lines) - 1]
lineSize := 0
for _, curLine := range setter.lines {
for _, curWord := range curLine.Words {
lineSize += len(curWord.Runes)
}
if curLine.BreakAfter { lineSize ++ }
index += lineSize
metrics := setter.face.Metrics()
lastLine := setter.lines[len(setter.lines) - 1]
for _, curLine := range setter.lines {
if curLine.Y + metrics.Descent > position.Y {
line = curLine
lastLine = curLine
break
}
index += curLine.Length()
}
index -= lineSize
if line.Words == nil { return }
if lastLine.Words == nil { return }
// find the first rune who's right bound is greater than position.X.
for _, curWord := range line.Words {
for _, curWord := range lastLine.Words {
for _, curChar := range curWord.Runes {
x := curWord.X + curChar.X + curChar.Width
if x > position.X { goto foundRune }