From 771c4ba4a1c17a95acbfdcbdb489094b04947713 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Tue, 13 Mar 2018 14:33:23 +0100 Subject: [PATCH] Text justification: tests (WIP) + code (WIP) --- src/be/nikiroo/utils/StringJustifier.java | 149 +++++++++--------- .../nikiroo/utils/test/StringUtilsTest.java | 101 +++++++++--- 2 files changed, 151 insertions(+), 99 deletions(-) diff --git a/src/be/nikiroo/utils/StringJustifier.java b/src/be/nikiroo/utils/StringJustifier.java index d968098..174c7e9 100644 --- a/src/be/nikiroo/utils/StringJustifier.java +++ b/src/be/nikiroo/utils/StringJustifier.java @@ -48,95 +48,78 @@ class StringJustifier { * * @return the list of justified lines */ - static List left2(final String data, final int width) { - List result = new LinkedList(); + static List left(final String data, final int width) { + List lines = new LinkedList(); - return result; - } + for (String dataLine : data.split("\n")) { + String line = rightTrim(dataLine.replace("\t", " ")); - /** - * Left-justify a string into a list of lines. - * - * @param str - * the string - * @param n - * the maximum number of characters in a line - * @return the list of lines - */ - static List left(final String str, final int n) { - List result = new LinkedList(); + if (width > 0 && line.length() > width) { + while (line.length() > 0) { + int i = Math.min(line.length(), width - 1); // -1 for "-" - /* - * General procedure: - * - * 1. Split on '\n' into paragraphs. - * - * 2. Scan each line, noting the position of the last - * beginning-of-a-word. - * - * 3. Chop at the last #2 if the next beginning-of-a-word exceeds n. - * - * 4. Return the lines. - */ + boolean needDash = true; + // find the best space if any and if needed + int prevSpace = 0; + if (i < line.length()) { + prevSpace = -1; + int space = line.indexOf(' '); - String[] rawLines = str.split("\n"); - for (int i = 0; i < rawLines.length; i++) { - StringBuilder line = new StringBuilder(); - StringBuilder word = new StringBuilder(); - boolean inWord = false; - for (int j = 0; j < rawLines[i].length(); j++) { - char ch = rawLines[i].charAt(j); - if ((ch == ' ') || (ch == '\t')) { - if (inWord == true) { - // We have just transitioned from a word to - // whitespace. See if we have enough space to add - // the word to the line. - if (word.length() + line.length() > n) { - // This word will exceed the line length. Wrap - // at it instead. - result.add(line.toString()); - line = new StringBuilder(); + while (space > -1 && space <= i) { + prevSpace = space; + space = line.indexOf(' ', space + 1); } - if ((word.toString().startsWith(" ")) - && (line.length() == 0)) { - line.append(word.substring(1)); - } else { - line.append(word); + + if (prevSpace > 0) { + i = prevSpace; + needDash = false; + } + } + // + + // no dash before space/dash + if ((i + 1) < line.length()) { + char car = line.charAt(i); + char nextCar = line.charAt(i + 1); + if (nextCar == ' ' || car == '-' || nextCar == '-') { + needDash = false; } - word = new StringBuilder(); - word.append(ch); - inWord = false; - } else { - // We are in the whitespace before another word. Do - // nothing. } - } else { - if (inWord == true) { - // We are appending to a word. - word.append(ch); + + // if the space freed by the removed dash allows it, or if + // it is the last char, add the next char + if (!needDash || i >= line.length() - 1) { + int checkI = Math.min(i + 1, line.length()); + if (checkI == i || checkI <= width) { + needDash = false; + i = checkI; + } + } + + // no dash before parenthesis (but cannot add one more + // after) + if ((i + 1) < line.length()) { + char car = line.charAt(i + 1); + if (car == '(' || car == ')') { + needDash = false; + } + } + + if (needDash) { + lines.add(rightTrim(line.substring(0, i)) + "-"); } else { - // We have transitioned from whitespace to a word. - word.append(ch); - inWord = true; + lines.add(rightTrim(line.substring(0, i))); } - } - } // next j - if (word.length() + line.length() > n) { - // This word will exceed the line length. Wrap at it - // instead. - result.add(line.toString()); - line = new StringBuilder(); - } - if ((word.toString().startsWith(" ")) && (line.length() == 0)) { - line.append(word.substring(1)); + // full trim (remove spaces when cutting) + line = line.substring(i).trim(); + } } else { - line.append(word); + lines.add(line); } - result.add(line.toString()); - } // next i + } - return result; + return lines; } /** @@ -257,4 +240,18 @@ class StringJustifier { return result; } + + /** + * Trim the given {@link String} on the right only. + * + * @param data + * the source {@link String} + * @return the right-trimmed String or Empty if it was NULL + */ + static private String rightTrim(String data) { + if (data == null) + return ""; + + return ("|" + data).trim().substring(1); + } } diff --git a/src/be/nikiroo/utils/test/StringUtilsTest.java b/src/be/nikiroo/utils/test/StringUtilsTest.java index 452b906..4be7504 100644 --- a/src/be/nikiroo/utils/test/StringUtilsTest.java +++ b/src/be/nikiroo/utils/test/StringUtilsTest.java @@ -1,5 +1,6 @@ package be.nikiroo.utils.test; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -142,32 +143,59 @@ class StringUtilsTest extends TestLauncher { addTest(new TestCase("Justifying") { @Override public void test() throws Exception { - for (String data : new String[] { "test", - "let's test some words", "" }) { - int total = 0; - for (String word : data.split((" "))) { - total += word.replace("-", "").replace(" ", "") - .length(); - } - List result = StringUtils.justifyText(data, 5, - StringUtils.Alignment.LEFT); - - System.out.println("["+data+"] -> ["); - - int totalResult = 0; - for (String resultLine : result) { - System.out.println(resultLine); - for (String word : resultLine.split((" "))) { - totalResult += word.replace("-", "") - .replace(" ", "").length(); + Map>>> source = new HashMap>>>(); + addValue(source, Alignment.LEFT, "testy", 5, "testy"); + addValue(source, Alignment.LEFT, "testy", 3, "te-", "sty"); + addValue(source, Alignment.LEFT, + "Un petit texte qui se mettra sur plusieurs lignes", + 10, "Un petit", "texte qui", "se mettra", "sur", + "plusieurs", "lignes"); + addValue(source, Alignment.LEFT, + "Un petit texte qui se mettra sur plusieurs lignes", 7, + "Un", "petit", "texte", "qui se", "mettra", "sur", + "plusie-", "urs", "lignes"); + addValue(source, Alignment.RIGHT, + "Un petit texte qui se mettra sur plusieurs lignes", 7, + " Un", " petit", " texte", " qui se", " mettra", + " sur", "plusie-", " urs", " lignes"); + addValue(source, Alignment.CENTER, + "Un petit texte qui se mettra sur plusieurs lignes", 7, + " Un ", " petit ", " texte ", "qui se ", "mettra ", + " sur ", "plusie-", " urs ", "lignes "); + addValue(source, Alignment.JUSTIFY, + "Un petit texte qui se mettra sur plusieurs lignes", 7, + "Un", "petit", "texte", "qui se", "mettra", "sur", + "plusie-", "urs", "lignes"); + addValue(source, Alignment.JUSTIFY, + "Un petit texte qui se mettra sur plusieurs lignes", + 14, "Un petit", "texte qui se", + "mettra sur", "plusieurs lig-", "nes"); + + System.out.println(); + for (String data : source.keySet()) { + for (int size : source.get(data).keySet()) { + Alignment align = source.get(data).get(size).getKey(); + List values = source.get(data).get(size) + .getValue(); + + List result = StringUtils.justifyText(data, + size, align); + + System.out.println("[" + data + " (" + size + ")" + + "] -> ["); + for (int i = 0; i < result.size(); i++) { + String resultLine = result.get(i); + System.out.println(i + ": " + resultLine); } - } - System.out.println("]"); + System.out.println("]"); - assertEquals( - "The number of letters ('-' not included) should be identical before and after", - total, totalResult); + for (int i = 0; i < result.size() && i < values.size(); i++) { + assertEquals("The line " + i + " is not correct", + values.get(i), result.get(i)); + } + } } + System.out.println(); } }); @@ -200,4 +228,31 @@ class StringUtilsTest extends TestLauncher { } }); } + + static private void addValue( + Map>>> source, + final Alignment align, String input, int size, + final String... result) { + if (!source.containsKey(input)) { + source.put(input, + new HashMap>>()); + } + + source.get(input).put(size, new Entry>() { + @Override + public Alignment getKey() { + return align; + } + + @Override + public List getValue() { + return Arrays.asList(result); + } + + @Override + public List setValue(List value) { + return null; + } + }); + } } -- 2.27.0