Text justification: tests (WIP) + code (WIP)
authorNiki Roo <niki@nikiroo.be>
Tue, 13 Mar 2018 13:33:23 +0000 (14:33 +0100)
committerNiki Roo <niki@nikiroo.be>
Tue, 13 Mar 2018 13:33:23 +0000 (14:33 +0100)
src/be/nikiroo/utils/StringJustifier.java
src/be/nikiroo/utils/test/StringUtilsTest.java

index d968098aa9e02f5fc895e578bd42b79c8619d163..174c7e910d431dc11c79b3af720e553a28b411e2 100644 (file)
@@ -48,95 +48,78 @@ class StringJustifier {
         * 
         * @return the list of justified lines
         */
-       static List<String> left2(final String data, final int width) {
-               List<String> result = new LinkedList<String>();
+       static List<String> left(final String data, final int width) {
+               List<String> lines = new LinkedList<String>();
 
-               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<String> left(final String str, final int n) {
-               List<String> result = new LinkedList<String>();
+                       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);
+       }
 }
index 452b9061185f9838b2145fb74c7e533bb99d0c11..4be75040b9966106beb932b825f60d2e53a8adeb 100644 (file)
@@ -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<String> 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<String, Map<Integer, Entry<Alignment, List<String>>>> source = new HashMap<String, Map<Integer, Entry<Alignment, List<String>>>>();
+                               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<String> values = source.get(data).get(size)
+                                                               .getValue();
+
+                                               List<String> 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<String, Map<Integer, Entry<Alignment, List<String>>>> source,
+                       final Alignment align, String input, int size,
+                       final String... result) {
+               if (!source.containsKey(input)) {
+                       source.put(input,
+                                       new HashMap<Integer, Entry<Alignment, List<String>>>());
+               }
+
+               source.get(input).put(size, new Entry<Alignment, List<String>>() {
+                       @Override
+                       public Alignment getKey() {
+                               return align;
+                       }
+
+                       @Override
+                       public List<String> getValue() {
+                               return Arrays.asList(result);
+                       }
+
+                       @Override
+                       public List<String> setValue(List<String> value) {
+                               return null;
+                       }
+               });
+       }
 }