Back up old files
This commit is contained in:
parent
aa00b93bd3
commit
650ecf0c2e
84
old/drawer.go
Normal file
84
old/drawer.go
Normal file
@ -0,0 +1,84 @@
|
||||
package typeset
|
||||
|
||||
import "image"
|
||||
import "unicode"
|
||||
import "image/draw"
|
||||
import "image/color"
|
||||
import "golang.org/x/image/math/fixed"
|
||||
|
||||
// Drawer is an extended TypeSetter that is able to draw text. Much like
|
||||
// TypeSetter, It has no constructor and its zero value can be used safely.
|
||||
type Drawer struct { TypeSetter }
|
||||
|
||||
// Draw draws the drawer's text onto the specified canvas at the given offset.
|
||||
func (drawer Drawer) Draw (
|
||||
destination draw.Image,
|
||||
col color.Color,
|
||||
offset image.Point,
|
||||
) (
|
||||
updatedRegion image.Rectangle,
|
||||
) {
|
||||
source := image.NewUniform(col)
|
||||
|
||||
drawer.ForRunes (func (
|
||||
index int,
|
||||
char rune,
|
||||
position fixed.Point26_6,
|
||||
) bool {
|
||||
// leave empty space for space characters
|
||||
if unicode.IsSpace(char) {
|
||||
return true
|
||||
}
|
||||
|
||||
dot := fixed.P (
|
||||
offset.X + position.X.Round(),
|
||||
offset.Y + position.Y.Round())
|
||||
destinationRectangle,
|
||||
mask, maskPoint, _, ok := drawer.face.Glyph(dot, char)
|
||||
// tofu
|
||||
if !ok {
|
||||
drawer.drawTofu(char, destination, col, dot)
|
||||
return true
|
||||
}
|
||||
|
||||
// FIXME:? clip destination rectangle if we are on the cusp of
|
||||
// the maximum height.
|
||||
|
||||
draw.DrawMask (
|
||||
destination,
|
||||
destinationRectangle,
|
||||
source, image.Point { },
|
||||
mask, maskPoint,
|
||||
draw.Over)
|
||||
|
||||
updatedRegion = updatedRegion.Union(destinationRectangle)
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (drawer Drawer) drawTofu (
|
||||
char rune,
|
||||
destination draw.Image,
|
||||
col color.Color,
|
||||
position fixed.Point26_6,
|
||||
) {
|
||||
bounds, _ := tofuBounds(drawer.face)
|
||||
rectBounds := image.Rect (
|
||||
bounds.Min.X.Round(),
|
||||
bounds.Min.Y.Round(),
|
||||
bounds.Max.X.Round(),
|
||||
bounds.Max.Y.Round()).Add(image.Pt(
|
||||
position.X.Round(),
|
||||
position.Y.Round()))
|
||||
for x := rectBounds.Min.X; x < rectBounds.Max.X; x ++ {
|
||||
destination.Set(x, rectBounds.Min.Y, col)
|
||||
}
|
||||
for y := rectBounds.Min.Y; y < rectBounds.Max.Y; y ++ {
|
||||
destination.Set(rectBounds.Min.X, y, col)
|
||||
destination.Set(rectBounds.Max.X - 1, y, col)
|
||||
}
|
||||
for x := rectBounds.Min.X; x < rectBounds.Max.X; x ++ {
|
||||
destination.Set(x, rectBounds.Max.Y - 1, col)
|
||||
}
|
||||
}
|
250
old/layout.go
Normal file
250
old/layout.go
Normal file
@ -0,0 +1,250 @@
|
||||
package typeset
|
||||
|
||||
import "unicode"
|
||||
import "golang.org/x/image/font"
|
||||
import "golang.org/x/image/math/fixed"
|
||||
|
||||
// Align specifies a text alignment method.
|
||||
type Align int
|
||||
|
||||
const (
|
||||
// X | Y
|
||||
AlignStart Align = iota // left | top
|
||||
AlignMiddle // center | center
|
||||
AlignEnd // right | bottom
|
||||
AlignEven // justified | evenly spaced
|
||||
)
|
||||
|
||||
// RuneLayout contains layout information for a single rune relative to its
|
||||
// word.
|
||||
type RuneLayout struct {
|
||||
X fixed.Int26_6
|
||||
Width fixed.Int26_6
|
||||
Rune rune
|
||||
}
|
||||
|
||||
// WordLayout contains layout information for a single word relative to its
|
||||
// line.
|
||||
type WordLayout struct {
|
||||
X fixed.Int26_6
|
||||
Width fixed.Int26_6
|
||||
SpaceAfter fixed.Int26_6
|
||||
Runes []RuneLayout
|
||||
}
|
||||
|
||||
// DoWord consumes exactly one word from the given string, and produces a word
|
||||
// layout according to the given font. It returns the remaining text as well.
|
||||
func DoWord (text []rune, face font.Face) (word WordLayout, remaining []rune) {
|
||||
remaining = text
|
||||
gettingSpace := false
|
||||
x := fixed.Int26_6(0)
|
||||
lastRune := rune(-1)
|
||||
for _, char := range text {
|
||||
// if we run into a line break, we must break out immediately
|
||||
// because it is not DoWord's job to handle that.
|
||||
if char == '\n' { break }
|
||||
|
||||
// if we suddenly run into spaces, and then run into a word
|
||||
// again, we must break out immediately.
|
||||
if unicode.IsSpace(char) {
|
||||
gettingSpace = true
|
||||
} else if gettingSpace {
|
||||
break
|
||||
}
|
||||
|
||||
// apply kerning
|
||||
if lastRune >= 0 { x += face.Kern(lastRune, char) }
|
||||
lastRune = char
|
||||
|
||||
// consume and process the rune
|
||||
remaining = remaining[1:]
|
||||
advance, ok := face.GlyphAdvance(char)
|
||||
if !ok {
|
||||
advance = tofuAdvance(face)
|
||||
}
|
||||
runeLayout := RuneLayout {
|
||||
X: x,
|
||||
Width: advance,
|
||||
Rune: char,
|
||||
}
|
||||
word.Runes = append(word.Runes, runeLayout)
|
||||
|
||||
// advance
|
||||
if gettingSpace {
|
||||
word.SpaceAfter += advance
|
||||
} else {
|
||||
word.Width += advance
|
||||
}
|
||||
x += advance
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LastRune returns the last rune in the word.
|
||||
func (word WordLayout) LastRune () rune {
|
||||
if word.Runes == nil {
|
||||
return -1
|
||||
} else {
|
||||
return word.Runes[len(word.Runes) - 1].Rune
|
||||
}
|
||||
}
|
||||
|
||||
// FirstRune returns the last rune in the word.
|
||||
func (word WordLayout) FirstRune () rune {
|
||||
if word.Runes == nil {
|
||||
return -1
|
||||
} else {
|
||||
return word.Runes[0].Rune
|
||||
}
|
||||
}
|
||||
|
||||
// LineLayout contains layout information for a single line.
|
||||
type LineLayout struct {
|
||||
Y fixed.Int26_6
|
||||
Width fixed.Int26_6
|
||||
ContentWidth fixed.Int26_6
|
||||
SpaceAfter fixed.Int26_6
|
||||
Words []WordLayout
|
||||
BreakAfter bool
|
||||
}
|
||||
|
||||
// DoLine consumes exactly one line from the given string, and produces a line
|
||||
// layout according to the given font. It returns the remaining text as well. If
|
||||
// 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, width fixed.Int26_6) (line LineLayout, remaining []rune) {
|
||||
remaining = text
|
||||
x := fixed.Int26_6(0)
|
||||
isFirstWord := true
|
||||
for {
|
||||
// process one word
|
||||
word, remainingFromWord := DoWord(remaining, face)
|
||||
x += word.Width
|
||||
|
||||
// if we have gone over the preferred width, stop processing
|
||||
// words (if wrap is enabled)
|
||||
if !isFirstWord && wrap && x > width {
|
||||
break
|
||||
}
|
||||
|
||||
x += word.SpaceAfter
|
||||
remaining = remainingFromWord
|
||||
|
||||
// if the word actually has contents, add it
|
||||
if word.Runes != nil {
|
||||
line.Words = append(line.Words, word)
|
||||
}
|
||||
|
||||
// if we have hit the end of the line, stop processing words
|
||||
if len(remaining) == 0 { break }
|
||||
if remaining[0] == '\n' {
|
||||
line.BreakAfter = true
|
||||
remaining = remaining[1:]
|
||||
break
|
||||
}
|
||||
|
||||
isFirstWord = false
|
||||
}
|
||||
|
||||
// set the width of the line's content.
|
||||
line.Width = width
|
||||
// TODO: just have RecommendedHeight want aligned layout?
|
||||
if len(line.Words) > 0 {
|
||||
lastWord := line.Words[len(line.Words) - 1]
|
||||
line.ContentWidth = x - lastWord.SpaceAfter
|
||||
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, tabWidth fixed.Int26_6) {
|
||||
if len(line.Words) == 0 { return }
|
||||
|
||||
if align == AlignEven {
|
||||
line.justify(tabWidth)
|
||||
} else {
|
||||
line.contract(tabWidth)
|
||||
|
||||
var leftOffset fixed.Int26_6
|
||||
if align == AlignMiddle {
|
||||
leftOffset = (line.Width - line.ContentWidth) / 2
|
||||
} else if align == AlignEnd {
|
||||
leftOffset = line.Width - line.ContentWidth
|
||||
}
|
||||
|
||||
for index := range line.Words {
|
||||
line.Words[index].X += leftOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// assume line has content > 0
|
||||
func (line *LineLayout) contract (tabWidth fixed.Int26_6) {
|
||||
x := fixed.Int26_6(0)
|
||||
for index, word := range line.Words {
|
||||
word.X = x
|
||||
x += word.Width
|
||||
x += word.SpaceAfter
|
||||
line.Words[index] = word
|
||||
}
|
||||
lastWord := line.Words[len(line.Words) - 1]
|
||||
line.ContentWidth = lastWord.X + lastWord.Width
|
||||
line.SpaceAfter = lastWord.SpaceAfter
|
||||
}
|
||||
|
||||
// assume line has content > 0
|
||||
func (line *LineLayout) justify (tabWidth fixed.Int26_6) {
|
||||
if len(line.Words) <= 1 {
|
||||
line.Align(AlignStart, tabWidth)
|
||||
return
|
||||
}
|
||||
|
||||
// We are going to be moving the words, so we can't take SpaceAfter into
|
||||
// account.
|
||||
trueContentWidth := fixed.Int26_6(0)
|
||||
for _, word := range line.Words {
|
||||
trueContentWidth += word.Width
|
||||
}
|
||||
|
||||
spaceCount := len(line.Words) - 1
|
||||
spacePerWord := (line.Width - trueContentWidth) / fixed.Int26_6(spaceCount)
|
||||
x := fixed.Int26_6(0)
|
||||
for index, word := range line.Words {
|
||||
line.Words[index].X = x
|
||||
x += spacePerWord + word.Width
|
||||
}
|
||||
}
|
||||
|
||||
func tabStop (x, tabWidth fixed.Int26_6, delta int) fixed.Int26_6 {
|
||||
return fixed.I((tabWidth * 64 / x).Floor() + delta).Mul(tabWidth)
|
||||
}
|
||||
|
||||
func tofuAdvance (face font.Face) fixed.Int26_6 {
|
||||
if advance, ok := face.GlyphAdvance('M'); ok {
|
||||
return advance
|
||||
} else {
|
||||
return 16
|
||||
}
|
||||
}
|
||||
|
||||
func tofuBounds (face font.Face) (fixed.Rectangle26_6, fixed.Int26_6) {
|
||||
if bounds, advance, ok := face.GlyphBounds('M'); ok {
|
||||
return bounds, advance
|
||||
} else {
|
||||
return fixed.R(0, -16, 14, 0), 16
|
||||
}
|
||||
}
|
428
old/setter.go
Normal file
428
old/setter.go
Normal file
@ -0,0 +1,428 @@
|
||||
package typeset
|
||||
|
||||
import "image"
|
||||
import "golang.org/x/image/font"
|
||||
import "golang.org/x/image/math/fixed"
|
||||
|
||||
// TypeSetter manages several lines of text, and can perform layout operations
|
||||
// on them. It automatically avoids performing redundant work. It has no
|
||||
// constructor and its zero value can be used safely.
|
||||
type TypeSetter struct {
|
||||
lines []LineLayout
|
||||
text []rune
|
||||
|
||||
layoutClean bool
|
||||
alignClean bool
|
||||
|
||||
hAlign, vAlign Align
|
||||
face font.Face
|
||||
width, height int
|
||||
wrap bool
|
||||
tabWidth fixed.Int26_6
|
||||
|
||||
minWidth fixed.Int26_6
|
||||
layoutBounds image.Rectangle
|
||||
layoutBoundsSpace image.Rectangle
|
||||
}
|
||||
|
||||
func (setter *TypeSetter) needLayout () {
|
||||
if setter.layoutClean { return }
|
||||
setter.layoutClean = true
|
||||
setter.alignClean = false
|
||||
|
||||
setter.lines = nil
|
||||
setter.layoutBounds = image.Rectangle { }
|
||||
setter.layoutBoundsSpace = image.Rectangle { }
|
||||
setter.minWidth = 0
|
||||
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)
|
||||
|
||||
// function to add line and update bounds statistics
|
||||
addLine := func (line LineLayout) {
|
||||
line.Y = y
|
||||
y += metrics.Height
|
||||
if line.ContentWidth > horizontalExtent {
|
||||
horizontalExtent = line.ContentWidth
|
||||
}
|
||||
lineWidthSpace := line.ContentWidth + line.SpaceAfter
|
||||
if lineWidthSpace > horizontalExtentSpace {
|
||||
horizontalExtentSpace = lineWidthSpace
|
||||
}
|
||||
setter.lines = append(setter.lines, line)
|
||||
}
|
||||
|
||||
// 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.width))
|
||||
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 { }) }
|
||||
|
||||
// if we are wrapping text, the width must be the user-set width
|
||||
if setter.wrap {
|
||||
horizontalExtent = fixed.I(setter.width)
|
||||
horizontalExtentSpace = fixed.I(setter.width)
|
||||
}
|
||||
|
||||
// calculate layout boundaries
|
||||
setter.minWidth = horizontalExtentSpace
|
||||
setter.layoutBounds.Max.X = horizontalExtent.Round()
|
||||
setter.layoutBoundsSpace.Max.X = horizontalExtentSpace.Round()
|
||||
|
||||
y -= metrics.Height
|
||||
setter.layoutBounds.Min.Y = -metrics.Ascent.Round()
|
||||
setter.layoutBounds.Max.Y =
|
||||
y.Round() +
|
||||
metrics.Descent.Round()
|
||||
setter.layoutBoundsSpace.Min.Y = setter.layoutBounds.Min.Y
|
||||
setter.layoutBoundsSpace.Max.Y = setter.layoutBounds.Max.Y
|
||||
}
|
||||
|
||||
func (setter *TypeSetter) needAlignedLayout () {
|
||||
if setter.alignClean && setter.layoutClean { return }
|
||||
setter.needLayout()
|
||||
setter.alignClean = true
|
||||
|
||||
setter.alignHorizontally()
|
||||
setter.alignVertically()
|
||||
}
|
||||
|
||||
// should only be called from within setter.needAlignedLayout
|
||||
func (setter *TypeSetter) alignHorizontally () {
|
||||
if len(setter.lines) == 0 { return }
|
||||
|
||||
for index := range setter.lines {
|
||||
align := setter.hAlign
|
||||
|
||||
// if the horizontal align is even, align lines with breaks
|
||||
// after them to the left anyways
|
||||
if align == AlignEven {
|
||||
except :=
|
||||
index == len(setter.lines) - 1 ||
|
||||
setter.lines[index].BreakAfter
|
||||
if except { align = AlignStart }
|
||||
}
|
||||
|
||||
// align line
|
||||
setter.lines[index].Align(align, setter.tabWidth)
|
||||
}
|
||||
}
|
||||
|
||||
// should only be called from within setter.needAlignedLayout
|
||||
func (setter *TypeSetter) alignVertically () {
|
||||
if setter.height == 0 { return }
|
||||
if len(setter.lines) == 0 { return }
|
||||
if setter.vAlign == AlignEven {
|
||||
setter.justifyVertically()
|
||||
return
|
||||
}
|
||||
|
||||
// determine how much to shift lines
|
||||
topOffset := fixed.I(0)
|
||||
contentHeight := setter.layoutBoundsSpace.Dy()
|
||||
if setter.vAlign == AlignMiddle {
|
||||
topOffset += fixed.I((setter.height - contentHeight) / 2)
|
||||
} else if setter.vAlign == AlignEnd {
|
||||
topOffset += fixed.I(setter.height - contentHeight)
|
||||
}
|
||||
|
||||
// we may be re-aligning already aligned text. if the text is shifted
|
||||
// away from the origin, account for that.
|
||||
if len(setter.lines) > 0 {
|
||||
topOffset -= setter.lines[0].Y
|
||||
}
|
||||
|
||||
// shift lines
|
||||
for index := range setter.lines {
|
||||
setter.lines[index].Y += topOffset
|
||||
}
|
||||
}
|
||||
|
||||
// should only be called from within setter.alignVertically
|
||||
func (setter *TypeSetter) justifyVertically () {
|
||||
spaceCount := len(setter.lines) - 1
|
||||
contentHeight := setter.layoutBoundsSpace.Dy()
|
||||
spacePerLine :=
|
||||
fixed.Int26_6(setter.height - contentHeight) /
|
||||
fixed.Int26_6(spaceCount)
|
||||
|
||||
y := fixed.Int26_6(0)
|
||||
for index := range setter.lines {
|
||||
setter.lines[index].Y = y
|
||||
y += spacePerLine + setter.LineHeight()
|
||||
}
|
||||
}
|
||||
|
||||
// SetWrap sets whether or not the text wraps around and forms new lines.
|
||||
func (setter *TypeSetter) SetWrap (wrap bool) {
|
||||
if setter.wrap == wrap { return }
|
||||
setter.layoutClean = false
|
||||
setter.wrap = wrap
|
||||
}
|
||||
|
||||
// SetAlign sets the alignment method of the typesetter.
|
||||
func (setter *TypeSetter) SetAlign (horizontal, vertical Align) {
|
||||
if setter.hAlign == horizontal && setter.vAlign == vertical { return }
|
||||
setter.alignClean = false
|
||||
setter.hAlign = horizontal
|
||||
setter.vAlign = vertical
|
||||
}
|
||||
|
||||
// SetText sets the text content of the typesetter.
|
||||
func (setter *TypeSetter) SetText (text []rune) {
|
||||
setter.layoutClean = false
|
||||
setter.alignClean = false
|
||||
setter.text = text
|
||||
}
|
||||
|
||||
// SetFace sets the font face of the typesetter.
|
||||
func (setter *TypeSetter) SetFace (face font.Face) {
|
||||
if setter.face == face { return }
|
||||
setter.layoutClean = false
|
||||
setter.alignClean = false
|
||||
setter.face = face
|
||||
}
|
||||
|
||||
// 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) SetWidth (width int) {
|
||||
if setter.width == width { return }
|
||||
setter.layoutClean = false
|
||||
setter.alignClean = false
|
||||
setter.width = width
|
||||
}
|
||||
|
||||
// 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) SetHeight (heignt int) {
|
||||
if setter.height == heignt { return }
|
||||
setter.layoutClean = false
|
||||
setter.alignClean = false
|
||||
setter.height = heignt
|
||||
}
|
||||
|
||||
// SetTabWidth sets the distance between tab stops.
|
||||
func (setter *TypeSetter) SetTabWidth (tabWidth fixed.Int26_6) {
|
||||
if setter.tabWidth == tabWidth { return }
|
||||
setter.layoutClean = false
|
||||
setter.alignClean = false
|
||||
setter.tabWidth = tabWidth
|
||||
}
|
||||
|
||||
// Em returns the width of one emspace according to the typesetter's font, which
|
||||
// is the width of the capital letter 'M'.
|
||||
func (setter *TypeSetter) Em () (width fixed.Int26_6) {
|
||||
if setter.face == nil { return 0 }
|
||||
width, _ = setter.face.GlyphAdvance('M')
|
||||
return
|
||||
}
|
||||
|
||||
// LineHeight returns the height of one line according to the typesetter's font.
|
||||
func (setter *TypeSetter) LineHeight () fixed.Int26_6 {
|
||||
if setter.face == nil { return 0 }
|
||||
return setter.face.Metrics().Height
|
||||
}
|
||||
|
||||
// Width returns the height of the typesetter as set by SetWidth.
|
||||
func (setter *TypeSetter) Width () int {
|
||||
return setter.width
|
||||
}
|
||||
|
||||
// Height returns the height of the typesetter as set by SetHeight.
|
||||
func (setter *TypeSetter) Height () int {
|
||||
return setter.height
|
||||
}
|
||||
|
||||
// Face returns the TypeSetter's font face as set by SetFace.
|
||||
func (setter *TypeSetter) Face () font.Face {
|
||||
return setter.face
|
||||
}
|
||||
|
||||
// Length returns the amount of runes in the typesetter.
|
||||
func (setter *TypeSetter) Length () int {
|
||||
return len(setter.text)
|
||||
}
|
||||
|
||||
// RuneIterator is a function that can iterate accross a typesetter's runes.
|
||||
type RuneIterator func (
|
||||
index int,
|
||||
char rune,
|
||||
position fixed.Point26_6,
|
||||
) (
|
||||
keepGoing bool,
|
||||
)
|
||||
|
||||
// For calls the specified iterator for every rune in the typesetter. If the
|
||||
// iterator returns false, the loop will immediately stop. This method will
|
||||
// insert a fake null rune at the end.
|
||||
func (setter *TypeSetter) For (iterator RuneIterator) {
|
||||
setter.forInternal(iterator, true)
|
||||
}
|
||||
|
||||
// ForRunes is like For, but leaves out the fake null rune.
|
||||
func (setter *TypeSetter) ForRunes (iterator RuneIterator) {
|
||||
setter.forInternal(iterator, false)
|
||||
}
|
||||
|
||||
func (setter *TypeSetter) forInternal (iterator RuneIterator, fakeNull bool) {
|
||||
setter.needAlignedLayout()
|
||||
|
||||
index := 0
|
||||
lastLineY := fixed.Int26_6(0)
|
||||
lastCharRightBound := fixed.Int26_6(0)
|
||||
for _, line := range setter.lines {
|
||||
lastLineY = line.Y
|
||||
for _, word := range line.Words {
|
||||
for _, char := range word.Runes {
|
||||
lastCharRightBound = word.X + char.X + char.Width
|
||||
keepGoing := iterator(index, char.Rune, fixed.Point26_6 {
|
||||
X: word.X + char.X,
|
||||
Y: line.Y,
|
||||
})
|
||||
if !keepGoing { return }
|
||||
index ++
|
||||
}}
|
||||
|
||||
if line.BreakAfter {
|
||||
keepGoing := iterator(index, '\n', fixed.Point26_6 {
|
||||
X: lastCharRightBound,
|
||||
Y: line.Y,
|
||||
})
|
||||
if !keepGoing { return }
|
||||
index ++
|
||||
lastCharRightBound = fixed.Int26_6(0)
|
||||
}
|
||||
}
|
||||
|
||||
if fakeNull {
|
||||
keepGoing := iterator (index, '\000', fixed.Point26_6 {
|
||||
X: lastCharRightBound,
|
||||
Y: lastLineY,
|
||||
})
|
||||
if !keepGoing { return }
|
||||
index ++
|
||||
}
|
||||
}
|
||||
|
||||
// AtPosition returns the index of the rune at the specified position.
|
||||
func (setter *TypeSetter) AtPosition (position fixed.Point26_6) (index int) {
|
||||
setter.needAlignedLayout()
|
||||
|
||||
if setter.lines == nil { return }
|
||||
if setter.face == nil { return }
|
||||
|
||||
// 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()
|
||||
lastLine := setter.lines[len(setter.lines) - 1]
|
||||
for _, curLine := range setter.lines {
|
||||
if curLine.Y + metrics.Descent > position.Y {
|
||||
lastLine = curLine
|
||||
break
|
||||
}
|
||||
|
||||
index += curLine.Length()
|
||||
}
|
||||
|
||||
if lastLine.Words == nil { return }
|
||||
|
||||
// find the first rune who's right bound is greater than position.X.
|
||||
for _, curWord := range lastLine.Words {
|
||||
for _, curChar := range curWord.Runes {
|
||||
x := curWord.X + curChar.X + curChar.Width
|
||||
if x > position.X { goto foundRune }
|
||||
index ++
|
||||
}
|
||||
}
|
||||
foundRune:
|
||||
return
|
||||
}
|
||||
|
||||
// PositionAt returns the position of the rune at the specified index.
|
||||
func (setter *TypeSetter) PositionAt (index int) (position fixed.Point26_6) {
|
||||
setter.needAlignedLayout()
|
||||
|
||||
setter.For (func (i int, r rune, p fixed.Point26_6) bool {
|
||||
position = p
|
||||
return i < index
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// LayoutBounds returns the semantic bounding box of the text. The origin point
|
||||
// (0, 0) of the rectangle corresponds to the origin of the first line's
|
||||
// baseline.
|
||||
func (setter *TypeSetter) LayoutBounds () (image.Rectangle) {
|
||||
setter.needLayout()
|
||||
return setter.layoutBounds
|
||||
|
||||
}
|
||||
|
||||
// LayoutBoundsSpace is like LayoutBounds, but it also takes into account the
|
||||
// trailing whitespace at the end of each line (if it exists).
|
||||
func (setter *TypeSetter) LayoutBoundsSpace () (image.Rectangle) {
|
||||
setter.needLayout()
|
||||
return setter.layoutBoundsSpace
|
||||
}
|
||||
|
||||
// MinimumSize returns the minimum width and height needed to display text. If
|
||||
// wrapping is enabled, this method will return (Em(), 0)
|
||||
func (setter *TypeSetter) MinimumSize () image.Point {
|
||||
setter.needLayout()
|
||||
if setter.wrap {
|
||||
return image.Pt(setter.Em().Round(), 0)
|
||||
}
|
||||
|
||||
width := setter.minWidth
|
||||
height := fixed.Int26_6(len(setter.lines)) * setter.LineHeight()
|
||||
|
||||
return image.Pt(width.Round(), height.Round())
|
||||
}
|
||||
|
||||
// RecommendedHeight returns the reccomended max height if the text were to have
|
||||
// its maximum width set to the given width. This does not alter the
|
||||
// typesetter's state.
|
||||
func (setter *TypeSetter) RecommendedHeight (width int) (height int) {
|
||||
setter.needLayout()
|
||||
|
||||
if setter.lines == nil { return }
|
||||
if setter.face == nil { return }
|
||||
|
||||
metrics := setter.face.Metrics()
|
||||
dot := fixed.Point26_6 { X: 0, Y: metrics.Height }
|
||||
firstWord := true
|
||||
for _, line := range setter.lines {
|
||||
for _, word := range line.Words {
|
||||
if word.Width + dot.X > fixed.I(width) && !firstWord {
|
||||
dot.Y += metrics.Height
|
||||
dot.X = 0
|
||||
firstWord = true
|
||||
}
|
||||
dot.X += word.Width + word.SpaceAfter
|
||||
firstWord = false
|
||||
}
|
||||
if line.BreakAfter {
|
||||
dot.Y += metrics.Height
|
||||
dot.X = 0
|
||||
firstWord = true
|
||||
}
|
||||
}
|
||||
|
||||
return dot.Y.Round()
|
||||
}
|
Loading…
Reference in New Issue
Block a user