TestLineWrap can overflow into additional boxes
This commit is contained in:
parent
e3547f26d3
commit
6c645a5264
2 changed files with 127 additions and 52 deletions
|
@ -1,11 +1,11 @@
|
|||
package net.kemitix.text.fit;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -40,25 +40,46 @@ class TextLineWrapImpl implements WordWrapper {
|
|||
List<Word> words,
|
||||
List<Rectangle2D> boxes
|
||||
) {
|
||||
Rectangle2D rectangle2D = boxes.get(0);
|
||||
double width = rectangle2D.getWidth();
|
||||
List<String> lines = new ArrayList<>();
|
||||
int end = 0;
|
||||
List<String> line = new ArrayList<>();
|
||||
for (Word word : words) {
|
||||
if ((end + word.width) > width) {
|
||||
lines.add(String.join(" ", line));
|
||||
line.clear();
|
||||
end = 0;
|
||||
}
|
||||
line.add(word.word);
|
||||
end += word.width;
|
||||
}
|
||||
lines.add(String.join(" ", line));
|
||||
return Collections.singletonList(
|
||||
lines.stream()
|
||||
.filter(l -> l.length() > 0)
|
||||
.collect(Collectors.toList()));
|
||||
Deque<Word> wordQ = new ArrayDeque<>(words);
|
||||
return boxes.stream()
|
||||
.map(rectangle2D -> {
|
||||
double width = rectangle2D.getWidth();
|
||||
double height = rectangle2D.getHeight();
|
||||
List<String> lines = new ArrayList<>();
|
||||
int bottom = 0;
|
||||
int end = 0;
|
||||
Deque<Word> lineQ = new ArrayDeque<>();
|
||||
while (!wordQ.isEmpty()) {
|
||||
Word word = wordQ.pop();
|
||||
if ((bottom + word.height) > height) {
|
||||
wordQ.push(word);
|
||||
lineQ.forEach(wordQ::push);
|
||||
return removeBlankLines(lines);
|
||||
}
|
||||
if ((end + word.width) > width) {
|
||||
lines.add(wordsAsString((Deque<Word>) lineQ));
|
||||
lineQ.clear();
|
||||
end = 0;
|
||||
bottom += word.height;
|
||||
}
|
||||
lineQ.push(word);
|
||||
end += word.width;
|
||||
}
|
||||
lines.add(wordsAsString(lineQ));
|
||||
return removeBlankLines(lines);
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<String> removeBlankLines(List<String> lines) {
|
||||
return lines.stream()
|
||||
.filter(l -> l.length() > 0)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private String wordsAsString(Deque<Word> lineQ) {
|
||||
return lineQ.stream()
|
||||
.map(Word::getWord)
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
private List<Word> wordLengths(String[] words, Font font, Graphics2D graphics2D) {
|
||||
|
@ -69,13 +90,16 @@ class TextLineWrapImpl implements WordWrapper {
|
|||
}
|
||||
|
||||
private static class Word {
|
||||
@Getter
|
||||
private final String word;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
public Word(String word, Font font, FontRenderContext fontRenderContext) {
|
||||
this.word = word;
|
||||
Rectangle2D stringBounds = font.getStringBounds(word + " ", fontRenderContext);
|
||||
this.width = Double.valueOf(stringBounds.getWidth()).intValue();
|
||||
this.height = Double.valueOf(stringBounds.getHeight()).intValue();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.Nested;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
|
@ -35,6 +34,8 @@ public class TextLineWrapTest
|
|||
private final int spaceWidth;
|
||||
private final int wordWidth;
|
||||
private final int wordsPerLine;
|
||||
private final int wordHeight;
|
||||
private final int linesPerBox;
|
||||
|
||||
public TextLineWrapTest()
|
||||
throws FontFormatException,
|
||||
|
@ -43,15 +44,23 @@ public class TextLineWrapTest
|
|||
URL resource = this.getClass().getResource("alice/Alice-Regular.ttf");
|
||||
font = Font.createFont(Font.TRUETYPE_FONT, new File(resource.toURI()))
|
||||
.deriveFont(Font.PLAIN, fontSize);
|
||||
spaceWidth = stringWidth(SPACE, font, graphics2D);
|
||||
wordWidth = stringWidth(WORD, font, graphics2D);
|
||||
Rectangle2D spaceBounds = stringBounds(SPACE, font, graphics2D);
|
||||
Rectangle2D wordBounds = stringBounds(WORD, font, graphics2D);
|
||||
spaceWidth = (int) spaceBounds.getWidth();
|
||||
wordWidth = (int) wordBounds.getWidth();
|
||||
wordHeight = (int) wordBounds.getHeight();
|
||||
wordsPerLine = imageSize / (wordWidth + spaceWidth);
|
||||
System.out.println("wordsPerLine = " + wordsPerLine);
|
||||
linesPerBox = imageSize / wordHeight;
|
||||
System.out.println("linesPerBox = " + linesPerBox);
|
||||
}
|
||||
|
||||
private int stringWidth(String string, Font font, Graphics2D graphics2D) {
|
||||
FontRenderContext context = graphics2D.getFontRenderContext();
|
||||
Rectangle2D stringBounds = font.getStringBounds(string, context);
|
||||
return (int) stringBounds.getWidth();
|
||||
private Rectangle2D stringBounds(
|
||||
String string,
|
||||
Font font,
|
||||
Graphics2D graphics2D
|
||||
) {
|
||||
return font.getStringBounds(string, graphics2D.getFontRenderContext());
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
@ -77,24 +86,24 @@ public class TextLineWrapTest
|
|||
@Test
|
||||
@DisplayName("Fits max 'words' per line on one line")
|
||||
public void fitMaxWordsOneLine() {
|
||||
String input = words(wordsPerLine);
|
||||
String input = words(wordsPerLine, WORD);
|
||||
assertThat(invoke(input)).containsExactly(input);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Wraps just over max 'words' per line onto two lines")
|
||||
public void wrapOneWordTooManyOntoTwoLines() {
|
||||
assertThat(invoke(words(wordsPerLine + 1)))
|
||||
assertThat(invoke(words(wordsPerLine + 1, WORD)))
|
||||
.containsExactly(
|
||||
words(wordsPerLine),
|
||||
words(wordsPerLine, WORD),
|
||||
WORD);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Wraps onto three lines")
|
||||
public void longerStringOnThreeLines() {
|
||||
String oneLinesWorthOfWords = words(wordsPerLine);
|
||||
assertThat(invoke(words(wordsPerLine + wordsPerLine + 1)))
|
||||
String oneLinesWorthOfWords = words(wordsPerLine, WORD);
|
||||
assertThat(invoke(words(wordsPerLine + wordsPerLine + 1, WORD)))
|
||||
.containsExactly(
|
||||
oneLinesWorthOfWords,
|
||||
oneLinesWorthOfWords,
|
||||
|
@ -102,9 +111,9 @@ public class TextLineWrapTest
|
|||
}
|
||||
}
|
||||
|
||||
private String words(int number) {
|
||||
private String words(int number, String word) {
|
||||
return IntStream.range(0, number)
|
||||
.mapToObj(i -> WORD + SPACE)
|
||||
.mapToObj(i -> word + SPACE)
|
||||
.collect(Collectors.joining()).trim();
|
||||
}
|
||||
|
||||
|
@ -152,7 +161,7 @@ public class TextLineWrapTest
|
|||
@Test
|
||||
@DisplayName("Fits max 'words' per line on one line")
|
||||
public void fitMaxWordsOneLine() {
|
||||
String input = words(wordsPerLine);
|
||||
String input = words(wordsPerLine, WORD);
|
||||
assertThat(invoke(input))
|
||||
.containsExactly(Collections.singletonList(input));
|
||||
}
|
||||
|
@ -160,17 +169,17 @@ public class TextLineWrapTest
|
|||
@Test
|
||||
@DisplayName("Wraps just over max 'words' per line onto two lines")
|
||||
public void wrapOneWordTooManyOntoTwoLines() {
|
||||
String input = words(wordsPerLine + 1);
|
||||
String input = words(wordsPerLine + 1, WORD);
|
||||
assertThat(invoke(input)).containsExactly(Arrays.asList(
|
||||
words(wordsPerLine),
|
||||
words(wordsPerLine, WORD),
|
||||
WORD));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Wraps onto three lines")
|
||||
public void longerStringOnThreeLines() {
|
||||
String input = words(wordsPerLine + wordsPerLine + 1);
|
||||
String oneLinesWorthOfWords = words(wordsPerLine);
|
||||
String input = words(wordsPerLine + wordsPerLine + 1, WORD);
|
||||
String oneLinesWorthOfWords = words(wordsPerLine, WORD);
|
||||
assertThat(invoke(input)).containsExactly(Arrays.asList(
|
||||
oneLinesWorthOfWords,
|
||||
oneLinesWorthOfWords,
|
||||
|
@ -189,46 +198,88 @@ public class TextLineWrapTest
|
|||
boxes.add(box);
|
||||
}
|
||||
|
||||
|
||||
// Check that even with two boxes the shorted strings are unaffected
|
||||
@Test
|
||||
@DisplayName("Empty String give empty List")
|
||||
public void emptyStringEmptyList() {
|
||||
assertThat(invoke("")).isEmpty();
|
||||
assertThat(invoke("")).containsExactly(
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Short string fits on one line")
|
||||
public void shortStringOnOneLine() {
|
||||
assertThat(invoke("x"))
|
||||
.containsExactly(Collections.singletonList("x"));
|
||||
.containsExactly(
|
||||
Collections.singletonList("x"),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Fits max 'words' per line on one line")
|
||||
public void fitMaxWordsOneLine() {
|
||||
String input = words(wordsPerLine);
|
||||
String input = words(wordsPerLine, WORD);
|
||||
assertThat(invoke(input))
|
||||
.containsExactly(Collections.singletonList(input));
|
||||
.containsExactly(
|
||||
Collections.singletonList(input),
|
||||
Collections.emptyList() );
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Wraps just over max 'words' per line onto two lines")
|
||||
public void wrapOneWordTooManyOntoTwoLines() {
|
||||
String input = words(wordsPerLine + 1);
|
||||
String input = words(wordsPerLine + 1, WORD);
|
||||
assertThat(invoke(input)).containsExactly(Arrays.asList(
|
||||
words(wordsPerLine),
|
||||
WORD));
|
||||
words(wordsPerLine, WORD),
|
||||
WORD),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Wraps onto three lines")
|
||||
public void longerStringOnThreeLines() {
|
||||
String input = words(wordsPerLine + wordsPerLine + 1);
|
||||
String oneLinesWorthOfWords = words(wordsPerLine);
|
||||
String input = words(wordsPerLine + wordsPerLine + 1, WORD);
|
||||
String oneLinesWorthOfWords = words(wordsPerLine, WORD);
|
||||
assertThat(invoke(input)).containsExactly(Arrays.asList(
|
||||
oneLinesWorthOfWords,
|
||||
oneLinesWorthOfWords,
|
||||
WORD));
|
||||
WORD),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Fit max lines into the first box")
|
||||
public void fitMaxLinesInFirstBox() {
|
||||
String oneLinesWorthOfWords = words(wordsPerLine, WORD);
|
||||
String input = words(linesPerBox, oneLinesWorthOfWords);
|
||||
System.out.println("input = " + input);
|
||||
List<List<String>> result = invoke(input);
|
||||
System.out.println("result = " + result);
|
||||
List<String> linesBoxOne = result.get(0);
|
||||
assertThat(linesBoxOne)
|
||||
.hasSize(linesPerBox)
|
||||
.allSatisfy(line ->
|
||||
assertThat(line)
|
||||
.isEqualTo(oneLinesWorthOfWords));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Overflow just over max lines into the second box")
|
||||
public void overflowMaxPlusOneLinesIntoSecondBox() {
|
||||
String oneLinesWorthOfWords = words(wordsPerLine, WORD);
|
||||
String input = words(linesPerBox + 1, oneLinesWorthOfWords);
|
||||
System.out.println("input = " + input);
|
||||
List<List<String>> result = invoke(input);
|
||||
System.out.println("result = " + result);
|
||||
List<String> linesBoxOne = result.get(0);
|
||||
assertThat(linesBoxOne)
|
||||
.hasSize(linesPerBox)
|
||||
.allSatisfy(line ->
|
||||
assertThat(line)
|
||||
.isEqualTo(oneLinesWorthOfWords));
|
||||
assertThat(result.get(1))
|
||||
.containsExactly(oneLinesWorthOfWords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue