TestLineWrap can overflow into additional boxes

This commit is contained in:
Paul Campbell 2020-05-20 23:12:39 +01:00
parent e3547f26d3
commit 6c645a5264
2 changed files with 127 additions and 52 deletions

View file

@ -1,11 +1,11 @@
package net.kemitix.text.fit; package net.kemitix.text.fit;
import lombok.Getter;
import java.awt.*; import java.awt.*;
import java.awt.font.FontRenderContext; import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -40,25 +40,46 @@ class TextLineWrapImpl implements WordWrapper {
List<Word> words, List<Word> words,
List<Rectangle2D> boxes List<Rectangle2D> boxes
) { ) {
Rectangle2D rectangle2D = boxes.get(0); Deque<Word> wordQ = new ArrayDeque<>(words);
return boxes.stream()
.map(rectangle2D -> {
double width = rectangle2D.getWidth(); double width = rectangle2D.getWidth();
double height = rectangle2D.getHeight();
List<String> lines = new ArrayList<>(); List<String> lines = new ArrayList<>();
int bottom = 0;
int end = 0; int end = 0;
List<String> line = new ArrayList<>(); Deque<Word> lineQ = new ArrayDeque<>();
for (Word word : words) { while (!wordQ.isEmpty()) {
if ((end + word.width) > width) { Word word = wordQ.pop();
lines.add(String.join(" ", line)); if ((bottom + word.height) > height) {
line.clear(); wordQ.push(word);
end = 0; lineQ.forEach(wordQ::push);
return removeBlankLines(lines);
} }
line.add(word.word); if ((end + word.width) > width) {
lines.add(wordsAsString((Deque<Word>) lineQ));
lineQ.clear();
end = 0;
bottom += word.height;
}
lineQ.push(word);
end += word.width; end += word.width;
} }
lines.add(String.join(" ", line)); lines.add(wordsAsString(lineQ));
return Collections.singletonList( return removeBlankLines(lines);
lines.stream() }).collect(Collectors.toList());
}
private List<String> removeBlankLines(List<String> lines) {
return lines.stream()
.filter(l -> l.length() > 0) .filter(l -> l.length() > 0)
.collect(Collectors.toList())); .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) { private List<Word> wordLengths(String[] words, Font font, Graphics2D graphics2D) {
@ -69,13 +90,16 @@ class TextLineWrapImpl implements WordWrapper {
} }
private static class Word { private static class Word {
@Getter
private final String word; private final String word;
private final int width; private final int width;
private final int height;
public Word(String word, Font font, FontRenderContext fontRenderContext) { public Word(String word, Font font, FontRenderContext fontRenderContext) {
this.word = word; this.word = word;
Rectangle2D stringBounds = font.getStringBounds(word + " ", fontRenderContext); Rectangle2D stringBounds = font.getStringBounds(word + " ", fontRenderContext);
this.width = Double.valueOf(stringBounds.getWidth()).intValue(); this.width = Double.valueOf(stringBounds.getWidth()).intValue();
this.height = Double.valueOf(stringBounds.getHeight()).intValue();
} }
} }

View file

@ -7,7 +7,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
import java.awt.*; import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
@ -35,6 +34,8 @@ public class TextLineWrapTest
private final int spaceWidth; private final int spaceWidth;
private final int wordWidth; private final int wordWidth;
private final int wordsPerLine; private final int wordsPerLine;
private final int wordHeight;
private final int linesPerBox;
public TextLineWrapTest() public TextLineWrapTest()
throws FontFormatException, throws FontFormatException,
@ -43,15 +44,23 @@ public class TextLineWrapTest
URL resource = this.getClass().getResource("alice/Alice-Regular.ttf"); URL resource = this.getClass().getResource("alice/Alice-Regular.ttf");
font = Font.createFont(Font.TRUETYPE_FONT, new File(resource.toURI())) font = Font.createFont(Font.TRUETYPE_FONT, new File(resource.toURI()))
.deriveFont(Font.PLAIN, fontSize); .deriveFont(Font.PLAIN, fontSize);
spaceWidth = stringWidth(SPACE, font, graphics2D); Rectangle2D spaceBounds = stringBounds(SPACE, font, graphics2D);
wordWidth = stringWidth(WORD, font, graphics2D); Rectangle2D wordBounds = stringBounds(WORD, font, graphics2D);
spaceWidth = (int) spaceBounds.getWidth();
wordWidth = (int) wordBounds.getWidth();
wordHeight = (int) wordBounds.getHeight();
wordsPerLine = imageSize / (wordWidth + spaceWidth); 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) { private Rectangle2D stringBounds(
FontRenderContext context = graphics2D.getFontRenderContext(); String string,
Rectangle2D stringBounds = font.getStringBounds(string, context); Font font,
return (int) stringBounds.getWidth(); Graphics2D graphics2D
) {
return font.getStringBounds(string, graphics2D.getFontRenderContext());
} }
@Nested @Nested
@ -77,24 +86,24 @@ public class TextLineWrapTest
@Test @Test
@DisplayName("Fits max 'words' per line on one line") @DisplayName("Fits max 'words' per line on one line")
public void fitMaxWordsOneLine() { public void fitMaxWordsOneLine() {
String input = words(wordsPerLine); String input = words(wordsPerLine, WORD);
assertThat(invoke(input)).containsExactly(input); assertThat(invoke(input)).containsExactly(input);
} }
@Test @Test
@DisplayName("Wraps just over max 'words' per line onto two lines") @DisplayName("Wraps just over max 'words' per line onto two lines")
public void wrapOneWordTooManyOntoTwoLines() { public void wrapOneWordTooManyOntoTwoLines() {
assertThat(invoke(words(wordsPerLine + 1))) assertThat(invoke(words(wordsPerLine + 1, WORD)))
.containsExactly( .containsExactly(
words(wordsPerLine), words(wordsPerLine, WORD),
WORD); WORD);
} }
@Test @Test
@DisplayName("Wraps onto three lines") @DisplayName("Wraps onto three lines")
public void longerStringOnThreeLines() { public void longerStringOnThreeLines() {
String oneLinesWorthOfWords = words(wordsPerLine); String oneLinesWorthOfWords = words(wordsPerLine, WORD);
assertThat(invoke(words(wordsPerLine + wordsPerLine + 1))) assertThat(invoke(words(wordsPerLine + wordsPerLine + 1, WORD)))
.containsExactly( .containsExactly(
oneLinesWorthOfWords, oneLinesWorthOfWords,
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) return IntStream.range(0, number)
.mapToObj(i -> WORD + SPACE) .mapToObj(i -> word + SPACE)
.collect(Collectors.joining()).trim(); .collect(Collectors.joining()).trim();
} }
@ -152,7 +161,7 @@ public class TextLineWrapTest
@Test @Test
@DisplayName("Fits max 'words' per line on one line") @DisplayName("Fits max 'words' per line on one line")
public void fitMaxWordsOneLine() { public void fitMaxWordsOneLine() {
String input = words(wordsPerLine); String input = words(wordsPerLine, WORD);
assertThat(invoke(input)) assertThat(invoke(input))
.containsExactly(Collections.singletonList(input)); .containsExactly(Collections.singletonList(input));
} }
@ -160,17 +169,17 @@ public class TextLineWrapTest
@Test @Test
@DisplayName("Wraps just over max 'words' per line onto two lines") @DisplayName("Wraps just over max 'words' per line onto two lines")
public void wrapOneWordTooManyOntoTwoLines() { public void wrapOneWordTooManyOntoTwoLines() {
String input = words(wordsPerLine + 1); String input = words(wordsPerLine + 1, WORD);
assertThat(invoke(input)).containsExactly(Arrays.asList( assertThat(invoke(input)).containsExactly(Arrays.asList(
words(wordsPerLine), words(wordsPerLine, WORD),
WORD)); WORD));
} }
@Test @Test
@DisplayName("Wraps onto three lines") @DisplayName("Wraps onto three lines")
public void longerStringOnThreeLines() { public void longerStringOnThreeLines() {
String input = words(wordsPerLine + wordsPerLine + 1); String input = words(wordsPerLine + wordsPerLine + 1, WORD);
String oneLinesWorthOfWords = words(wordsPerLine); String oneLinesWorthOfWords = words(wordsPerLine, WORD);
assertThat(invoke(input)).containsExactly(Arrays.asList( assertThat(invoke(input)).containsExactly(Arrays.asList(
oneLinesWorthOfWords, oneLinesWorthOfWords,
oneLinesWorthOfWords, oneLinesWorthOfWords,
@ -189,46 +198,88 @@ public class TextLineWrapTest
boxes.add(box); boxes.add(box);
} }
// Check that even with two boxes the shorted strings are unaffected
@Test @Test
@DisplayName("Empty String give empty List") @DisplayName("Empty String give empty List")
public void emptyStringEmptyList() { public void emptyStringEmptyList() {
assertThat(invoke("")).isEmpty(); assertThat(invoke("")).containsExactly(
Collections.emptyList(),
Collections.emptyList());
} }
@Test @Test
@DisplayName("Short string fits on one line") @DisplayName("Short string fits on one line")
public void shortStringOnOneLine() { public void shortStringOnOneLine() {
assertThat(invoke("x")) assertThat(invoke("x"))
.containsExactly(Collections.singletonList("x")); .containsExactly(
Collections.singletonList("x"),
Collections.emptyList());
} }
@Test @Test
@DisplayName("Fits max 'words' per line on one line") @DisplayName("Fits max 'words' per line on one line")
public void fitMaxWordsOneLine() { public void fitMaxWordsOneLine() {
String input = words(wordsPerLine); String input = words(wordsPerLine, WORD);
assertThat(invoke(input)) assertThat(invoke(input))
.containsExactly(Collections.singletonList(input)); .containsExactly(
Collections.singletonList(input),
Collections.emptyList() );
} }
@Test @Test
@DisplayName("Wraps just over max 'words' per line onto two lines") @DisplayName("Wraps just over max 'words' per line onto two lines")
public void wrapOneWordTooManyOntoTwoLines() { public void wrapOneWordTooManyOntoTwoLines() {
String input = words(wordsPerLine + 1); String input = words(wordsPerLine + 1, WORD);
assertThat(invoke(input)).containsExactly(Arrays.asList( assertThat(invoke(input)).containsExactly(Arrays.asList(
words(wordsPerLine), words(wordsPerLine, WORD),
WORD)); WORD),
Collections.emptyList());
} }
@Test @Test
@DisplayName("Wraps onto three lines") @DisplayName("Wraps onto three lines")
public void longerStringOnThreeLines() { public void longerStringOnThreeLines() {
String input = words(wordsPerLine + wordsPerLine + 1); String input = words(wordsPerLine + wordsPerLine + 1, WORD);
String oneLinesWorthOfWords = words(wordsPerLine); String oneLinesWorthOfWords = words(wordsPerLine, WORD);
assertThat(invoke(input)).containsExactly(Arrays.asList( assertThat(invoke(input)).containsExactly(Arrays.asList(
oneLinesWorthOfWords, oneLinesWorthOfWords,
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);
} }
} }
} }