From 367aee4570689806d3bfce15ab4874eb300b6d6a Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 16 Feb 2023 01:55:00 -0500 Subject: [PATCH] Improved accuracy of TypeSetter again --- textdraw/layout.go | 25 +++++++++++-------------- textdraw/layout_test.go | 19 +++++++++++++++++++ textdraw/setter.go | 8 ++++++-- textdraw/setter_test.go | 40 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 74 insertions(+), 18 deletions(-) diff --git a/textdraw/layout.go b/textdraw/layout.go index 5ecfb81..6cc5a77 100644 --- a/textdraw/layout.go +++ b/textdraw/layout.go @@ -111,28 +111,23 @@ type LineLayout struct { // the limit is crossed. The word which would have crossed over the limit will // not be processed. func DoLine (text []rune, face font.Face, maxWidth fixed.Int26_6) (line LineLayout, remaining []rune) { - remaining = text - x := fixed.Int26_6(0) - lastRune := rune(-1) - lastWord := WordLayout { } + remaining = text + x := fixed.Int26_6(0) + lastWord := WordLayout { } + isFirstWord := true for { // process one word word, remainingFromWord := DoWord(remaining, face) - - // apply kerning and position. yeah, its unlikely that a letter - // will have kerning with a whitespace character. but like, what - // if, you know? - if lastRune >= 0 && word.FirstRune() >= 0 { - x += face.Kern(lastRune, word.FirstRune()) - } - lastRune = word.LastRune() word.X = x - x += word.Width + word.SpaceAfter + x += word.Width // if we have gone over the maximum width, stop processing // words (if maxWidth is even specified) - if maxWidth > 0 && x > maxWidth { break } + if !isFirstWord && maxWidth > 0 && x > maxWidth { + break + } + x += word.SpaceAfter remaining = remainingFromWord // if the word actually has contents, add it @@ -148,6 +143,8 @@ func DoLine (text []rune, face font.Face, maxWidth fixed.Int26_6) (line LineLayo remaining = remaining[1:] break } + + isFirstWord = false } // set the line's width. this is subject to be overridden by the diff --git a/textdraw/layout_test.go b/textdraw/layout_test.go index c5b59e8..3706db7 100644 --- a/textdraw/layout_test.go +++ b/textdraw/layout_test.go @@ -89,4 +89,23 @@ func TestDoLine (test *testing.T) { if line.BreakAfter { test.Fatalf(`did not set BreakAfter to false`) } + + // case 4 + text = []rune("jumped over the lazy dog") + line, remaining = DoLine(text, defaultfont.FaceRegular, fixed.I(10)) + + expect = "over the lazy dog" + if string(remaining) != expect { + test.Fatalf ( + `text: "%s", remaining: "%s" expected: "%s"`, + string(text), string(remaining), expect) + } + + if len(line.Words) != 1 { + test.Fatalf(`wrong word count %d`, len(line.Words)) + } + + if line.BreakAfter { + test.Fatalf(`did not set BreakAfter to false`) + } } diff --git a/textdraw/setter.go b/textdraw/setter.go index 58ede88..74cead7 100644 --- a/textdraw/setter.go +++ b/textdraw/setter.go @@ -278,20 +278,24 @@ func (setter *TypeSetter) ReccomendedHeightFor (width int) (height int) { if setter.lines == nil { return } if setter.face == nil { return } - + metrics := setter.face.Metrics() dot := fixed.Point26_6 { 0, metrics.Height } + firstWord := true for _, line := range setter.lines { for _, word := range line.Words { - if word.Width + dot.X > fixed.I(width) { + 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 } } diff --git a/textdraw/setter_test.go b/textdraw/setter_test.go index 9af8309..c971d20 100644 --- a/textdraw/setter_test.go +++ b/textdraw/setter_test.go @@ -5,6 +5,7 @@ import "golang.org/x/image/math/fixed" import "git.tebibyte.media/sashakoshka/tomo/defaultfont" func TestSetterLength (test *testing.T) { + // case 1 text := []rune("The quick brown fox\njumped over the lazy dog.") setter := TypeSetter { } setter.SetText(text) @@ -19,12 +20,24 @@ func TestSetterLength (test *testing.T) { `setter rune count: %d, expected: %d`, length, len(text) - 1) } + + // case 2 + setter.SetMaxWidth(10) + length = 0 + setter.For (func (i int, r rune, p fixed.Point26_6) bool { + length ++ + return true + }) + if length != len(text) - 1 { + test.Fatalf ( + `setter rune count: %d, expected: %d`, + length, len(text) - 1) + } } func TestSetterBounds (test *testing.T) { - text := []rune("The quick brown fox\njumped over the lazy dog.") setter := TypeSetter { } - setter.SetText(text) + setter.SetText([]rune("The quick brown fox\njumped over the lazy dog.")) setter.SetFace(defaultfont.FaceRegular) bounds := setter.LayoutBounds() @@ -41,4 +54,27 @@ func TestSetterBounds (test *testing.T) { `setter bounds Dx: %d, expected: %d`, bounds.Dx(), expectDx) } + + testLargeRecHeight(test, 256) + testLargeRecHeight(test, 100) + testLargeRecHeight(test, 20) + testLargeRecHeight(test, 400) } + +func testLargeRecHeight (test *testing.T, width int) { + setter := TypeSetter { } + setter.SetText([]rune(lipsum)) + setter.SetFace(defaultfont.FaceRegular) + setter.SetMaxWidth(width) + recHeight := setter.ReccomendedHeightFor(width) + bounds := setter.LayoutBounds() + + if recHeight != bounds.Dy() { + test.Fatalf ( + `setter bounds mismatch rec. height: %d, Dy: %d ` + + `for width: %d`, + recHeight, bounds.Dy(), width) + } +} + +const lipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Fermentum et sollicitudin ac orci phasellus egestas tellus rutrum. Aliquam vestibulum morbi blandit cursus risus at ultrices mi. Gravida dictum fusce ut placerat. Cursus metus aliquam eleifend mi in nulla posuere. Sit amet nulla facilisi morbi tempus iaculis urna id. Amet volutpat consequat mauris nunc congue nisi vitae. Varius duis at consectetur lorem donec massa sapien faucibus et. Vitae elementum curabitur vitae nunc sed velit dignissim. In hac habitasse platea dictumst quisque sagittis purus. Enim nulla aliquet porttitor lacus luctus accumsan tortor. Lectus magna fringilla urna porttitor rhoncus dolor purus non.\n\nNon pulvinar neque laoreet suspendisse. Viverra adipiscing at in tellus integer. Vulputate dignissim suspendisse in est ante. Purus in mollis nunc sed id semper. In est ante in nibh mauris cursus. Risus pretium quam vulputate dignissim suspendisse in est. Blandit aliquam etiam erat velit scelerisque in dictum. Lectus quam id leo in. Odio tempor orci dapibus ultrices in iaculis. Pharetra sit amet aliquam id. Elit ut aliquam purus sit. Egestas dui id ornare arcu odio ut sem nulla pharetra. Massa tempor nec feugiat nisl pretium fusce id. Dui accumsan sit amet nulla facilisi morbi. A lacus vestibulum sed arcu non odio euismod. Nam libero justo laoreet sit amet cursus. Mattis rhoncus urna neque viverra justo nec. Mauris augue neque gravida in fermentum et sollicitudin ac. Vulputate mi sit amet mauris. Ut sem nulla pharetra diam sit amet."