From a860647e01baf3000bb1898d7c82ec26b2c8712f Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Sat, 27 Oct 2018 07:38:17 +0900 Subject: [PATCH 01/12] Mavenize the project. --- .gitignore | 5 ++ pom.xml | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore index 65bb389..1194eb3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,9 @@ build/testclasses/ releases/ tests/images/ tests/images-expected/ + +# Files copied by mvn's build procedure +tests/text/ +tests/build.xml target/ +/.lein-failures diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7267a04 --- /dev/null +++ b/pom.xml @@ -0,0 +1,159 @@ + + 4.0.0 + stathissideris + ditaa + jar + 0.11.0 + ditaa + command-line utility that can convert diagrams drawn using ascii art into proper bitmap graphics + https://github.com/stathissideris/ditaa + + + GNU Lesser General Public License v3.0 + https://www.gnu.org/licenses/lgpl-3.0.en.html + + + + https://github.com/dakusui/ditaa + scm:git:git://github.com/dakusui/ditaa.git + scm:git:ssh://git@github.com/dakusui/ditaa.git + 95a481e5a01d07317edc4af4cbdfb4c821090437 + + + src/java + test/java + + + resources + + + target + target/classes + + + maven-resources-plugin + 3.1.0 + + + copy-resources + + validate + + copy-resources + + + ${basedir}/tests + + + test-resources + true + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.7 + + + add-source + generate-sources + + add-source + + + + src/java + + + + + + + + + + central + https://repo1.maven.org/maven2/ + + false + + + true + + + + clojars + https://repo.clojars.org/ + + true + + + true + + + + + + + + + org.clojure + clojure + 1.9.0 + + + commons-cli + commons-cli + 1.4 + + + net.htmlparser.jericho + jericho-html + 3.4 + + + org.apache.xmlgraphics + batik-gvt + 1.9 + + + org.apache.xmlgraphics + batik-codec + 1.9 + + + org.apache.xmlgraphics + batik-bridge + 1.9 + + + junit + junit + 4.12 + test + + + + + From cd1771f5e902cd9a4e40479b1df5086e207f5c24 Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Sat, 27 Oct 2018 16:15:45 +0900 Subject: [PATCH 02/12] Implement 'LaTeX math mode'. (WIP) --- pom.xml | 327 +- .../ascii2image/graphics/BitmapRenderer.java | 42 +- .../ascii2image/graphics/DiagramText.java | 397 +- .../ascii2image/text/StringUtils.java | 289 +- .../ascii2image/text/TextGrid.java | 3643 +++++++++-------- test-resources/text/art-latexmath-1.txt | 13 + test/java/sandbox/Sandbox.java | 143 + 7 files changed, 2629 insertions(+), 2225 deletions(-) create mode 100644 test-resources/text/art-latexmath-1.txt create mode 100644 test/java/sandbox/Sandbox.java diff --git a/pom.xml b/pom.xml index 7267a04..215e9b8 100644 --- a/pom.xml +++ b/pom.xml @@ -1,159 +1,172 @@ - - 4.0.0 - stathissideris - ditaa - jar - 0.11.0 - ditaa - command-line utility that can convert diagrams drawn using ascii art into proper bitmap graphics - https://github.com/stathissideris/ditaa - - - GNU Lesser General Public License v3.0 - https://www.gnu.org/licenses/lgpl-3.0.en.html - - - - https://github.com/dakusui/ditaa - scm:git:git://github.com/dakusui/ditaa.git - scm:git:ssh://git@github.com/dakusui/ditaa.git - 95a481e5a01d07317edc4af4cbdfb4c821090437 - - - src/java - test/java - - - resources - - - target - target/classes - - - maven-resources-plugin - 3.1.0 - - - copy-resources - - validate - - copy-resources - - - ${basedir}/tests - - - test-resources - true - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.8 - 1.8 - - - - - - - org.codehaus.mojo - build-helper-maven-plugin - 1.7 - - - add-source - generate-sources - - add-source - - - - src/java - - - - - - - - - - central - https://repo1.maven.org/maven2/ - - false - - - true - - - - clojars - https://repo.clojars.org/ - - true - - - true - - - - - - - - - org.clojure - clojure - 1.9.0 - - - commons-cli - commons-cli - 1.4 - - - net.htmlparser.jericho - jericho-html - 3.4 - - - org.apache.xmlgraphics - batik-gvt - 1.9 - - - org.apache.xmlgraphics - batik-codec - 1.9 - - - org.apache.xmlgraphics - batik-bridge - 1.9 - - - junit - junit - 4.12 - test - - + + + 4.0.0 + stathissideris + ditaa + jar + 0.11.0 + ditaa + command-line utility that can convert diagrams drawn using ascii art into proper bitmap graphics + + https://github.com/stathissideris/ditaa + + + GNU Lesser General Public License v3.0 + https://www.gnu.org/licenses/lgpl-3.0.en.html + + + + https://github.com/dakusui/ditaa + scm:git:git://github.com/dakusui/ditaa.git + scm:git:ssh://git@github.com/dakusui/ditaa.git + 95a481e5a01d07317edc4af4cbdfb4c821090437 + + + src/java + test/java + + + resources + + + target + target/classes + + + maven-resources-plugin + 3.1.0 + + + copy-resources + + validate + + copy-resources + + + ${basedir}/tests + + + test-resources + true + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.7 + + + add-source + generate-sources + + add-source + + + + src/java + + + + + + + + + + central + https://repo1.maven.org/maven2/ + + false + + + true + + + + clojars + https://repo.clojars.org/ + + true + + + true + + + + + + + + + org.clojure + clojure + 1.9.0 + + + commons-cli + commons-cli + 1.4 + + + net.htmlparser.jericho + jericho-html + 3.4 + + + org.apache.xmlgraphics + batik-gvt + 1.9 + + + org.apache.xmlgraphics + batik-codec + 1.9 + + + org.apache.xmlgraphics + batik-bridge + 1.9 + + + org.scilab.forge + jlatexmath + 1.0.7 + + + junit + junit + 4.12 + test + + + com.github.dakusui + thincrest + 3.5.1 + + - + diff --git a/src/java/org/stathissideris/ascii2image/graphics/BitmapRenderer.java b/src/java/org/stathissideris/ascii2image/graphics/BitmapRenderer.java index 40d1bd3..5f40976 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/BitmapRenderer.java +++ b/src/java/org/stathissideris/ascii2image/graphics/BitmapRenderer.java @@ -19,15 +19,14 @@ */ package org.stathissideris.ascii2image.graphics; -import java.awt.BasicStroke; -import java.awt.Canvas; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.Stroke; +import org.stathissideris.ascii2image.core.ConversionOptions; +import org.stathissideris.ascii2image.core.RenderingOptions; +import org.stathissideris.ascii2image.core.Shape3DOrderingComparator; +import org.stathissideris.ascii2image.core.ShapeAreaComparator; +import org.stathissideris.ascii2image.text.TextGrid; + +import javax.imageio.ImageIO; +import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.image.BufferedImage; @@ -40,14 +39,6 @@ import java.util.Collections; import java.util.Iterator; -import javax.imageio.ImageIO; - -import org.stathissideris.ascii2image.core.ConversionOptions; -import org.stathissideris.ascii2image.core.RenderingOptions; -import org.stathissideris.ascii2image.core.Shape3DOrderingComparator; -import org.stathissideris.ascii2image.core.ShapeAreaComparator; -import org.stathissideris.ascii2image.text.TextGrid; - /** * * @author Efstathios Sideris @@ -342,6 +333,8 @@ public RenderedImage render(Diagram diagram, BufferedImage image, RenderingOpti Iterator textIt = diagram.getTextObjects().iterator(); while(textIt.hasNext()){ DiagramText text = textIt.next(); + text.drawOn(g2); + /* g2.setFont(text.getFont()); if(text.hasOutline()){ g2.setColor(text.getOutlineColor()); @@ -352,6 +345,7 @@ public RenderedImage render(Diagram diagram, BufferedImage image, RenderingOpti } g2.setColor(text.getColor()); g2.drawString(text.getText(), text.getXPos(), text.getYPos()); + */ } if(options.renderDebugLines() || DEBUG_LINES){ @@ -392,20 +386,10 @@ public TextCanvas(ArrayList textObjects){ } public void paint(Graphics g){ - Graphics g2 = (Graphics2D) g; + Graphics2D g2 = (Graphics2D) g; Iterator textIt = textObjects.iterator(); while(textIt.hasNext()){ - DiagramText text = (DiagramText) textIt.next(); - g2.setFont(text.getFont()); - if(text.hasOutline()){ - g2.setColor(text.getOutlineColor()); - g2.drawString(text.getText(), text.getXPos() + 1, text.getYPos()); - g2.drawString(text.getText(), text.getXPos() - 1, text.getYPos()); - g2.drawString(text.getText(), text.getXPos(), text.getYPos() + 1); - g2.drawString(text.getText(), text.getXPos(), text.getYPos() - 1); - } - g2.setColor(text.getColor()); - g2.drawString(text.getText(), text.getXPos(), text.getYPos()); + textIt.next().drawOn(g2); } } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java b/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java index ef411ca..b22d5ce 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java +++ b/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java @@ -1,192 +1,251 @@ /** * ditaa - Diagrams Through Ascii Art - * + *

* Copyright (C) 2004-2011 Efstathios Sideris - * + *

* ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + *

* ditaa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + *

* You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.graphics; -import java.awt.Color; -import java.awt.Font; -import java.awt.Rectangle; +import org.scilab.forge.jlatexmath.TeXConstants; +import org.scilab.forge.jlatexmath.TeXFormula; +import org.scilab.forge.jlatexmath.TeXIcon; +import org.stathissideris.ascii2image.text.StringUtils; + +import javax.swing.*; +import java.awt.*; import java.awt.geom.Rectangle2D; +import java.util.Iterator; +import java.util.regex.Pattern; +import java.util.stream.Stream; /** - * * @author Efstathios Sideris */ public class DiagramText extends DiagramComponent { - public static final Color DEFAULT_COLOR = Color.black; - - private String text; - private Font font; - private int xPos, yPos; - private Color color = Color.black; - private boolean isTextOnLine = false; - private boolean hasOutline = false; - private Color outlineColor = Color.white; - - public DiagramText(int x, int y, String text, Font font){ - if(text == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null string"); - if(font == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null font"); - - this.xPos = x; - this.yPos = y; - this.text = text; - this.font = font; - } - - public void centerInBounds(Rectangle2D bounds){ - centerHorizontallyBetween((int) bounds.getMinX(), (int) bounds.getMaxX()); - centerVerticallyBetween((int) bounds.getMinY(), (int) bounds.getMaxY()); - } - - public void centerHorizontallyBetween(int minX, int maxX){ - int width = FontMeasurer.instance().getWidthFor(text, font); - int center = Math.abs(maxX - minX) / 2; - xPos += Math.abs(center - width / 2); - - } - - public void centerVerticallyBetween(int minY, int maxY){ - int zHeight = FontMeasurer.instance().getZHeight(font); - int center = Math.abs(maxY - minY) / 2; - yPos -= Math.abs(center - zHeight / 2); - } - - public void alignRightEdgeTo(int x){ - int width = FontMeasurer.instance().getWidthFor(text, font); - xPos = x - width; - } - - - /** - * @return - */ - public Color getColor() { - return color; - } - - /** - * @return - */ - public Font getFont() { - return font; - } - - /** - * @return - */ - public String getText() { - return text; - } - - /** - * @return - */ - public int getXPos() { - return xPos; - } - - /** - * @return - */ - public int getYPos() { - return yPos; - } - - /** - * @param color - */ - public void setColor(Color color) { - this.color = color; - } - - /** - * @param font - */ - public void setFont(Font font) { - this.font = font; - } - - /** - * @param string - */ - public void setText(String string) { - text = string; - } - - /** - * @param i - */ - public void setXPos(int i) { - xPos = i; - } - - /** - * @param i - */ - public void setYPos(int i) { - yPos = i; - } - - public Rectangle2D getBounds(){ - Rectangle2D bounds = FontMeasurer.instance().getBoundsFor(text, font); - bounds.setRect( - bounds.getMinX() + xPos, - bounds.getMinY() + yPos, - bounds.getWidth(), - bounds.getHeight()); - return bounds; - } - - public String toString(){ - return "DiagramText, at ("+xPos+", "+yPos+"), within "+getBounds()+" '"+text+"', "+color+" "+font; - } - - /** - * @return - */ - public boolean isTextOnLine() { - return isTextOnLine; - } - - /** - * @param b - */ - public void setTextOnLine(boolean b) { - isTextOnLine = b; - } - - public boolean hasOutline() { - return hasOutline; - } - - public void setHasOutline(boolean hasOutline) { - this.hasOutline = hasOutline; - } - - public Color getOutlineColor() { - return outlineColor; - } - - public void setOutlineColor(Color outlineColor) { - this.outlineColor = outlineColor; - } - - + public static final Color DEFAULT_COLOR = Color.black; + public static final Pattern TEXT_SPLITTING_REGEX = Pattern.compile("([^$]+|\\$[^$]*\\$)"); + + private String text; + private Font font; + private int xPos, yPos; + private Color color = Color.black; + private boolean isTextOnLine = false; + private boolean hasOutline = false; + private Color outlineColor = Color.white; + + public DiagramText(int x, int y, String text, Font font) { + if (text == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null string"); + if (font == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null font"); + + this.xPos = x; + this.yPos = y; + this.text = text; + this.font = font; + } + + public void centerInBounds(Rectangle2D bounds) { + centerHorizontallyBetween((int) bounds.getMinX(), (int) bounds.getMaxX()); + centerVerticallyBetween((int) bounds.getMinY(), (int) bounds.getMaxY()); + } + + public void centerHorizontallyBetween(int minX, int maxX) { + int width = FontMeasurer.instance().getWidthFor(text, font); + int center = Math.abs(maxX - minX) / 2; + xPos += Math.abs(center - width / 2); + + } + + public void centerVerticallyBetween(int minY, int maxY) { + int zHeight = FontMeasurer.instance().getZHeight(font); + int center = Math.abs(maxY - minY) / 2; + yPos -= Math.abs(center - zHeight / 2); + } + + public void alignRightEdgeTo(int x) { + int width = FontMeasurer.instance().getWidthFor(text, font); + xPos = x - width; + } + + + /** + * @return + */ + public Color getColor() { + return color; + } + + /** + * @return + */ + public Font getFont() { + return font; + } + + /** + * @return + */ + public String getText() { + return text; + } + + public void drawOn(Graphics2D g2) { + g2.setFont(this.getFont()); + if (this.hasOutline()) { + g2.setColor(this.getOutlineColor()); + Stream.of(1, -1) + .peek(d -> draw(g2, this.getXPos() + d, this.getYPos())) + .peek(d -> draw(g2, this.getXPos(), this.getYPos() + d)) + .forEach(d -> { + }); + } + g2.setColor(this.getColor()); + draw(g2, this.getXPos(), this.getYPos()); + } + + private void draw(Graphics2D g2, int xPos, int yPos) { + Iterator i = StringUtils.createTextSplitter(TEXT_SPLITTING_REGEX, getText()); + int x = xPos; + while (i.hasNext()) { + String text = i.next(); + if (text.startsWith("$")) + x += drawTeXFormula( + g2, + text, + x, yPos, this.getColor(), + this.getFont().getSize()); + else + x += drawString( + g2, + text, + x, yPos, this.getColor(), + this.getFont()); + } + } + + /** + * @return + */ + public int getXPos() { + return xPos; + } + + /** + * @return + */ + public int getYPos() { + return yPos; + } + + /** + * @param color + */ + public void setColor(Color color) { + this.color = color; + } + + /** + * @param font + */ + public void setFont(Font font) { + this.font = font; + } + + /** + * @param string + */ + public void setText(String string) { + text = string; + } + + /** + * @param i + */ + public void setXPos(int i) { + xPos = i; + } + + /** + * @param i + */ + public void setYPos(int i) { + yPos = i; + } + + public Rectangle2D getBounds() { + Rectangle2D bounds = FontMeasurer.instance().getBoundsFor(text, font); + bounds.setRect( + bounds.getMinX() + xPos, + bounds.getMinY() + yPos, + bounds.getWidth(), + bounds.getHeight()); + return bounds; + } + + public String toString() { + return "DiagramText, at (" + xPos + ", " + yPos + "), within " + getBounds() + " '" + text + "', " + color + " " + font; + } + + /** + * @return + */ + public boolean isTextOnLine() { + return isTextOnLine; + } + + /** + * @param b + */ + public void setTextOnLine(boolean b) { + isTextOnLine = b; + } + + public boolean hasOutline() { + return hasOutline; + } + + public void setHasOutline(boolean hasOutline) { + this.hasOutline = hasOutline; + } + + public Color getOutlineColor() { + return outlineColor; + } + + public void setOutlineColor(Color outlineColor) { + this.outlineColor = outlineColor; + } + + public static int drawTeXFormula(Graphics2D g2, String text, int x, int y, Color color, float fontSize) { + TeXFormula formula = new TeXFormula(text); + TeXIcon icon = formula.new TeXIconBuilder() + .setStyle(TeXConstants.STYLE_DISPLAY) + .setSize(fontSize) + .build(); + /* 12 is a magic number to adjust vertical position */ + icon.paintIcon(new JLabel() {{ + setForeground(color); + }}, g2, x, y - 12); + return icon.getIconWidth(); + } + + private static int drawString(Graphics2D g2, String text, int xPos, int yPos, Color color, Font font) { + g2.setColor(color); + g2.setFont(font); + g2.drawString(text, xPos, yPos); + return FontMeasurer.instance().getWidthFor(text, font); + } + } diff --git a/src/java/org/stathissideris/ascii2image/text/StringUtils.java b/src/java/org/stathissideris/ascii2image/text/StringUtils.java index e753245..cd5ff29 100644 --- a/src/java/org/stathissideris/ascii2image/text/StringUtils.java +++ b/src/java/org/stathissideris/ascii2image/text/StringUtils.java @@ -1,24 +1,28 @@ /** * ditaa - Diagrams Through Ascii Art - * + *

* Copyright (C) 2004-2011 Efstathios Sideris - * + *

* ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + *

* ditaa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + *

* You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.text; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * @author sideris * @@ -27,25 +31,25 @@ */ public class StringUtils { - /** - * The indexOf idiom - * - * @param big - * @param fragment - * @return - */ - public static boolean contains(String big, String fragment){ - return (big.indexOf(fragment) != -1); - } - - public static String repeatString(String string, int repeats){ - if(repeats == 0) return ""; - String buffer = ""; - for(int i=0; i < repeats; i++){ - buffer += string; - } - return buffer; - } + /** + * The indexOf idiom + * + * @param big + * @param fragment + * @return + */ + public static boolean contains(String big, String fragment) { + return (big.indexOf(fragment) != -1); + } + + public static String repeatString(String string, int repeats) { + if (repeats == 0) return ""; + String buffer = ""; + for (int i = 0; i < repeats; i++) { + buffer += string; + } + return buffer; + } /*public static String repeatString(String string, int repeats){ if(repeats == 0) return ""; @@ -55,115 +59,138 @@ public static String repeatString(String string, int repeats){ } return buffer.toString(); }*/ - - public static boolean isBlank(String s){ - return (s.length() == 0 || s.matches("^\\s*$")); - } - - /** - * - * Converts the first character of string into a capital letter - * - * @param string - * @return - */ - public static String firstToUpper(String string){ - return string.substring(0,1).toUpperCase()+string.substring(1); - } - - public static String insertSpaceAtCaps(String string){ - int uppers = 0; + public static boolean isBlank(String s) { + return (s.length() == 0 || s.matches("^\\s*$")); + } - //first we count - for(int i=0; i < string.length(); i++){ - if(Character.isUpperCase(string.charAt(i))) uppers++; - } + /** + * + * Converts the first character of string into a capital letter + * + * @param string + * @return + */ + public static String firstToUpper(String string) { + return string.substring(0, 1).toUpperCase() + string.substring(1); + } - int[] indexes = null; - - if(Character.isUpperCase(string.charAt(0))){ - indexes = new int[uppers]; - } else { - indexes = new int[++uppers]; - } - indexes[0] = 0; - int k = 1; + public static String insertSpaceAtCaps(String string) { - //then we find the indexes (we have checked the first char already) - for(int j =1; j < string.length(); j++){ - if(Character.isUpperCase(string.charAt(j))) indexes[k++] = j; - } - - StringBuffer buffer = new StringBuffer(""); - //and finally we breakup the String - for(int i =0; i < indexes.length; i++){ - if(i+1 < indexes.length){ - buffer.append(string.substring(indexes[i], indexes[i+1])); - buffer.append(" "); - } else { - buffer.append(string.substring(indexes[i])); - } - } - return buffer.toString(); - } - - - public static boolean isOneOf(char c, char[] group){ - for(int i = 0; i < group.length; i++) - if(c == group[i]) return true; - return false; - } - - public static boolean isOneOf(String str, String[] group){ - for(int i = 0; i < group.length; i++) - if(str.equals(group[i])) return true; - return false; - } - - public static String getPath(String fullPath){ - if(fullPath.lastIndexOf("\\") != -1) - return fullPath.substring(0, fullPath.lastIndexOf("\\")); - else return ""; - } - - public static String getBaseFilename(String fullPath){ - if(fullPath.lastIndexOf(".") != -1 && fullPath.lastIndexOf("\\") != -1) - return fullPath.substring(fullPath.lastIndexOf("\\") + 1, fullPath.lastIndexOf(".")); - else return fullPath; - } - - public static String getExtension(String fullPath){ - if(fullPath.lastIndexOf(".") != -1) - return fullPath.substring(fullPath.lastIndexOf(".") + 1); - else return ""; - } + int uppers = 0; - - public static void main(String[] args){ - System.out.println("1 "+StringUtils.firstToUpper("testing")); - System.out.println("2 "+StringUtils.firstToUpper(" testing")); - System.out.println("3 "+StringUtils.firstToUpper("_testing")); - System.out.println("4 "+StringUtils.firstToUpper("Testing")); - System.out.println("5 "+StringUtils.firstToUpper("ttesting")); - String path = "C:\\Files\\test.txt"; - System.out.println(path); - System.out.println(StringUtils.getPath(path)); - System.out.println(StringUtils.getBaseFilename(path)); - System.out.println(StringUtils.getExtension(path)); - - path = "test.txt"; - System.out.println(path); - System.out.println(StringUtils.getPath(path)); - System.out.println(StringUtils.getBaseFilename(path)); - System.out.println(StringUtils.getExtension(path)); - - path = "test"; - System.out.println(path); - System.out.println("path: "+StringUtils.getPath(path)); - System.out.println("base: "+StringUtils.getBaseFilename(path)); - System.out.println(" ext: "+StringUtils.getExtension(path)); - - - } + //first we count + for (int i = 0; i < string.length(); i++) { + if (Character.isUpperCase(string.charAt(i))) uppers++; + } + + int[] indexes = null; + + if (Character.isUpperCase(string.charAt(0))) { + indexes = new int[uppers]; + } else { + indexes = new int[++uppers]; + } + indexes[0] = 0; + int k = 1; + + //then we find the indexes (we have checked the first char already) + for (int j = 1; j < string.length(); j++) { + if (Character.isUpperCase(string.charAt(j))) indexes[k++] = j; + } + + StringBuffer buffer = new StringBuffer(""); + //and finally we breakup the String + for (int i = 0; i < indexes.length; i++) { + if (i + 1 < indexes.length) { + buffer.append(string.substring(indexes[i], indexes[i + 1])); + buffer.append(" "); + } else { + buffer.append(string.substring(indexes[i])); + } + } + return buffer.toString(); + } + + + public static boolean isOneOf(char c, char[] group) { + for (int i = 0; i < group.length; i++) + if (c == group[i]) return true; + return false; + } + + public static boolean isOneOf(String str, String[] group) { + for (int i = 0; i < group.length; i++) + if (str.equals(group[i])) return true; + return false; + } + + public static String getPath(String fullPath) { + if (fullPath.lastIndexOf("\\") != -1) + return fullPath.substring(0, fullPath.lastIndexOf("\\")); + else return ""; + } + + public static String getBaseFilename(String fullPath) { + if (fullPath.lastIndexOf(".") != -1 && fullPath.lastIndexOf("\\") != -1) + return fullPath.substring(fullPath.lastIndexOf("\\") + 1, fullPath.lastIndexOf(".")); + else return fullPath; + } + + public static String getExtension(String fullPath) { + if (fullPath.lastIndexOf(".") != -1) + return fullPath.substring(fullPath.lastIndexOf(".") + 1); + else return ""; + } + + + public static void main(String[] args) { + System.out.println("1 " + StringUtils.firstToUpper("testing")); + System.out.println("2 " + StringUtils.firstToUpper(" testing")); + System.out.println("3 " + StringUtils.firstToUpper("_testing")); + System.out.println("4 " + StringUtils.firstToUpper("Testing")); + System.out.println("5 " + StringUtils.firstToUpper("ttesting")); + String path = "C:\\Files\\test.txt"; + System.out.println(path); + System.out.println(StringUtils.getPath(path)); + System.out.println(StringUtils.getBaseFilename(path)); + System.out.println(StringUtils.getExtension(path)); + + path = "test.txt"; + System.out.println(path); + System.out.println(StringUtils.getPath(path)); + System.out.println(StringUtils.getBaseFilename(path)); + System.out.println(StringUtils.getExtension(path)); + + path = "test"; + System.out.println(path); + System.out.println("path: " + StringUtils.getPath(path)); + System.out.println("base: " + StringUtils.getBaseFilename(path)); + System.out.println(" ext: " + StringUtils.getExtension(path)); + + + } + + public static Iterator createTextSplitter(Pattern pattern, CharSequence s) { + return new Iterator() { + Pattern regex = pattern; + CharSequence rest = s; + + @Override + public boolean hasNext() { + return rest.length() > 0; + } + + @Override + public String next() { + Matcher m = regex.matcher(rest); + if (m.find()) { + String ret = m.group(1); + rest = rest.subSequence(ret.length(), rest.length()); + return ret; + } + throw new NoSuchElementException(); + } + }; + } } diff --git a/src/java/org/stathissideris/ascii2image/text/TextGrid.java b/src/java/org/stathissideris/ascii2image/text/TextGrid.java index 1ead278..af6fb90 100644 --- a/src/java/org/stathissideris/ascii2image/text/TextGrid.java +++ b/src/java/org/stathissideris/ascii2image/text/TextGrid.java @@ -1,898 +1,1022 @@ /** * ditaa - Diagrams Through Ascii Art - * + *

* Copyright (C) 2004-2011 Efstathios Sideris - * + *

* ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + *

* ditaa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + *

* You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.text; -import java.awt.Color; +import org.stathissideris.ascii2image.core.FileUtils; +import org.stathissideris.ascii2image.core.ProcessingOptions; + +import java.awt.*; import java.io.*; import java.util.*; +import java.util.List; +import java.util.function.IntUnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import org.stathissideris.ascii2image.core.FileUtils; -import org.stathissideris.ascii2image.core.ProcessingOptions; -import org.stathissideris.ascii2image.graphics.CustomShapeDefinition; +import static java.util.stream.Collectors.toList; +import static org.stathissideris.ascii2image.text.StringUtils.createTextSplitter; /** - * * @author Efstathios Sideris */ public class TextGrid { - private static final boolean DEBUG = false; - - private ArrayList rows; - - private static char[] boundaries = {'/', '\\', '|', '-', '*', '=', ':'}; - private static char[] undisputableBoundaries = {'|', '-', '*', '=', ':'}; - private static char[] horizontalLines = {'-', '='}; - private static char[] verticalLines = {'|', ':'}; - private static char[] arrowHeads = {'<', '>', '^', 'v', 'V'}; - private static char[] cornerChars = {'\\', '/', '+'}; - private static char[] pointMarkers = {'*'}; - private static char[] dashedLines = {':', '~', '='}; - - private static char[] entryPoints1 = {'\\'}; - private static char[] entryPoints2 = {'|', ':', '+', '\\', '/'}; - private static char[] entryPoints3 = {'/'}; - private static char[] entryPoints4 = {'-', '=', '+', '\\', '/'}; - private static char[] entryPoints5 = {'\\'}; - private static char[] entryPoints6 = {'|', ':', '+', '\\', '/'}; - private static char[] entryPoints7 = {'/'}; - private static char[] entryPoints8 = {'-', '=', '+', '\\', '/'}; - - - - private static HashMap humanColorCodes = new HashMap(); - static { - humanColorCodes.put("GRE", "9D9"); - humanColorCodes.put("BLU", "55B"); - humanColorCodes.put("PNK", "FAA"); - humanColorCodes.put("RED", "E32"); - humanColorCodes.put("YEL", "FF3"); - humanColorCodes.put("BLK", "000"); - - } - - private static HashSet markupTags = - new HashSet(); - - static { - markupTags.add("d"); - markupTags.add("s"); - markupTags.add("io"); - markupTags.add("c"); - markupTags.add("mo"); - markupTags.add("tr"); - markupTags.add("o"); - } - - public void addToMarkupTags(Collection tags){ - markupTags.addAll(tags); - } - - public static void main(String[] args) throws Exception { - TextGrid grid = new TextGrid(); - grid.loadFrom("tests/text/art10.txt"); - - grid.writeStringTo(grid.new Cell(28, 1), "testing"); - - grid.findMarkupTags(); - - grid.printDebug(); - //System.out.println(grid.fillContinuousArea(0, 0, '-').size()+" cells filled"); - //grid.fillContinuousArea(4, 4, '-'); - //grid.getSubGrid(1,1,3,3).printDebug(); - //grid.printDebug(); - } - - - public TextGrid(){ - rows = new ArrayList(); - } - - public TextGrid(int width, int height){ - String space = StringUtils.repeatString(" ", width); - rows = new ArrayList(); - for(int i = 0; i < height; i++) - rows.add(new StringBuilder(space)); - } - - public static TextGrid makeSameSizeAs(TextGrid grid){ - return new TextGrid(grid.getWidth(), grid.getHeight()); - } - - - public TextGrid(TextGrid otherGrid){ - rows = new ArrayList(); - for(StringBuilder row : otherGrid.getRows()) { - rows.add(new StringBuilder(row)); - } - } - - public void clear(){ - String blank = StringUtils.repeatString(" ", getWidth()); - int height = getHeight(); - rows.clear(); - for(int i = 0; i < height; i++) - rows.add(new StringBuilder(blank)); - } - -// duplicated code due to lots of hits to this function - public char get(int x, int y){ - if(x > getWidth() - 1 - || y > getHeight() - 1 - || x < 0 - || y < 0) return 0; - return rows.get(y).charAt(x); - } - - //duplicated code due to lots of hits to this function - public char get(Cell cell){ - if(cell.x > getWidth() - 1 - || cell.y > getHeight() - 1 - || cell.x < 0 - || cell.y < 0) return 0; - return rows.get(cell.y).charAt(cell.x); - } - - public StringBuilder getRow(int y){ - return rows.get(y); - } - - public TextGrid getSubGrid(int x, int y, int width, int height){ - TextGrid grid = new TextGrid(width, height); - for(int i = 0; i < height; i++){ - grid.setRow(i, new StringBuilder(getRow(y + i).subSequence(x, x + width))); - } - return grid; - } - - public TextGrid getTestingSubGrid(Cell cell){ - return getSubGrid(cell.x - 1, cell.y - 1, 3, 3); - } - - - public String getStringAt(int x, int y, int length){ - return getStringAt(new Cell(x, y), length); - } - - public String getStringAt(Cell cell, int length){ - int x = cell.x; - int y = cell.y; - if(x > getWidth() - 1 - || y > getHeight() - 1 - || x < 0 - || y < 0) return null; - return rows.get(y).substring(x, x + length); - } - - public char getNorthOf(int x, int y){ return get(x, y - 1); } - public char getSouthOf(int x, int y){ return get(x, y + 1); } - public char getEastOf(int x, int y){ return get(x + 1, y); } - public char getWestOf(int x, int y){ return get(x - 1, y); } - - public char getNorthOf(Cell cell){ return getNorthOf(cell.x, cell.y); } - public char getSouthOf(Cell cell){ return getSouthOf(cell.x, cell.y); } - public char getEastOf(Cell cell){ return getEastOf(cell.x, cell.y); } - public char getWestOf(Cell cell){ return getWestOf(cell.x, cell.y); } - - public void writeStringTo(int x, int y, String str){ - writeStringTo(new Cell(x, y), str); - } - - public void writeStringTo(Cell cell, String str){ - if(isOutOfBounds(cell)) return; - rows.get(cell.y).replace(cell.x, cell.x + str.length(), str); - } - - public void set(Cell cell, char c){ - set(cell.x, cell.y, c); - } - - public void set(int x, int y, char c){ - if(x > getWidth() - 1 || y > getHeight() - 1) return; - StringBuilder row = rows.get(y); - row.setCharAt(x, c); - } - - public void setRow(int y, String row){ - if(y > getHeight() || row.length() != getWidth()) - throw new IllegalArgumentException("setRow out of bounds or string wrong size"); - rows.set(y, new StringBuilder(row)); - } - - public void setRow(int y, StringBuilder row){ - if(y > getHeight() || row.length() != getWidth()) - throw new IllegalArgumentException("setRow out of bounds or string wrong size"); - rows.set(y, row); - } - - public int getWidth(){ - if(rows.size() == 0) return 0; //empty buffer - return rows.get(0).length(); - } - - public int getHeight(){ - return rows.size(); - } - - public void printDebug(){ - Iterator it = rows.iterator(); - int i = 0; - System.out.println( - " " - +StringUtils.repeatString("0123456789", (int) Math.floor(getWidth()/10)+1)); - while(it.hasNext()){ - String row = it.next().toString(); - String index = new Integer(i).toString(); - if(i < 10) index = " "+index; - System.out.println(index+" ("+row+")"); - i++; - } - } - - public String getDebugString(){ - StringBuilder buffer = new StringBuilder(); - Iterator it = rows.iterator(); - int i = 0; - buffer.append( - " " - +StringUtils.repeatString("0123456789", (int) Math.floor(getWidth()/10)+1)+"\n"); - while(it.hasNext()){ - String row = it.next().toString(); - String index = new Integer(i).toString(); - if(i < 10) index = " "+index; - row = row.replaceAll("\n", "\\\\n"); - row = row.replaceAll("\r", "\\\\r"); - buffer.append(index+" ("+row+")\n"); - i++; - } - return buffer.toString(); - } - - public String toString(){ - return getDebugString(); - } - - /** - * Adds grid to this. Space characters in this grid - * are replaced with the corresponding contents of - * grid, otherwise the contents are unchanged. - * - * @param grid - * @return false if the grids are of different size - */ - public boolean add(TextGrid grid){ - if(getWidth() != grid.getWidth() - || getHeight() != grid.getHeight()) return false; - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - if(get(xi, yi) == ' ') set(xi, yi, grid.get(xi, yi)); - } - } - return true; - } - - /** - * Replaces letters or numbers that are on horizontal or vertical - * lines, with the appropriate character that will make the line - * continuous (| for vertical and - for horizontal lines) - * - */ - public void replaceTypeOnLine(){ - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - char c = get(xi, yi); - if(Character.isLetterOrDigit(c)) { - boolean isOnHorizontalLine = isOnHorizontalLine(xi, yi); - boolean isOnVerticalLine = isOnVerticalLine(xi, yi); - if(isOnHorizontalLine && isOnVerticalLine){ - set(xi, yi, '+'); - if(DEBUG) System.out.println("replaced type on line '"+c+"' with +"); - } else if(isOnHorizontalLine){ - set(xi, yi, '-'); - if(DEBUG) System.out.println("replaced type on line '"+c+"' with -"); - } else if(isOnVerticalLine){ - set(xi, yi, '|'); - if(DEBUG) System.out.println("replaced type on line '"+c+"' with |"); - } - } - } - } - } - - public void replacePointMarkersOnLine(){ - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - char c = get(xi, yi); - Cell cell = new Cell(xi, yi); - if(StringUtils.isOneOf(c, pointMarkers) - && isStarOnLine(cell)){ - - boolean isOnHorizontalLine = false; - if(StringUtils.isOneOf(get(cell.getEast()), horizontalLines)) - isOnHorizontalLine = true; - if(StringUtils.isOneOf(get(cell.getWest()), horizontalLines)) - isOnHorizontalLine = true; - - boolean isOnVerticalLine = false; - if(StringUtils.isOneOf(get(cell.getNorth()), verticalLines)) - isOnVerticalLine = true; - if(StringUtils.isOneOf(get(cell.getSouth()), verticalLines)) - isOnVerticalLine = true; - - if(isOnHorizontalLine && isOnVerticalLine){ - set(xi, yi, '+'); - if(DEBUG) System.out.println("replaced marker on line '"+c+"' with +"); - } else if(isOnHorizontalLine){ - set(xi, yi, '-'); - if(DEBUG) System.out.println("replaced marker on line '"+c+"' with -"); - } else if(isOnVerticalLine){ - set(xi, yi, '|'); - if(DEBUG) System.out.println("replaced marker on line '"+c+"' with |"); - } - } - } - } - } - - public CellSet getPointMarkersOnLine(){ - CellSet result = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - char c = get(xi, yi); - if(StringUtils.isOneOf(c, pointMarkers) - && isStarOnLine(new Cell(xi, yi))){ - result.add(new Cell(xi, yi)); - } - } - } - return result; - } - - - public void replaceHumanColorCodes(){ - int height = getHeight(); - for(int y = 0; y < height; y++){ - String row = rows.get(y).toString(); - Iterator it = humanColorCodes.keySet().iterator(); - while(it.hasNext()){ - String humanCode = (String) it.next(); - String hexCode = (String) humanColorCodes.get(humanCode); - if(hexCode != null){ - humanCode = "c" + humanCode; - hexCode = "c" + hexCode; - row = row.replaceAll(humanCode, hexCode); - rows.set(y, new StringBuilder(row)); //TODO: this is not the most efficient way to do this - row = rows.get(y).toString(); - } - } - } - } - - - /** - * Replace all occurrences of c1 with c2 - * - * @param c1 - * @param c2 - */ - public void replaceAll(char c1, char c2){ - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - char c = get(xi, yi); - if(c == c1) set(xi, yi, c2); - } - } - } - - public boolean hasBlankCells(){ - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - Cell cell = new Cell(x, y); - if(isBlank(cell)) return true; - } - } - return false; - } - - - public CellSet getAllNonBlank(){ - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - Cell cell = new Cell(x, y); - if(!isBlank(cell)) set.add(cell); - } - } - return set; - } - - public CellSet getAllBoundaries(){ - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - Cell cell = new Cell(x, y); - if(isBoundary(cell)) set.add(cell); - } - } - return set; - } - - - public CellSet getAllBlanksBetweenCharacters(){ - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - Cell cell = new Cell(x, y); - if(isBlankBetweenCharacters(cell)) set.add(cell); - } - } - return set; - } - - - /** - * Returns an ArrayList of CellStringPairs that - * represents all the continuous (non-blank) Strings - * in the grid. Used on buffers that contain only - * type, in order to find the positions and the - * contents of the strings. - * - * @return - */ - public ArrayList findStrings(){ - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - if(!isBlank(x, y)){ - Cell start = new Cell(x, y); - String str = String.valueOf(get(x,y)); - char c = get(++x, y); - boolean finished = false; - //while(c != ' '){ - while(!finished){ - str += String.valueOf(c); - c = get(++x, y); - char next = get(x + 1, y); - if((c == ' ' || c == 0) && (next == ' ' || next == 0)) - finished = true; - } - result.add(new CellStringPair(start, str)); - } - } - } - return result; - } - - /** - * This is done in a bit of a messy way, should be impossible - * to go out of sync with corresponding GridPatternGroup. - * - * @param cell - * @param entryPointId - * @return - */ - public boolean hasEntryPoint(Cell cell, int entryPointId){ - String result = ""; - char c = get(cell); - if(entryPointId == 1) { - return StringUtils.isOneOf(c, entryPoints1); - - } else if(entryPointId == 2) { - return StringUtils.isOneOf(c, entryPoints2); - - } else if(entryPointId == 3) { - return StringUtils.isOneOf(c, entryPoints3); - - } else if(entryPointId == 4) { - return StringUtils.isOneOf(c, entryPoints4); - - } else if(entryPointId == 5) { - return StringUtils.isOneOf(c, entryPoints5); - - } else if(entryPointId == 6) { - return StringUtils.isOneOf(c, entryPoints6); - - } else if(entryPointId == 7) { - return StringUtils.isOneOf(c, entryPoints7); - - } else if(entryPointId == 8) { - return StringUtils.isOneOf(c, entryPoints8); - } - return false; - } - - /** - * true if cell is blank and the east and west cells are not - * (used to find gaps between words) - * - * @param cell - * @return - */ - public boolean isBlankBetweenCharacters(Cell cell){ - return (isBlank(cell) - && !isBlank(cell.getEast()) - && !isBlank(cell.getWest())); - } - - /** - * Makes blank all the cells that contain non-text - * elements. - */ - public void removeNonText(){ - //the following order is significant - //since the south-pointing arrowheads - //are determined based on the surrounding boundaries - removeArrowheads(); - removeColorCodes(); - removeBoundaries(); - removeMarkupTags(); - } - - public void removeArrowheads(){ - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - Cell cell = new Cell(xi, yi); - if(isArrowhead(cell)) set(cell, ' '); - } - } - } - - public void removeColorCodes(){ - Iterator cells = findColorCodes().iterator(); - while(cells.hasNext()){ - Cell cell = ((CellColorPair) cells.next()).cell; - set(cell, ' '); - cell = cell.getEast(); set(cell, ' '); - cell = cell.getEast(); set(cell, ' '); - cell = cell.getEast(); set(cell, ' '); - } - } - - public void removeBoundaries(){ - ArrayList toBeRemoved = new ArrayList(); - - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - Cell cell = new Cell(xi, yi); - if(isBoundary(cell)) toBeRemoved.add(cell); - } - } - - //remove in two stages, because decision of - //isBoundary depends on content of surrounding - //cells - Iterator it = toBeRemoved.iterator(); - while(it.hasNext()){ - Cell cell = (Cell) it.next(); - set(cell, ' '); - } - } - - public ArrayList findArrowheads(){ - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - Cell cell = new Cell(xi, yi); - if(isArrowhead(cell)) result.add(cell); - } - } - if(DEBUG) System.out.println(result.size()+" arrowheads found"); - return result; - } - - - public ArrayList findColorCodes(){ - Pattern colorCodePattern = Pattern.compile("c[A-F0-9]{3}"); - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width - 3; xi++){ - Cell cell = new Cell(xi, yi); - String s = getStringAt(cell, 4); - Matcher matcher = colorCodePattern.matcher(s); - if(matcher.matches()){ - char cR = s.charAt(1); - char cG = s.charAt(2); - char cB = s.charAt(3); - int r = Integer.valueOf(String.valueOf(cR), 16).intValue() * 17; - int g = Integer.valueOf(String.valueOf(cG), 16).intValue() * 17; - int b = Integer.valueOf(String.valueOf(cB), 16).intValue() * 17; - result.add(new CellColorPair(cell, new Color(r, g, b))); - } - } - } - if(DEBUG) System.out.println(result.size()+" color codes found"); - return result; - } - - public ArrayList findMarkupTags(){ - Pattern tagPattern = Pattern.compile("\\{(.+?)\\}"); - ArrayList result = new ArrayList(); - - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width - 3; x++){ - Cell cell = new Cell(x, y); - char c = get(cell); - if(c == '{'){ - String rowPart = rows.get(y).substring(x); - Matcher matcher = tagPattern.matcher(rowPart); - if(matcher.find()){ - String tagName = matcher.group(1); - if(markupTags.contains(tagName)){ - if(DEBUG) System.out.println("found tag "+tagName+" at "+x+", "+y); - result.add(new CellTagPair(new Cell(x, y), tagName)); - } - } - } - } - } - return result; - } - - public void removeMarkupTags(){ - Iterator it = findMarkupTags().iterator(); - while (it.hasNext()) { - CellTagPair pair = (CellTagPair) it.next(); - String tagName = pair.tag; - if(tagName == null) continue; - int length = 2 + tagName.length(); - writeStringTo(pair.cell, StringUtils.repeatString(" ", length)); - } - } - - - - public boolean matchesAny(GridPatternGroup criteria){ - return criteria.isAnyMatchedBy(this); - } - - public boolean matchesAll(GridPatternGroup criteria){ - return criteria.areAllMatchedBy(this); - } - - public boolean matches(GridPattern criteria){ - return criteria.isMatchedBy(this); - } - - - public boolean isOnHorizontalLine(Cell cell){ return isOnHorizontalLine(cell.x, cell.y); } - private boolean isOnHorizontalLine(int x, int y){ - char c1 = get(x - 1, y); - char c2 = get(x + 1, y); - if(isHorizontalLine(c1) && isHorizontalLine(c2)) return true; - return false; - } - - public boolean isOnVerticalLine(Cell cell){ return isOnVerticalLine(cell.x, cell.y); } - private boolean isOnVerticalLine(int x, int y){ - char c1 = get(x, y - 1); - char c2 = get(x, y + 1); - if(isVerticalLine(c1) && isVerticalLine(c2)) return true; - return false; - } - - - public static boolean isBoundary(char c){ - return StringUtils.isOneOf(c, boundaries); - } - public boolean isBoundary(int x, int y){ return isBoundary(new Cell(x, y)); } - public boolean isBoundary(Cell cell){ - char c = get(cell.x, cell.y); - if(0 == c) return false; - if('+' == c || '\\' == c || '/' == c){ - System.out.print(""); - if( - isIntersection(cell) - || isCorner(cell) - || isStub(cell) - || isCrossOnLine(cell)){ - return true; - } else return false; - } - //return StringUtils.isOneOf(c, undisputableBoundaries); - if(StringUtils.isOneOf(c, boundaries) && !isLoneDiagonal(cell)){ - return true; - } - return false; - } - - public boolean isLine(Cell cell){ - return isHorizontalLine(cell) || isVerticalLine(cell); - } - - public static boolean isHorizontalLine(char c){ - return StringUtils.isOneOf(c, horizontalLines); - } - public boolean isHorizontalLine(Cell cell){ return isHorizontalLine(cell.x, cell.y); } - public boolean isHorizontalLine(int x, int y){ - char c = get(x, y); - if(0 == c) return false; - return StringUtils.isOneOf(c, horizontalLines); - } - - public static boolean isVerticalLine(char c){ - return StringUtils.isOneOf(c, verticalLines); - } - public boolean isVerticalLine(Cell cell){ return isVerticalLine(cell.x, cell.y); } - public boolean isVerticalLine(int x, int y){ - char c = get(x, y); - if(0 == c) return false; - return StringUtils.isOneOf(c, verticalLines); - } - - public boolean isLinesEnd(int x, int y){ - return isLinesEnd(new Cell(x, y)); - } - - /** - * Stubs are also considered end of lines - * - * @param cell - * @return - */ - public boolean isLinesEnd(Cell cell){ - return matchesAny(cell, GridPatternGroup.linesEndCriteria); - } - - public boolean isVerticalLinesEnd(Cell cell){ - return matchesAny(cell, GridPatternGroup.verticalLinesEndCriteria); - } - - public boolean isHorizontalLinesEnd(Cell cell){ - return matchesAny(cell, GridPatternGroup.horizontalLinesEndCriteria); - } - - - public boolean isPointCell(Cell cell){ - return ( - isCorner(cell) - || isIntersection(cell) - || isStub(cell) - || isLinesEnd(cell)); - } - - - public boolean containsAtLeastOneDashedLine(CellSet set){ - Iterator it = set.iterator(); - while(it.hasNext()) { - Cell cell = (Cell) it.next(); - if(StringUtils.isOneOf(get(cell), dashedLines)) return true; - } - return false; - } - - public boolean exactlyOneNeighbourIsBoundary(Cell cell) { - int howMany = 0; - if(isBoundary(cell.getNorth())) howMany++; - if(isBoundary(cell.getSouth())) howMany++; - if(isBoundary(cell.getEast())) howMany++; - if(isBoundary(cell.getWest())) howMany++; - return (howMany == 1); - } - - /** - * - * A stub looks like that: - * - *

-	 * 
-	 * +- or -+ or + or + or /- or -/ or / (you get the point)
-	 *             |    |                |
-	 * 
-	 * 
- * - * @param cell - * @return - */ - - public boolean isStub(Cell cell){ - return matchesAny(cell, GridPatternGroup.stubCriteria); - } - - public boolean isCrossOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.crossOnLineCriteria); - } - - public boolean isHorizontalCrossOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.horizontalCrossOnLineCriteria); - } - - public boolean isVerticalCrossOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.verticalCrossOnLineCriteria); - } - - public boolean isStarOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.starOnLineCriteria); - } - - public boolean isLoneDiagonal(Cell cell){ - return matchesAny(cell, GridPatternGroup.loneDiagonalCriteria); - } - - - public boolean isHorizontalStarOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.horizontalStarOnLineCriteria); - } - - public boolean isVerticalStarOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.verticalStarOnLineCriteria); - } - - public boolean isArrowhead(Cell cell){ - return (isNorthArrowhead(cell) - || isSouthArrowhead(cell) - || isWestArrowhead(cell) - || isEastArrowhead(cell)); - } - - public boolean isNorthArrowhead(Cell cell){ - return get(cell) == '^'; - } - - public boolean isEastArrowhead(Cell cell){ - return get(cell) == '>'; - } - - public boolean isWestArrowhead(Cell cell){ - return get(cell) == '<'; - } - - public boolean isSouthArrowhead(Cell cell){ - return (get(cell) == 'v' || get(cell) == 'V') - && isVerticalLine(cell.getNorth()); - } - - + private static final boolean DEBUG = false; + private static final char PLAIN_MODE = 'P'; + public static final char LATEX_MODE = 'L'; + + private ArrayList rows; + private List modeRows; + + private static char[] boundaries = {'/', '\\', '|', '-', '*', '=', ':'}; + private static char[] undisputableBoundaries = {'|', '-', '*', '=', ':'}; + private static char[] horizontalLines = {'-', '='}; + private static char[] verticalLines = {'|', ':'}; + private static char[] arrowHeads = {'<', '>', '^', 'v', 'V'}; + private static char[] cornerChars = {'\\', '/', '+'}; + private static char[] pointMarkers = {'*'}; + private static char[] dashedLines = {':', '~', '='}; + + private static char[] entryPoints1 = {'\\'}; + private static char[] entryPoints2 = {'|', ':', '+', '\\', '/'}; + private static char[] entryPoints3 = {'/'}; + private static char[] entryPoints4 = {'-', '=', '+', '\\', '/'}; + private static char[] entryPoints5 = {'\\'}; + private static char[] entryPoints6 = {'|', ':', '+', '\\', '/'}; + private static char[] entryPoints7 = {'/'}; + private static char[] entryPoints8 = {'-', '=', '+', '\\', '/'}; + + + private static HashMap humanColorCodes = new HashMap(); + + static { + humanColorCodes.put("GRE", "9D9"); + humanColorCodes.put("BLU", "55B"); + humanColorCodes.put("PNK", "FAA"); + humanColorCodes.put("RED", "E32"); + humanColorCodes.put("YEL", "FF3"); + humanColorCodes.put("BLK", "000"); + + } + + private static HashSet markupTags = + new HashSet(); + + static { + markupTags.add("d"); + markupTags.add("s"); + markupTags.add("io"); + markupTags.add("c"); + markupTags.add("mo"); + markupTags.add("tr"); + markupTags.add("o"); + } + + private void updateModeRows() { + // Collectors.toList creates ArrayList and you see no performance penalty + // caused by using LinkedList here. + this.modeRows = this.rows.stream().map(TextGrid::transformRowToModeRow).collect(toList()); + } + + public static String transformRowToModeRow(CharSequence row) { + StringBuilder b = new StringBuilder(); + row.chars().map(new IntUnaryOperator() { + /** + * This field hold current mode of a cell in the grid. + * Only 2 values can be assigned, which are 'P' and 'L'. + * They respectively represent 'Plain', which is normal ditaa mode, and 'LaTeX', which is LaTeX + * formula mode. + */ + int cur = PLAIN_MODE; + + @Override + public int applyAsInt(int operand) { + if (cur == PLAIN_MODE) { + if (operand == '$') { + cur = LATEX_MODE; + return LATEX_MODE; + } + return PLAIN_MODE; + } else if (cur == LATEX_MODE) { + if (operand == '$') { + cur = PLAIN_MODE; + return LATEX_MODE; + } + return this.cur; + } + throw new IllegalStateException(); + } + }).forEach(c -> b.append((char) c)); + String ret = b.toString(); + if (ret.endsWith("L")) + if (row.charAt(row.length() - 1) != '$') + throw new IllegalArgumentException("LaTex mode was started but not finished."); + return ret; + } + + public void addToMarkupTags(Collection tags) { + markupTags.addAll(tags); + } + + public static void main(String[] args) throws Exception { + TextGrid grid = new TextGrid(); + grid.loadFrom("tests/text/art10.txt"); + + grid.writeStringTo(grid.new Cell(28, 1), "testing"); + + grid.findMarkupTags(); + + grid.printDebug(); + //System.out.println(grid.fillContinuousArea(0, 0, '-').size()+" cells filled"); + //grid.fillContinuousArea(4, 4, '-'); + //grid.getSubGrid(1,1,3,3).printDebug(); + //grid.printDebug(); + } + + + public TextGrid() { + rows = new ArrayList(); + this.updateModeRows(); + } + + public TextGrid(int width, int height) { + String space = StringUtils.repeatString(" ", width); + rows = new ArrayList(); + for (int i = 0; i < height; i++) + rows.add(new StringBuilder(space)); + this.updateModeRows(); + } + + public static TextGrid makeSameSizeAs(TextGrid grid) { + return new TextGrid(grid.getWidth(), grid.getHeight()); + } + + + public TextGrid(TextGrid otherGrid) { + rows = new ArrayList(); + for (StringBuilder row : otherGrid.getRows()) { + rows.add(new StringBuilder(row)); + } + this.updateModeRows(); + } + + public void clear() { + String blank = StringUtils.repeatString(" ", getWidth()); + int height = getHeight(); + rows.clear(); + for (int i = 0; i < height; i++) + rows.add(new StringBuilder(blank)); + } + + // duplicated code due to lots of hits to this function + public char get(int x, int y) { + if (x > getWidth() - 1 + || y > getHeight() - 1 + || x < 0 + || y < 0) return 0; + return rows.get(y).charAt(x); + } + + //duplicated code due to lots of hits to this function + public char get(Cell cell) { + if (cell.x > getWidth() - 1 + || cell.y > getHeight() - 1 + || cell.x < 0 + || cell.y < 0) return 0; + return rows.get(cell.y).charAt(cell.x); + } + + public StringBuilder getRow(int y) { + return rows.get(y); + } + + public TextGrid getSubGrid(int x, int y, int width, int height) { + TextGrid grid = new TextGrid(width, height); + for (int i = 0; i < height; i++) { + grid.setRow(i, new StringBuilder(getRow(y + i).subSequence(x, x + width))); + } + return grid; + } + + public TextGrid getTestingSubGrid(Cell cell) { + return getSubGrid(cell.x - 1, cell.y - 1, 3, 3); + } + + + public String getStringAt(int x, int y, int length) { + return getStringAt(new Cell(x, y), length); + } + + public String getStringAt(Cell cell, int length) { + int x = cell.x; + int y = cell.y; + if (x > getWidth() - 1 + || y > getHeight() - 1 + || x < 0 + || y < 0) return null; + return rows.get(y).substring(x, x + length); + } + + public char getNorthOf(int x, int y) { + return get(x, y - 1); + } + + public char getSouthOf(int x, int y) { + return get(x, y + 1); + } + + public char getEastOf(int x, int y) { + return get(x + 1, y); + } + + public char getWestOf(int x, int y) { + return get(x - 1, y); + } + + public char getNorthOf(Cell cell) { + return getNorthOf(cell.x, cell.y); + } + + public char getSouthOf(Cell cell) { + return getSouthOf(cell.x, cell.y); + } + + public char getEastOf(Cell cell) { + return getEastOf(cell.x, cell.y); + } + + public char getWestOf(Cell cell) { + return getWestOf(cell.x, cell.y); + } + + public void writeStringTo(int x, int y, String str) { + writeStringTo(new Cell(x, y), str); + } + + public void writeStringTo(Cell cell, String str) { + if (isOutOfBounds(cell)) return; + rows.get(cell.y).replace(cell.x, cell.x + str.length(), str); + } + + public void set(Cell cell, char c) { + set(cell.x, cell.y, c); + } + + public void set(int x, int y, char c) { + if (x > getWidth() - 1 || y > getHeight() - 1) return; + StringBuilder row = rows.get(y); + row.setCharAt(x, c); + } + + public void setRow(int y, String row) { + if (y > getHeight() || row.length() != getWidth()) + throw new IllegalArgumentException("setRow out of bounds or string wrong size"); + rows.set(y, new StringBuilder(row)); + } + + public void setRow(int y, StringBuilder row) { + if (y > getHeight() || row.length() != getWidth()) + throw new IllegalArgumentException("setRow out of bounds or string wrong size"); + rows.set(y, row); + } + + public int getWidth() { + if (rows.size() == 0) return 0; //empty buffer + return rows.get(0).length(); + } + + public int getHeight() { + return rows.size(); + } + + public void printDebug() { + Iterator it = rows.iterator(); + int i = 0; + System.out.println( + " " + + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1)); + while (it.hasNext()) { + String row = it.next().toString(); + String index = new Integer(i).toString(); + if (i < 10) index = " " + index; + System.out.println(index + " (" + row + ")"); + i++; + } + } + + public String getDebugString() { + StringBuilder buffer = new StringBuilder(); + Iterator it = rows.iterator(); + int i = 0; + buffer.append( + " " + + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1) + "\n"); + while (it.hasNext()) { + String row = it.next().toString(); + String index = new Integer(i).toString(); + if (i < 10) index = " " + index; + row = row.replaceAll("\n", "\\\\n"); + row = row.replaceAll("\r", "\\\\r"); + buffer.append(index + " (" + row + ")\n"); + i++; + } + return buffer.toString(); + } + + public String toString() { + return getDebugString(); + } + + /** + * Adds grid to this. Space characters in this grid + * are replaced with the corresponding contents of + * grid, otherwise the contents are unchanged. + * + * @param grid + * @return false if the grids are of different size + */ + public boolean add(TextGrid grid) { + if (getWidth() != grid.getWidth() + || getHeight() != grid.getHeight()) return false; + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + if (get(xi, yi) == ' ') set(xi, yi, grid.get(xi, yi)); + } + } + return true; + } + + /** + * Replaces letters or numbers that are on horizontal or vertical + * lines, with the appropriate character that will make the line + * continuous (| for vertical and - for horizontal lines) + */ + public void replaceTypeOnLine() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + if (Character.isLetterOrDigit(c)) { + boolean isOnHorizontalLine = isOnHorizontalLine(xi, yi); + boolean isOnVerticalLine = isOnVerticalLine(xi, yi); + if (isOnHorizontalLine && isOnVerticalLine) { + set(xi, yi, '+'); + if (DEBUG) System.out.println("replaced type on line '" + c + "' with +"); + } else if (isOnHorizontalLine) { + set(xi, yi, '-'); + if (DEBUG) System.out.println("replaced type on line '" + c + "' with -"); + } else if (isOnVerticalLine) { + set(xi, yi, '|'); + if (DEBUG) System.out.println("replaced type on line '" + c + "' with |"); + } + } + } + } + } + + public void replacePointMarkersOnLine() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + Cell cell = new Cell(xi, yi); + if (StringUtils.isOneOf(c, pointMarkers) + && isStarOnLine(cell)) { + + boolean isOnHorizontalLine = false; + if (StringUtils.isOneOf(get(cell.getEast()), horizontalLines)) + isOnHorizontalLine = true; + if (StringUtils.isOneOf(get(cell.getWest()), horizontalLines)) + isOnHorizontalLine = true; + + boolean isOnVerticalLine = false; + if (StringUtils.isOneOf(get(cell.getNorth()), verticalLines)) + isOnVerticalLine = true; + if (StringUtils.isOneOf(get(cell.getSouth()), verticalLines)) + isOnVerticalLine = true; + + if (isOnHorizontalLine && isOnVerticalLine) { + set(xi, yi, '+'); + if (DEBUG) System.out.println("replaced marker on line '" + c + "' with +"); + } else if (isOnHorizontalLine) { + set(xi, yi, '-'); + if (DEBUG) System.out.println("replaced marker on line '" + c + "' with -"); + } else if (isOnVerticalLine) { + set(xi, yi, '|'); + if (DEBUG) System.out.println("replaced marker on line '" + c + "' with |"); + } + } + } + } + } + + public CellSet getPointMarkersOnLine() { + CellSet result = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + if (StringUtils.isOneOf(c, pointMarkers) + && isStarOnLine(new Cell(xi, yi))) { + result.add(new Cell(xi, yi)); + } + } + } + return result; + } + + + public void _replaceHumanColorCodes() { + int height = getHeight(); + for (int y = 0; y < height; y++) { + String row = rows.get(y).toString(); + Iterator it = humanColorCodes.keySet().iterator(); + while (it.hasNext()) { + String humanCode = (String) it.next(); + String hexCode = (String) humanColorCodes.get(humanCode); + if (hexCode != null) { + humanCode = "c" + humanCode; + hexCode = "c" + hexCode; + row = row.replaceAll(humanCode, hexCode); + rows.set(y, new StringBuilder(row)); //TODO: this is not the most efficient way to do this + row = rows.get(y).toString(); + } + } + } + } + + public void replaceHumanColorCodes() { + int height = getHeight(); + for (int y = 0; y < height; y++) + rows.set(y, replaceHumanColorCodes(y, rows.get(y))); + } + + private StringBuilder replaceHumanColorCodes(int rowIndex, StringBuilder in) { + Pattern p = Pattern.compile( + Stream.concat( + humanColorCodes.keySet().stream().map(s -> "c" + s), + Stream.of(".*")) + .collect(Collectors.joining("|", "(", ")") + )); + StringBuilder ret = new StringBuilder(in.length()); + Iterator i = createTextSplitter(p, in); + while (i.hasNext()) { + String next = i.next(); + if (isInPlainMode(new Cell(ret.length(), rowIndex)) && looksColorCode(next)) { + ret.append(humanColorCodes.getOrDefault(next.substring(1, 4), next)); + } else + ret.append(next); + } + return ret; + } + + private static boolean looksColorCode(String word) { + return word.length() >= 4 && word.startsWith("c"); + } + + /** + * Replace all occurrences of c1 with c2 + * + * @param c1 + * @param c2 + */ + public void replaceAll(char c1, char c2) { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + if (c == c1) set(xi, yi, c2); + } + } + } + + public boolean hasBlankCells() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (isBlank(cell)) return true; + } + } + return false; + } + + + public CellSet getAllNonBlank() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (!isBlank(cell)) set.add(cell); + } + } + return set; + } + + public CellSet getAllBoundaries() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (isBoundary(cell)) set.add(cell); + } + } + return set; + } + + + public CellSet getAllBlanksBetweenCharacters() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (isBlankBetweenCharacters(cell)) set.add(cell); + } + } + return set; + } + + + /** + * Returns an ArrayList of CellStringPairs that + * represents all the continuous (non-blank) Strings + * in the grid. Used on buffers that contain only + * type, in order to find the positions and the + * contents of the strings. + * + * @return + */ + public ArrayList findStrings() { + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (!isBlank(x, y)) { + Cell start = new Cell(x, y); + String str = String.valueOf(get(x, y)); + char c = get(++x, y); + boolean finished = false; + //while(c != ' '){ + while (!finished) { + str += String.valueOf(c); + c = get(++x, y); + char next = get(x + 1, y); + if ((c == ' ' || c == 0) && (next == ' ' || next == 0)) + finished = true; + } + result.add(new CellStringPair(start, str)); + } + } + } + return result; + } + + /** + * This is done in a bit of a messy way, should be impossible + * to go out of sync with corresponding GridPatternGroup. + * + * @param cell + * @param entryPointId + * @return + */ + public boolean hasEntryPoint(Cell cell, int entryPointId) { + String result = ""; + char c = get(cell); + if (entryPointId == 1) { + return StringUtils.isOneOf(c, entryPoints1); + + } else if (entryPointId == 2) { + return StringUtils.isOneOf(c, entryPoints2); + + } else if (entryPointId == 3) { + return StringUtils.isOneOf(c, entryPoints3); + + } else if (entryPointId == 4) { + return StringUtils.isOneOf(c, entryPoints4); + + } else if (entryPointId == 5) { + return StringUtils.isOneOf(c, entryPoints5); + + } else if (entryPointId == 6) { + return StringUtils.isOneOf(c, entryPoints6); + + } else if (entryPointId == 7) { + return StringUtils.isOneOf(c, entryPoints7); + + } else if (entryPointId == 8) { + return StringUtils.isOneOf(c, entryPoints8); + } + return false; + } + + /** + * true if cell is blank and the east and west cells are not + * (used to find gaps between words) + * + * @param cell + * @return + */ + public boolean isBlankBetweenCharacters(Cell cell) { + return (isBlank(cell) + && !isBlank(cell.getEast()) + && !isBlank(cell.getWest())); + } + + /** + * Makes blank all the cells that contain non-text + * elements. + */ + public void removeNonText() { + //the following order is significant + //since the south-pointing arrowheads + //are determined based on the surrounding boundaries + removeArrowheads(); + removeColorCodes(); + removeBoundaries(); + removeMarkupTags(); + } + + public void removeArrowheads() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isArrowhead(cell)) set(cell, ' '); + } + } + } + + public void removeColorCodes() { + Iterator cells = findColorCodes().iterator(); + while (cells.hasNext()) { + Cell cell = ((CellColorPair) cells.next()).cell; + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + } + } + + public void removeBoundaries() { + ArrayList toBeRemoved = new ArrayList(); + + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isBoundary(cell)) toBeRemoved.add(cell); + } + } + + //remove in two stages, because decision of + //isBoundary depends on content of surrounding + //cells + Iterator it = toBeRemoved.iterator(); + while (it.hasNext()) { + Cell cell = (Cell) it.next(); + if (isInPlainMode(cell)) + set(cell, ' '); + } + } + + public ArrayList findArrowheads() { + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isArrowhead(cell)) result.add(cell); + } + } + if (DEBUG) System.out.println(result.size() + " arrowheads found"); + return result; + } + + + public ArrayList findColorCodes() { + Pattern colorCodePattern = Pattern.compile("c[A-F0-9]{3}"); + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width - 3; xi++) { + Cell cell = new Cell(xi, yi); + if (!isInPlainMode(cell)) + continue; + String s = getStringAt(cell, 4); + Matcher matcher = colorCodePattern.matcher(s); + if (matcher.matches()) { + char cR = s.charAt(1); + char cG = s.charAt(2); + char cB = s.charAt(3); + int r = Integer.valueOf(String.valueOf(cR), 16).intValue() * 17; + int g = Integer.valueOf(String.valueOf(cG), 16).intValue() * 17; + int b = Integer.valueOf(String.valueOf(cB), 16).intValue() * 17; + result.add(new CellColorPair(cell, new Color(r, g, b))); + } + } + } + if (DEBUG) System.out.println(result.size() + " color codes found"); + return result; + } + + public ArrayList findMarkupTags() { + Pattern tagPattern = Pattern.compile("\\{(.+?)\\}"); + ArrayList result = new ArrayList(); + + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width - 3; x++) { + Cell cell = new Cell(x, y); + char c = get(cell); + if (c == '{') { + String rowPart = rows.get(y).substring(x); + Matcher matcher = tagPattern.matcher(rowPart); + if (matcher.find()) { + String tagName = matcher.group(1); + if (markupTags.contains(tagName)) { + if (DEBUG) System.out.println("found tag " + tagName + " at " + x + ", " + y); + result.add(new CellTagPair(new Cell(x, y), tagName)); + } + } + } + } + } + return result; + } + + public void removeMarkupTags() { + Iterator it = findMarkupTags().iterator(); + while (it.hasNext()) { + CellTagPair pair = (CellTagPair) it.next(); + String tagName = pair.tag; + if (tagName == null) continue; + int length = 2 + tagName.length(); + writeStringTo(pair.cell, StringUtils.repeatString(" ", length)); + } + } + + + public boolean matchesAny(GridPatternGroup criteria) { + return criteria.isAnyMatchedBy(this); + } + + public boolean matchesAll(GridPatternGroup criteria) { + return criteria.areAllMatchedBy(this); + } + + public boolean matches(GridPattern criteria) { + return criteria.isMatchedBy(this); + } + + + public boolean isOnHorizontalLine(Cell cell) { + return isOnHorizontalLine(cell.x, cell.y); + } + + private boolean isOnHorizontalLine(int x, int y) { + char c1 = get(x - 1, y); + char c2 = get(x + 1, y); + if (isHorizontalLine(c1) && isHorizontalLine(c2)) return true; + return false; + } + + public boolean isOnVerticalLine(Cell cell) { + return isOnVerticalLine(cell.x, cell.y); + } + + private boolean isOnVerticalLine(int x, int y) { + char c1 = get(x, y - 1); + char c2 = get(x, y + 1); + if (isVerticalLine(c1) && isVerticalLine(c2)) return true; + return false; + } + + + public static boolean isBoundary(char c) { + return StringUtils.isOneOf(c, boundaries); + } + + public boolean isBoundary(int x, int y) { + return isBoundary(new Cell(x, y)); + } + + public boolean isBoundary(Cell cell) { + char c = get(cell.x, cell.y); + if (0 == c) return false; + if ('+' == c || '\\' == c || '/' == c) { + System.out.print(""); + if ( + isIntersection(cell) + || isCorner(cell) + || isStub(cell) + || isCrossOnLine(cell)) { + return true; + } else return false; + } + //return StringUtils.isOneOf(c, undisputableBoundaries); + if (StringUtils.isOneOf(c, boundaries) && !isLoneDiagonal(cell)) { + return true; + } + return false; + } + + public boolean isLine(Cell cell) { + return isHorizontalLine(cell) || isVerticalLine(cell); + } + + public static boolean isHorizontalLine(char c) { + return StringUtils.isOneOf(c, horizontalLines); + } + + public boolean isHorizontalLine(Cell cell) { + return isHorizontalLine(cell.x, cell.y) && isInPlainMode(cell); + } + + public boolean isHorizontalLine(int x, int y) { + char c = get(x, y); + if (0 == c) return false; + return StringUtils.isOneOf(c, horizontalLines); + } + + public static boolean isVerticalLine(char c) { + return StringUtils.isOneOf(c, verticalLines); + } + + public boolean isVerticalLine(Cell cell) { + return isVerticalLine(cell.x, cell.y) && isInPlainMode(cell); + } + + public boolean isVerticalLine(int x, int y) { + char c = get(x, y); + if (0 == c) return false; + return StringUtils.isOneOf(c, verticalLines); + } + + public boolean isLinesEnd(int x, int y) { + return isLinesEnd(new Cell(x, y)); + } + + /** + * Stubs are also considered end of lines + * + * @param cell + * @return + */ + public boolean isLinesEnd(Cell cell) { + return matchesAny(cell, GridPatternGroup.linesEndCriteria); + } + + public boolean isVerticalLinesEnd(Cell cell) { + return matchesAny(cell, GridPatternGroup.verticalLinesEndCriteria); + } + + public boolean isHorizontalLinesEnd(Cell cell) { + return matchesAny(cell, GridPatternGroup.horizontalLinesEndCriteria); + } + + + public boolean isPointCell(Cell cell) { + return ( + isCorner(cell) + || isIntersection(cell) + || isStub(cell) + || isLinesEnd(cell)); + } + + + public boolean containsAtLeastOneDashedLine(CellSet set) { + Iterator it = set.iterator(); + while (it.hasNext()) { + Cell cell = (Cell) it.next(); + if (StringUtils.isOneOf(get(cell), dashedLines)) return true; + } + return false; + } + + public boolean exactlyOneNeighbourIsBoundary(Cell cell) { + int howMany = 0; + if (isBoundary(cell.getNorth())) howMany++; + if (isBoundary(cell.getSouth())) howMany++; + if (isBoundary(cell.getEast())) howMany++; + if (isBoundary(cell.getWest())) howMany++; + return (howMany == 1); + } + + /** + * A stub looks like that: + * + *
+     *
+     * +- or -+ or + or + or /- or -/ or / (you get the point)
+     *             |    |                |
+     *
+     * 
+ * + * @param cell + * @return + */ + + public boolean isStub(Cell cell) { + return matchesAny(cell, GridPatternGroup.stubCriteria); + } + + public boolean isCrossOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.crossOnLineCriteria); + } + + public boolean isHorizontalCrossOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.horizontalCrossOnLineCriteria); + } + + public boolean isVerticalCrossOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.verticalCrossOnLineCriteria); + } + + public boolean isStarOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.starOnLineCriteria); + } + + public boolean isLoneDiagonal(Cell cell) { + return matchesAny(cell, GridPatternGroup.loneDiagonalCriteria); + } + + + public boolean isHorizontalStarOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.horizontalStarOnLineCriteria); + } + + public boolean isVerticalStarOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.verticalStarOnLineCriteria); + } + + public boolean isArrowhead(Cell cell) { + return (isNorthArrowhead(cell) + || isSouthArrowhead(cell) + || isWestArrowhead(cell) + || isEastArrowhead(cell)); + } + + public boolean isNorthArrowhead(Cell cell) { + return get(cell) == '^' && isInPlainMode(cell); + } + + public boolean isEastArrowhead(Cell cell) { + return get(cell) == '>' && isInPlainMode(cell); + } + + public boolean isWestArrowhead(Cell cell) { + return get(cell) == '<' && isInPlainMode(cell); + } + + public boolean isSouthArrowhead(Cell cell) { + return (get(cell) == 'v' || get(cell) == 'V') + && isVerticalLine(cell.getNorth()) + && isInPlainMode(cell); + } + + // unicode for bullets // // 2022 bullet @@ -904,883 +1028,924 @@ public boolean isSouthArrowhead(Cell cell){ // 25BA black right-pointing pointer - public boolean isBullet(int x, int y){ - return isBullet(new Cell(x, y)); - } - - public boolean isBullet(Cell cell){ - char c = get(cell); - if((c == 'o' || c == '*') - && isBlank(cell.getEast()) - && isBlank(cell.getWest()) - && Character.isLetterOrDigit(get(cell.getEast().getEast())) ) - return true; - return false; - } - - public void replaceBullets(){ - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - Cell cell = new Cell(xi, yi); - if(isBullet(cell)){ - set(cell, ' '); - set(cell.getEast(), '\u2022'); - } - } - } - } - - /** - * true if the cell is not blank - * but the previous (west) is - * - * @param cell - * @return - */ - public boolean isStringsStart(Cell cell){ - return (!isBlank(cell) && isBlank(cell.getWest())); - } - - /** - * true if the cell is not blank - * but the next (east) is - * - * @param cell - * @return - */ - public boolean isStringsEnd(Cell cell){ - return (!isBlank(cell) - //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); - && isBlank(cell.getEast())); - } - - public int otherStringsStartInTheSameColumn(Cell cell){ - if(!isStringsStart(cell)) return 0; - int result = 0; - int height = getHeight(); - for(int y = 0; y < height; y++){ - Cell cCell = new Cell(cell.x, y); - if(!cCell.equals(cell) && isStringsStart(cCell)){ - result++; - } - } - return result; - } - - public int otherStringsEndInTheSameColumn(Cell cell){ - if(!isStringsEnd(cell)) return 0; - int result = 0; - int height = getHeight(); - for(int y = 0; y < height; y++){ - Cell cCell = new Cell(cell.x, y); - if(!cCell.equals(cell) && isStringsEnd(cCell)){ - result++; - } - } - return result; - } - - public boolean isColumnBlank(int x){ - int height = getHeight(); - for(int y = 0; y < height; y++){ - if(!isBlank(x, y)) return false; - } - return true; - } - - - public CellSet followLine(int x, int y){ - return followLine(new Cell(x, y)); - } - - public CellSet followIntersection(Cell cell){ - return followIntersection(cell, null); - } - - public CellSet followIntersection(Cell cell, Cell blocked){ - if(!isIntersection(cell)) return null; - CellSet result = new CellSet(); - Cell cN = cell.getNorth(); - Cell cS = cell.getSouth(); - Cell cE = cell.getEast(); - Cell cW = cell.getWest(); - if(hasEntryPoint(cN, 6)) result.add(cN); - if(hasEntryPoint(cS, 2)) result.add(cS); - if(hasEntryPoint(cE, 8)) result.add(cE); - if(hasEntryPoint(cW, 4)) result.add(cW); - if(result.contains(blocked)) result.remove(blocked); - return result; - } - - /** - * Returns the neighbours of a line-cell that are boundaries - * (0 to 2 cells are returned) - * - * @param cell - * @return null if the cell is not a line - */ - public CellSet followLine(Cell cell){ - if(isHorizontalLine(cell)){ - CellSet result = new CellSet(); - if(isBoundary(cell.getEast())) result.add(cell.getEast()); - if(isBoundary(cell.getWest())) result.add(cell.getWest()); - return result; - } else if (isVerticalLine(cell)){ - CellSet result = new CellSet(); - if(isBoundary(cell.getNorth())) result.add(cell.getNorth()); - if(isBoundary(cell.getSouth())) result.add(cell.getSouth()); - return result; - } - return null; - } - - public CellSet followLine(Cell cell, Cell blocked){ - CellSet nextCells = followLine(cell); - if(nextCells.contains(blocked)) nextCells.remove(blocked); - return nextCells; - } - - public CellSet followCorner(Cell cell){ - return followCorner(cell, null); - } - - public CellSet followCorner(Cell cell, Cell blocked){ - if(!isCorner(cell)) return null; - if(isCorner1(cell)) return followCorner1(cell, blocked); - if(isCorner2(cell)) return followCorner2(cell, blocked); - if(isCorner3(cell)) return followCorner3(cell, blocked); - if(isCorner4(cell)) return followCorner4(cell, blocked); - return null; - } - - public CellSet followCorner1(Cell cell){ - return followCorner1(cell, null); - } - public CellSet followCorner1(Cell cell, Cell blocked){ - if(!isCorner1(cell)) return null; - CellSet result = new CellSet(); - if(!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); - if(!cell.getEast().equals(blocked)) result.add(cell.getEast()); - return result; - } - - public CellSet followCorner2(Cell cell){ - return followCorner2(cell, null); - } - public CellSet followCorner2(Cell cell, Cell blocked){ - if(!isCorner2(cell)) return null; - CellSet result = new CellSet(); - if(!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); - if(!cell.getWest().equals(blocked)) result.add(cell.getWest()); - return result; - } - - public CellSet followCorner3(Cell cell){ - return followCorner3(cell, null); - } - public CellSet followCorner3(Cell cell, Cell blocked){ - if(!isCorner3(cell)) return null; - CellSet result = new CellSet(); - if(!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); - if(!cell.getWest().equals(blocked)) result.add(cell.getWest()); - return result; - } - - public CellSet followCorner4(Cell cell){ - return followCorner4(cell, null); - } - public CellSet followCorner4(Cell cell, Cell blocked){ - if(!isCorner4(cell)) return null; - CellSet result = new CellSet(); - if(!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); - if(!cell.getEast().equals(blocked)) result.add(cell.getEast()); - return result; - } - - - public CellSet followStub(Cell cell){ - return followStub(cell, null); - } - public CellSet followStub(Cell cell, Cell blocked){ - if(!isStub(cell)) return null; - CellSet result = new CellSet(); - if(isBoundary(cell.getEast())) result.add(cell.getEast()); - else if(isBoundary(cell.getWest())) result.add(cell.getWest()); - else if(isBoundary(cell.getNorth())) result.add(cell.getNorth()); - else if(isBoundary(cell.getSouth())) result.add(cell.getSouth()); - if(result.contains(blocked)) result.remove(blocked); - return result; - } - - public CellSet followCell(Cell cell){ - return followCell(cell, null); - } - - public CellSet followCell(Cell cell, Cell blocked){ - if(isIntersection(cell)) return followIntersection(cell, blocked); - if(isCorner(cell)) return followCorner(cell, blocked); - if(isLine(cell)) return followLine(cell, blocked); - if(isStub(cell)) return followStub(cell, blocked); - if(isCrossOnLine(cell)) return followCrossOnLine(cell, blocked); - System.err.println("Ambiguous input at position "+cell+":"); - TextGrid subGrid = getTestingSubGrid(cell); - subGrid.printDebug(); - throw new RuntimeException("Cannot follow cell "+cell+": cannot determine cell type"); - } - - public String getCellTypeAsString(Cell cell){ - if(isK(cell)) return "K"; - if(isT(cell)) return "T"; - if(isInverseK(cell)) return "inverse K"; - if(isInverseT(cell)) return "inverse T"; - if(isCorner1(cell)) return "corner 1"; - if(isCorner2(cell)) return "corner 2"; - if(isCorner3(cell)) return "corner 3"; - if(isCorner4(cell)) return "corner 4"; - if(isLine(cell)) return "line"; - if(isStub(cell)) return "stub"; - if(isCrossOnLine(cell)) return "crossOnLine"; - return "unrecognisable type"; - } - - - public CellSet followCrossOnLine(Cell cell, Cell blocked){ - CellSet result = new CellSet(); - if(isHorizontalCrossOnLine(cell)){ - result.add(cell.getEast()); - result.add(cell.getWest()); - } else if(isVerticalCrossOnLine(cell)){ - result.add(cell.getNorth()); - result.add(cell.getSouth()); - } - if(result.contains(blocked)) result.remove(blocked); - return result; - } - - public boolean isOutOfBounds(Cell cell){ - if(cell.x > getWidth() - 1 - || cell.y > getHeight() - 1 - || cell.x < 0 - || cell.y < 0) return true; - return false; - } - - public boolean isOutOfBounds(int x, int y){ - char c = get(x, y); - if(0 == c) return true; - return false; - } - - public boolean isBlank(Cell cell){ - char c = get(cell); - if(0 == c) return false; - return c == ' '; - } - - public boolean isBlank(int x, int y){ - char c = get(x, y); - if(0 == c) return true; - return c == ' '; - } - - public boolean isCorner(Cell cell){ - return isCorner(cell.x, cell.y); - } - public boolean isCorner(int x, int y){ - return (isNormalCorner(x,y) || isRoundCorner(x,y)); - } - - - public boolean matchesAny(Cell cell, GridPatternGroup criteria){ - TextGrid subGrid = getTestingSubGrid(cell); - return subGrid.matchesAny(criteria); - } - - public boolean isCorner1(Cell cell){ - return matchesAny(cell, GridPatternGroup.corner1Criteria); - } - - public boolean isCorner2(Cell cell){ - return matchesAny(cell, GridPatternGroup.corner2Criteria); - } - - public boolean isCorner3(Cell cell){ - return matchesAny(cell, GridPatternGroup.corner3Criteria); - } - - public boolean isCorner4(Cell cell){ - return matchesAny(cell, GridPatternGroup.corner4Criteria); - } - - public boolean isCross(Cell cell){ - return matchesAny(cell, GridPatternGroup.crossCriteria); - } - - public boolean isK(Cell cell){ - return matchesAny(cell, GridPatternGroup.KCriteria); - } - - public boolean isInverseK(Cell cell){ - return matchesAny(cell, GridPatternGroup.inverseKCriteria); - } - - public boolean isT(Cell cell){ - return matchesAny(cell, GridPatternGroup.TCriteria); - } - - public boolean isInverseT(Cell cell){ - return matchesAny(cell, GridPatternGroup.inverseTCriteria); - } - - public boolean isNormalCorner(Cell cell){ - return matchesAny(cell, GridPatternGroup.normalCornerCriteria); - } - public boolean isNormalCorner(int x, int y){ - return isNormalCorner(new Cell(x, y)); - } - - public boolean isRoundCorner(Cell cell){ - return matchesAny(cell, GridPatternGroup.roundCornerCriteria); - } - - public boolean isRoundCorner(int x, int y){ - return isRoundCorner(new Cell(x, y)); - } - - public boolean isIntersection(Cell cell){ - return matchesAny(cell, GridPatternGroup.intersectionCriteria); - } - public boolean isIntersection(int x, int y){ - return isIntersection(new Cell(x, y)); - } - - public void copyCellsTo(CellSet cells, TextGrid grid){ - Iterator it = cells.iterator(); - while(it.hasNext()){ - Cell cell = (Cell) it.next(); - grid.set(cell, this.get(cell)); - } - } - - public boolean equals(TextGrid grid){ - if(grid.getHeight() != this.getHeight() - || grid.getWidth() != this.getWidth() - ){ - return false; - } - int height = grid.getHeight(); - for(int i = 0; i < height; i++){ - String row1 = this.getRow(i).toString(); - String row2 = grid.getRow(i).toString(); - if(!row1.equals(row2)) return false; - } - return true; - } - - /** - * Fills all the cells in cells with c - * - * @param cells - * @param c - */ - public void fillCellsWith(Iterable cells, char c){ - Iterator it = cells.iterator(); - while(it.hasNext()){ - Cell cell = it.next(); - set(cell.x, cell.y, c); - } - } - - /** - * - * Fills the continuous area with if c1 characters with c2, - * flooding from cell x, y - * - * @param x - * @param y - * @param c1 the character to replace - * @param c2 the character to replace c1 with - * @return the list of cells filled - */ + public boolean isBullet(int x, int y) { + return isBullet(new Cell(x, y)); + } + + public boolean isBullet(Cell cell) { + char c = get(cell); + if ((c == 'o' || c == '*') + && isBlank(cell.getEast()) + && isBlank(cell.getWest()) + && Character.isLetterOrDigit(get(cell.getEast().getEast())) + && isInPlainMode(cell)) + return true; + return false; + } + + public void replaceBullets() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isBullet(cell)) { + set(cell, ' '); + set(cell.getEast(), '\u2022'); + } + } + } + } + + /** + * true if the cell is not blank + * but the previous (west) is + * + * @param cell + * @return + */ + public boolean isStringsStart(Cell cell) { + return (!isBlank(cell) && isBlank(cell.getWest())); + } + + /** + * true if the cell is not blank + * but the next (east) is + * + * @param cell + * @return + */ + public boolean isStringsEnd(Cell cell) { + return (!isBlank(cell) + //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); + && isBlank(cell.getEast())); + } + + public int otherStringsStartInTheSameColumn(Cell cell) { + if (!isStringsStart(cell)) return 0; + int result = 0; + int height = getHeight(); + for (int y = 0; y < height; y++) { + Cell cCell = new Cell(cell.x, y); + if (!cCell.equals(cell) && isStringsStart(cCell)) { + result++; + } + } + return result; + } + + public int otherStringsEndInTheSameColumn(Cell cell) { + if (!isStringsEnd(cell)) return 0; + int result = 0; + int height = getHeight(); + for (int y = 0; y < height; y++) { + Cell cCell = new Cell(cell.x, y); + if (!cCell.equals(cell) && isStringsEnd(cCell)) { + result++; + } + } + return result; + } + + public boolean isColumnBlank(int x) { + int height = getHeight(); + for (int y = 0; y < height; y++) { + if (!isBlank(x, y)) return false; + } + return true; + } + + + public CellSet followLine(int x, int y) { + return followLine(new Cell(x, y)); + } + + public CellSet followIntersection(Cell cell) { + return followIntersection(cell, null); + } + + public CellSet followIntersection(Cell cell, Cell blocked) { + if (!isIntersection(cell)) return null; + CellSet result = new CellSet(); + Cell cN = cell.getNorth(); + Cell cS = cell.getSouth(); + Cell cE = cell.getEast(); + Cell cW = cell.getWest(); + if (hasEntryPoint(cN, 6)) result.add(cN); + if (hasEntryPoint(cS, 2)) result.add(cS); + if (hasEntryPoint(cE, 8)) result.add(cE); + if (hasEntryPoint(cW, 4)) result.add(cW); + if (result.contains(blocked)) result.remove(blocked); + return result; + } + + /** + * Returns the neighbours of a line-cell that are boundaries + * (0 to 2 cells are returned) + * + * @param cell + * @return null if the cell is not a line + */ + public CellSet followLine(Cell cell) { + if (isHorizontalLine(cell)) { + CellSet result = new CellSet(); + if (isBoundary(cell.getEast())) result.add(cell.getEast()); + if (isBoundary(cell.getWest())) result.add(cell.getWest()); + return result; + } else if (isVerticalLine(cell)) { + CellSet result = new CellSet(); + if (isBoundary(cell.getNorth())) result.add(cell.getNorth()); + if (isBoundary(cell.getSouth())) result.add(cell.getSouth()); + return result; + } + return null; + } + + public CellSet followLine(Cell cell, Cell blocked) { + CellSet nextCells = followLine(cell); + if (nextCells.contains(blocked)) nextCells.remove(blocked); + return nextCells; + } + + public CellSet followCorner(Cell cell) { + return followCorner(cell, null); + } + + public CellSet followCorner(Cell cell, Cell blocked) { + if (!isCorner(cell)) return null; + if (isCorner1(cell)) return followCorner1(cell, blocked); + if (isCorner2(cell)) return followCorner2(cell, blocked); + if (isCorner3(cell)) return followCorner3(cell, blocked); + if (isCorner4(cell)) return followCorner4(cell, blocked); + return null; + } + + public CellSet followCorner1(Cell cell) { + return followCorner1(cell, null); + } + + public CellSet followCorner1(Cell cell, Cell blocked) { + if (!isCorner1(cell)) return null; + CellSet result = new CellSet(); + if (!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); + if (!cell.getEast().equals(blocked)) result.add(cell.getEast()); + return result; + } + + public CellSet followCorner2(Cell cell) { + return followCorner2(cell, null); + } + + public CellSet followCorner2(Cell cell, Cell blocked) { + if (!isCorner2(cell)) return null; + CellSet result = new CellSet(); + if (!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); + if (!cell.getWest().equals(blocked)) result.add(cell.getWest()); + return result; + } + + public CellSet followCorner3(Cell cell) { + return followCorner3(cell, null); + } + + public CellSet followCorner3(Cell cell, Cell blocked) { + if (!isCorner3(cell)) return null; + CellSet result = new CellSet(); + if (!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); + if (!cell.getWest().equals(blocked)) result.add(cell.getWest()); + return result; + } + + public CellSet followCorner4(Cell cell) { + return followCorner4(cell, null); + } + + public CellSet followCorner4(Cell cell, Cell blocked) { + if (!isCorner4(cell)) return null; + CellSet result = new CellSet(); + if (!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); + if (!cell.getEast().equals(blocked)) result.add(cell.getEast()); + return result; + } + + + public CellSet followStub(Cell cell) { + return followStub(cell, null); + } + + public CellSet followStub(Cell cell, Cell blocked) { + if (!isStub(cell)) return null; + CellSet result = new CellSet(); + if (isBoundary(cell.getEast())) result.add(cell.getEast()); + else if (isBoundary(cell.getWest())) result.add(cell.getWest()); + else if (isBoundary(cell.getNorth())) result.add(cell.getNorth()); + else if (isBoundary(cell.getSouth())) result.add(cell.getSouth()); + if (result.contains(blocked)) result.remove(blocked); + return result; + } + + public CellSet followCell(Cell cell) { + return followCell(cell, null); + } + + public CellSet followCell(Cell cell, Cell blocked) { + if (isIntersection(cell)) return followIntersection(cell, blocked); + if (isCorner(cell)) return followCorner(cell, blocked); + if (isLine(cell)) return followLine(cell, blocked); + if (isStub(cell)) return followStub(cell, blocked); + if (isCrossOnLine(cell)) return followCrossOnLine(cell, blocked); + System.err.println("Ambiguous input at position " + cell + ":"); + TextGrid subGrid = getTestingSubGrid(cell); + subGrid.printDebug(); + throw new RuntimeException("Cannot follow cell " + cell + ": cannot determine cell type"); + } + + public String getCellTypeAsString(Cell cell) { + if (isK(cell)) return "K"; + if (isT(cell)) return "T"; + if (isInverseK(cell)) return "inverse K"; + if (isInverseT(cell)) return "inverse T"; + if (isCorner1(cell)) return "corner 1"; + if (isCorner2(cell)) return "corner 2"; + if (isCorner3(cell)) return "corner 3"; + if (isCorner4(cell)) return "corner 4"; + if (isLine(cell)) return "line"; + if (isStub(cell)) return "stub"; + if (isCrossOnLine(cell)) return "crossOnLine"; + return "unrecognisable type"; + } + + + public CellSet followCrossOnLine(Cell cell, Cell blocked) { + CellSet result = new CellSet(); + if (isHorizontalCrossOnLine(cell)) { + result.add(cell.getEast()); + result.add(cell.getWest()); + } else if (isVerticalCrossOnLine(cell)) { + result.add(cell.getNorth()); + result.add(cell.getSouth()); + } + if (result.contains(blocked)) result.remove(blocked); + return result; + } + + public boolean isOutOfBounds(Cell cell) { + if (cell.x > getWidth() - 1 + || cell.y > getHeight() - 1 + || cell.x < 0 + || cell.y < 0) return true; + return false; + } + + public boolean isOutOfBounds(int x, int y) { + char c = get(x, y); + if (0 == c) return true; + return false; + } + + public boolean isBlank(Cell cell) { + char c = get(cell); + if (0 == c) return false; + return c == ' '; + } + + public boolean isBlank(int x, int y) { + char c = get(x, y); + if (0 == c) return true; + return c == ' '; + } + + public boolean isCorner(Cell cell) { + return isCorner(cell.x, cell.y); + } + + public boolean isCorner(int x, int y) { + return (isNormalCorner(x, y) || isRoundCorner(x, y)); + } + + + public boolean matchesAny(Cell cell, GridPatternGroup criteria) { + TextGrid subGrid = getTestingSubGrid(cell); + return subGrid.matchesAny(criteria); + } + + public boolean isCorner1(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner1Criteria); + } + + public boolean isCorner2(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner2Criteria); + } + + public boolean isCorner3(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner3Criteria); + } + + public boolean isCorner4(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner4Criteria); + } + + public boolean isCross(Cell cell) { + return matchesAny(cell, GridPatternGroup.crossCriteria); + } + + public boolean isK(Cell cell) { + return matchesAny(cell, GridPatternGroup.KCriteria); + } + + public boolean isInverseK(Cell cell) { + return matchesAny(cell, GridPatternGroup.inverseKCriteria); + } + + public boolean isT(Cell cell) { + return matchesAny(cell, GridPatternGroup.TCriteria); + } + + public boolean isInverseT(Cell cell) { + return matchesAny(cell, GridPatternGroup.inverseTCriteria); + } + + public boolean isNormalCorner(Cell cell) { + return matchesAny(cell, GridPatternGroup.normalCornerCriteria); + } + + public boolean isNormalCorner(int x, int y) { + return isNormalCorner(new Cell(x, y)); + } + + public boolean isRoundCorner(Cell cell) { + return matchesAny(cell, GridPatternGroup.roundCornerCriteria); + } + + public boolean isRoundCorner(int x, int y) { + return isRoundCorner(new Cell(x, y)); + } + + public boolean isIntersection(Cell cell) { + return matchesAny(cell, GridPatternGroup.intersectionCriteria); + } + + public boolean isIntersection(int x, int y) { + return isIntersection(new Cell(x, y)); + } + + public void copyCellsTo(CellSet cells, TextGrid grid) { + Iterator it = cells.iterator(); + while (it.hasNext()) { + Cell cell = (Cell) it.next(); + grid.set(cell, this.get(cell)); + } + } + + public boolean equals(TextGrid grid) { + if (grid.getHeight() != this.getHeight() + || grid.getWidth() != this.getWidth() + ) { + return false; + } + int height = grid.getHeight(); + for (int i = 0; i < height; i++) { + String row1 = this.getRow(i).toString(); + String row2 = grid.getRow(i).toString(); + if (!row1.equals(row2)) return false; + } + return true; + } + + /** + * Fills all the cells in cells with c + * + * @param cells + * @param c + */ + public void fillCellsWith(Iterable cells, char c) { + Iterator it = cells.iterator(); + while (it.hasNext()) { + Cell cell = it.next(); + set(cell.x, cell.y, c); + } + } + + /** + * Fills the continuous area with if c1 characters with c2, + * flooding from cell x, y + * + * @param x + * @param y + * @param c1 the character to replace + * @param c2 the character to replace c1 with + * @return the list of cells filled + */ // public CellSet fillContinuousArea(int x, int y, char c1, char c2){ // CellSet cells = new CellSet(); // //fillContinuousArea_internal(x, y, c1, c2, cells); // seedFill(new Cell(x, y), c1, c2); // return cells; // } + public CellSet fillContinuousArea(int x, int y, char c) { + return fillContinuousArea(new Cell(x, y), c); + } + + public CellSet fillContinuousArea(Cell cell, char c) { + if (isOutOfBounds(cell)) throw new IllegalArgumentException("Attempted to fill area out of bounds: " + cell); + return seedFillOld(cell, c); + } + + private CellSet seedFill(Cell seed, char newChar) { + CellSet cellsFilled = new CellSet(); + char oldChar = get(seed); + + if (oldChar == newChar) return cellsFilled; + if (isOutOfBounds(seed)) return cellsFilled; + + Stack stack = new Stack(); + + stack.push(seed); + + while (!stack.isEmpty()) { + Cell cell = (Cell) stack.pop(); + + //set(cell, newChar); + cellsFilled.add(cell); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if (get(nCell) == oldChar && !cellsFilled.contains(nCell)) stack.push(nCell); + if (get(sCell) == oldChar && !cellsFilled.contains(sCell)) stack.push(sCell); + if (get(eCell) == oldChar && !cellsFilled.contains(eCell)) stack.push(eCell); + if (get(wCell) == oldChar && !cellsFilled.contains(wCell)) stack.push(wCell); + } + + return cellsFilled; + } + + private CellSet seedFillOld(Cell seed, char newChar) { + CellSet cellsFilled = new CellSet(); + char oldChar = get(seed); + + if (oldChar == newChar) return cellsFilled; + if (isOutOfBounds(seed)) return cellsFilled; + + Stack stack = new Stack(); + + stack.push(seed); + + while (!stack.isEmpty()) { + Cell cell = (Cell) stack.pop(); + + set(cell, newChar); + cellsFilled.add(cell); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if (get(nCell) == oldChar) stack.push(nCell); + if (get(sCell) == oldChar) stack.push(sCell); + if (get(eCell) == oldChar) stack.push(eCell); + if (get(wCell) == oldChar) stack.push(wCell); + } + + return cellsFilled; + } + + + /** + * Locates and returns the '*' boundaries that we would + * encounter if we did a flood-fill at seed. + * + * @param seed + * @return + */ + public CellSet findBoundariesExpandingFrom(Cell seed) { + CellSet boundaries = new CellSet(); + char oldChar = get(seed); + + if (isOutOfBounds(seed)) return boundaries; + + char newChar = 1; //TODO: kludge + + Stack stack = new Stack(); + + stack.push(seed); + + while (!stack.isEmpty()) { + Cell cell = (Cell) stack.pop(); + + set(cell, newChar); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if (get(nCell) == oldChar) stack.push(nCell); + else if (get(nCell) == '*') boundaries.add(nCell); + + if (get(sCell) == oldChar) stack.push(sCell); + else if (get(sCell) == '*') boundaries.add(sCell); + + if (get(eCell) == oldChar) stack.push(eCell); + else if (get(eCell) == '*') boundaries.add(eCell); + + if (get(wCell) == oldChar) stack.push(wCell); + else if (get(wCell) == '*') boundaries.add(wCell); + } + + return boundaries; + } + + + //TODO: incomplete method seedFillLine() + private CellSet seedFillLine(Cell cell, char newChar) { + CellSet cellsFilled = new CellSet(); + + Stack stack = new Stack(); + + char oldChar = get(cell); + + if (oldChar == newChar) return cellsFilled; + if (isOutOfBounds(cell)) return cellsFilled; + + stack.push(new LineSegment(cell.x, cell.x, cell.y, 1)); + stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, -1)); + + int left; + while (!stack.isEmpty()) { + LineSegment segment = (LineSegment) stack.pop(); + int x; + //expand to the left + for ( + x = segment.x1; + x >= 0 && get(x, segment.y) == oldChar; + --x) { + set(x, segment.y, newChar); + cellsFilled.add(new Cell(x, segment.y)); + } + + left = cell.getEast().x; + boolean skip = (x > segment.x1) ? true : false; + + if (left < segment.x1) { //leak on left? + //TODO: i think the first param should be x + stack.push( + //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); + new LineSegment(x, left, segment.y - 1, -segment.dy)); + } + + x = segment.x1 + 1; + do { + if (!skip) { + for (; x < getWidth() && get(x, segment.y) == oldChar; ++x) { + set(x, segment.y, newChar); + cellsFilled.add(new Cell(x, segment.y)); + } + + stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); + if (x > segment.x2 + 1) //leak on right? + stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, -segment.dy)); + } + skip = false; //skip only once + + for (++x; x <= segment.x2 && get(x, segment.y) != oldChar; ++x) { + ; + } + left = x; + } while (x < segment.x2); + } + + return cellsFilled; + } + + public boolean cellContainsDashedLineChar(Cell cell) { + char c = get(cell); + return StringUtils.isOneOf(c, dashedLines); + } + + public boolean loadFrom(String filename) + throws FileNotFoundException, IOException { + return loadFrom(filename, null); + } + + public boolean loadFrom(String filename, ProcessingOptions options) + throws IOException { + + String encoding = (options == null) ? null : options.getCharacterEncoding(); + ArrayList lines = new ArrayList(); + InputStream is; + if ("-".equals(filename)) + is = System.in; + else + is = new FileInputStream(filename); + String[] linesArray = FileUtils.readFile(is, filename, encoding).split("(\r)?\n"); + for (int i = 0; i < linesArray.length; i++) + lines.add(new StringBuilder(linesArray[i])); + + return initialiseWithLines(lines, options); + } + + public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException { + + ArrayList lines = new ArrayList(); + String[] linesArray = text.split("(\r)?\n"); + for (int i = 0; i < linesArray.length; i++) + lines.add(new StringBuilder(linesArray[i])); + + return initialiseWithLines(lines, options); + } + + public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException { + + //remove blank rows at the bottom + boolean done = false; + int i; + for (i = lines.size() - 1; i >= 0 && !done; i--) { + StringBuilder row = lines.get(i); + if (!StringUtils.isBlank(row.toString())) done = true; + } + rows = new ArrayList(lines.subList(0, i + 2)); + this.updateModeRows(); + try { + + if (options != null) fixTabs(options.getTabSize()); + else fixTabs(options.DEFAULT_TAB_SIZE); + + + // make all lines of equal length + // add blank outline around the buffer to prevent fill glitch + // convert tabs to spaces (or remove them if setting is 0) + + int blankBorderSize = 2; + + int maxLength = 0; + int index = 0; + + String encoding = null; + if (options != null) encoding = options.getCharacterEncoding(); + + Iterator it = rows.iterator(); + while (it.hasNext()) { + String row = it.next().toString(); + if (encoding != null) { + byte[] bytes = row.getBytes(); + row = new String(bytes, encoding); + } + if (row.length() > maxLength) maxLength = row.length(); + rows.set(index, new StringBuilder(row)); + index++; + } + + it = rows.iterator(); + ArrayList newRows = new ArrayList(); + //TODO: make the following depend on blankBorderSize + + StringBuilder topBottomRow = + new StringBuilder(StringUtils.repeatString(" ", maxLength + blankBorderSize * 2)); + + newRows.add(topBottomRow); + newRows.add(topBottomRow); + while (it.hasNext()) { + StringBuilder row = it.next(); + + if (row.length() < maxLength) { + String borderString = StringUtils.repeatString(" ", blankBorderSize); + StringBuilder newRow = new StringBuilder(); + + newRow.append(borderString); + newRow.append(row); + newRow.append(StringUtils.repeatString(" ", maxLength - row.length())); + newRow.append(borderString); + + newRows.add(newRow); + } else { //TODO: why is the following line like that? + newRows.add(new StringBuilder(" ").append(row).append(" ")); + } + } + //TODO: make the following depend on blankBorderSize + newRows.add(topBottomRow); + newRows.add(topBottomRow); + rows = newRows; + } finally { + this.updateModeRows(); + } + + replaceBullets(); + replaceHumanColorCodes(); + + return true; + } + + private void fixTabs(int tabSize) { + + int rowIndex = 0; + Iterator it = rows.iterator(); + + while (it.hasNext()) { + String row = it.next().toString(); + StringBuilder newRow = new StringBuilder(); + + char[] chars = row.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '\t') { + int spacesLeft = tabSize - newRow.length() % tabSize; + if (DEBUG) { + System.out.println("Found tab. Spaces left: " + spacesLeft); + } + String spaces = StringUtils.repeatString(" ", spacesLeft); + newRow.append(spaces); + } else { + String character = Character.toString(chars[i]); + newRow.append(character); + } + } + rows.set(rowIndex, newRow); + rowIndex++; + } + } + + /** + * @return + */ + protected ArrayList getRows() { + return rows; + } + + private boolean isInPlainMode(Cell cell) { + return this.modeRows.get(cell.y).charAt(cell.x) == PLAIN_MODE; + } + + public class CellColorPair { + public CellColorPair(Cell cell, Color color) { + this.cell = cell; + this.color = color; + } + + public Color color; + public Cell cell; + } + + public class CellStringPair { + public CellStringPair(Cell cell, String string) { + this.cell = cell; + this.string = string; + } + + public Cell cell; + public String string; + } + + public class CellTagPair { + public CellTagPair(Cell cell, String tag) { + this.cell = cell; + this.tag = tag; + } + + public Cell cell; + public String tag; + } + + + public class Cell { + + public int x, y; + + public Cell(Cell cell) { + this(cell.x, cell.y); + } + + public Cell(int x, int y) { + this.x = x; + this.y = y; + } + + public Cell getNorth() { + return new Cell(x, y - 1); + } + + public Cell getSouth() { + return new Cell(x, y + 1); + } + + public Cell getEast() { + return new Cell(x + 1, y); + } + + public Cell getWest() { + return new Cell(x - 1, y); + } + + public Cell getNW() { + return new Cell(x - 1, y - 1); + } + + public Cell getNE() { + return new Cell(x + 1, y - 1); + } + + public Cell getSW() { + return new Cell(x - 1, y + 1); + } + + public Cell getSE() { + return new Cell(x + 1, y + 1); + } + + public CellSet getNeighbours4() { + CellSet result = new CellSet(); + + result.add(getNorth()); + result.add(getSouth()); + result.add(getWest()); + result.add(getEast()); + + return result; + } + + public CellSet getNeighbours8() { + CellSet result = new CellSet(); + + result.add(getNorth()); + result.add(getSouth()); + result.add(getWest()); + result.add(getEast()); + + result.add(getNW()); + result.add(getNE()); + result.add(getSW()); + result.add(getSE()); + + return result; + } + + + public boolean isNorthOf(Cell cell) { + if (this.y < cell.y) return true; + return false; + } + + public boolean isSouthOf(Cell cell) { + if (this.y > cell.y) return true; + return false; + } + + public boolean isWestOf(Cell cell) { + if (this.x < cell.x) return true; + return false; + } + + public boolean isEastOf(Cell cell) { + if (this.x > cell.x) return true; + return false; + } + - public CellSet fillContinuousArea(int x, int y, char c){ - return fillContinuousArea(new Cell(x, y), c); - } - - public CellSet fillContinuousArea(Cell cell, char c){ - if(isOutOfBounds(cell)) throw new IllegalArgumentException("Attempted to fill area out of bounds: "+cell); - return seedFillOld(cell, c); - } - - private CellSet seedFill(Cell seed, char newChar){ - CellSet cellsFilled = new CellSet(); - char oldChar = get(seed); - - if(oldChar == newChar) return cellsFilled; - if(isOutOfBounds(seed)) return cellsFilled; - - Stack stack = new Stack(); - - stack.push(seed); - - while(!stack.isEmpty()){ - Cell cell = (Cell) stack.pop(); - - //set(cell, newChar); - cellsFilled.add(cell); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if(get(nCell) == oldChar && !cellsFilled.contains(nCell)) stack.push(nCell); - if(get(sCell) == oldChar && !cellsFilled.contains(sCell)) stack.push(sCell); - if(get(eCell) == oldChar && !cellsFilled.contains(eCell)) stack.push(eCell); - if(get(wCell) == oldChar && !cellsFilled.contains(wCell)) stack.push(wCell); - } - - return cellsFilled; - } - - private CellSet seedFillOld(Cell seed, char newChar){ - CellSet cellsFilled = new CellSet(); - char oldChar = get(seed); - - if(oldChar == newChar) return cellsFilled; - if(isOutOfBounds(seed)) return cellsFilled; - - Stack stack = new Stack(); - - stack.push(seed); - - while(!stack.isEmpty()){ - Cell cell = (Cell) stack.pop(); - - set(cell, newChar); - cellsFilled.add(cell); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if(get(nCell) == oldChar) stack.push(nCell); - if(get(sCell) == oldChar) stack.push(sCell); - if(get(eCell) == oldChar) stack.push(eCell); - if(get(wCell) == oldChar) stack.push(wCell); - } - - return cellsFilled; - } - - - /** - * - * Locates and returns the '*' boundaries that we would - * encounter if we did a flood-fill at seed. - * - * @param seed - * @return - */ - public CellSet findBoundariesExpandingFrom(Cell seed){ - CellSet boundaries = new CellSet(); - char oldChar = get(seed); - - if(isOutOfBounds(seed)) return boundaries; - - char newChar = 1; //TODO: kludge - - Stack stack = new Stack(); - - stack.push(seed); - - while(!stack.isEmpty()){ - Cell cell = (Cell) stack.pop(); - - set(cell, newChar); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if(get(nCell) == oldChar) stack.push(nCell); - else if(get(nCell) == '*') boundaries.add(nCell); - - if(get(sCell) == oldChar) stack.push(sCell); - else if(get(sCell) == '*') boundaries.add(sCell); - - if(get(eCell) == oldChar) stack.push(eCell); - else if(get(eCell) == '*') boundaries.add(eCell); - - if(get(wCell) == oldChar) stack.push(wCell); - else if(get(wCell) == '*') boundaries.add(wCell); - } - - return boundaries; - } - - - //TODO: incomplete method seedFillLine() - private CellSet seedFillLine(Cell cell, char newChar){ - CellSet cellsFilled = new CellSet(); - - Stack stack = new Stack(); - - char oldChar = get(cell); - - if(oldChar == newChar) return cellsFilled; - if(isOutOfBounds(cell)) return cellsFilled; - - stack.push(new LineSegment(cell.x, cell.x, cell.y, 1)); - stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, -1)); - - int left; - while(!stack.isEmpty()){ - LineSegment segment = (LineSegment) stack.pop(); - int x; - //expand to the left - for( - x = segment.x1; - x >= 0 && get(x, segment.y) == oldChar; - --x){ - set(x, segment.y, newChar); - cellsFilled.add(new Cell(x, segment.y)); - } - - left = cell.getEast().x; - boolean skip = (x > segment.x1)? true : false; - - if(left < segment.x1){ //leak on left? - //TODO: i think the first param should be x - stack.push( - //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); - new LineSegment(x, left, segment.y - 1, -segment.dy)); - } - - x = segment.x1 + 1; - do { - if(!skip) { - for( ; x < getWidth() && get(x, segment.y) == oldChar; ++x){ - set(x, segment.y, newChar); - cellsFilled.add(new Cell(x, segment.y)); - } - - stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); - if(x > segment.x2 + 1) //leak on right? - stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, -segment.dy)); - } - skip = false; //skip only once - - for(++x; x <= segment.x2 && get(x, segment.y) != oldChar; ++x){;} - left = x; - } while( x < segment.x2); - } - - return cellsFilled; - } - - public boolean cellContainsDashedLineChar(Cell cell){ - char c = get(cell); - return StringUtils.isOneOf(c, dashedLines); - } - - public boolean loadFrom(String filename) - throws FileNotFoundException, IOException - { - return loadFrom(filename, null); - } - - public boolean loadFrom(String filename, ProcessingOptions options) - throws IOException - { - - String encoding = (options == null) ? null : options.getCharacterEncoding(); - ArrayList lines = new ArrayList(); - InputStream is; - if ("-".equals(filename)) - is = System.in; - else - is = new FileInputStream(filename); - String[] linesArray = FileUtils.readFile(is, filename, encoding).split("(\r)?\n"); - for(int i = 0; i < linesArray.length; i++) - lines.add(new StringBuilder(linesArray[i])); - - return initialiseWithLines(lines, options); - } - - public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException { - - ArrayList lines = new ArrayList(); - String[] linesArray = text.split("(\r)?\n"); - for(int i = 0; i < linesArray.length; i++) - lines.add(new StringBuilder(linesArray[i])); - - return initialiseWithLines(lines, options); - } - - public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException { - - //remove blank rows at the bottom - boolean done = false; - int i; - for(i = lines.size() - 1; i >= 0 && !done; i--){ - StringBuilder row = lines.get(i); - if(!StringUtils.isBlank(row.toString())) done = true; - } - rows = new ArrayList(lines.subList(0, i + 2)); - - if(options != null) fixTabs(options.getTabSize()); - else fixTabs(options.DEFAULT_TAB_SIZE); - - - // make all lines of equal length - // add blank outline around the buffer to prevent fill glitch - // convert tabs to spaces (or remove them if setting is 0) - - int blankBorderSize = 2; - - int maxLength = 0; - int index = 0; - - String encoding = null; - if(options != null) encoding = options.getCharacterEncoding(); - - Iterator it = rows.iterator(); - while(it.hasNext()){ - String row = it.next().toString(); - if(encoding != null){ - byte[] bytes = row.getBytes(); - row = new String(bytes, encoding); - } - if(row.length() > maxLength) maxLength = row.length(); - rows.set(index, new StringBuilder(row)); - index++; - } - - it = rows.iterator(); - ArrayList newRows = new ArrayList(); - //TODO: make the following depend on blankBorderSize - - StringBuilder topBottomRow = - new StringBuilder(StringUtils.repeatString(" ", maxLength + blankBorderSize * 2)); - - newRows.add(topBottomRow); - newRows.add(topBottomRow); - while(it.hasNext()){ - StringBuilder row = it.next(); - - if(row.length() < maxLength) { - String borderString = StringUtils.repeatString(" ", blankBorderSize); - StringBuilder newRow = new StringBuilder(); - - newRow.append(borderString); - newRow.append(row); - newRow.append(StringUtils.repeatString(" ", maxLength - row.length())); - newRow.append(borderString); - - newRows.add(newRow); - } else { //TODO: why is the following line like that? - newRows.add(new StringBuilder(" ").append(row).append(" ")); - } - } - //TODO: make the following depend on blankBorderSize - newRows.add(topBottomRow); - newRows.add(topBottomRow); - rows = newRows; - - replaceBullets(); - replaceHumanColorCodes(); - - return true; - } - - private void fixTabs(int tabSize){ - - int rowIndex = 0; - Iterator it = rows.iterator(); - - while(it.hasNext()){ - String row = it.next().toString(); - StringBuilder newRow = new StringBuilder(); - - char[] chars = row.toCharArray(); - for(int i = 0; i < chars.length; i++){ - if(chars[i] == '\t'){ - int spacesLeft = tabSize - newRow.length() % tabSize; - if(DEBUG){ - System.out.println("Found tab. Spaces left: "+spacesLeft); - } - String spaces = StringUtils.repeatString(" ", spacesLeft); - newRow.append(spaces); - } else { - String character = Character.toString(chars[i]); - newRow.append(character); - } - } - rows.set(rowIndex, newRow); - rowIndex++; - } - } - - /** - * @return - */ - protected ArrayList getRows() { - return rows; - } - - public class CellColorPair{ - public CellColorPair(Cell cell, Color color){ - this.cell = cell; - this.color = color; - } - public Color color; - public Cell cell; - } - - public class CellStringPair{ - public CellStringPair(Cell cell, String string){ - this.cell = cell; - this.string = string; - } - public Cell cell; - public String string; - } - - public class CellTagPair{ - public CellTagPair(Cell cell, String tag){ - this.cell = cell; - this.tag = tag; - } - public Cell cell; - public String tag; - } - - - public class Cell{ - - public int x, y; - - public Cell(Cell cell){ - this(cell.x, cell.y); - } - - public Cell(int x, int y){ - this.x = x; - this.y = y; - } - - public Cell getNorth(){ return new Cell(x, y - 1); } - public Cell getSouth(){ return new Cell(x, y + 1); } - public Cell getEast(){ return new Cell(x + 1, y); } - public Cell getWest(){ return new Cell(x - 1, y); } - - public Cell getNW(){ return new Cell(x - 1, y - 1); } - public Cell getNE(){ return new Cell(x + 1, y - 1); } - public Cell getSW(){ return new Cell(x - 1, y + 1); } - public Cell getSE(){ return new Cell(x + 1, y + 1); } - - public CellSet getNeighbours4(){ - CellSet result = new CellSet(); - - result.add(getNorth()); - result.add(getSouth()); - result.add(getWest()); - result.add(getEast()); - - return result; - } - - public CellSet getNeighbours8(){ - CellSet result = new CellSet(); - - result.add(getNorth()); - result.add(getSouth()); - result.add(getWest()); - result.add(getEast()); - - result.add(getNW()); - result.add(getNE()); - result.add(getSW()); - result.add(getSE()); - - return result; - } - - - public boolean isNorthOf(Cell cell){ - if(this.y < cell.y) return true; - return false; - } - - public boolean isSouthOf(Cell cell){ - if(this.y > cell.y) return true; - return false; - } - - public boolean isWestOf(Cell cell){ - if(this.x < cell.x) return true; - return false; - } - - public boolean isEastOf(Cell cell){ - if(this.x > cell.x) return true; - return false; - } - - - public boolean equals(Object o){ - Cell cell = (Cell) o; - if(cell == null) return false; - if(x == cell.x && y == cell.y) return true; - else return false; - } - - public int hashCode() { - return (x << 16) | y; - } - - public boolean isNextTo(int x2, int y2){ - if(Math.abs(x2 - x) == 1 && Math.abs(y2 - y) == 1) return false; - if(Math.abs(x2 - x) == 1 && y2 == y) return true; - if(Math.abs(y2 - y) == 1 && x2 == x) return true; - return false; - } - - public boolean isNextTo(Cell cell){ - if(cell == null) throw new IllegalArgumentException("cell cannot be null"); - return this.isNextTo(cell.x, cell.y); - } - - public String toString(){ - return "("+x+", "+y+")"; - } - - public void scale(int s){ - x = x * s; - y = y * s; - } - - } - - private class LineSegment{ - int x1, x2, y, dy; - public LineSegment(int x1, int x2, int y, int dy){ - this.x1 = x1; - this.x2 = x2; - this.y = y; - this.dy = dy; - } - } + public boolean equals(Object o) { + Cell cell = (Cell) o; + if (cell == null) return false; + if (x == cell.x && y == cell.y) return true; + else return false; + } + + public int hashCode() { + return (x << 16) | y; + } + + public boolean isNextTo(int x2, int y2) { + if (Math.abs(x2 - x) == 1 && Math.abs(y2 - y) == 1) return false; + if (Math.abs(x2 - x) == 1 && y2 == y) return true; + if (Math.abs(y2 - y) == 1 && x2 == x) return true; + return false; + } + + public boolean isNextTo(Cell cell) { + if (cell == null) throw new IllegalArgumentException("cell cannot be null"); + return this.isNextTo(cell.x, cell.y); + } + + public String toString() { + return "(" + x + ", " + y + ")"; + } + + public void scale(int s) { + x = x * s; + y = y * s; + } + + } + + private class LineSegment { + int x1, x2, y, dy; + + public LineSegment(int x1, int x2, int y, int dy) { + this.x1 = x1; + this.x2 = x2; + this.y = y; + this.dy = dy; + } + } } diff --git a/test-resources/text/art-latexmath-1.txt b/test-resources/text/art-latexmath-1.txt new file mode 100644 index 0000000..69513b5 --- /dev/null +++ b/test-resources/text/art-latexmath-1.txt @@ -0,0 +1,13 @@ + ++---------------------+ +------+ +|$\sum_{i=0}^{n}x^i$ | |$cBLU$| /----\ +| +--->|cRED +--+cGRE| +| | | | \----/ ++---------------------+ +---+--+ + | + V + +-----------------+ + |$A_i$ hello $B^i$| + +-----------------+ + +$Q_u^i$, $C_k$, $B_r^{own}$, $F_{ox}$ jumps over a lazy $d\cdot\frac{o}{g}$. diff --git a/test/java/sandbox/Sandbox.java b/test/java/sandbox/Sandbox.java new file mode 100644 index 0000000..7ec90df --- /dev/null +++ b/test/java/sandbox/Sandbox.java @@ -0,0 +1,143 @@ +package sandbox; + +import org.junit.Test; +import org.scilab.forge.jlatexmath.TeXConstants; +import org.scilab.forge.jlatexmath.TeXFormula; +import org.scilab.forge.jlatexmath.TeXIcon; +import org.stathissideris.ascii2image.graphics.DiagramText; +import org.stathissideris.ascii2image.text.StringUtils; +import org.stathissideris.ascii2image.text.TextGrid; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.io.File; +import java.io.IOException; +import java.util.Iterator; + +import static com.github.dakusui.crest.Crest.asString; +import static com.github.dakusui.crest.Crest.assertThat; + +public class Sandbox { + @Test + public void latexMath() throws IOException { + String latex = "$\\sum_{i=0}^{n}x^i$"; + TeXFormula formula = new TeXFormula(latex); + + // Note: Old interface for creating icons: + // TeXIcon icon = formula.createTeXIcon(TeXConstants.STYLE_DISPLAY, 20); + // Note: New interface using builder pattern (inner class): + TeXIcon icon = formula.new TeXIconBuilder().setStyle(TeXConstants.STYLE_DISPLAY).setSize(20) + .build(); + + icon.setInsets(new Insets(5, 5, 5, 5)); + + BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), + BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = image.createGraphics(); + g2.setColor(Color.white); + g2.fillRect(0, 0, icon.getIconWidth(), icon.getIconHeight()); + JLabel jl = new JLabel(); + jl.setForeground(new Color(0, 0, 0)); + icon.paintIcon(jl, g2, 0, 0); + File file = new File("target/Example1.png"); + ImageIO.write(image, "png", file.getAbsoluteFile()); + } + + @Test + public void latexMath2() throws IOException { + String latex = "$\\sum_{i=0}^{n}x^i$"; + + BufferedImage image = new BufferedImage( + 640, 480, + BufferedImage.TYPE_INT_ARGB + ); + DiagramText.drawTeXFormula(image.createGraphics(), latex, 0, 0, Color.black, 20); + File file = new File("target/Example1.png"); + ImageIO.write(image, "png", file.getAbsoluteFile()); + } + + /** + * This method is based on a discussion How to compare images for similarity made in the Stackoverflow.com + * If 2 images are identical, this method will return 1.0. + * + * @param fileA input image A + * @param fileB input image B + * @return The similarity. + */ + public static double compareImages(File fileA, File fileB) { + double percentage = 0; + try { + // take buffer data from both image files // + BufferedImage biA = ImageIO.read(fileA); + DataBuffer dbA = biA.getData().getDataBuffer(); + int sizeA = dbA.getSize(); + BufferedImage biB = ImageIO.read(fileB); + DataBuffer dbB = biB.getData().getDataBuffer(); + int sizeB = dbB.getSize(); + int count = 0; + // compare data-buffer objects // + if (sizeA == sizeB) { + + for (int i = 0; i < sizeA; i++) { + + if (dbA.getElem(i) == dbB.getElem(i)) { + count = count + 1; + } + + } + percentage = ((double) count * 100) / sizeA; + } else { + System.out.println("Both the images are not of same size"); + } + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + return percentage; + } + + + @Test + public void givenStringContainingLaTeXFormula4whenTransform$thenResultIsCorrect() { + assertThat( + TextGrid.transformRowToModeRow("xyz$xyz^^^$xyz"), + asString().equalTo("PPPLLLLLLLLPPP").$() + ); + } + + @Test(expected = IllegalArgumentException.class) + public void givenStringContainingUnfinishedLaTeXFormula$whenTransform$thenIllegalArgumentException() { + TextGrid.transformRowToModeRow("xyz$xyz^^^_xyz"); + } + + @Test + public void givenStringNotContainingLaTeXFormula$whenTransform$thenResultIsCorrect() { + assertThat( + TextGrid.transformRowToModeRow("xyz_xyz^^^_xyz"), + asString().equalTo("PPPPPPPPPPPPPP").$() + ); + } + + @Test + public void givenEmptyString$whenTransform$thenResultIsEmpty() { + assertThat( + TextGrid.transformRowToModeRow(""), + asString().equalTo("").$() + ); + } + + + @Test + public void testSplit() { + String s = "hello$HELLO$ world"; + Iterator i = StringUtils.createTextSplitter(DiagramText.TEXT_SPLITTING_REGEX, s); + + while (i.hasNext()) + System.out.println(i.next()); + } + +} From f988a641b0d93e324fc10bdaf57c63502a182a04 Mon Sep 17 00:00:00 2001 From: dakusui Date: Sat, 27 Oct 2018 17:17:32 +0900 Subject: [PATCH 03/12] Fix a bug in rendering equations containing ditaa's symbols. --- .../ascii2image/text/TextGrid.java | 3719 +++++++++-------- test-resources/text/art-latexmath-1.txt | 12 +- 2 files changed, 1941 insertions(+), 1790 deletions(-) diff --git a/src/java/org/stathissideris/ascii2image/text/TextGrid.java b/src/java/org/stathissideris/ascii2image/text/TextGrid.java index af6fb90..5ffe52e 100644 --- a/src/java/org/stathissideris/ascii2image/text/TextGrid.java +++ b/src/java/org/stathissideris/ascii2image/text/TextGrid.java @@ -28,8 +28,6 @@ import java.util.function.IntUnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static java.util.stream.Collectors.toList; import static org.stathissideris.ascii2image.text.StringUtils.createTextSplitter; @@ -40,1912 +38,2055 @@ */ public class TextGrid { - private static final boolean DEBUG = false; - private static final char PLAIN_MODE = 'P'; - public static final char LATEX_MODE = 'L'; - - private ArrayList rows; - private List modeRows; - - private static char[] boundaries = {'/', '\\', '|', '-', '*', '=', ':'}; - private static char[] undisputableBoundaries = {'|', '-', '*', '=', ':'}; - private static char[] horizontalLines = {'-', '='}; - private static char[] verticalLines = {'|', ':'}; - private static char[] arrowHeads = {'<', '>', '^', 'v', 'V'}; - private static char[] cornerChars = {'\\', '/', '+'}; - private static char[] pointMarkers = {'*'}; - private static char[] dashedLines = {':', '~', '='}; - - private static char[] entryPoints1 = {'\\'}; - private static char[] entryPoints2 = {'|', ':', '+', '\\', '/'}; - private static char[] entryPoints3 = {'/'}; - private static char[] entryPoints4 = {'-', '=', '+', '\\', '/'}; - private static char[] entryPoints5 = {'\\'}; - private static char[] entryPoints6 = {'|', ':', '+', '\\', '/'}; - private static char[] entryPoints7 = {'/'}; - private static char[] entryPoints8 = {'-', '=', '+', '\\', '/'}; - - - private static HashMap humanColorCodes = new HashMap(); - - static { - humanColorCodes.put("GRE", "9D9"); - humanColorCodes.put("BLU", "55B"); - humanColorCodes.put("PNK", "FAA"); - humanColorCodes.put("RED", "E32"); - humanColorCodes.put("YEL", "FF3"); - humanColorCodes.put("BLK", "000"); - - } - - private static HashSet markupTags = - new HashSet(); - - static { - markupTags.add("d"); - markupTags.add("s"); - markupTags.add("io"); - markupTags.add("c"); - markupTags.add("mo"); - markupTags.add("tr"); - markupTags.add("o"); - } - - private void updateModeRows() { - // Collectors.toList creates ArrayList and you see no performance penalty - // caused by using LinkedList here. - this.modeRows = this.rows.stream().map(TextGrid::transformRowToModeRow).collect(toList()); - } - - public static String transformRowToModeRow(CharSequence row) { - StringBuilder b = new StringBuilder(); - row.chars().map(new IntUnaryOperator() { - /** - * This field hold current mode of a cell in the grid. - * Only 2 values can be assigned, which are 'P' and 'L'. - * They respectively represent 'Plain', which is normal ditaa mode, and 'LaTeX', which is LaTeX - * formula mode. - */ - int cur = PLAIN_MODE; - - @Override - public int applyAsInt(int operand) { - if (cur == PLAIN_MODE) { - if (operand == '$') { - cur = LATEX_MODE; - return LATEX_MODE; - } - return PLAIN_MODE; - } else if (cur == LATEX_MODE) { - if (operand == '$') { - cur = PLAIN_MODE; - return LATEX_MODE; - } - return this.cur; - } - throw new IllegalStateException(); - } - }).forEach(c -> b.append((char) c)); - String ret = b.toString(); - if (ret.endsWith("L")) - if (row.charAt(row.length() - 1) != '$') - throw new IllegalArgumentException("LaTex mode was started but not finished."); - return ret; - } - - public void addToMarkupTags(Collection tags) { - markupTags.addAll(tags); - } - - public static void main(String[] args) throws Exception { - TextGrid grid = new TextGrid(); - grid.loadFrom("tests/text/art10.txt"); - - grid.writeStringTo(grid.new Cell(28, 1), "testing"); - - grid.findMarkupTags(); - - grid.printDebug(); - //System.out.println(grid.fillContinuousArea(0, 0, '-').size()+" cells filled"); - //grid.fillContinuousArea(4, 4, '-'); - //grid.getSubGrid(1,1,3,3).printDebug(); - //grid.printDebug(); - } - - - public TextGrid() { - rows = new ArrayList(); - this.updateModeRows(); - } - - public TextGrid(int width, int height) { - String space = StringUtils.repeatString(" ", width); - rows = new ArrayList(); - for (int i = 0; i < height; i++) - rows.add(new StringBuilder(space)); - this.updateModeRows(); - } - - public static TextGrid makeSameSizeAs(TextGrid grid) { - return new TextGrid(grid.getWidth(), grid.getHeight()); - } - - - public TextGrid(TextGrid otherGrid) { - rows = new ArrayList(); - for (StringBuilder row : otherGrid.getRows()) { - rows.add(new StringBuilder(row)); - } - this.updateModeRows(); - } - - public void clear() { - String blank = StringUtils.repeatString(" ", getWidth()); - int height = getHeight(); - rows.clear(); - for (int i = 0; i < height; i++) - rows.add(new StringBuilder(blank)); - } - - // duplicated code due to lots of hits to this function - public char get(int x, int y) { - if (x > getWidth() - 1 - || y > getHeight() - 1 - || x < 0 - || y < 0) return 0; - return rows.get(y).charAt(x); - } - - //duplicated code due to lots of hits to this function - public char get(Cell cell) { - if (cell.x > getWidth() - 1 - || cell.y > getHeight() - 1 - || cell.x < 0 - || cell.y < 0) return 0; - return rows.get(cell.y).charAt(cell.x); - } - - public StringBuilder getRow(int y) { - return rows.get(y); - } - - public TextGrid getSubGrid(int x, int y, int width, int height) { - TextGrid grid = new TextGrid(width, height); - for (int i = 0; i < height; i++) { - grid.setRow(i, new StringBuilder(getRow(y + i).subSequence(x, x + width))); - } - return grid; - } - - public TextGrid getTestingSubGrid(Cell cell) { - return getSubGrid(cell.x - 1, cell.y - 1, 3, 3); - } - - - public String getStringAt(int x, int y, int length) { - return getStringAt(new Cell(x, y), length); - } - - public String getStringAt(Cell cell, int length) { - int x = cell.x; - int y = cell.y; - if (x > getWidth() - 1 - || y > getHeight() - 1 - || x < 0 - || y < 0) return null; - return rows.get(y).substring(x, x + length); - } - - public char getNorthOf(int x, int y) { - return get(x, y - 1); - } - - public char getSouthOf(int x, int y) { - return get(x, y + 1); - } - - public char getEastOf(int x, int y) { - return get(x + 1, y); - } - - public char getWestOf(int x, int y) { - return get(x - 1, y); - } - - public char getNorthOf(Cell cell) { - return getNorthOf(cell.x, cell.y); - } - - public char getSouthOf(Cell cell) { - return getSouthOf(cell.x, cell.y); - } - - public char getEastOf(Cell cell) { - return getEastOf(cell.x, cell.y); - } - - public char getWestOf(Cell cell) { - return getWestOf(cell.x, cell.y); - } - - public void writeStringTo(int x, int y, String str) { - writeStringTo(new Cell(x, y), str); - } - - public void writeStringTo(Cell cell, String str) { - if (isOutOfBounds(cell)) return; - rows.get(cell.y).replace(cell.x, cell.x + str.length(), str); - } - - public void set(Cell cell, char c) { - set(cell.x, cell.y, c); - } - - public void set(int x, int y, char c) { - if (x > getWidth() - 1 || y > getHeight() - 1) return; - StringBuilder row = rows.get(y); - row.setCharAt(x, c); - } - - public void setRow(int y, String row) { - if (y > getHeight() || row.length() != getWidth()) - throw new IllegalArgumentException("setRow out of bounds or string wrong size"); - rows.set(y, new StringBuilder(row)); - } - - public void setRow(int y, StringBuilder row) { - if (y > getHeight() || row.length() != getWidth()) - throw new IllegalArgumentException("setRow out of bounds or string wrong size"); - rows.set(y, row); - } - - public int getWidth() { - if (rows.size() == 0) return 0; //empty buffer - return rows.get(0).length(); - } - - public int getHeight() { - return rows.size(); - } - - public void printDebug() { - Iterator it = rows.iterator(); - int i = 0; - System.out.println( - " " - + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1)); - while (it.hasNext()) { - String row = it.next().toString(); - String index = new Integer(i).toString(); - if (i < 10) index = " " + index; - System.out.println(index + " (" + row + ")"); - i++; - } - } - - public String getDebugString() { - StringBuilder buffer = new StringBuilder(); - Iterator it = rows.iterator(); - int i = 0; - buffer.append( - " " - + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1) + "\n"); - while (it.hasNext()) { - String row = it.next().toString(); - String index = new Integer(i).toString(); - if (i < 10) index = " " + index; - row = row.replaceAll("\n", "\\\\n"); - row = row.replaceAll("\r", "\\\\r"); - buffer.append(index + " (" + row + ")\n"); - i++; - } - return buffer.toString(); - } - - public String toString() { - return getDebugString(); - } - - /** - * Adds grid to this. Space characters in this grid - * are replaced with the corresponding contents of - * grid, otherwise the contents are unchanged. - * - * @param grid - * @return false if the grids are of different size - */ - public boolean add(TextGrid grid) { - if (getWidth() != grid.getWidth() - || getHeight() != grid.getHeight()) return false; - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - if (get(xi, yi) == ' ') set(xi, yi, grid.get(xi, yi)); - } - } - return true; - } - - /** - * Replaces letters or numbers that are on horizontal or vertical - * lines, with the appropriate character that will make the line - * continuous (| for vertical and - for horizontal lines) - */ - public void replaceTypeOnLine() { - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - char c = get(xi, yi); - if (Character.isLetterOrDigit(c)) { - boolean isOnHorizontalLine = isOnHorizontalLine(xi, yi); - boolean isOnVerticalLine = isOnVerticalLine(xi, yi); - if (isOnHorizontalLine && isOnVerticalLine) { - set(xi, yi, '+'); - if (DEBUG) System.out.println("replaced type on line '" + c + "' with +"); - } else if (isOnHorizontalLine) { - set(xi, yi, '-'); - if (DEBUG) System.out.println("replaced type on line '" + c + "' with -"); - } else if (isOnVerticalLine) { - set(xi, yi, '|'); - if (DEBUG) System.out.println("replaced type on line '" + c + "' with |"); - } - } - } - } - } - - public void replacePointMarkersOnLine() { - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - char c = get(xi, yi); - Cell cell = new Cell(xi, yi); - if (StringUtils.isOneOf(c, pointMarkers) - && isStarOnLine(cell)) { - - boolean isOnHorizontalLine = false; - if (StringUtils.isOneOf(get(cell.getEast()), horizontalLines)) - isOnHorizontalLine = true; - if (StringUtils.isOneOf(get(cell.getWest()), horizontalLines)) - isOnHorizontalLine = true; - - boolean isOnVerticalLine = false; - if (StringUtils.isOneOf(get(cell.getNorth()), verticalLines)) - isOnVerticalLine = true; - if (StringUtils.isOneOf(get(cell.getSouth()), verticalLines)) - isOnVerticalLine = true; - - if (isOnHorizontalLine && isOnVerticalLine) { - set(xi, yi, '+'); - if (DEBUG) System.out.println("replaced marker on line '" + c + "' with +"); - } else if (isOnHorizontalLine) { - set(xi, yi, '-'); - if (DEBUG) System.out.println("replaced marker on line '" + c + "' with -"); - } else if (isOnVerticalLine) { - set(xi, yi, '|'); - if (DEBUG) System.out.println("replaced marker on line '" + c + "' with |"); - } - } - } - } - } - - public CellSet getPointMarkersOnLine() { - CellSet result = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - char c = get(xi, yi); - if (StringUtils.isOneOf(c, pointMarkers) - && isStarOnLine(new Cell(xi, yi))) { - result.add(new Cell(xi, yi)); - } - } - } - return result; - } - - - public void _replaceHumanColorCodes() { - int height = getHeight(); - for (int y = 0; y < height; y++) { - String row = rows.get(y).toString(); - Iterator it = humanColorCodes.keySet().iterator(); - while (it.hasNext()) { - String humanCode = (String) it.next(); - String hexCode = (String) humanColorCodes.get(humanCode); - if (hexCode != null) { - humanCode = "c" + humanCode; - hexCode = "c" + hexCode; - row = row.replaceAll(humanCode, hexCode); - rows.set(y, new StringBuilder(row)); //TODO: this is not the most efficient way to do this - row = rows.get(y).toString(); - } - } - } - } - - public void replaceHumanColorCodes() { - int height = getHeight(); - for (int y = 0; y < height; y++) - rows.set(y, replaceHumanColorCodes(y, rows.get(y))); - } - - private StringBuilder replaceHumanColorCodes(int rowIndex, StringBuilder in) { - Pattern p = Pattern.compile( - Stream.concat( - humanColorCodes.keySet().stream().map(s -> "c" + s), - Stream.of(".*")) - .collect(Collectors.joining("|", "(", ")") - )); - StringBuilder ret = new StringBuilder(in.length()); - Iterator i = createTextSplitter(p, in); - while (i.hasNext()) { - String next = i.next(); - if (isInPlainMode(new Cell(ret.length(), rowIndex)) && looksColorCode(next)) { - ret.append(humanColorCodes.getOrDefault(next.substring(1, 4), next)); - } else - ret.append(next); - } - return ret; - } - - private static boolean looksColorCode(String word) { - return word.length() >= 4 && word.startsWith("c"); - } - - /** - * Replace all occurrences of c1 with c2 - * - * @param c1 - * @param c2 - */ - public void replaceAll(char c1, char c2) { - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - char c = get(xi, yi); - if (c == c1) set(xi, yi, c2); - } - } - } - - public boolean hasBlankCells() { - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - Cell cell = new Cell(x, y); - if (isBlank(cell)) return true; - } - } - return false; - } - - - public CellSet getAllNonBlank() { - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - Cell cell = new Cell(x, y); - if (!isBlank(cell)) set.add(cell); - } - } - return set; - } - - public CellSet getAllBoundaries() { - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - Cell cell = new Cell(x, y); - if (isBoundary(cell)) set.add(cell); - } - } - return set; - } - - - public CellSet getAllBlanksBetweenCharacters() { - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - Cell cell = new Cell(x, y); - if (isBlankBetweenCharacters(cell)) set.add(cell); - } - } - return set; - } - - - /** - * Returns an ArrayList of CellStringPairs that - * represents all the continuous (non-blank) Strings - * in the grid. Used on buffers that contain only - * type, in order to find the positions and the - * contents of the strings. - * - * @return - */ - public ArrayList findStrings() { - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - if (!isBlank(x, y)) { - Cell start = new Cell(x, y); - String str = String.valueOf(get(x, y)); - char c = get(++x, y); - boolean finished = false; - //while(c != ' '){ - while (!finished) { - str += String.valueOf(c); - c = get(++x, y); - char next = get(x + 1, y); - if ((c == ' ' || c == 0) && (next == ' ' || next == 0)) - finished = true; - } - result.add(new CellStringPair(start, str)); - } - } - } - return result; - } - - /** - * This is done in a bit of a messy way, should be impossible - * to go out of sync with corresponding GridPatternGroup. - * - * @param cell - * @param entryPointId - * @return - */ - public boolean hasEntryPoint(Cell cell, int entryPointId) { - String result = ""; + private static final boolean DEBUG = false; + private static final char PLAIN_MODE = 'P'; + public static final char LATEX_MODE = 'L'; + + private ArrayList rows; + private List modeRows; + + private static char[] boundaries = { '/', '\\', '|', '-', '*', '=', ':' }; + private static char[] undisputableBoundaries = { '|', '-', '*', '=', ':' }; + private static char[] horizontalLines = { '-', '=' }; + private static char[] verticalLines = { '|', ':' }; + private static char[] arrowHeads = { '<', '>', '^', 'v', 'V' }; + private static char[] cornerChars = { '\\', '/', '+' }; + private static char[] pointMarkers = { '*' }; + private static char[] dashedLines = { ':', '~', '=' }; + + private static char[] entryPoints1 = { '\\' }; + private static char[] entryPoints2 = { '|', ':', '+', '\\', '/' }; + private static char[] entryPoints3 = { '/' }; + private static char[] entryPoints4 = { '-', '=', '+', '\\', '/' }; + private static char[] entryPoints5 = { '\\' }; + private static char[] entryPoints6 = { '|', ':', '+', '\\', '/' }; + private static char[] entryPoints7 = { '/' }; + private static char[] entryPoints8 = { '-', '=', '+', '\\', '/' }; + + + private static HashMap humanColorCodes = new HashMap(); + + static { + humanColorCodes.put("GRE", "9D9"); + humanColorCodes.put("BLU", "55B"); + humanColorCodes.put("PNK", "FAA"); + humanColorCodes.put("RED", "E32"); + humanColorCodes.put("YEL", "FF3"); + humanColorCodes.put("BLK", "000"); + + } + + private static HashSet markupTags = + new HashSet(); + + static { + markupTags.add("d"); + markupTags.add("s"); + markupTags.add("io"); + markupTags.add("c"); + markupTags.add("mo"); + markupTags.add("tr"); + markupTags.add("o"); + } + + private void updateModeRows() { + // Collectors.toList creates ArrayList and you see no performance penalty + // caused by using LinkedList here. + this.modeRows = this.rows.stream().map(TextGrid::transformRowToModeRow).collect(toList()); + } + + public static String transformRowToModeRow(CharSequence row) { + StringBuilder b = new StringBuilder(); + row.chars().map(new IntUnaryOperator() { + /** + * This field hold current mode of a cell in the grid. + * Only 2 values can be assigned, which are 'P' and 'L'. + * They respectively represent 'Plain', which is normal ditaa mode, and 'LaTeX', which is LaTeX + * formula mode. + */ + int cur = PLAIN_MODE; + + @Override + public int applyAsInt(int operand) { + if (cur == PLAIN_MODE) { + if (operand == '$') { + cur = LATEX_MODE; + return LATEX_MODE; + } + return PLAIN_MODE; + } else if (cur == LATEX_MODE) { + if (operand == '$') { + cur = PLAIN_MODE; + return LATEX_MODE; + } + return this.cur; + } + throw new IllegalStateException(); + } + }).forEach(c -> b.append((char) c)); + String ret = b.toString(); + if (ret.endsWith("L")) + if (row.charAt(row.length() - 1) != '$') + throw new IllegalArgumentException("LaTex mode was started but not finished."); + return ret; + } + + public void addToMarkupTags(Collection tags) { + markupTags.addAll(tags); + } + + public static void main(String[] args) throws Exception { + TextGrid grid = new TextGrid(); + grid.loadFrom("tests/text/art10.txt"); + + grid.writeStringTo(grid.new Cell(28, 1), "testing"); + + grid.findMarkupTags(); + + grid.printDebug(); + //System.out.println(grid.fillContinuousArea(0, 0, '-').size()+" cells filled"); + //grid.fillContinuousArea(4, 4, '-'); + //grid.getSubGrid(1,1,3,3).printDebug(); + //grid.printDebug(); + } + + + public TextGrid() { + rows = new ArrayList(); + this.updateModeRows(); + } + + public TextGrid(int width, int height) { + String space = StringUtils.repeatString(" ", width); + rows = new ArrayList(); + for (int i = 0; i < height; i++) + rows.add(new StringBuilder(space)); + this.updateModeRows(); + } + + public static TextGrid makeSameSizeAs(TextGrid grid) { + return new TextGrid(grid.getWidth(), grid.getHeight()); + } + + + public TextGrid(TextGrid otherGrid) { + rows = new ArrayList(); + for (StringBuilder row : otherGrid.getRows()) { + rows.add(new StringBuilder(row)); + } + this.updateModeRows(); + } + + public void clear() { + String blank = StringUtils.repeatString(" ", getWidth()); + int height = getHeight(); + rows.clear(); + for (int i = 0; i < height; i++) + rows.add(new StringBuilder(blank)); + } + + // duplicated code due to lots of hits to this function + public char get(int x, int y) { + if (x > getWidth() - 1 + || y > getHeight() - 1 + || x < 0 + || y < 0) + return 0; + return rows.get(y).charAt(x); + } + + //duplicated code due to lots of hits to this function + public char get(Cell cell) { + if (cell.x > getWidth() - 1 + || cell.y > getHeight() - 1 + || cell.x < 0 + || cell.y < 0) + return 0; + return rows.get(cell.y).charAt(cell.x); + } + + public StringBuilder getRow(int y) { + return rows.get(y); + } + + public TextGrid getSubGrid(int x, int y, int width, int height) { + TextGrid grid = new TextGrid(width, height); + for (int i = 0; i < height; i++) { + grid.setRow(i, new StringBuilder(getRow(y + i).subSequence(x, x + width))); + } + return grid; + } + + public TextGrid getTestingSubGrid(Cell cell) { + return getSubGrid(cell.x - 1, cell.y - 1, 3, 3); + } + + + public String getStringAt(int x, int y, int length) { + return getStringAt(new Cell(x, y), length); + } + + public String getStringAt(Cell cell, int length) { + int x = cell.x; + int y = cell.y; + if (x > getWidth() - 1 + || y > getHeight() - 1 + || x < 0 + || y < 0) + return null; + return rows.get(y).substring(x, x + length); + } + + public char getNorthOf(int x, int y) { + return get(x, y - 1); + } + + public char getSouthOf(int x, int y) { + return get(x, y + 1); + } + + public char getEastOf(int x, int y) { + return get(x + 1, y); + } + + public char getWestOf(int x, int y) { + return get(x - 1, y); + } + + public char getNorthOf(Cell cell) { + return getNorthOf(cell.x, cell.y); + } + + public char getSouthOf(Cell cell) { + return getSouthOf(cell.x, cell.y); + } + + public char getEastOf(Cell cell) { + return getEastOf(cell.x, cell.y); + } + + public char getWestOf(Cell cell) { + return getWestOf(cell.x, cell.y); + } + + public void writeStringTo(int x, int y, String str) { + writeStringTo(new Cell(x, y), str); + } + + public void writeStringTo(Cell cell, String str) { + if (isOutOfBounds(cell)) + return; + rows.get(cell.y).replace(cell.x, cell.x + str.length(), str); + } + + public void set(Cell cell, char c) { + set(cell.x, cell.y, c); + } + + public void set(int x, int y, char c) { + if (x > getWidth() - 1 || y > getHeight() - 1) + return; + StringBuilder row = rows.get(y); + row.setCharAt(x, c); + } + + public void setRow(int y, String row) { + if (y > getHeight() || row.length() != getWidth()) + throw new IllegalArgumentException("setRow out of bounds or string wrong size"); + rows.set(y, new StringBuilder(row)); + } + + public void setRow(int y, StringBuilder row) { + if (y > getHeight() || row.length() != getWidth()) + throw new IllegalArgumentException("setRow out of bounds or string wrong size"); + rows.set(y, row); + } + + public int getWidth() { + if (rows.size() == 0) + return 0; //empty buffer + return rows.get(0).length(); + } + + public int getHeight() { + return rows.size(); + } + + public void printDebug() { + Iterator it = rows.iterator(); + int i = 0; + System.out.println( + " " + + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1)); + while (it.hasNext()) { + String row = it.next().toString(); + String index = new Integer(i).toString(); + if (i < 10) + index = " " + index; + System.out.println(index + " (" + row + ")"); + i++; + } + } + + public String getDebugString() { + StringBuilder buffer = new StringBuilder(); + Iterator it = rows.iterator(); + int i = 0; + buffer.append( + " " + + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1) + "\n"); + while (it.hasNext()) { + String row = it.next().toString(); + String index = new Integer(i).toString(); + if (i < 10) + index = " " + index; + row = row.replaceAll("\n", "\\\\n"); + row = row.replaceAll("\r", "\\\\r"); + buffer.append(index + " (" + row + ")\n"); + i++; + } + return buffer.toString(); + } + + public String toString() { + return getDebugString(); + } + + /** + * Adds grid to this. Space characters in this grid + * are replaced with the corresponding contents of + * grid, otherwise the contents are unchanged. + * + * @param grid + * @return false if the grids are of different size + */ + public boolean add(TextGrid grid) { + if (getWidth() != grid.getWidth() + || getHeight() != grid.getHeight()) + return false; + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + if (get(xi, yi) == ' ') + set(xi, yi, grid.get(xi, yi)); + } + } + return true; + } + + /** + * Replaces letters or numbers that are on horizontal or vertical + * lines, with the appropriate character that will make the line + * continuous (| for vertical and - for horizontal lines) + */ + public void replaceTypeOnLine() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + if (Character.isLetterOrDigit(c)) { + boolean isOnHorizontalLine = isOnHorizontalLine(xi, yi); + boolean isOnVerticalLine = isOnVerticalLine(xi, yi); + if (isOnHorizontalLine && isOnVerticalLine) { + set(xi, yi, '+'); + if (DEBUG) + System.out.println("replaced type on line '" + c + "' with +"); + } else if (isOnHorizontalLine) { + set(xi, yi, '-'); + if (DEBUG) + System.out.println("replaced type on line '" + c + "' with -"); + } else if (isOnVerticalLine) { + set(xi, yi, '|'); + if (DEBUG) + System.out.println("replaced type on line '" + c + "' with |"); + } + } + } + } + } + + public void replacePointMarkersOnLine() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + Cell cell = new Cell(xi, yi); + if (StringUtils.isOneOf(c, pointMarkers) + && isStarOnLine(cell)) { + + boolean isOnHorizontalLine = false; + if (StringUtils.isOneOf(get(cell.getEast()), horizontalLines)) + isOnHorizontalLine = true; + if (StringUtils.isOneOf(get(cell.getWest()), horizontalLines)) + isOnHorizontalLine = true; + + boolean isOnVerticalLine = false; + if (StringUtils.isOneOf(get(cell.getNorth()), verticalLines)) + isOnVerticalLine = true; + if (StringUtils.isOneOf(get(cell.getSouth()), verticalLines)) + isOnVerticalLine = true; + + if (isOnHorizontalLine && isOnVerticalLine) { + set(xi, yi, '+'); + if (DEBUG) + System.out.println("replaced marker on line '" + c + "' with +"); + } else if (isOnHorizontalLine) { + set(xi, yi, '-'); + if (DEBUG) + System.out.println("replaced marker on line '" + c + "' with -"); + } else if (isOnVerticalLine) { + set(xi, yi, '|'); + if (DEBUG) + System.out.println("replaced marker on line '" + c + "' with |"); + } + } + } + } + } + + public CellSet getPointMarkersOnLine() { + CellSet result = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + if (StringUtils.isOneOf(c, pointMarkers) + && isStarOnLine(new Cell(xi, yi))) { + result.add(new Cell(xi, yi)); + } + } + } + return result; + } + + + public void _replaceHumanColorCodes() { + int height = getHeight(); + for (int y = 0; y < height; y++) { + String row = rows.get(y).toString(); + Iterator it = humanColorCodes.keySet().iterator(); + while (it.hasNext()) { + String humanCode = (String) it.next(); + String hexCode = (String) humanColorCodes.get(humanCode); + if (hexCode != null) { + humanCode = "c" + humanCode; + hexCode = "c" + hexCode; + row = row.replaceAll(humanCode, hexCode); + rows.set(y, new StringBuilder(row)); //TODO: this is not the most efficient way to do this + row = rows.get(y).toString(); + } + } + } + } + + public void replaceHumanColorCodes() { + int height = getHeight(); + for (int y = 0; y < height; y++) + rows.set(y, replaceHumanColorCodes(y, rows.get(y))); + } + + private StringBuilder replaceHumanColorCodes(int rowIndex, StringBuilder in) { + Pattern p = Pattern.compile("(c.{0,3}|[^c]+)"); + StringBuilder ret = new StringBuilder(in.length()); + Iterator i = createTextSplitter(p, in); + while (i.hasNext()) { + String next = i.next(); + if (isInPlainMode(ret.length(), rowIndex) && looksColorCode(next)) { + String replacement = humanColorCodes.get(next.substring(1, 4)); + if (replacement != null) + ret.append(String.format("c%s", replacement)); + else + ret.append(next); + } else + ret.append(next); + } + return ret; + } + + private static boolean looksColorCode(String word) { + return word.length() >= 4 && word.startsWith("c"); + } + + /** + * Replace all occurrences of c1 with c2 + * + * @param c1 + * @param c2 + */ + public void replaceAll(char c1, char c2) { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + if (c == c1) + set(xi, yi, c2); + } + } + } + + public boolean hasBlankCells() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (isBlank(cell)) + return true; + } + } + return false; + } + + + public CellSet getAllNonBlank() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (!isBlank(cell)) + set.add(cell); + } + } + return set; + } + + public CellSet getAllBoundaries() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (isBoundary(cell)) + set.add(cell); + } + } + return set; + } + + + public CellSet getAllBlanksBetweenCharacters() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (isBlankBetweenCharacters(cell)) + set.add(cell); + } + } + return set; + } + + + /** + * Returns an ArrayList of CellStringPairs that + * represents all the continuous (non-blank) Strings + * in the grid. Used on buffers that contain only + * type, in order to find the positions and the + * contents of the strings. + * + * @return + */ + public ArrayList findStrings() { + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (!isBlank(x, y)) { + Cell start = new Cell(x, y); + String str = String.valueOf(get(x, y)); + char c = get(++x, y); + boolean finished = false; + //while(c != ' '){ + while (!finished) { + str += String.valueOf(c); + c = get(++x, y); + char next = get(x + 1, y); + if ((c == ' ' || c == 0) && (next == ' ' || next == 0)) + finished = true; + } + result.add(new CellStringPair(start, str)); + } + } + } + return result; + } + + /** + * This is done in a bit of a messy way, should be impossible + * to go out of sync with corresponding GridPatternGroup. + * + * @param cell + * @param entryPointId + * @return + */ + public boolean hasEntryPoint(Cell cell, int entryPointId) { + String result = ""; + char c = get(cell); + if (entryPointId == 1) { + return StringUtils.isOneOf(c, entryPoints1); + + } else if (entryPointId == 2) { + return StringUtils.isOneOf(c, entryPoints2); + + } else if (entryPointId == 3) { + return StringUtils.isOneOf(c, entryPoints3); + + } else if (entryPointId == 4) { + return StringUtils.isOneOf(c, entryPoints4); + + } else if (entryPointId == 5) { + return StringUtils.isOneOf(c, entryPoints5); + + } else if (entryPointId == 6) { + return StringUtils.isOneOf(c, entryPoints6); + + } else if (entryPointId == 7) { + return StringUtils.isOneOf(c, entryPoints7); + + } else if (entryPointId == 8) { + return StringUtils.isOneOf(c, entryPoints8); + } + return false; + } + + /** + * true if cell is blank and the east and west cells are not + * (used to find gaps between words) + * + * @param cell + * @return + */ + public boolean isBlankBetweenCharacters(Cell cell) { + return (isBlank(cell) + && !isBlank(cell.getEast()) + && !isBlank(cell.getWest())); + } + + /** + * Makes blank all the cells that contain non-text + * elements. + */ + public void removeNonText() { + //the following order is significant + //since the south-pointing arrowheads + //are determined based on the surrounding boundaries + removeArrowheads(); + removeColorCodes(); + removeBoundaries(); + removeMarkupTags(); + } + + public void removeArrowheads() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isArrowhead(cell)) + set(cell, ' '); + } + } + } + + public void removeColorCodes() { + Iterator cells = findColorCodes().iterator(); + while (cells.hasNext()) { + Cell cell = ((CellColorPair) cells.next()).cell; + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + } + } + + public void removeBoundaries() { + ArrayList toBeRemoved = new ArrayList(); + + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isBoundary(cell)) + toBeRemoved.add(cell); + } + } + + //remove in two stages, because decision of + //isBoundary depends on content of surrounding + //cells + Iterator it = toBeRemoved.iterator(); + while (it.hasNext()) { + Cell cell = (Cell) it.next(); + if (isInPlainMode(cell)) + set(cell, ' '); + } + } + + public ArrayList findArrowheads() { + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isArrowhead(cell)) + result.add(cell); + } + } + if (DEBUG) + System.out.println(result.size() + " arrowheads found"); + return result; + } + + + public ArrayList findColorCodes() { + Pattern colorCodePattern = Pattern.compile("c[A-F0-9]{3}"); + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width - 3; xi++) { + Cell cell = new Cell(xi, yi); + if (!isInPlainMode(cell)) + continue; + String s = getStringAt(cell, 4); + Matcher matcher = colorCodePattern.matcher(s); + if (matcher.matches()) { + char cR = s.charAt(1); + char cG = s.charAt(2); + char cB = s.charAt(3); + int r = Integer.valueOf(String.valueOf(cR), 16).intValue() * 17; + int g = Integer.valueOf(String.valueOf(cG), 16).intValue() * 17; + int b = Integer.valueOf(String.valueOf(cB), 16).intValue() * 17; + result.add(new CellColorPair(cell, new Color(r, g, b))); + } + } + } + if (DEBUG) + System.out.println(result.size() + " color codes found"); + return result; + } + + public ArrayList findMarkupTags() { + Pattern tagPattern = Pattern.compile("\\{(.+?)\\}"); + ArrayList result = new ArrayList(); + + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width - 3; x++) { + if (!isInPlainMode(x, y)) + continue; + Cell cell = new Cell(x, y); char c = get(cell); - if (entryPointId == 1) { - return StringUtils.isOneOf(c, entryPoints1); - - } else if (entryPointId == 2) { - return StringUtils.isOneOf(c, entryPoints2); - - } else if (entryPointId == 3) { - return StringUtils.isOneOf(c, entryPoints3); - - } else if (entryPointId == 4) { - return StringUtils.isOneOf(c, entryPoints4); - - } else if (entryPointId == 5) { - return StringUtils.isOneOf(c, entryPoints5); - - } else if (entryPointId == 6) { - return StringUtils.isOneOf(c, entryPoints6); - - } else if (entryPointId == 7) { - return StringUtils.isOneOf(c, entryPoints7); - - } else if (entryPointId == 8) { - return StringUtils.isOneOf(c, entryPoints8); - } - return false; - } - - /** - * true if cell is blank and the east and west cells are not - * (used to find gaps between words) - * - * @param cell - * @return - */ - public boolean isBlankBetweenCharacters(Cell cell) { - return (isBlank(cell) - && !isBlank(cell.getEast()) - && !isBlank(cell.getWest())); - } - - /** - * Makes blank all the cells that contain non-text - * elements. - */ - public void removeNonText() { - //the following order is significant - //since the south-pointing arrowheads - //are determined based on the surrounding boundaries - removeArrowheads(); - removeColorCodes(); - removeBoundaries(); - removeMarkupTags(); - } - - public void removeArrowheads() { - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - Cell cell = new Cell(xi, yi); - if (isArrowhead(cell)) set(cell, ' '); + if (c == '{') { + String rowPart = rows.get(y).substring(x); + Matcher matcher = tagPattern.matcher(rowPart); + if (matcher.find()) { + String tagName = matcher.group(1); + if (markupTags.contains(tagName)) { + if (DEBUG) + System.out.println("found tag " + tagName + " at " + x + ", " + y); + result.add(new CellTagPair(new Cell(x, y), tagName)); } - } - } - - public void removeColorCodes() { - Iterator cells = findColorCodes().iterator(); - while (cells.hasNext()) { - Cell cell = ((CellColorPair) cells.next()).cell; - set(cell, ' '); - cell = cell.getEast(); - set(cell, ' '); - cell = cell.getEast(); - set(cell, ' '); - cell = cell.getEast(); - set(cell, ' '); - } - } - - public void removeBoundaries() { - ArrayList toBeRemoved = new ArrayList(); - - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - Cell cell = new Cell(xi, yi); - if (isBoundary(cell)) toBeRemoved.add(cell); - } - } - - //remove in two stages, because decision of - //isBoundary depends on content of surrounding - //cells - Iterator it = toBeRemoved.iterator(); - while (it.hasNext()) { - Cell cell = (Cell) it.next(); - if (isInPlainMode(cell)) - set(cell, ' '); - } - } - - public ArrayList findArrowheads() { - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - Cell cell = new Cell(xi, yi); - if (isArrowhead(cell)) result.add(cell); - } - } - if (DEBUG) System.out.println(result.size() + " arrowheads found"); - return result; - } - - - public ArrayList findColorCodes() { - Pattern colorCodePattern = Pattern.compile("c[A-F0-9]{3}"); - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width - 3; xi++) { - Cell cell = new Cell(xi, yi); - if (!isInPlainMode(cell)) - continue; - String s = getStringAt(cell, 4); - Matcher matcher = colorCodePattern.matcher(s); - if (matcher.matches()) { - char cR = s.charAt(1); - char cG = s.charAt(2); - char cB = s.charAt(3); - int r = Integer.valueOf(String.valueOf(cR), 16).intValue() * 17; - int g = Integer.valueOf(String.valueOf(cG), 16).intValue() * 17; - int b = Integer.valueOf(String.valueOf(cB), 16).intValue() * 17; - result.add(new CellColorPair(cell, new Color(r, g, b))); - } - } - } - if (DEBUG) System.out.println(result.size() + " color codes found"); - return result; - } - - public ArrayList findMarkupTags() { - Pattern tagPattern = Pattern.compile("\\{(.+?)\\}"); - ArrayList result = new ArrayList(); - - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width - 3; x++) { - Cell cell = new Cell(x, y); - char c = get(cell); - if (c == '{') { - String rowPart = rows.get(y).substring(x); - Matcher matcher = tagPattern.matcher(rowPart); - if (matcher.find()) { - String tagName = matcher.group(1); - if (markupTags.contains(tagName)) { - if (DEBUG) System.out.println("found tag " + tagName + " at " + x + ", " + y); - result.add(new CellTagPair(new Cell(x, y), tagName)); - } - } - } - } - } - return result; - } - - public void removeMarkupTags() { - Iterator it = findMarkupTags().iterator(); - while (it.hasNext()) { - CellTagPair pair = (CellTagPair) it.next(); - String tagName = pair.tag; - if (tagName == null) continue; - int length = 2 + tagName.length(); - writeStringTo(pair.cell, StringUtils.repeatString(" ", length)); - } - } - - - public boolean matchesAny(GridPatternGroup criteria) { - return criteria.isAnyMatchedBy(this); - } - - public boolean matchesAll(GridPatternGroup criteria) { - return criteria.areAllMatchedBy(this); - } - - public boolean matches(GridPattern criteria) { - return criteria.isMatchedBy(this); - } - - - public boolean isOnHorizontalLine(Cell cell) { - return isOnHorizontalLine(cell.x, cell.y); - } - - private boolean isOnHorizontalLine(int x, int y) { - char c1 = get(x - 1, y); - char c2 = get(x + 1, y); - if (isHorizontalLine(c1) && isHorizontalLine(c2)) return true; + } + } + } + } + return result; + } + + public void removeMarkupTags() { + Iterator it = findMarkupTags().iterator(); + while (it.hasNext()) { + CellTagPair pair = (CellTagPair) it.next(); + String tagName = pair.tag; + if (tagName == null) + continue; + int length = 2 + tagName.length(); + writeStringTo(pair.cell, StringUtils.repeatString(" ", length)); + } + } + + + public boolean matchesAny(GridPatternGroup criteria) { + return criteria.isAnyMatchedBy(this); + } + + public boolean matchesAll(GridPatternGroup criteria) { + return criteria.areAllMatchedBy(this); + } + + public boolean matches(GridPattern criteria) { + return criteria.isMatchedBy(this); + } + + + public boolean isOnHorizontalLine(Cell cell) { + return isOnHorizontalLine(cell.x, cell.y); + } + + private boolean isOnHorizontalLine(int x, int y) { + char c1 = get(x - 1, y); + char c2 = get(x + 1, y); + if (isHorizontalLine(c1) && isHorizontalLine(c2)) + return true; + return false; + } + + public boolean isOnVerticalLine(Cell cell) { + return isOnVerticalLine(cell.x, cell.y); + } + + private boolean isOnVerticalLine(int x, int y) { + char c1 = get(x, y - 1); + char c2 = get(x, y + 1); + if (isVerticalLine(c1) && isVerticalLine(c2)) + return true; + return false; + } + + + public static boolean isBoundary(char c) { + return StringUtils.isOneOf(c, boundaries); + } + + public boolean isBoundary(int x, int y) { + return isBoundary(new Cell(x, y)); + } + + public boolean isBoundary(Cell cell) { + if (!isInPlainMode(cell)) + return false; + char c = get(cell.x, cell.y); + if (0 == c) + return false; + if ('+' == c || '\\' == c || '/' == c) { + System.out.print(""); + if ( + isIntersection(cell) + || isCorner(cell) + || isStub(cell) + || isCrossOnLine(cell)) { + return true; + } else return false; } - - public boolean isOnVerticalLine(Cell cell) { - return isOnVerticalLine(cell.x, cell.y); + //return StringUtils.isOneOf(c, undisputableBoundaries); + if (StringUtils.isOneOf(c, boundaries) && !isLoneDiagonal(cell)) { + return true; + } + return false; + } + + public boolean isLine(Cell cell) { + return isHorizontalLine(cell) || isVerticalLine(cell); + } + + public static boolean isHorizontalLine(char c) { + return StringUtils.isOneOf(c, horizontalLines); + } + + public boolean isHorizontalLine(Cell cell) { + return isHorizontalLine(cell.x, cell.y) && isInPlainMode(cell); + } + + public boolean isHorizontalLine(int x, int y) { + char c = get(x, y); + if (0 == c) + return false; + return StringUtils.isOneOf(c, horizontalLines) && isInPlainMode(x, y); + } + + public static boolean isVerticalLine(char c) { + return StringUtils.isOneOf(c, verticalLines); + } + + public boolean isVerticalLine(Cell cell) { + return isVerticalLine(cell.x, cell.y) && isInPlainMode(cell); + } + + public boolean isVerticalLine(int x, int y) { + char c = get(x, y); + if (0 == c) + return false; + return StringUtils.isOneOf(c, verticalLines); + } + + public boolean isLinesEnd(int x, int y) { + return isLinesEnd(new Cell(x, y)); + } + + /** + * Stubs are also considered end of lines + * + * @param cell + * @return + */ + public boolean isLinesEnd(Cell cell) { + return matchesAny(cell, GridPatternGroup.linesEndCriteria) && isInPlainMode(cell); + } + + public boolean isVerticalLinesEnd(Cell cell) { + return matchesAny(cell, GridPatternGroup.verticalLinesEndCriteria) && isInPlainMode(cell); + } + + public boolean isHorizontalLinesEnd(Cell cell) { + return matchesAny(cell, GridPatternGroup.horizontalLinesEndCriteria) && isInPlainMode(cell); + } + + + public boolean isPointCell(Cell cell) { + return ( + isCorner(cell) + || isIntersection(cell) + || isStub(cell) + || isLinesEnd(cell)) && isInPlainMode(cell); + } + + + public boolean containsAtLeastOneDashedLine(CellSet set) { + Iterator it = set.iterator(); + while (it.hasNext()) { + Cell cell = (Cell) it.next(); + if (StringUtils.isOneOf(get(cell), dashedLines)) + return true; } - - private boolean isOnVerticalLine(int x, int y) { - char c1 = get(x, y - 1); - char c2 = get(x, y + 1); - if (isVerticalLine(c1) && isVerticalLine(c2)) return true; + return false; + } + + public boolean exactlyOneNeighbourIsBoundary(Cell cell) { + int howMany = 0; + if (isBoundary(cell.getNorth())) + howMany++; + if (isBoundary(cell.getSouth())) + howMany++; + if (isBoundary(cell.getEast())) + howMany++; + if (isBoundary(cell.getWest())) + howMany++; + return (howMany == 1); + } + + /** + * A stub looks like that: + * + *
+   *
+   * +- or -+ or + or + or /- or -/ or / (you get the point)
+   *             |    |                |
+   *
+   * 
+ * + * @param cell + * @return + */ + + public boolean isStub(Cell cell) { + return matchesAny(cell, GridPatternGroup.stubCriteria); + } + + public boolean isCrossOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.crossOnLineCriteria); + } + + public boolean isHorizontalCrossOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.horizontalCrossOnLineCriteria); + } + + public boolean isVerticalCrossOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.verticalCrossOnLineCriteria); + } + + public boolean isStarOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.starOnLineCriteria); + } + + public boolean isLoneDiagonal(Cell cell) { + return matchesAny(cell, GridPatternGroup.loneDiagonalCriteria); + } + + + public boolean isHorizontalStarOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.horizontalStarOnLineCriteria); + } + + public boolean isVerticalStarOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.verticalStarOnLineCriteria); + } + + public boolean isArrowhead(Cell cell) { + return (isNorthArrowhead(cell) + || isSouthArrowhead(cell) + || isWestArrowhead(cell) + || isEastArrowhead(cell)); + } + + public boolean isNorthArrowhead(Cell cell) { + return get(cell) == '^' && isInPlainMode(cell); + } + + public boolean isEastArrowhead(Cell cell) { + return get(cell) == '>' && isInPlainMode(cell); + } + + public boolean isWestArrowhead(Cell cell) { + return get(cell) == '<' && isInPlainMode(cell); + } + + public boolean isSouthArrowhead(Cell cell) { + return (get(cell) == 'v' || get(cell) == 'V') + && isVerticalLine(cell.getNorth()) + && isInPlainMode(cell); + } + + // unicode for bullets + // + // 2022 bullet + // 25CF black circle + // 25AA black circle (small) + // 25A0 black square + // 25A1 white square + // 25CB white circle + // 25BA black right-pointing pointer + + + public boolean isBullet(int x, int y) { + return isBullet(new Cell(x, y)); + } + + public boolean isBullet(Cell cell) { + char c = get(cell); + if ((c == 'o' || c == '*') + && isBlank(cell.getEast()) + && isBlank(cell.getWest()) + && Character.isLetterOrDigit(get(cell.getEast().getEast())) + && isInPlainMode(cell)) + return true; + return false; + } + + public void replaceBullets() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isBullet(cell)) { + set(cell, ' '); + set(cell.getEast(), '\u2022'); + } + } + } + } + + /** + * true if the cell is not blank + * but the previous (west) is + * + * @param cell + * @return + */ + public boolean isStringsStart(Cell cell) { + return (!isBlank(cell) && isBlank(cell.getWest())); + } + + /** + * true if the cell is not blank + * but the next (east) is + * + * @param cell + * @return + */ + public boolean isStringsEnd(Cell cell) { + return (!isBlank(cell) + //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); + && isBlank(cell.getEast())); + } + + public int otherStringsStartInTheSameColumn(Cell cell) { + if (!isStringsStart(cell)) + return 0; + int result = 0; + int height = getHeight(); + for (int y = 0; y < height; y++) { + Cell cCell = new Cell(cell.x, y); + if (!cCell.equals(cell) && isStringsStart(cCell)) { + result++; + } + } + return result; + } + + public int otherStringsEndInTheSameColumn(Cell cell) { + if (!isStringsEnd(cell)) + return 0; + int result = 0; + int height = getHeight(); + for (int y = 0; y < height; y++) { + Cell cCell = new Cell(cell.x, y); + if (!cCell.equals(cell) && isStringsEnd(cCell)) { + result++; + } + } + return result; + } + + public boolean isColumnBlank(int x) { + int height = getHeight(); + for (int y = 0; y < height; y++) { + if (!isBlank(x, y)) return false; } - - - public static boolean isBoundary(char c) { - return StringUtils.isOneOf(c, boundaries); - } - - public boolean isBoundary(int x, int y) { - return isBoundary(new Cell(x, y)); + return true; + } + + + public CellSet followLine(int x, int y) { + return followLine(new Cell(x, y)); + } + + public CellSet followIntersection(Cell cell) { + return followIntersection(cell, null); + } + + public CellSet followIntersection(Cell cell, Cell blocked) { + if (!isIntersection(cell)) + return null; + CellSet result = new CellSet(); + Cell cN = cell.getNorth(); + Cell cS = cell.getSouth(); + Cell cE = cell.getEast(); + Cell cW = cell.getWest(); + if (hasEntryPoint(cN, 6)) + result.add(cN); + if (hasEntryPoint(cS, 2)) + result.add(cS); + if (hasEntryPoint(cE, 8)) + result.add(cE); + if (hasEntryPoint(cW, 4)) + result.add(cW); + if (result.contains(blocked)) + result.remove(blocked); + return result; + } + + /** + * Returns the neighbours of a line-cell that are boundaries + * (0 to 2 cells are returned) + * + * @param cell + * @return null if the cell is not a line + */ + public CellSet followLine(Cell cell) { + if (isHorizontalLine(cell)) { + CellSet result = new CellSet(); + if (isBoundary(cell.getEast())) + result.add(cell.getEast()); + if (isBoundary(cell.getWest())) + result.add(cell.getWest()); + return result; + } else if (isVerticalLine(cell)) { + CellSet result = new CellSet(); + if (isBoundary(cell.getNorth())) + result.add(cell.getNorth()); + if (isBoundary(cell.getSouth())) + result.add(cell.getSouth()); + return result; + } + return null; + } + + public CellSet followLine(Cell cell, Cell blocked) { + CellSet nextCells = followLine(cell); + if (nextCells.contains(blocked)) + nextCells.remove(blocked); + return nextCells; + } + + public CellSet followCorner(Cell cell) { + return followCorner(cell, null); + } + + public CellSet followCorner(Cell cell, Cell blocked) { + if (!isCorner(cell)) + return null; + if (isCorner1(cell)) + return followCorner1(cell, blocked); + if (isCorner2(cell)) + return followCorner2(cell, blocked); + if (isCorner3(cell)) + return followCorner3(cell, blocked); + if (isCorner4(cell)) + return followCorner4(cell, blocked); + return null; + } + + public CellSet followCorner1(Cell cell) { + return followCorner1(cell, null); + } + + public CellSet followCorner1(Cell cell, Cell blocked) { + if (!isCorner1(cell)) + return null; + CellSet result = new CellSet(); + if (!cell.getSouth().equals(blocked)) + result.add(cell.getSouth()); + if (!cell.getEast().equals(blocked)) + result.add(cell.getEast()); + return result; + } + + public CellSet followCorner2(Cell cell) { + return followCorner2(cell, null); + } + + public CellSet followCorner2(Cell cell, Cell blocked) { + if (!isCorner2(cell)) + return null; + CellSet result = new CellSet(); + if (!cell.getSouth().equals(blocked)) + result.add(cell.getSouth()); + if (!cell.getWest().equals(blocked)) + result.add(cell.getWest()); + return result; + } + + public CellSet followCorner3(Cell cell) { + return followCorner3(cell, null); + } + + public CellSet followCorner3(Cell cell, Cell blocked) { + if (!isCorner3(cell)) + return null; + CellSet result = new CellSet(); + if (!cell.getNorth().equals(blocked)) + result.add(cell.getNorth()); + if (!cell.getWest().equals(blocked)) + result.add(cell.getWest()); + return result; + } + + public CellSet followCorner4(Cell cell) { + return followCorner4(cell, null); + } + + public CellSet followCorner4(Cell cell, Cell blocked) { + if (!isCorner4(cell)) + return null; + CellSet result = new CellSet(); + if (!cell.getNorth().equals(blocked)) + result.add(cell.getNorth()); + if (!cell.getEast().equals(blocked)) + result.add(cell.getEast()); + return result; + } + + + public CellSet followStub(Cell cell) { + return followStub(cell, null); + } + + public CellSet followStub(Cell cell, Cell blocked) { + if (!isStub(cell)) + return null; + CellSet result = new CellSet(); + if (isBoundary(cell.getEast())) + result.add(cell.getEast()); + else if (isBoundary(cell.getWest())) + result.add(cell.getWest()); + else if (isBoundary(cell.getNorth())) + result.add(cell.getNorth()); + else if (isBoundary(cell.getSouth())) + result.add(cell.getSouth()); + if (result.contains(blocked)) + result.remove(blocked); + return result; + } + + public CellSet followCell(Cell cell) { + return followCell(cell, null); + } + + public CellSet followCell(Cell cell, Cell blocked) { + if (isIntersection(cell)) + return followIntersection(cell, blocked); + if (isCorner(cell)) + return followCorner(cell, blocked); + if (isLine(cell)) + return followLine(cell, blocked); + if (isStub(cell)) + return followStub(cell, blocked); + if (isCrossOnLine(cell)) + return followCrossOnLine(cell, blocked); + System.err.println("Ambiguous input at position " + cell + ":"); + TextGrid subGrid = getTestingSubGrid(cell); + subGrid.printDebug(); + throw new RuntimeException("Cannot follow cell " + cell + ": cannot determine cell type"); + } + + public String getCellTypeAsString(Cell cell) { + if (isK(cell)) + return "K"; + if (isT(cell)) + return "T"; + if (isInverseK(cell)) + return "inverse K"; + if (isInverseT(cell)) + return "inverse T"; + if (isCorner1(cell)) + return "corner 1"; + if (isCorner2(cell)) + return "corner 2"; + if (isCorner3(cell)) + return "corner 3"; + if (isCorner4(cell)) + return "corner 4"; + if (isLine(cell)) + return "line"; + if (isStub(cell)) + return "stub"; + if (isCrossOnLine(cell)) + return "crossOnLine"; + return "unrecognisable type"; + } + + + public CellSet followCrossOnLine(Cell cell, Cell blocked) { + CellSet result = new CellSet(); + if (isHorizontalCrossOnLine(cell)) { + result.add(cell.getEast()); + result.add(cell.getWest()); + } else if (isVerticalCrossOnLine(cell)) { + result.add(cell.getNorth()); + result.add(cell.getSouth()); + } + if (result.contains(blocked)) + result.remove(blocked); + return result; + } + + public boolean isOutOfBounds(Cell cell) { + if (cell.x > getWidth() - 1 + || cell.y > getHeight() - 1 + || cell.x < 0 + || cell.y < 0) + return true; + return false; + } + + public boolean isOutOfBounds(int x, int y) { + char c = get(x, y); + if (0 == c) + return true; + return false; + } + + public boolean isBlank(Cell cell) { + char c = get(cell); + if (0 == c) + return false; + return c == ' '; + } + + public boolean isBlank(int x, int y) { + char c = get(x, y); + if (0 == c) + return true; + return c == ' '; + } + + public boolean isCorner(Cell cell) { + return isCorner(cell.x, cell.y); + } + + public boolean isCorner(int x, int y) { + return (isNormalCorner(x, y) || isRoundCorner(x, y)); + } + + + public boolean matchesAny(Cell cell, GridPatternGroup criteria) { + TextGrid subGrid = getTestingSubGrid(cell); + return subGrid.matchesAny(criteria); + } + + public boolean isCorner1(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner1Criteria); + } + + public boolean isCorner2(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner2Criteria); + } + + public boolean isCorner3(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner3Criteria); + } + + public boolean isCorner4(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner4Criteria); + } + + public boolean isCross(Cell cell) { + return matchesAny(cell, GridPatternGroup.crossCriteria); + } + + public boolean isK(Cell cell) { + return matchesAny(cell, GridPatternGroup.KCriteria); + } + + public boolean isInverseK(Cell cell) { + return matchesAny(cell, GridPatternGroup.inverseKCriteria); + } + + public boolean isT(Cell cell) { + return matchesAny(cell, GridPatternGroup.TCriteria); + } + + public boolean isInverseT(Cell cell) { + return matchesAny(cell, GridPatternGroup.inverseTCriteria); + } + + public boolean isNormalCorner(Cell cell) { + return matchesAny(cell, GridPatternGroup.normalCornerCriteria); + } + + public boolean isNormalCorner(int x, int y) { + return isNormalCorner(new Cell(x, y)); + } + + public boolean isRoundCorner(Cell cell) { + return matchesAny(cell, GridPatternGroup.roundCornerCriteria); + } + + public boolean isRoundCorner(int x, int y) { + return isRoundCorner(new Cell(x, y)); + } + + public boolean isIntersection(Cell cell) { + return matchesAny(cell, GridPatternGroup.intersectionCriteria); + } + + public boolean isIntersection(int x, int y) { + return isIntersection(new Cell(x, y)); + } + + public void copyCellsTo(CellSet cells, TextGrid grid) { + Iterator it = cells.iterator(); + while (it.hasNext()) { + Cell cell = (Cell) it.next(); + grid.set(cell, this.get(cell)); + } + } + + public boolean equals(TextGrid grid) { + if (grid.getHeight() != this.getHeight() + || grid.getWidth() != this.getWidth() + ) { + return false; } - - public boolean isBoundary(Cell cell) { - char c = get(cell.x, cell.y); - if (0 == c) return false; - if ('+' == c || '\\' == c || '/' == c) { - System.out.print(""); - if ( - isIntersection(cell) - || isCorner(cell) - || isStub(cell) - || isCrossOnLine(cell)) { - return true; - } else return false; - } - //return StringUtils.isOneOf(c, undisputableBoundaries); - if (StringUtils.isOneOf(c, boundaries) && !isLoneDiagonal(cell)) { - return true; - } + int height = grid.getHeight(); + for (int i = 0; i < height; i++) { + String row1 = this.getRow(i).toString(); + String row2 = grid.getRow(i).toString(); + if (!row1.equals(row2)) return false; } + return true; + } + + /** + * Fills all the cells in cells with c + * + * @param cells + * @param c + */ + public void fillCellsWith(Iterable cells, char c) { + Iterator it = cells.iterator(); + while (it.hasNext()) { + Cell cell = it.next(); + set(cell.x, cell.y, c); + } + } + + /** + * Fills the continuous area with if c1 characters with c2, + * flooding from cell x, y + * + * @param x + * @param y + * @param c1 the character to replace + * @param c2 the character to replace c1 with + * @return the list of cells filled + */ + // public CellSet fillContinuousArea(int x, int y, char c1, char c2){ + // CellSet cells = new CellSet(); + // //fillContinuousArea_internal(x, y, c1, c2, cells); + // seedFill(new Cell(x, y), c1, c2); + // return cells; + // } + public CellSet fillContinuousArea(int x, int y, char c) { + return fillContinuousArea(new Cell(x, y), c); + } + + public CellSet fillContinuousArea(Cell cell, char c) { + if (isOutOfBounds(cell)) + throw new IllegalArgumentException("Attempted to fill area out of bounds: " + cell); + return seedFillOld(cell, c); + } + + private CellSet seedFill(Cell seed, char newChar) { + CellSet cellsFilled = new CellSet(); + char oldChar = get(seed); + + if (oldChar == newChar) + return cellsFilled; + if (isOutOfBounds(seed)) + return cellsFilled; + + Stack stack = new Stack(); + + stack.push(seed); + + while (!stack.isEmpty()) { + Cell cell = (Cell) stack.pop(); + + //set(cell, newChar); + cellsFilled.add(cell); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if (get(nCell) == oldChar && !cellsFilled.contains(nCell)) + stack.push(nCell); + if (get(sCell) == oldChar && !cellsFilled.contains(sCell)) + stack.push(sCell); + if (get(eCell) == oldChar && !cellsFilled.contains(eCell)) + stack.push(eCell); + if (get(wCell) == oldChar && !cellsFilled.contains(wCell)) + stack.push(wCell); + } + + return cellsFilled; + } + + private CellSet seedFillOld(Cell seed, char newChar) { + CellSet cellsFilled = new CellSet(); + char oldChar = get(seed); + + if (oldChar == newChar) + return cellsFilled; + if (isOutOfBounds(seed)) + return cellsFilled; - public boolean isLine(Cell cell) { - return isHorizontalLine(cell) || isVerticalLine(cell); - } + Stack stack = new Stack(); + + stack.push(seed); + + while (!stack.isEmpty()) { + Cell cell = (Cell) stack.pop(); + + set(cell, newChar); + cellsFilled.add(cell); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if (get(nCell) == oldChar) + stack.push(nCell); + if (get(sCell) == oldChar) + stack.push(sCell); + if (get(eCell) == oldChar) + stack.push(eCell); + if (get(wCell) == oldChar) + stack.push(wCell); + } + + return cellsFilled; + } + + + /** + * Locates and returns the '*' boundaries that we would + * encounter if we did a flood-fill at seed. + * + * @param seed + * @return + */ + public CellSet findBoundariesExpandingFrom(Cell seed) { + CellSet boundaries = new CellSet(); + char oldChar = get(seed); - public static boolean isHorizontalLine(char c) { - return StringUtils.isOneOf(c, horizontalLines); - } + if (isOutOfBounds(seed)) + return boundaries; - public boolean isHorizontalLine(Cell cell) { - return isHorizontalLine(cell.x, cell.y) && isInPlainMode(cell); - } + char newChar = 1; //TODO: kludge - public boolean isHorizontalLine(int x, int y) { - char c = get(x, y); - if (0 == c) return false; - return StringUtils.isOneOf(c, horizontalLines); - } + Stack stack = new Stack(); - public static boolean isVerticalLine(char c) { - return StringUtils.isOneOf(c, verticalLines); - } - - public boolean isVerticalLine(Cell cell) { - return isVerticalLine(cell.x, cell.y) && isInPlainMode(cell); - } + stack.push(seed); - public boolean isVerticalLine(int x, int y) { - char c = get(x, y); - if (0 == c) return false; - return StringUtils.isOneOf(c, verticalLines); - } + while (!stack.isEmpty()) { + Cell cell = (Cell) stack.pop(); - public boolean isLinesEnd(int x, int y) { - return isLinesEnd(new Cell(x, y)); - } + set(cell, newChar); - /** - * Stubs are also considered end of lines - * - * @param cell - * @return - */ - public boolean isLinesEnd(Cell cell) { - return matchesAny(cell, GridPatternGroup.linesEndCriteria); - } + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); - public boolean isVerticalLinesEnd(Cell cell) { - return matchesAny(cell, GridPatternGroup.verticalLinesEndCriteria); - } + if (get(nCell) == oldChar) + stack.push(nCell); + else if (get(nCell) == '*') + boundaries.add(nCell); - public boolean isHorizontalLinesEnd(Cell cell) { - return matchesAny(cell, GridPatternGroup.horizontalLinesEndCriteria); - } + if (get(sCell) == oldChar) + stack.push(sCell); + else if (get(sCell) == '*') + boundaries.add(sCell); + if (get(eCell) == oldChar) + stack.push(eCell); + else if (get(eCell) == '*') + boundaries.add(eCell); - public boolean isPointCell(Cell cell) { - return ( - isCorner(cell) - || isIntersection(cell) - || isStub(cell) - || isLinesEnd(cell)); + if (get(wCell) == oldChar) + stack.push(wCell); + else if (get(wCell) == '*') + boundaries.add(wCell); } + return boundaries; + } - public boolean containsAtLeastOneDashedLine(CellSet set) { - Iterator it = set.iterator(); - while (it.hasNext()) { - Cell cell = (Cell) it.next(); - if (StringUtils.isOneOf(get(cell), dashedLines)) return true; - } - return false; - } - - public boolean exactlyOneNeighbourIsBoundary(Cell cell) { - int howMany = 0; - if (isBoundary(cell.getNorth())) howMany++; - if (isBoundary(cell.getSouth())) howMany++; - if (isBoundary(cell.getEast())) howMany++; - if (isBoundary(cell.getWest())) howMany++; - return (howMany == 1); - } - - /** - * A stub looks like that: - * - *
-     *
-     * +- or -+ or + or + or /- or -/ or / (you get the point)
-     *             |    |                |
-     *
-     * 
- * - * @param cell - * @return - */ - public boolean isStub(Cell cell) { - return matchesAny(cell, GridPatternGroup.stubCriteria); - } - - public boolean isCrossOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.crossOnLineCriteria); - } + //TODO: incomplete method seedFillLine() + private CellSet seedFillLine(Cell cell, char newChar) { + CellSet cellsFilled = new CellSet(); - public boolean isHorizontalCrossOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.horizontalCrossOnLineCriteria); - } + Stack stack = new Stack(); - public boolean isVerticalCrossOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.verticalCrossOnLineCriteria); - } + char oldChar = get(cell); - public boolean isStarOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.starOnLineCriteria); - } + if (oldChar == newChar) + return cellsFilled; + if (isOutOfBounds(cell)) + return cellsFilled; - public boolean isLoneDiagonal(Cell cell) { - return matchesAny(cell, GridPatternGroup.loneDiagonalCriteria); - } + stack.push(new LineSegment(cell.x, cell.x, cell.y, 1)); + stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, -1)); + int left; + while (!stack.isEmpty()) { + LineSegment segment = (LineSegment) stack.pop(); + int x; + //expand to the left + for ( + x = segment.x1; + x >= 0 && get(x, segment.y) == oldChar; + --x) { + set(x, segment.y, newChar); + cellsFilled.add(new Cell(x, segment.y)); + } - public boolean isHorizontalStarOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.horizontalStarOnLineCriteria); - } + left = cell.getEast().x; + boolean skip = (x > segment.x1) ? true : false; - public boolean isVerticalStarOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.verticalStarOnLineCriteria); - } + if (left < segment.x1) { //leak on left? + //TODO: i think the first param should be x + stack.push( + //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); + new LineSegment(x, left, segment.y - 1, -segment.dy)); + } - public boolean isArrowhead(Cell cell) { - return (isNorthArrowhead(cell) - || isSouthArrowhead(cell) - || isWestArrowhead(cell) - || isEastArrowhead(cell)); - } - - public boolean isNorthArrowhead(Cell cell) { - return get(cell) == '^' && isInPlainMode(cell); - } - - public boolean isEastArrowhead(Cell cell) { - return get(cell) == '>' && isInPlainMode(cell); - } - - public boolean isWestArrowhead(Cell cell) { - return get(cell) == '<' && isInPlainMode(cell); - } - - public boolean isSouthArrowhead(Cell cell) { - return (get(cell) == 'v' || get(cell) == 'V') - && isVerticalLine(cell.getNorth()) - && isInPlainMode(cell); - } - - -// unicode for bullets -// -// 2022 bullet -// 25CF black circle -// 25AA black circle (small) -// 25A0 black square -// 25A1 white square -// 25CB white circle -// 25BA black right-pointing pointer - - - public boolean isBullet(int x, int y) { - return isBullet(new Cell(x, y)); - } + x = segment.x1 + 1; + do { + if (!skip) { + for (; x < getWidth() && get(x, segment.y) == oldChar; ++x) { + set(x, segment.y, newChar); + cellsFilled.add(new Cell(x, segment.y)); + } + + stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); + if (x > segment.x2 + 1) //leak on right? + stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, -segment.dy)); + } + skip = false; //skip only once - public boolean isBullet(Cell cell) { - char c = get(cell); - if ((c == 'o' || c == '*') - && isBlank(cell.getEast()) - && isBlank(cell.getWest()) - && Character.isLetterOrDigit(get(cell.getEast().getEast())) - && isInPlainMode(cell)) - return true; - return false; - } - - public void replaceBullets() { - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - Cell cell = new Cell(xi, yi); - if (isBullet(cell)) { - set(cell, ' '); - set(cell.getEast(), '\u2022'); - } - } - } - } - - /** - * true if the cell is not blank - * but the previous (west) is - * - * @param cell - * @return - */ - public boolean isStringsStart(Cell cell) { - return (!isBlank(cell) && isBlank(cell.getWest())); - } - - /** - * true if the cell is not blank - * but the next (east) is - * - * @param cell - * @return - */ - public boolean isStringsEnd(Cell cell) { - return (!isBlank(cell) - //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); - && isBlank(cell.getEast())); - } - - public int otherStringsStartInTheSameColumn(Cell cell) { - if (!isStringsStart(cell)) return 0; - int result = 0; - int height = getHeight(); - for (int y = 0; y < height; y++) { - Cell cCell = new Cell(cell.x, y); - if (!cCell.equals(cell) && isStringsStart(cCell)) { - result++; - } - } - return result; - } - - public int otherStringsEndInTheSameColumn(Cell cell) { - if (!isStringsEnd(cell)) return 0; - int result = 0; - int height = getHeight(); - for (int y = 0; y < height; y++) { - Cell cCell = new Cell(cell.x, y); - if (!cCell.equals(cell) && isStringsEnd(cCell)) { - result++; - } - } - return result; - } - - public boolean isColumnBlank(int x) { - int height = getHeight(); - for (int y = 0; y < height; y++) { - if (!isBlank(x, y)) return false; + for (++x; x <= segment.x2 && get(x, segment.y) != oldChar; ++x) { + ; } - return true; + left = x; + } while (x < segment.x2); } + return cellsFilled; + } + + public boolean cellContainsDashedLineChar(Cell cell) { + char c = get(cell); + return StringUtils.isOneOf(c, dashedLines); + } + + public boolean loadFrom(String filename) + throws FileNotFoundException, IOException { + return loadFrom(filename, null); + } + + public boolean loadFrom(String filename, ProcessingOptions options) + throws IOException { + + String encoding = (options == null) ? null : options.getCharacterEncoding(); + ArrayList lines = new ArrayList(); + InputStream is; + if ("-".equals(filename)) + is = System.in; + else + is = new FileInputStream(filename); + String[] linesArray = FileUtils.readFile(is, filename, encoding).split("(\r)?\n"); + for (int i = 0; i < linesArray.length; i++) + lines.add(new StringBuilder(linesArray[i])); + + return initialiseWithLines(lines, options); + } + + public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException { + + ArrayList lines = new ArrayList(); + String[] linesArray = text.split("(\r)?\n"); + for (int i = 0; i < linesArray.length; i++) + lines.add(new StringBuilder(linesArray[i])); + + return initialiseWithLines(lines, options); + } + + public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException { + + //remove blank rows at the bottom + boolean done = false; + int i; + for (i = lines.size() - 1; i >= 0 && !done; i--) { + StringBuilder row = lines.get(i); + if (!StringUtils.isBlank(row.toString())) + done = true; + } + rows = new ArrayList(lines.subList(0, i + 2)); + this.updateModeRows(); + try { + + if (options != null) + fixTabs(options.getTabSize()); + else + fixTabs(options.DEFAULT_TAB_SIZE); + + // make all lines of equal length + // add blank outline around the buffer to prevent fill glitch + // convert tabs to spaces (or remove them if setting is 0) + + int blankBorderSize = 2; + + int maxLength = 0; + int index = 0; + + String encoding = null; + if (options != null) + encoding = options.getCharacterEncoding(); + + Iterator it = rows.iterator(); + while (it.hasNext()) { + String row = it.next().toString(); + if (encoding != null) { + byte[] bytes = row.getBytes(); + row = new String(bytes, encoding); + } + if (row.length() > maxLength) + maxLength = row.length(); + rows.set(index, new StringBuilder(row)); + index++; + } + + it = rows.iterator(); + ArrayList newRows = new ArrayList(); + //TODO: make the following depend on blankBorderSize + + StringBuilder topBottomRow = + new StringBuilder(StringUtils.repeatString(" ", maxLength + blankBorderSize * 2)); + + newRows.add(topBottomRow); + newRows.add(topBottomRow); + while (it.hasNext()) { + StringBuilder row = it.next(); + + if (row.length() < maxLength) { + String borderString = StringUtils.repeatString(" ", blankBorderSize); + StringBuilder newRow = new StringBuilder(); + + newRow.append(borderString); + newRow.append(row); + newRow.append(StringUtils.repeatString(" ", maxLength - row.length())); + newRow.append(borderString); - public CellSet followLine(int x, int y) { - return followLine(new Cell(x, y)); - } - - public CellSet followIntersection(Cell cell) { - return followIntersection(cell, null); - } - - public CellSet followIntersection(Cell cell, Cell blocked) { - if (!isIntersection(cell)) return null; - CellSet result = new CellSet(); - Cell cN = cell.getNorth(); - Cell cS = cell.getSouth(); - Cell cE = cell.getEast(); - Cell cW = cell.getWest(); - if (hasEntryPoint(cN, 6)) result.add(cN); - if (hasEntryPoint(cS, 2)) result.add(cS); - if (hasEntryPoint(cE, 8)) result.add(cE); - if (hasEntryPoint(cW, 4)) result.add(cW); - if (result.contains(blocked)) result.remove(blocked); - return result; - } - - /** - * Returns the neighbours of a line-cell that are boundaries - * (0 to 2 cells are returned) - * - * @param cell - * @return null if the cell is not a line - */ - public CellSet followLine(Cell cell) { - if (isHorizontalLine(cell)) { - CellSet result = new CellSet(); - if (isBoundary(cell.getEast())) result.add(cell.getEast()); - if (isBoundary(cell.getWest())) result.add(cell.getWest()); - return result; - } else if (isVerticalLine(cell)) { - CellSet result = new CellSet(); - if (isBoundary(cell.getNorth())) result.add(cell.getNorth()); - if (isBoundary(cell.getSouth())) result.add(cell.getSouth()); - return result; + newRows.add(newRow); + } else { //TODO: why is the following line like that? + newRows.add(new StringBuilder(" ").append(row).append(" ")); } - return null; - } - - public CellSet followLine(Cell cell, Cell blocked) { - CellSet nextCells = followLine(cell); - if (nextCells.contains(blocked)) nextCells.remove(blocked); - return nextCells; - } - - public CellSet followCorner(Cell cell) { - return followCorner(cell, null); + } + //TODO: make the following depend on blankBorderSize + newRows.add(topBottomRow); + newRows.add(topBottomRow); + rows = newRows; + } finally { + this.updateModeRows(); } - public CellSet followCorner(Cell cell, Cell blocked) { - if (!isCorner(cell)) return null; - if (isCorner1(cell)) return followCorner1(cell, blocked); - if (isCorner2(cell)) return followCorner2(cell, blocked); - if (isCorner3(cell)) return followCorner3(cell, blocked); - if (isCorner4(cell)) return followCorner4(cell, blocked); - return null; - } + replaceBullets(); + replaceHumanColorCodes(); - public CellSet followCorner1(Cell cell) { - return followCorner1(cell, null); - } + return true; + } - public CellSet followCorner1(Cell cell, Cell blocked) { - if (!isCorner1(cell)) return null; - CellSet result = new CellSet(); - if (!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); - if (!cell.getEast().equals(blocked)) result.add(cell.getEast()); - return result; - } + private void fixTabs(int tabSize) { - public CellSet followCorner2(Cell cell) { - return followCorner2(cell, null); - } + int rowIndex = 0; + Iterator it = rows.iterator(); - public CellSet followCorner2(Cell cell, Cell blocked) { - if (!isCorner2(cell)) return null; - CellSet result = new CellSet(); - if (!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); - if (!cell.getWest().equals(blocked)) result.add(cell.getWest()); - return result; - } - - public CellSet followCorner3(Cell cell) { - return followCorner3(cell, null); - } + while (it.hasNext()) { + String row = it.next().toString(); + StringBuilder newRow = new StringBuilder(); - public CellSet followCorner3(Cell cell, Cell blocked) { - if (!isCorner3(cell)) return null; - CellSet result = new CellSet(); - if (!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); - if (!cell.getWest().equals(blocked)) result.add(cell.getWest()); - return result; - } - - public CellSet followCorner4(Cell cell) { - return followCorner4(cell, null); - } - - public CellSet followCorner4(Cell cell, Cell blocked) { - if (!isCorner4(cell)) return null; - CellSet result = new CellSet(); - if (!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); - if (!cell.getEast().equals(blocked)) result.add(cell.getEast()); - return result; - } - - - public CellSet followStub(Cell cell) { - return followStub(cell, null); - } - - public CellSet followStub(Cell cell, Cell blocked) { - if (!isStub(cell)) return null; - CellSet result = new CellSet(); - if (isBoundary(cell.getEast())) result.add(cell.getEast()); - else if (isBoundary(cell.getWest())) result.add(cell.getWest()); - else if (isBoundary(cell.getNorth())) result.add(cell.getNorth()); - else if (isBoundary(cell.getSouth())) result.add(cell.getSouth()); - if (result.contains(blocked)) result.remove(blocked); - return result; - } - - public CellSet followCell(Cell cell) { - return followCell(cell, null); - } - - public CellSet followCell(Cell cell, Cell blocked) { - if (isIntersection(cell)) return followIntersection(cell, blocked); - if (isCorner(cell)) return followCorner(cell, blocked); - if (isLine(cell)) return followLine(cell, blocked); - if (isStub(cell)) return followStub(cell, blocked); - if (isCrossOnLine(cell)) return followCrossOnLine(cell, blocked); - System.err.println("Ambiguous input at position " + cell + ":"); - TextGrid subGrid = getTestingSubGrid(cell); - subGrid.printDebug(); - throw new RuntimeException("Cannot follow cell " + cell + ": cannot determine cell type"); - } - - public String getCellTypeAsString(Cell cell) { - if (isK(cell)) return "K"; - if (isT(cell)) return "T"; - if (isInverseK(cell)) return "inverse K"; - if (isInverseT(cell)) return "inverse T"; - if (isCorner1(cell)) return "corner 1"; - if (isCorner2(cell)) return "corner 2"; - if (isCorner3(cell)) return "corner 3"; - if (isCorner4(cell)) return "corner 4"; - if (isLine(cell)) return "line"; - if (isStub(cell)) return "stub"; - if (isCrossOnLine(cell)) return "crossOnLine"; - return "unrecognisable type"; - } - - - public CellSet followCrossOnLine(Cell cell, Cell blocked) { - CellSet result = new CellSet(); - if (isHorizontalCrossOnLine(cell)) { - result.add(cell.getEast()); - result.add(cell.getWest()); - } else if (isVerticalCrossOnLine(cell)) { - result.add(cell.getNorth()); - result.add(cell.getSouth()); + char[] chars = row.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '\t') { + int spacesLeft = tabSize - newRow.length() % tabSize; + if (DEBUG) { + System.out.println("Found tab. Spaces left: " + spacesLeft); + } + String spaces = StringUtils.repeatString(" ", spacesLeft); + newRow.append(spaces); + } else { + String character = Character.toString(chars[i]); + newRow.append(character); } - if (result.contains(blocked)) result.remove(blocked); - return result; - } - - public boolean isOutOfBounds(Cell cell) { - if (cell.x > getWidth() - 1 - || cell.y > getHeight() - 1 - || cell.x < 0 - || cell.y < 0) return true; - return false; - } - - public boolean isOutOfBounds(int x, int y) { - char c = get(x, y); - if (0 == c) return true; - return false; - } - - public boolean isBlank(Cell cell) { - char c = get(cell); - if (0 == c) return false; - return c == ' '; + } + rows.set(rowIndex, newRow); + rowIndex++; } + } - public boolean isBlank(int x, int y) { - char c = get(x, y); - if (0 == c) return true; - return c == ' '; - } - - public boolean isCorner(Cell cell) { - return isCorner(cell.x, cell.y); - } + /** + * @return + */ + protected ArrayList getRows() { + return rows; + } - public boolean isCorner(int x, int y) { - return (isNormalCorner(x, y) || isRoundCorner(x, y)); - } - - - public boolean matchesAny(Cell cell, GridPatternGroup criteria) { - TextGrid subGrid = getTestingSubGrid(cell); - return subGrid.matchesAny(criteria); - } + private boolean isInPlainMode(Cell cell) { + return this.modeRows.get(cell.y).charAt(cell.x) == PLAIN_MODE; + } - public boolean isCorner1(Cell cell) { - return matchesAny(cell, GridPatternGroup.corner1Criteria); - } + private boolean isInPlainMode(int x, int y) { + return this.modeRows.get(y).charAt(x) == PLAIN_MODE; + } - public boolean isCorner2(Cell cell) { - return matchesAny(cell, GridPatternGroup.corner2Criteria); + public class CellColorPair { + public CellColorPair(Cell cell, Color color) { + this.cell = cell; + this.color = color; } - public boolean isCorner3(Cell cell) { - return matchesAny(cell, GridPatternGroup.corner3Criteria); - } + public Color color; + public Cell cell; + } - public boolean isCorner4(Cell cell) { - return matchesAny(cell, GridPatternGroup.corner4Criteria); + public class CellStringPair { + public CellStringPair(Cell cell, String string) { + this.cell = cell; + this.string = string; } - public boolean isCross(Cell cell) { - return matchesAny(cell, GridPatternGroup.crossCriteria); - } + public Cell cell; + public String string; + } - public boolean isK(Cell cell) { - return matchesAny(cell, GridPatternGroup.KCriteria); + public class CellTagPair { + public CellTagPair(Cell cell, String tag) { + this.cell = cell; + this.tag = tag; } - public boolean isInverseK(Cell cell) { - return matchesAny(cell, GridPatternGroup.inverseKCriteria); - } + public Cell cell; + public String tag; + } - public boolean isT(Cell cell) { - return matchesAny(cell, GridPatternGroup.TCriteria); - } - public boolean isInverseT(Cell cell) { - return matchesAny(cell, GridPatternGroup.inverseTCriteria); - } + public class Cell { - public boolean isNormalCorner(Cell cell) { - return matchesAny(cell, GridPatternGroup.normalCornerCriteria); - } + public int x, y; - public boolean isNormalCorner(int x, int y) { - return isNormalCorner(new Cell(x, y)); + public Cell(Cell cell) { + this(cell.x, cell.y); } - public boolean isRoundCorner(Cell cell) { - return matchesAny(cell, GridPatternGroup.roundCornerCriteria); + public Cell(int x, int y) { + this.x = x; + this.y = y; } - public boolean isRoundCorner(int x, int y) { - return isRoundCorner(new Cell(x, y)); + public Cell getNorth() { + return new Cell(x, y - 1); } - public boolean isIntersection(Cell cell) { - return matchesAny(cell, GridPatternGroup.intersectionCriteria); + public Cell getSouth() { + return new Cell(x, y + 1); } - public boolean isIntersection(int x, int y) { - return isIntersection(new Cell(x, y)); + public Cell getEast() { + return new Cell(x + 1, y); } - public void copyCellsTo(CellSet cells, TextGrid grid) { - Iterator it = cells.iterator(); - while (it.hasNext()) { - Cell cell = (Cell) it.next(); - grid.set(cell, this.get(cell)); - } + public Cell getWest() { + return new Cell(x - 1, y); } - public boolean equals(TextGrid grid) { - if (grid.getHeight() != this.getHeight() - || grid.getWidth() != this.getWidth() - ) { - return false; - } - int height = grid.getHeight(); - for (int i = 0; i < height; i++) { - String row1 = this.getRow(i).toString(); - String row2 = grid.getRow(i).toString(); - if (!row1.equals(row2)) return false; - } - return true; + public Cell getNW() { + return new Cell(x - 1, y - 1); } - /** - * Fills all the cells in cells with c - * - * @param cells - * @param c - */ - public void fillCellsWith(Iterable cells, char c) { - Iterator it = cells.iterator(); - while (it.hasNext()) { - Cell cell = it.next(); - set(cell.x, cell.y, c); - } + public Cell getNE() { + return new Cell(x + 1, y - 1); } - /** - * Fills the continuous area with if c1 characters with c2, - * flooding from cell x, y - * - * @param x - * @param y - * @param c1 the character to replace - * @param c2 the character to replace c1 with - * @return the list of cells filled - */ -// public CellSet fillContinuousArea(int x, int y, char c1, char c2){ -// CellSet cells = new CellSet(); -// //fillContinuousArea_internal(x, y, c1, c2, cells); -// seedFill(new Cell(x, y), c1, c2); -// return cells; -// } - public CellSet fillContinuousArea(int x, int y, char c) { - return fillContinuousArea(new Cell(x, y), c); + public Cell getSW() { + return new Cell(x - 1, y + 1); } - public CellSet fillContinuousArea(Cell cell, char c) { - if (isOutOfBounds(cell)) throw new IllegalArgumentException("Attempted to fill area out of bounds: " + cell); - return seedFillOld(cell, c); + public Cell getSE() { + return new Cell(x + 1, y + 1); } - private CellSet seedFill(Cell seed, char newChar) { - CellSet cellsFilled = new CellSet(); - char oldChar = get(seed); - - if (oldChar == newChar) return cellsFilled; - if (isOutOfBounds(seed)) return cellsFilled; + public CellSet getNeighbours4() { + CellSet result = new CellSet(); - Stack stack = new Stack(); + result.add(getNorth()); + result.add(getSouth()); + result.add(getWest()); + result.add(getEast()); - stack.push(seed); - - while (!stack.isEmpty()) { - Cell cell = (Cell) stack.pop(); - - //set(cell, newChar); - cellsFilled.add(cell); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if (get(nCell) == oldChar && !cellsFilled.contains(nCell)) stack.push(nCell); - if (get(sCell) == oldChar && !cellsFilled.contains(sCell)) stack.push(sCell); - if (get(eCell) == oldChar && !cellsFilled.contains(eCell)) stack.push(eCell); - if (get(wCell) == oldChar && !cellsFilled.contains(wCell)) stack.push(wCell); - } - - return cellsFilled; + return result; } - private CellSet seedFillOld(Cell seed, char newChar) { - CellSet cellsFilled = new CellSet(); - char oldChar = get(seed); - - if (oldChar == newChar) return cellsFilled; - if (isOutOfBounds(seed)) return cellsFilled; + public CellSet getNeighbours8() { + CellSet result = new CellSet(); - Stack stack = new Stack(); + result.add(getNorth()); + result.add(getSouth()); + result.add(getWest()); + result.add(getEast()); - stack.push(seed); + result.add(getNW()); + result.add(getNE()); + result.add(getSW()); + result.add(getSE()); - while (!stack.isEmpty()) { - Cell cell = (Cell) stack.pop(); - - set(cell, newChar); - cellsFilled.add(cell); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if (get(nCell) == oldChar) stack.push(nCell); - if (get(sCell) == oldChar) stack.push(sCell); - if (get(eCell) == oldChar) stack.push(eCell); - if (get(wCell) == oldChar) stack.push(wCell); - } - - return cellsFilled; + return result; } - /** - * Locates and returns the '*' boundaries that we would - * encounter if we did a flood-fill at seed. - * - * @param seed - * @return - */ - public CellSet findBoundariesExpandingFrom(Cell seed) { - CellSet boundaries = new CellSet(); - char oldChar = get(seed); - - if (isOutOfBounds(seed)) return boundaries; - - char newChar = 1; //TODO: kludge - - Stack stack = new Stack(); - - stack.push(seed); - - while (!stack.isEmpty()) { - Cell cell = (Cell) stack.pop(); - - set(cell, newChar); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if (get(nCell) == oldChar) stack.push(nCell); - else if (get(nCell) == '*') boundaries.add(nCell); - - if (get(sCell) == oldChar) stack.push(sCell); - else if (get(sCell) == '*') boundaries.add(sCell); - - if (get(eCell) == oldChar) stack.push(eCell); - else if (get(eCell) == '*') boundaries.add(eCell); - - if (get(wCell) == oldChar) stack.push(wCell); - else if (get(wCell) == '*') boundaries.add(wCell); - } - - return boundaries; - } - - - //TODO: incomplete method seedFillLine() - private CellSet seedFillLine(Cell cell, char newChar) { - CellSet cellsFilled = new CellSet(); - - Stack stack = new Stack(); - - char oldChar = get(cell); - - if (oldChar == newChar) return cellsFilled; - if (isOutOfBounds(cell)) return cellsFilled; - - stack.push(new LineSegment(cell.x, cell.x, cell.y, 1)); - stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, -1)); - - int left; - while (!stack.isEmpty()) { - LineSegment segment = (LineSegment) stack.pop(); - int x; - //expand to the left - for ( - x = segment.x1; - x >= 0 && get(x, segment.y) == oldChar; - --x) { - set(x, segment.y, newChar); - cellsFilled.add(new Cell(x, segment.y)); - } - - left = cell.getEast().x; - boolean skip = (x > segment.x1) ? true : false; - - if (left < segment.x1) { //leak on left? - //TODO: i think the first param should be x - stack.push( - //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); - new LineSegment(x, left, segment.y - 1, -segment.dy)); - } - - x = segment.x1 + 1; - do { - if (!skip) { - for (; x < getWidth() && get(x, segment.y) == oldChar; ++x) { - set(x, segment.y, newChar); - cellsFilled.add(new Cell(x, segment.y)); - } - - stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); - if (x > segment.x2 + 1) //leak on right? - stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, -segment.dy)); - } - skip = false; //skip only once - - for (++x; x <= segment.x2 && get(x, segment.y) != oldChar; ++x) { - ; - } - left = x; - } while (x < segment.x2); - } - - return cellsFilled; - } - - public boolean cellContainsDashedLineChar(Cell cell) { - char c = get(cell); - return StringUtils.isOneOf(c, dashedLines); + public boolean isNorthOf(Cell cell) { + if (this.y < cell.y) + return true; + return false; } - public boolean loadFrom(String filename) - throws FileNotFoundException, IOException { - return loadFrom(filename, null); + public boolean isSouthOf(Cell cell) { + if (this.y > cell.y) + return true; + return false; } - public boolean loadFrom(String filename, ProcessingOptions options) - throws IOException { - - String encoding = (options == null) ? null : options.getCharacterEncoding(); - ArrayList lines = new ArrayList(); - InputStream is; - if ("-".equals(filename)) - is = System.in; - else - is = new FileInputStream(filename); - String[] linesArray = FileUtils.readFile(is, filename, encoding).split("(\r)?\n"); - for (int i = 0; i < linesArray.length; i++) - lines.add(new StringBuilder(linesArray[i])); - - return initialiseWithLines(lines, options); + public boolean isWestOf(Cell cell) { + if (this.x < cell.x) + return true; + return false; } - public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException { - - ArrayList lines = new ArrayList(); - String[] linesArray = text.split("(\r)?\n"); - for (int i = 0; i < linesArray.length; i++) - lines.add(new StringBuilder(linesArray[i])); - - return initialiseWithLines(lines, options); + public boolean isEastOf(Cell cell) { + if (this.x > cell.x) + return true; + return false; } - public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException { - - //remove blank rows at the bottom - boolean done = false; - int i; - for (i = lines.size() - 1; i >= 0 && !done; i--) { - StringBuilder row = lines.get(i); - if (!StringUtils.isBlank(row.toString())) done = true; - } - rows = new ArrayList(lines.subList(0, i + 2)); - this.updateModeRows(); - try { - - if (options != null) fixTabs(options.getTabSize()); - else fixTabs(options.DEFAULT_TAB_SIZE); - - - // make all lines of equal length - // add blank outline around the buffer to prevent fill glitch - // convert tabs to spaces (or remove them if setting is 0) - - int blankBorderSize = 2; - - int maxLength = 0; - int index = 0; - - String encoding = null; - if (options != null) encoding = options.getCharacterEncoding(); - - Iterator it = rows.iterator(); - while (it.hasNext()) { - String row = it.next().toString(); - if (encoding != null) { - byte[] bytes = row.getBytes(); - row = new String(bytes, encoding); - } - if (row.length() > maxLength) maxLength = row.length(); - rows.set(index, new StringBuilder(row)); - index++; - } - - it = rows.iterator(); - ArrayList newRows = new ArrayList(); - //TODO: make the following depend on blankBorderSize - - StringBuilder topBottomRow = - new StringBuilder(StringUtils.repeatString(" ", maxLength + blankBorderSize * 2)); - - newRows.add(topBottomRow); - newRows.add(topBottomRow); - while (it.hasNext()) { - StringBuilder row = it.next(); - - if (row.length() < maxLength) { - String borderString = StringUtils.repeatString(" ", blankBorderSize); - StringBuilder newRow = new StringBuilder(); - - newRow.append(borderString); - newRow.append(row); - newRow.append(StringUtils.repeatString(" ", maxLength - row.length())); - newRow.append(borderString); - - newRows.add(newRow); - } else { //TODO: why is the following line like that? - newRows.add(new StringBuilder(" ").append(row).append(" ")); - } - } - //TODO: make the following depend on blankBorderSize - newRows.add(topBottomRow); - newRows.add(topBottomRow); - rows = newRows; - } finally { - this.updateModeRows(); - } - - replaceBullets(); - replaceHumanColorCodes(); + public boolean equals(Object o) { + Cell cell = (Cell) o; + if (cell == null) + return false; + if (x == cell.x && y == cell.y) return true; + else + return false; } - private void fixTabs(int tabSize) { - - int rowIndex = 0; - Iterator it = rows.iterator(); - - while (it.hasNext()) { - String row = it.next().toString(); - StringBuilder newRow = new StringBuilder(); - - char[] chars = row.toCharArray(); - for (int i = 0; i < chars.length; i++) { - if (chars[i] == '\t') { - int spacesLeft = tabSize - newRow.length() % tabSize; - if (DEBUG) { - System.out.println("Found tab. Spaces left: " + spacesLeft); - } - String spaces = StringUtils.repeatString(" ", spacesLeft); - newRow.append(spaces); - } else { - String character = Character.toString(chars[i]); - newRow.append(character); - } - } - rows.set(rowIndex, newRow); - rowIndex++; - } - } - - /** - * @return - */ - protected ArrayList getRows() { - return rows; + public int hashCode() { + return (x << 16) | y; } - private boolean isInPlainMode(Cell cell) { - return this.modeRows.get(cell.y).charAt(cell.x) == PLAIN_MODE; + public boolean isNextTo(int x2, int y2) { + if (Math.abs(x2 - x) == 1 && Math.abs(y2 - y) == 1) + return false; + if (Math.abs(x2 - x) == 1 && y2 == y) + return true; + if (Math.abs(y2 - y) == 1 && x2 == x) + return true; + return false; } - public class CellColorPair { - public CellColorPair(Cell cell, Color color) { - this.cell = cell; - this.color = color; - } - - public Color color; - public Cell cell; + public boolean isNextTo(Cell cell) { + if (cell == null) + throw new IllegalArgumentException("cell cannot be null"); + return this.isNextTo(cell.x, cell.y); } - public class CellStringPair { - public CellStringPair(Cell cell, String string) { - this.cell = cell; - this.string = string; - } - - public Cell cell; - public String string; + public String toString() { + return "(" + x + ", " + y + ")"; } - public class CellTagPair { - public CellTagPair(Cell cell, String tag) { - this.cell = cell; - this.tag = tag; - } - - public Cell cell; - public String tag; + public void scale(int s) { + x = x * s; + y = y * s; } + } - public class Cell { - - public int x, y; + private class LineSegment { + int x1, x2, y, dy; - public Cell(Cell cell) { - this(cell.x, cell.y); - } - - public Cell(int x, int y) { - this.x = x; - this.y = y; - } - - public Cell getNorth() { - return new Cell(x, y - 1); - } - - public Cell getSouth() { - return new Cell(x, y + 1); - } - - public Cell getEast() { - return new Cell(x + 1, y); - } - - public Cell getWest() { - return new Cell(x - 1, y); - } - - public Cell getNW() { - return new Cell(x - 1, y - 1); - } - - public Cell getNE() { - return new Cell(x + 1, y - 1); - } - - public Cell getSW() { - return new Cell(x - 1, y + 1); - } - - public Cell getSE() { - return new Cell(x + 1, y + 1); - } - - public CellSet getNeighbours4() { - CellSet result = new CellSet(); - - result.add(getNorth()); - result.add(getSouth()); - result.add(getWest()); - result.add(getEast()); - - return result; - } - - public CellSet getNeighbours8() { - CellSet result = new CellSet(); - - result.add(getNorth()); - result.add(getSouth()); - result.add(getWest()); - result.add(getEast()); - - result.add(getNW()); - result.add(getNE()); - result.add(getSW()); - result.add(getSE()); - - return result; - } - - - public boolean isNorthOf(Cell cell) { - if (this.y < cell.y) return true; - return false; - } - - public boolean isSouthOf(Cell cell) { - if (this.y > cell.y) return true; - return false; - } - - public boolean isWestOf(Cell cell) { - if (this.x < cell.x) return true; - return false; - } - - public boolean isEastOf(Cell cell) { - if (this.x > cell.x) return true; - return false; - } - - - public boolean equals(Object o) { - Cell cell = (Cell) o; - if (cell == null) return false; - if (x == cell.x && y == cell.y) return true; - else return false; - } - - public int hashCode() { - return (x << 16) | y; - } - - public boolean isNextTo(int x2, int y2) { - if (Math.abs(x2 - x) == 1 && Math.abs(y2 - y) == 1) return false; - if (Math.abs(x2 - x) == 1 && y2 == y) return true; - if (Math.abs(y2 - y) == 1 && x2 == x) return true; - return false; - } - - public boolean isNextTo(Cell cell) { - if (cell == null) throw new IllegalArgumentException("cell cannot be null"); - return this.isNextTo(cell.x, cell.y); - } - - public String toString() { - return "(" + x + ", " + y + ")"; - } - - public void scale(int s) { - x = x * s; - y = y * s; - } - - } - - private class LineSegment { - int x1, x2, y, dy; - - public LineSegment(int x1, int x2, int y, int dy) { - this.x1 = x1; - this.x2 = x2; - this.y = y; - this.dy = dy; - } + public LineSegment(int x1, int x2, int y, int dy) { + this.x1 = x1; + this.x2 = x2; + this.y = y; + this.dy = dy; } + } } diff --git a/test-resources/text/art-latexmath-1.txt b/test-resources/text/art-latexmath-1.txt index 69513b5..e273263 100644 --- a/test-resources/text/art-latexmath-1.txt +++ b/test-resources/text/art-latexmath-1.txt @@ -1,4 +1,5 @@ +$Box_1$ $Box^2$ +---------------------+ +------+ |$\sum_{i=0}^{n}x^i$ | |$cBLU$| /----\ | +--->|cRED +--+cGRE| @@ -10,4 +11,13 @@ |$A_i$ hello $B^i$| +-----------------+ -$Q_u^i$, $C_k$, $B_r^{own}$, $F_{ox}$ jumps over a lazy $d\cdot\frac{o}{g}$. +$Q_u^i$, $C_k$, $B_r^{own}$, $F_{ox}$ jumps + +over a lazy $d\cdot\frac{o}{g}$. + + +$\forall x \in X, \quad \exists y \leq \epsilon$ + +$\sin A \cos B = \frac{1}{2}\left[ \sin(A-B)+\sin(A+B) \right]$ + +$\frac{d}{dx}\left( \int_{0}^{x} f(u)\,du\right)=f(x).$ \ No newline at end of file From 65b99dfa4d7bdc1d06704ad008fd0769f711b51c Mon Sep 17 00:00:00 2001 From: dakusui Date: Sun, 28 Oct 2018 07:05:29 +0900 Subject: [PATCH 04/12] Update test input --- test-resources/text/art-latexmath-1.txt | 28 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/test-resources/text/art-latexmath-1.txt b/test-resources/text/art-latexmath-1.txt index e273263..b74ba1e 100644 --- a/test-resources/text/art-latexmath-1.txt +++ b/test-resources/text/art-latexmath-1.txt @@ -1,14 +1,15 @@ $Box_1$ $Box^2$ +---------------------+ +------+ -|$\sum_{i=0}^{n}x^i$ | |$cBLU$| /----\ -| +--->|cRED +--+cGRE| -| | | | \----/ -+---------------------+ +---+--+ - | - V - +-----------------+ - |$A_i$ hello $B^i$| +|$\sum_{i=0}^{n}x^i$ | |$cBLU$| /---------\ +| +--->|cRED +-=-+cGRE$C_k$| +| | |cXYZ | \---------/ ++----------+----------+ +---+--+ + | | + | : + | V + | +-----------------+ + +---------->|$A_i$ hello $B^i$| +-----------------+ $Q_u^i$, $C_k$, $B_r^{own}$, $F_{ox}$ jumps @@ -18,6 +19,13 @@ over a lazy $d\cdot\frac{o}{g}$. $\forall x \in X, \quad \exists y \leq \epsilon$ -$\sin A \cos B = \frac{1}{2}\left[ \sin(A-B)+\sin(A+B) \right]$ -$\frac{d}{dx}\left( \int_{0}^{x} f(u)\,du\right)=f(x).$ \ No newline at end of file +$\sin A \cos B =$ + + $ \frac{1}{2}\left[ \sin(A-B)+\sin(A+B) \right]$ + + +$\frac{d}{dx}\left( \int_{0}^{x} f(u)\,du\right)=f(x).$ + + + $v \sim \mathcal{N} (m,\sigma^2)$ \ No newline at end of file From 80cfd1e4efefb6880f96edb0fa7d580d010be83e Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Sun, 28 Oct 2018 13:22:12 +0900 Subject: [PATCH 05/12] Update a resource for testing and example. --- test-resources/text/art-latexmath-1.txt | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/test-resources/text/art-latexmath-1.txt b/test-resources/text/art-latexmath-1.txt index b74ba1e..f1f2a58 100644 --- a/test-resources/text/art-latexmath-1.txt +++ b/test-resources/text/art-latexmath-1.txt @@ -1,16 +1,23 @@ $Box_1$ $Box^2$ -+---------------------+ +------+ -|$\sum_{i=0}^{n}x^i$ | |$cBLU$| /---------\ ++---------------------+ +------+ /---------\ +|$\sum_{i=0}^{n}x^i$ | |$cBLU$| | | | +--->|cRED +-=-+cGRE$C_k$| -| | |cXYZ | \---------/ -+----------+----------+ +---+--+ +|{io} | |cXYZ | |{o} | ++----------+----------+ +---+--+ \---------/ | | | : | V - | +-----------------+ - +---------->|$A_i$ hello $B^i$| - +-----------------+ + | +-------------------+ + +---------->*$A_i$ hello $B^i$ | + | +----+ + | |c8FA| + +--------------+----+ + +$|Set| = o-*-Freunde-*-nicht=*=diese-=-*- * töne$ + +o Quick brown fox jumps over +* a lazy dog. $Q_u^i$, $C_k$, $B_r^{own}$, $F_{ox}$ jumps From 980c218888328333bfd2c3f1ceb5a0c07ba1d736 Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Sun, 28 Oct 2018 14:07:09 +0900 Subject: [PATCH 06/12] Fix a bug where '*' inside LaTeX mode is rendered as a point marker. --- src/java/org/stathissideris/ascii2image/text/TextGrid.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/java/org/stathissideris/ascii2image/text/TextGrid.java b/src/java/org/stathissideris/ascii2image/text/TextGrid.java index 5ffe52e..bd72364 100644 --- a/src/java/org/stathissideris/ascii2image/text/TextGrid.java +++ b/src/java/org/stathissideris/ascii2image/text/TextGrid.java @@ -415,7 +415,7 @@ public void replacePointMarkersOnLine() { char c = get(xi, yi); Cell cell = new Cell(xi, yi); if (StringUtils.isOneOf(c, pointMarkers) - && isStarOnLine(cell)) { + && isStarOnLine(cell) && isInPlainMode(cell)) { boolean isOnHorizontalLine = false; if (StringUtils.isOneOf(get(cell.getEast()), horizontalLines)) @@ -453,6 +453,8 @@ public CellSet getPointMarkersOnLine() { int height = getHeight(); for (int yi = 0; yi < height; yi++) { for (int xi = 0; xi < width; xi++) { + if (!isInPlainMode(xi, yi)) + continue; char c = get(xi, yi); if (StringUtils.isOneOf(c, pointMarkers) && isStarOnLine(new Cell(xi, yi))) { From 8280383d92f83f706f67549ed879ae88529b9baf Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Sun, 28 Oct 2018 20:04:36 +0900 Subject: [PATCH 07/12] Revert automatic code formatting changes made by IntelliJ --- .../ascii2image/text/TextGrid.java | 3899 ++++++++--------- 1 file changed, 1843 insertions(+), 2056 deletions(-) diff --git a/src/java/org/stathissideris/ascii2image/text/TextGrid.java b/src/java/org/stathissideris/ascii2image/text/TextGrid.java index bd72364..082057c 100644 --- a/src/java/org/stathissideris/ascii2image/text/TextGrid.java +++ b/src/java/org/stathissideris/ascii2image/text/TextGrid.java @@ -1,20 +1,21 @@ /** * ditaa - Diagrams Through Ascii Art - *

+ * * Copyright (C) 2004-2011 Efstathios Sideris - *

+ * * ditaa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - *

+ * * ditaa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - *

+ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . + * */ package org.stathissideris.ascii2image.text; @@ -34,2061 +35,1847 @@ /** + * * @author Efstathios Sideris */ public class TextGrid { - private static final boolean DEBUG = false; - private static final char PLAIN_MODE = 'P'; - public static final char LATEX_MODE = 'L'; - - private ArrayList rows; - private List modeRows; - - private static char[] boundaries = { '/', '\\', '|', '-', '*', '=', ':' }; - private static char[] undisputableBoundaries = { '|', '-', '*', '=', ':' }; - private static char[] horizontalLines = { '-', '=' }; - private static char[] verticalLines = { '|', ':' }; - private static char[] arrowHeads = { '<', '>', '^', 'v', 'V' }; - private static char[] cornerChars = { '\\', '/', '+' }; - private static char[] pointMarkers = { '*' }; - private static char[] dashedLines = { ':', '~', '=' }; - - private static char[] entryPoints1 = { '\\' }; - private static char[] entryPoints2 = { '|', ':', '+', '\\', '/' }; - private static char[] entryPoints3 = { '/' }; - private static char[] entryPoints4 = { '-', '=', '+', '\\', '/' }; - private static char[] entryPoints5 = { '\\' }; - private static char[] entryPoints6 = { '|', ':', '+', '\\', '/' }; - private static char[] entryPoints7 = { '/' }; - private static char[] entryPoints8 = { '-', '=', '+', '\\', '/' }; - - - private static HashMap humanColorCodes = new HashMap(); - - static { - humanColorCodes.put("GRE", "9D9"); - humanColorCodes.put("BLU", "55B"); - humanColorCodes.put("PNK", "FAA"); - humanColorCodes.put("RED", "E32"); - humanColorCodes.put("YEL", "FF3"); - humanColorCodes.put("BLK", "000"); - - } - - private static HashSet markupTags = - new HashSet(); - - static { - markupTags.add("d"); - markupTags.add("s"); - markupTags.add("io"); - markupTags.add("c"); - markupTags.add("mo"); - markupTags.add("tr"); - markupTags.add("o"); - } - - private void updateModeRows() { - // Collectors.toList creates ArrayList and you see no performance penalty - // caused by using LinkedList here. - this.modeRows = this.rows.stream().map(TextGrid::transformRowToModeRow).collect(toList()); - } - - public static String transformRowToModeRow(CharSequence row) { - StringBuilder b = new StringBuilder(); - row.chars().map(new IntUnaryOperator() { - /** - * This field hold current mode of a cell in the grid. - * Only 2 values can be assigned, which are 'P' and 'L'. - * They respectively represent 'Plain', which is normal ditaa mode, and 'LaTeX', which is LaTeX - * formula mode. - */ - int cur = PLAIN_MODE; - - @Override - public int applyAsInt(int operand) { - if (cur == PLAIN_MODE) { - if (operand == '$') { - cur = LATEX_MODE; - return LATEX_MODE; - } - return PLAIN_MODE; - } else if (cur == LATEX_MODE) { - if (operand == '$') { - cur = PLAIN_MODE; - return LATEX_MODE; - } - return this.cur; - } - throw new IllegalStateException(); - } - }).forEach(c -> b.append((char) c)); - String ret = b.toString(); - if (ret.endsWith("L")) - if (row.charAt(row.length() - 1) != '$') - throw new IllegalArgumentException("LaTex mode was started but not finished."); - return ret; - } - - public void addToMarkupTags(Collection tags) { - markupTags.addAll(tags); - } - - public static void main(String[] args) throws Exception { - TextGrid grid = new TextGrid(); - grid.loadFrom("tests/text/art10.txt"); - - grid.writeStringTo(grid.new Cell(28, 1), "testing"); - - grid.findMarkupTags(); - - grid.printDebug(); - //System.out.println(grid.fillContinuousArea(0, 0, '-').size()+" cells filled"); - //grid.fillContinuousArea(4, 4, '-'); - //grid.getSubGrid(1,1,3,3).printDebug(); - //grid.printDebug(); - } - - - public TextGrid() { - rows = new ArrayList(); - this.updateModeRows(); - } - - public TextGrid(int width, int height) { - String space = StringUtils.repeatString(" ", width); - rows = new ArrayList(); - for (int i = 0; i < height; i++) - rows.add(new StringBuilder(space)); - this.updateModeRows(); - } - - public static TextGrid makeSameSizeAs(TextGrid grid) { - return new TextGrid(grid.getWidth(), grid.getHeight()); - } - - - public TextGrid(TextGrid otherGrid) { - rows = new ArrayList(); - for (StringBuilder row : otherGrid.getRows()) { - rows.add(new StringBuilder(row)); - } - this.updateModeRows(); - } - - public void clear() { - String blank = StringUtils.repeatString(" ", getWidth()); - int height = getHeight(); - rows.clear(); - for (int i = 0; i < height; i++) - rows.add(new StringBuilder(blank)); - } - - // duplicated code due to lots of hits to this function - public char get(int x, int y) { - if (x > getWidth() - 1 - || y > getHeight() - 1 - || x < 0 - || y < 0) - return 0; - return rows.get(y).charAt(x); - } - - //duplicated code due to lots of hits to this function - public char get(Cell cell) { - if (cell.x > getWidth() - 1 - || cell.y > getHeight() - 1 - || cell.x < 0 - || cell.y < 0) - return 0; - return rows.get(cell.y).charAt(cell.x); - } - - public StringBuilder getRow(int y) { - return rows.get(y); - } - - public TextGrid getSubGrid(int x, int y, int width, int height) { - TextGrid grid = new TextGrid(width, height); - for (int i = 0; i < height; i++) { - grid.setRow(i, new StringBuilder(getRow(y + i).subSequence(x, x + width))); - } - return grid; - } - - public TextGrid getTestingSubGrid(Cell cell) { - return getSubGrid(cell.x - 1, cell.y - 1, 3, 3); - } - - - public String getStringAt(int x, int y, int length) { - return getStringAt(new Cell(x, y), length); - } - - public String getStringAt(Cell cell, int length) { - int x = cell.x; - int y = cell.y; - if (x > getWidth() - 1 - || y > getHeight() - 1 - || x < 0 - || y < 0) - return null; - return rows.get(y).substring(x, x + length); - } - - public char getNorthOf(int x, int y) { - return get(x, y - 1); - } - - public char getSouthOf(int x, int y) { - return get(x, y + 1); - } - - public char getEastOf(int x, int y) { - return get(x + 1, y); - } - - public char getWestOf(int x, int y) { - return get(x - 1, y); - } - - public char getNorthOf(Cell cell) { - return getNorthOf(cell.x, cell.y); - } - - public char getSouthOf(Cell cell) { - return getSouthOf(cell.x, cell.y); - } - - public char getEastOf(Cell cell) { - return getEastOf(cell.x, cell.y); - } - - public char getWestOf(Cell cell) { - return getWestOf(cell.x, cell.y); - } - - public void writeStringTo(int x, int y, String str) { - writeStringTo(new Cell(x, y), str); - } - - public void writeStringTo(Cell cell, String str) { - if (isOutOfBounds(cell)) - return; - rows.get(cell.y).replace(cell.x, cell.x + str.length(), str); - } - - public void set(Cell cell, char c) { - set(cell.x, cell.y, c); - } - - public void set(int x, int y, char c) { - if (x > getWidth() - 1 || y > getHeight() - 1) - return; - StringBuilder row = rows.get(y); - row.setCharAt(x, c); - } - - public void setRow(int y, String row) { - if (y > getHeight() || row.length() != getWidth()) - throw new IllegalArgumentException("setRow out of bounds or string wrong size"); - rows.set(y, new StringBuilder(row)); - } - - public void setRow(int y, StringBuilder row) { - if (y > getHeight() || row.length() != getWidth()) - throw new IllegalArgumentException("setRow out of bounds or string wrong size"); - rows.set(y, row); - } - - public int getWidth() { - if (rows.size() == 0) - return 0; //empty buffer - return rows.get(0).length(); - } - - public int getHeight() { - return rows.size(); - } - - public void printDebug() { - Iterator it = rows.iterator(); - int i = 0; - System.out.println( - " " - + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1)); - while (it.hasNext()) { - String row = it.next().toString(); - String index = new Integer(i).toString(); - if (i < 10) - index = " " + index; - System.out.println(index + " (" + row + ")"); - i++; - } - } - - public String getDebugString() { - StringBuilder buffer = new StringBuilder(); - Iterator it = rows.iterator(); - int i = 0; - buffer.append( - " " - + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1) + "\n"); - while (it.hasNext()) { - String row = it.next().toString(); - String index = new Integer(i).toString(); - if (i < 10) - index = " " + index; - row = row.replaceAll("\n", "\\\\n"); - row = row.replaceAll("\r", "\\\\r"); - buffer.append(index + " (" + row + ")\n"); - i++; - } - return buffer.toString(); - } - - public String toString() { - return getDebugString(); - } - - /** - * Adds grid to this. Space characters in this grid - * are replaced with the corresponding contents of - * grid, otherwise the contents are unchanged. - * - * @param grid - * @return false if the grids are of different size - */ - public boolean add(TextGrid grid) { - if (getWidth() != grid.getWidth() - || getHeight() != grid.getHeight()) - return false; - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - if (get(xi, yi) == ' ') - set(xi, yi, grid.get(xi, yi)); - } - } - return true; - } - - /** - * Replaces letters or numbers that are on horizontal or vertical - * lines, with the appropriate character that will make the line - * continuous (| for vertical and - for horizontal lines) - */ - public void replaceTypeOnLine() { - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - char c = get(xi, yi); - if (Character.isLetterOrDigit(c)) { - boolean isOnHorizontalLine = isOnHorizontalLine(xi, yi); - boolean isOnVerticalLine = isOnVerticalLine(xi, yi); - if (isOnHorizontalLine && isOnVerticalLine) { - set(xi, yi, '+'); - if (DEBUG) - System.out.println("replaced type on line '" + c + "' with +"); - } else if (isOnHorizontalLine) { - set(xi, yi, '-'); - if (DEBUG) - System.out.println("replaced type on line '" + c + "' with -"); - } else if (isOnVerticalLine) { - set(xi, yi, '|'); - if (DEBUG) - System.out.println("replaced type on line '" + c + "' with |"); - } - } - } - } - } - - public void replacePointMarkersOnLine() { - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - char c = get(xi, yi); - Cell cell = new Cell(xi, yi); - if (StringUtils.isOneOf(c, pointMarkers) - && isStarOnLine(cell) && isInPlainMode(cell)) { - - boolean isOnHorizontalLine = false; - if (StringUtils.isOneOf(get(cell.getEast()), horizontalLines)) - isOnHorizontalLine = true; - if (StringUtils.isOneOf(get(cell.getWest()), horizontalLines)) - isOnHorizontalLine = true; - - boolean isOnVerticalLine = false; - if (StringUtils.isOneOf(get(cell.getNorth()), verticalLines)) - isOnVerticalLine = true; - if (StringUtils.isOneOf(get(cell.getSouth()), verticalLines)) - isOnVerticalLine = true; - - if (isOnHorizontalLine && isOnVerticalLine) { - set(xi, yi, '+'); - if (DEBUG) - System.out.println("replaced marker on line '" + c + "' with +"); - } else if (isOnHorizontalLine) { - set(xi, yi, '-'); - if (DEBUG) - System.out.println("replaced marker on line '" + c + "' with -"); - } else if (isOnVerticalLine) { - set(xi, yi, '|'); - if (DEBUG) - System.out.println("replaced marker on line '" + c + "' with |"); - } - } - } - } - } - - public CellSet getPointMarkersOnLine() { - CellSet result = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - if (!isInPlainMode(xi, yi)) - continue; - char c = get(xi, yi); - if (StringUtils.isOneOf(c, pointMarkers) - && isStarOnLine(new Cell(xi, yi))) { - result.add(new Cell(xi, yi)); - } - } - } - return result; - } - - - public void _replaceHumanColorCodes() { - int height = getHeight(); - for (int y = 0; y < height; y++) { - String row = rows.get(y).toString(); - Iterator it = humanColorCodes.keySet().iterator(); - while (it.hasNext()) { - String humanCode = (String) it.next(); - String hexCode = (String) humanColorCodes.get(humanCode); - if (hexCode != null) { - humanCode = "c" + humanCode; - hexCode = "c" + hexCode; - row = row.replaceAll(humanCode, hexCode); - rows.set(y, new StringBuilder(row)); //TODO: this is not the most efficient way to do this - row = rows.get(y).toString(); - } - } - } - } - - public void replaceHumanColorCodes() { - int height = getHeight(); - for (int y = 0; y < height; y++) - rows.set(y, replaceHumanColorCodes(y, rows.get(y))); - } - - private StringBuilder replaceHumanColorCodes(int rowIndex, StringBuilder in) { - Pattern p = Pattern.compile("(c.{0,3}|[^c]+)"); - StringBuilder ret = new StringBuilder(in.length()); - Iterator i = createTextSplitter(p, in); - while (i.hasNext()) { - String next = i.next(); - if (isInPlainMode(ret.length(), rowIndex) && looksColorCode(next)) { - String replacement = humanColorCodes.get(next.substring(1, 4)); - if (replacement != null) - ret.append(String.format("c%s", replacement)); - else - ret.append(next); - } else - ret.append(next); - } - return ret; - } - - private static boolean looksColorCode(String word) { - return word.length() >= 4 && word.startsWith("c"); - } - - /** - * Replace all occurrences of c1 with c2 - * - * @param c1 - * @param c2 - */ - public void replaceAll(char c1, char c2) { - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - char c = get(xi, yi); - if (c == c1) - set(xi, yi, c2); - } - } - } - - public boolean hasBlankCells() { - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - Cell cell = new Cell(x, y); - if (isBlank(cell)) - return true; - } - } - return false; - } - - - public CellSet getAllNonBlank() { - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - Cell cell = new Cell(x, y); - if (!isBlank(cell)) - set.add(cell); - } - } - return set; - } - - public CellSet getAllBoundaries() { - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - Cell cell = new Cell(x, y); - if (isBoundary(cell)) - set.add(cell); - } - } - return set; - } - - - public CellSet getAllBlanksBetweenCharacters() { - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - Cell cell = new Cell(x, y); - if (isBlankBetweenCharacters(cell)) - set.add(cell); - } - } - return set; - } - - - /** - * Returns an ArrayList of CellStringPairs that - * represents all the continuous (non-blank) Strings - * in the grid. Used on buffers that contain only - * type, in order to find the positions and the - * contents of the strings. - * - * @return - */ - public ArrayList findStrings() { - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - if (!isBlank(x, y)) { - Cell start = new Cell(x, y); - String str = String.valueOf(get(x, y)); - char c = get(++x, y); - boolean finished = false; - //while(c != ' '){ - while (!finished) { - str += String.valueOf(c); - c = get(++x, y); - char next = get(x + 1, y); - if ((c == ' ' || c == 0) && (next == ' ' || next == 0)) - finished = true; - } - result.add(new CellStringPair(start, str)); - } - } - } - return result; - } - - /** - * This is done in a bit of a messy way, should be impossible - * to go out of sync with corresponding GridPatternGroup. - * - * @param cell - * @param entryPointId - * @return - */ - public boolean hasEntryPoint(Cell cell, int entryPointId) { - String result = ""; - char c = get(cell); - if (entryPointId == 1) { - return StringUtils.isOneOf(c, entryPoints1); - - } else if (entryPointId == 2) { - return StringUtils.isOneOf(c, entryPoints2); - - } else if (entryPointId == 3) { - return StringUtils.isOneOf(c, entryPoints3); - - } else if (entryPointId == 4) { - return StringUtils.isOneOf(c, entryPoints4); - - } else if (entryPointId == 5) { - return StringUtils.isOneOf(c, entryPoints5); - - } else if (entryPointId == 6) { - return StringUtils.isOneOf(c, entryPoints6); - - } else if (entryPointId == 7) { - return StringUtils.isOneOf(c, entryPoints7); - - } else if (entryPointId == 8) { - return StringUtils.isOneOf(c, entryPoints8); - } - return false; - } - - /** - * true if cell is blank and the east and west cells are not - * (used to find gaps between words) - * - * @param cell - * @return - */ - public boolean isBlankBetweenCharacters(Cell cell) { - return (isBlank(cell) - && !isBlank(cell.getEast()) - && !isBlank(cell.getWest())); - } - - /** - * Makes blank all the cells that contain non-text - * elements. - */ - public void removeNonText() { - //the following order is significant - //since the south-pointing arrowheads - //are determined based on the surrounding boundaries - removeArrowheads(); - removeColorCodes(); - removeBoundaries(); - removeMarkupTags(); - } - - public void removeArrowheads() { - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - Cell cell = new Cell(xi, yi); - if (isArrowhead(cell)) - set(cell, ' '); - } - } - } - - public void removeColorCodes() { - Iterator cells = findColorCodes().iterator(); - while (cells.hasNext()) { - Cell cell = ((CellColorPair) cells.next()).cell; - set(cell, ' '); - cell = cell.getEast(); - set(cell, ' '); - cell = cell.getEast(); - set(cell, ' '); - cell = cell.getEast(); - set(cell, ' '); - } - } - - public void removeBoundaries() { - ArrayList toBeRemoved = new ArrayList(); - - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - Cell cell = new Cell(xi, yi); - if (isBoundary(cell)) - toBeRemoved.add(cell); - } - } - - //remove in two stages, because decision of - //isBoundary depends on content of surrounding - //cells - Iterator it = toBeRemoved.iterator(); - while (it.hasNext()) { - Cell cell = (Cell) it.next(); - if (isInPlainMode(cell)) - set(cell, ' '); - } - } - - public ArrayList findArrowheads() { - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - Cell cell = new Cell(xi, yi); - if (isArrowhead(cell)) - result.add(cell); - } - } - if (DEBUG) - System.out.println(result.size() + " arrowheads found"); - return result; - } - - - public ArrayList findColorCodes() { - Pattern colorCodePattern = Pattern.compile("c[A-F0-9]{3}"); - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width - 3; xi++) { - Cell cell = new Cell(xi, yi); - if (!isInPlainMode(cell)) - continue; - String s = getStringAt(cell, 4); - Matcher matcher = colorCodePattern.matcher(s); - if (matcher.matches()) { - char cR = s.charAt(1); - char cG = s.charAt(2); - char cB = s.charAt(3); - int r = Integer.valueOf(String.valueOf(cR), 16).intValue() * 17; - int g = Integer.valueOf(String.valueOf(cG), 16).intValue() * 17; - int b = Integer.valueOf(String.valueOf(cB), 16).intValue() * 17; - result.add(new CellColorPair(cell, new Color(r, g, b))); - } - } - } - if (DEBUG) - System.out.println(result.size() + " color codes found"); - return result; - } - - public ArrayList findMarkupTags() { - Pattern tagPattern = Pattern.compile("\\{(.+?)\\}"); - ArrayList result = new ArrayList(); - - int width = getWidth(); - int height = getHeight(); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width - 3; x++) { - if (!isInPlainMode(x, y)) - continue; - Cell cell = new Cell(x, y); - char c = get(cell); - if (c == '{') { - String rowPart = rows.get(y).substring(x); - Matcher matcher = tagPattern.matcher(rowPart); - if (matcher.find()) { - String tagName = matcher.group(1); - if (markupTags.contains(tagName)) { - if (DEBUG) - System.out.println("found tag " + tagName + " at " + x + ", " + y); - result.add(new CellTagPair(new Cell(x, y), tagName)); - } - } - } - } - } - return result; - } - - public void removeMarkupTags() { - Iterator it = findMarkupTags().iterator(); - while (it.hasNext()) { - CellTagPair pair = (CellTagPair) it.next(); - String tagName = pair.tag; - if (tagName == null) - continue; - int length = 2 + tagName.length(); - writeStringTo(pair.cell, StringUtils.repeatString(" ", length)); - } - } - - - public boolean matchesAny(GridPatternGroup criteria) { - return criteria.isAnyMatchedBy(this); - } - - public boolean matchesAll(GridPatternGroup criteria) { - return criteria.areAllMatchedBy(this); - } - - public boolean matches(GridPattern criteria) { - return criteria.isMatchedBy(this); - } - - - public boolean isOnHorizontalLine(Cell cell) { - return isOnHorizontalLine(cell.x, cell.y); - } - - private boolean isOnHorizontalLine(int x, int y) { - char c1 = get(x - 1, y); - char c2 = get(x + 1, y); - if (isHorizontalLine(c1) && isHorizontalLine(c2)) - return true; - return false; - } - - public boolean isOnVerticalLine(Cell cell) { - return isOnVerticalLine(cell.x, cell.y); - } - - private boolean isOnVerticalLine(int x, int y) { - char c1 = get(x, y - 1); - char c2 = get(x, y + 1); - if (isVerticalLine(c1) && isVerticalLine(c2)) - return true; - return false; - } - - - public static boolean isBoundary(char c) { - return StringUtils.isOneOf(c, boundaries); - } - - public boolean isBoundary(int x, int y) { - return isBoundary(new Cell(x, y)); - } - - public boolean isBoundary(Cell cell) { - if (!isInPlainMode(cell)) - return false; - char c = get(cell.x, cell.y); - if (0 == c) - return false; - if ('+' == c || '\\' == c || '/' == c) { - System.out.print(""); - if ( - isIntersection(cell) - || isCorner(cell) - || isStub(cell) - || isCrossOnLine(cell)) { - return true; - } else - return false; - } - //return StringUtils.isOneOf(c, undisputableBoundaries); - if (StringUtils.isOneOf(c, boundaries) && !isLoneDiagonal(cell)) { - return true; - } - return false; - } - - public boolean isLine(Cell cell) { - return isHorizontalLine(cell) || isVerticalLine(cell); - } - - public static boolean isHorizontalLine(char c) { - return StringUtils.isOneOf(c, horizontalLines); - } - - public boolean isHorizontalLine(Cell cell) { - return isHorizontalLine(cell.x, cell.y) && isInPlainMode(cell); - } - - public boolean isHorizontalLine(int x, int y) { - char c = get(x, y); - if (0 == c) - return false; - return StringUtils.isOneOf(c, horizontalLines) && isInPlainMode(x, y); - } - - public static boolean isVerticalLine(char c) { - return StringUtils.isOneOf(c, verticalLines); - } - - public boolean isVerticalLine(Cell cell) { - return isVerticalLine(cell.x, cell.y) && isInPlainMode(cell); - } - - public boolean isVerticalLine(int x, int y) { - char c = get(x, y); - if (0 == c) - return false; - return StringUtils.isOneOf(c, verticalLines); - } - - public boolean isLinesEnd(int x, int y) { - return isLinesEnd(new Cell(x, y)); - } - - /** - * Stubs are also considered end of lines - * - * @param cell - * @return - */ - public boolean isLinesEnd(Cell cell) { - return matchesAny(cell, GridPatternGroup.linesEndCriteria) && isInPlainMode(cell); - } - - public boolean isVerticalLinesEnd(Cell cell) { - return matchesAny(cell, GridPatternGroup.verticalLinesEndCriteria) && isInPlainMode(cell); - } - - public boolean isHorizontalLinesEnd(Cell cell) { - return matchesAny(cell, GridPatternGroup.horizontalLinesEndCriteria) && isInPlainMode(cell); - } - - - public boolean isPointCell(Cell cell) { - return ( - isCorner(cell) - || isIntersection(cell) - || isStub(cell) - || isLinesEnd(cell)) && isInPlainMode(cell); - } - - - public boolean containsAtLeastOneDashedLine(CellSet set) { - Iterator it = set.iterator(); - while (it.hasNext()) { - Cell cell = (Cell) it.next(); - if (StringUtils.isOneOf(get(cell), dashedLines)) - return true; - } - return false; - } - - public boolean exactlyOneNeighbourIsBoundary(Cell cell) { - int howMany = 0; - if (isBoundary(cell.getNorth())) - howMany++; - if (isBoundary(cell.getSouth())) - howMany++; - if (isBoundary(cell.getEast())) - howMany++; - if (isBoundary(cell.getWest())) - howMany++; - return (howMany == 1); - } - - /** - * A stub looks like that: - * - *

-   *
-   * +- or -+ or + or + or /- or -/ or / (you get the point)
-   *             |    |                |
-   *
-   * 
- * - * @param cell - * @return - */ - - public boolean isStub(Cell cell) { - return matchesAny(cell, GridPatternGroup.stubCriteria); - } - - public boolean isCrossOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.crossOnLineCriteria); - } - - public boolean isHorizontalCrossOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.horizontalCrossOnLineCriteria); - } - - public boolean isVerticalCrossOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.verticalCrossOnLineCriteria); - } - - public boolean isStarOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.starOnLineCriteria); - } - - public boolean isLoneDiagonal(Cell cell) { - return matchesAny(cell, GridPatternGroup.loneDiagonalCriteria); - } - - - public boolean isHorizontalStarOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.horizontalStarOnLineCriteria); - } - - public boolean isVerticalStarOnLine(Cell cell) { - return matchesAny(cell, GridPatternGroup.verticalStarOnLineCriteria); - } - - public boolean isArrowhead(Cell cell) { - return (isNorthArrowhead(cell) - || isSouthArrowhead(cell) - || isWestArrowhead(cell) - || isEastArrowhead(cell)); - } - - public boolean isNorthArrowhead(Cell cell) { - return get(cell) == '^' && isInPlainMode(cell); - } - - public boolean isEastArrowhead(Cell cell) { - return get(cell) == '>' && isInPlainMode(cell); - } - - public boolean isWestArrowhead(Cell cell) { - return get(cell) == '<' && isInPlainMode(cell); - } - - public boolean isSouthArrowhead(Cell cell) { - return (get(cell) == 'v' || get(cell) == 'V') - && isVerticalLine(cell.getNorth()) - && isInPlainMode(cell); - } - - // unicode for bullets - // - // 2022 bullet - // 25CF black circle - // 25AA black circle (small) - // 25A0 black square - // 25A1 white square - // 25CB white circle - // 25BA black right-pointing pointer - - - public boolean isBullet(int x, int y) { - return isBullet(new Cell(x, y)); - } - - public boolean isBullet(Cell cell) { - char c = get(cell); - if ((c == 'o' || c == '*') - && isBlank(cell.getEast()) - && isBlank(cell.getWest()) - && Character.isLetterOrDigit(get(cell.getEast().getEast())) - && isInPlainMode(cell)) - return true; - return false; - } - - public void replaceBullets() { - int width = getWidth(); - int height = getHeight(); - for (int yi = 0; yi < height; yi++) { - for (int xi = 0; xi < width; xi++) { - Cell cell = new Cell(xi, yi); - if (isBullet(cell)) { - set(cell, ' '); - set(cell.getEast(), '\u2022'); - } - } - } - } - - /** - * true if the cell is not blank - * but the previous (west) is - * - * @param cell - * @return - */ - public boolean isStringsStart(Cell cell) { - return (!isBlank(cell) && isBlank(cell.getWest())); - } - - /** - * true if the cell is not blank - * but the next (east) is - * - * @param cell - * @return - */ - public boolean isStringsEnd(Cell cell) { - return (!isBlank(cell) - //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); - && isBlank(cell.getEast())); - } - - public int otherStringsStartInTheSameColumn(Cell cell) { - if (!isStringsStart(cell)) - return 0; - int result = 0; - int height = getHeight(); - for (int y = 0; y < height; y++) { - Cell cCell = new Cell(cell.x, y); - if (!cCell.equals(cell) && isStringsStart(cCell)) { - result++; - } - } - return result; - } - - public int otherStringsEndInTheSameColumn(Cell cell) { - if (!isStringsEnd(cell)) - return 0; - int result = 0; - int height = getHeight(); - for (int y = 0; y < height; y++) { - Cell cCell = new Cell(cell.x, y); - if (!cCell.equals(cell) && isStringsEnd(cCell)) { - result++; - } - } - return result; - } - - public boolean isColumnBlank(int x) { - int height = getHeight(); - for (int y = 0; y < height; y++) { - if (!isBlank(x, y)) - return false; - } - return true; - } - - - public CellSet followLine(int x, int y) { - return followLine(new Cell(x, y)); - } - - public CellSet followIntersection(Cell cell) { - return followIntersection(cell, null); - } - - public CellSet followIntersection(Cell cell, Cell blocked) { - if (!isIntersection(cell)) - return null; - CellSet result = new CellSet(); - Cell cN = cell.getNorth(); - Cell cS = cell.getSouth(); - Cell cE = cell.getEast(); - Cell cW = cell.getWest(); - if (hasEntryPoint(cN, 6)) - result.add(cN); - if (hasEntryPoint(cS, 2)) - result.add(cS); - if (hasEntryPoint(cE, 8)) - result.add(cE); - if (hasEntryPoint(cW, 4)) - result.add(cW); - if (result.contains(blocked)) - result.remove(blocked); - return result; - } - - /** - * Returns the neighbours of a line-cell that are boundaries - * (0 to 2 cells are returned) - * - * @param cell - * @return null if the cell is not a line - */ - public CellSet followLine(Cell cell) { - if (isHorizontalLine(cell)) { - CellSet result = new CellSet(); - if (isBoundary(cell.getEast())) - result.add(cell.getEast()); - if (isBoundary(cell.getWest())) - result.add(cell.getWest()); - return result; - } else if (isVerticalLine(cell)) { - CellSet result = new CellSet(); - if (isBoundary(cell.getNorth())) - result.add(cell.getNorth()); - if (isBoundary(cell.getSouth())) - result.add(cell.getSouth()); - return result; - } - return null; - } - - public CellSet followLine(Cell cell, Cell blocked) { - CellSet nextCells = followLine(cell); - if (nextCells.contains(blocked)) - nextCells.remove(blocked); - return nextCells; - } - - public CellSet followCorner(Cell cell) { - return followCorner(cell, null); - } - - public CellSet followCorner(Cell cell, Cell blocked) { - if (!isCorner(cell)) - return null; - if (isCorner1(cell)) - return followCorner1(cell, blocked); - if (isCorner2(cell)) - return followCorner2(cell, blocked); - if (isCorner3(cell)) - return followCorner3(cell, blocked); - if (isCorner4(cell)) - return followCorner4(cell, blocked); - return null; - } - - public CellSet followCorner1(Cell cell) { - return followCorner1(cell, null); - } - - public CellSet followCorner1(Cell cell, Cell blocked) { - if (!isCorner1(cell)) - return null; - CellSet result = new CellSet(); - if (!cell.getSouth().equals(blocked)) - result.add(cell.getSouth()); - if (!cell.getEast().equals(blocked)) - result.add(cell.getEast()); - return result; - } - - public CellSet followCorner2(Cell cell) { - return followCorner2(cell, null); - } - - public CellSet followCorner2(Cell cell, Cell blocked) { - if (!isCorner2(cell)) - return null; - CellSet result = new CellSet(); - if (!cell.getSouth().equals(blocked)) - result.add(cell.getSouth()); - if (!cell.getWest().equals(blocked)) - result.add(cell.getWest()); - return result; - } - - public CellSet followCorner3(Cell cell) { - return followCorner3(cell, null); - } - - public CellSet followCorner3(Cell cell, Cell blocked) { - if (!isCorner3(cell)) - return null; - CellSet result = new CellSet(); - if (!cell.getNorth().equals(blocked)) - result.add(cell.getNorth()); - if (!cell.getWest().equals(blocked)) - result.add(cell.getWest()); - return result; - } - - public CellSet followCorner4(Cell cell) { - return followCorner4(cell, null); - } - - public CellSet followCorner4(Cell cell, Cell blocked) { - if (!isCorner4(cell)) - return null; - CellSet result = new CellSet(); - if (!cell.getNorth().equals(blocked)) - result.add(cell.getNorth()); - if (!cell.getEast().equals(blocked)) - result.add(cell.getEast()); - return result; - } - - - public CellSet followStub(Cell cell) { - return followStub(cell, null); - } - - public CellSet followStub(Cell cell, Cell blocked) { - if (!isStub(cell)) - return null; - CellSet result = new CellSet(); - if (isBoundary(cell.getEast())) - result.add(cell.getEast()); - else if (isBoundary(cell.getWest())) - result.add(cell.getWest()); - else if (isBoundary(cell.getNorth())) - result.add(cell.getNorth()); - else if (isBoundary(cell.getSouth())) - result.add(cell.getSouth()); - if (result.contains(blocked)) - result.remove(blocked); - return result; - } - - public CellSet followCell(Cell cell) { - return followCell(cell, null); - } - - public CellSet followCell(Cell cell, Cell blocked) { - if (isIntersection(cell)) - return followIntersection(cell, blocked); - if (isCorner(cell)) - return followCorner(cell, blocked); - if (isLine(cell)) - return followLine(cell, blocked); - if (isStub(cell)) - return followStub(cell, blocked); - if (isCrossOnLine(cell)) - return followCrossOnLine(cell, blocked); - System.err.println("Ambiguous input at position " + cell + ":"); - TextGrid subGrid = getTestingSubGrid(cell); - subGrid.printDebug(); - throw new RuntimeException("Cannot follow cell " + cell + ": cannot determine cell type"); - } - - public String getCellTypeAsString(Cell cell) { - if (isK(cell)) - return "K"; - if (isT(cell)) - return "T"; - if (isInverseK(cell)) - return "inverse K"; - if (isInverseT(cell)) - return "inverse T"; - if (isCorner1(cell)) - return "corner 1"; - if (isCorner2(cell)) - return "corner 2"; - if (isCorner3(cell)) - return "corner 3"; - if (isCorner4(cell)) - return "corner 4"; - if (isLine(cell)) - return "line"; - if (isStub(cell)) - return "stub"; - if (isCrossOnLine(cell)) - return "crossOnLine"; - return "unrecognisable type"; - } - - - public CellSet followCrossOnLine(Cell cell, Cell blocked) { - CellSet result = new CellSet(); - if (isHorizontalCrossOnLine(cell)) { - result.add(cell.getEast()); - result.add(cell.getWest()); - } else if (isVerticalCrossOnLine(cell)) { - result.add(cell.getNorth()); - result.add(cell.getSouth()); - } - if (result.contains(blocked)) - result.remove(blocked); - return result; - } - - public boolean isOutOfBounds(Cell cell) { - if (cell.x > getWidth() - 1 - || cell.y > getHeight() - 1 - || cell.x < 0 - || cell.y < 0) - return true; - return false; - } - - public boolean isOutOfBounds(int x, int y) { - char c = get(x, y); - if (0 == c) - return true; - return false; - } - - public boolean isBlank(Cell cell) { - char c = get(cell); - if (0 == c) - return false; - return c == ' '; - } - - public boolean isBlank(int x, int y) { - char c = get(x, y); - if (0 == c) - return true; - return c == ' '; - } - - public boolean isCorner(Cell cell) { - return isCorner(cell.x, cell.y); - } - - public boolean isCorner(int x, int y) { - return (isNormalCorner(x, y) || isRoundCorner(x, y)); - } - - - public boolean matchesAny(Cell cell, GridPatternGroup criteria) { - TextGrid subGrid = getTestingSubGrid(cell); - return subGrid.matchesAny(criteria); - } - - public boolean isCorner1(Cell cell) { - return matchesAny(cell, GridPatternGroup.corner1Criteria); - } - - public boolean isCorner2(Cell cell) { - return matchesAny(cell, GridPatternGroup.corner2Criteria); - } - - public boolean isCorner3(Cell cell) { - return matchesAny(cell, GridPatternGroup.corner3Criteria); - } - - public boolean isCorner4(Cell cell) { - return matchesAny(cell, GridPatternGroup.corner4Criteria); - } - - public boolean isCross(Cell cell) { - return matchesAny(cell, GridPatternGroup.crossCriteria); - } - - public boolean isK(Cell cell) { - return matchesAny(cell, GridPatternGroup.KCriteria); - } - - public boolean isInverseK(Cell cell) { - return matchesAny(cell, GridPatternGroup.inverseKCriteria); - } - - public boolean isT(Cell cell) { - return matchesAny(cell, GridPatternGroup.TCriteria); - } - - public boolean isInverseT(Cell cell) { - return matchesAny(cell, GridPatternGroup.inverseTCriteria); - } - - public boolean isNormalCorner(Cell cell) { - return matchesAny(cell, GridPatternGroup.normalCornerCriteria); - } - - public boolean isNormalCorner(int x, int y) { - return isNormalCorner(new Cell(x, y)); - } - - public boolean isRoundCorner(Cell cell) { - return matchesAny(cell, GridPatternGroup.roundCornerCriteria); - } - - public boolean isRoundCorner(int x, int y) { - return isRoundCorner(new Cell(x, y)); - } - - public boolean isIntersection(Cell cell) { - return matchesAny(cell, GridPatternGroup.intersectionCriteria); - } - - public boolean isIntersection(int x, int y) { - return isIntersection(new Cell(x, y)); - } - - public void copyCellsTo(CellSet cells, TextGrid grid) { - Iterator it = cells.iterator(); - while (it.hasNext()) { - Cell cell = (Cell) it.next(); - grid.set(cell, this.get(cell)); - } - } - - public boolean equals(TextGrid grid) { - if (grid.getHeight() != this.getHeight() - || grid.getWidth() != this.getWidth() - ) { - return false; - } - int height = grid.getHeight(); - for (int i = 0; i < height; i++) { - String row1 = this.getRow(i).toString(); - String row2 = grid.getRow(i).toString(); - if (!row1.equals(row2)) - return false; - } - return true; - } - - /** - * Fills all the cells in cells with c - * - * @param cells - * @param c - */ - public void fillCellsWith(Iterable cells, char c) { - Iterator it = cells.iterator(); - while (it.hasNext()) { - Cell cell = it.next(); - set(cell.x, cell.y, c); - } - } - - /** - * Fills the continuous area with if c1 characters with c2, - * flooding from cell x, y - * - * @param x - * @param y - * @param c1 the character to replace - * @param c2 the character to replace c1 with - * @return the list of cells filled - */ - // public CellSet fillContinuousArea(int x, int y, char c1, char c2){ - // CellSet cells = new CellSet(); - // //fillContinuousArea_internal(x, y, c1, c2, cells); - // seedFill(new Cell(x, y), c1, c2); - // return cells; - // } - public CellSet fillContinuousArea(int x, int y, char c) { - return fillContinuousArea(new Cell(x, y), c); - } - - public CellSet fillContinuousArea(Cell cell, char c) { - if (isOutOfBounds(cell)) - throw new IllegalArgumentException("Attempted to fill area out of bounds: " + cell); - return seedFillOld(cell, c); - } - - private CellSet seedFill(Cell seed, char newChar) { - CellSet cellsFilled = new CellSet(); - char oldChar = get(seed); - - if (oldChar == newChar) - return cellsFilled; - if (isOutOfBounds(seed)) - return cellsFilled; - - Stack stack = new Stack(); - - stack.push(seed); - - while (!stack.isEmpty()) { - Cell cell = (Cell) stack.pop(); - - //set(cell, newChar); - cellsFilled.add(cell); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if (get(nCell) == oldChar && !cellsFilled.contains(nCell)) - stack.push(nCell); - if (get(sCell) == oldChar && !cellsFilled.contains(sCell)) - stack.push(sCell); - if (get(eCell) == oldChar && !cellsFilled.contains(eCell)) - stack.push(eCell); - if (get(wCell) == oldChar && !cellsFilled.contains(wCell)) - stack.push(wCell); - } - - return cellsFilled; - } - - private CellSet seedFillOld(Cell seed, char newChar) { - CellSet cellsFilled = new CellSet(); - char oldChar = get(seed); - - if (oldChar == newChar) - return cellsFilled; - if (isOutOfBounds(seed)) - return cellsFilled; - - Stack stack = new Stack(); - - stack.push(seed); - - while (!stack.isEmpty()) { - Cell cell = (Cell) stack.pop(); - - set(cell, newChar); - cellsFilled.add(cell); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if (get(nCell) == oldChar) - stack.push(nCell); - if (get(sCell) == oldChar) - stack.push(sCell); - if (get(eCell) == oldChar) - stack.push(eCell); - if (get(wCell) == oldChar) - stack.push(wCell); - } - - return cellsFilled; - } - - - /** - * Locates and returns the '*' boundaries that we would - * encounter if we did a flood-fill at seed. - * - * @param seed - * @return - */ - public CellSet findBoundariesExpandingFrom(Cell seed) { - CellSet boundaries = new CellSet(); - char oldChar = get(seed); - - if (isOutOfBounds(seed)) - return boundaries; - - char newChar = 1; //TODO: kludge - - Stack stack = new Stack(); - - stack.push(seed); - - while (!stack.isEmpty()) { - Cell cell = (Cell) stack.pop(); - - set(cell, newChar); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if (get(nCell) == oldChar) - stack.push(nCell); - else if (get(nCell) == '*') - boundaries.add(nCell); - - if (get(sCell) == oldChar) - stack.push(sCell); - else if (get(sCell) == '*') - boundaries.add(sCell); - - if (get(eCell) == oldChar) - stack.push(eCell); - else if (get(eCell) == '*') - boundaries.add(eCell); - - if (get(wCell) == oldChar) - stack.push(wCell); - else if (get(wCell) == '*') - boundaries.add(wCell); - } - - return boundaries; - } - - - //TODO: incomplete method seedFillLine() - private CellSet seedFillLine(Cell cell, char newChar) { - CellSet cellsFilled = new CellSet(); - - Stack stack = new Stack(); - - char oldChar = get(cell); - - if (oldChar == newChar) - return cellsFilled; - if (isOutOfBounds(cell)) - return cellsFilled; - - stack.push(new LineSegment(cell.x, cell.x, cell.y, 1)); - stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, -1)); - - int left; - while (!stack.isEmpty()) { - LineSegment segment = (LineSegment) stack.pop(); - int x; - //expand to the left - for ( - x = segment.x1; - x >= 0 && get(x, segment.y) == oldChar; - --x) { - set(x, segment.y, newChar); - cellsFilled.add(new Cell(x, segment.y)); - } - - left = cell.getEast().x; - boolean skip = (x > segment.x1) ? true : false; - - if (left < segment.x1) { //leak on left? - //TODO: i think the first param should be x - stack.push( - //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); - new LineSegment(x, left, segment.y - 1, -segment.dy)); - } - - x = segment.x1 + 1; - do { - if (!skip) { - for (; x < getWidth() && get(x, segment.y) == oldChar; ++x) { - set(x, segment.y, newChar); - cellsFilled.add(new Cell(x, segment.y)); - } - - stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); - if (x > segment.x2 + 1) //leak on right? - stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, -segment.dy)); - } - skip = false; //skip only once - - for (++x; x <= segment.x2 && get(x, segment.y) != oldChar; ++x) { - ; - } - left = x; - } while (x < segment.x2); - } - - return cellsFilled; - } - - public boolean cellContainsDashedLineChar(Cell cell) { - char c = get(cell); - return StringUtils.isOneOf(c, dashedLines); - } - - public boolean loadFrom(String filename) - throws FileNotFoundException, IOException { - return loadFrom(filename, null); - } - - public boolean loadFrom(String filename, ProcessingOptions options) - throws IOException { - - String encoding = (options == null) ? null : options.getCharacterEncoding(); - ArrayList lines = new ArrayList(); - InputStream is; - if ("-".equals(filename)) - is = System.in; - else - is = new FileInputStream(filename); - String[] linesArray = FileUtils.readFile(is, filename, encoding).split("(\r)?\n"); - for (int i = 0; i < linesArray.length; i++) - lines.add(new StringBuilder(linesArray[i])); - - return initialiseWithLines(lines, options); - } - - public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException { - - ArrayList lines = new ArrayList(); - String[] linesArray = text.split("(\r)?\n"); - for (int i = 0; i < linesArray.length; i++) - lines.add(new StringBuilder(linesArray[i])); - - return initialiseWithLines(lines, options); - } - - public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException { - - //remove blank rows at the bottom - boolean done = false; - int i; - for (i = lines.size() - 1; i >= 0 && !done; i--) { - StringBuilder row = lines.get(i); - if (!StringUtils.isBlank(row.toString())) - done = true; - } - rows = new ArrayList(lines.subList(0, i + 2)); - this.updateModeRows(); - try { - - if (options != null) - fixTabs(options.getTabSize()); - else - fixTabs(options.DEFAULT_TAB_SIZE); - - // make all lines of equal length - // add blank outline around the buffer to prevent fill glitch - // convert tabs to spaces (or remove them if setting is 0) - - int blankBorderSize = 2; - - int maxLength = 0; - int index = 0; - - String encoding = null; - if (options != null) - encoding = options.getCharacterEncoding(); - - Iterator it = rows.iterator(); - while (it.hasNext()) { - String row = it.next().toString(); - if (encoding != null) { - byte[] bytes = row.getBytes(); - row = new String(bytes, encoding); - } - if (row.length() > maxLength) - maxLength = row.length(); - rows.set(index, new StringBuilder(row)); - index++; - } - - it = rows.iterator(); - ArrayList newRows = new ArrayList(); - //TODO: make the following depend on blankBorderSize - - StringBuilder topBottomRow = - new StringBuilder(StringUtils.repeatString(" ", maxLength + blankBorderSize * 2)); - - newRows.add(topBottomRow); - newRows.add(topBottomRow); - while (it.hasNext()) { - StringBuilder row = it.next(); - - if (row.length() < maxLength) { - String borderString = StringUtils.repeatString(" ", blankBorderSize); - StringBuilder newRow = new StringBuilder(); - - newRow.append(borderString); - newRow.append(row); - newRow.append(StringUtils.repeatString(" ", maxLength - row.length())); - newRow.append(borderString); - - newRows.add(newRow); - } else { //TODO: why is the following line like that? - newRows.add(new StringBuilder(" ").append(row).append(" ")); - } - } - //TODO: make the following depend on blankBorderSize - newRows.add(topBottomRow); - newRows.add(topBottomRow); - rows = newRows; - } finally { - this.updateModeRows(); - } - - replaceBullets(); - replaceHumanColorCodes(); - - return true; - } - - private void fixTabs(int tabSize) { - - int rowIndex = 0; - Iterator it = rows.iterator(); - - while (it.hasNext()) { - String row = it.next().toString(); - StringBuilder newRow = new StringBuilder(); - - char[] chars = row.toCharArray(); - for (int i = 0; i < chars.length; i++) { - if (chars[i] == '\t') { - int spacesLeft = tabSize - newRow.length() % tabSize; - if (DEBUG) { - System.out.println("Found tab. Spaces left: " + spacesLeft); - } - String spaces = StringUtils.repeatString(" ", spacesLeft); - newRow.append(spaces); - } else { - String character = Character.toString(chars[i]); - newRow.append(character); - } - } - rows.set(rowIndex, newRow); - rowIndex++; - } - } - - /** - * @return - */ - protected ArrayList getRows() { - return rows; - } - - private boolean isInPlainMode(Cell cell) { - return this.modeRows.get(cell.y).charAt(cell.x) == PLAIN_MODE; - } - - private boolean isInPlainMode(int x, int y) { - return this.modeRows.get(y).charAt(x) == PLAIN_MODE; - } - - public class CellColorPair { - public CellColorPair(Cell cell, Color color) { - this.cell = cell; - this.color = color; - } - - public Color color; - public Cell cell; - } - - public class CellStringPair { - public CellStringPair(Cell cell, String string) { - this.cell = cell; - this.string = string; - } - - public Cell cell; - public String string; - } - - public class CellTagPair { - public CellTagPair(Cell cell, String tag) { - this.cell = cell; - this.tag = tag; - } - - public Cell cell; - public String tag; - } - - - public class Cell { - - public int x, y; - - public Cell(Cell cell) { - this(cell.x, cell.y); - } - - public Cell(int x, int y) { - this.x = x; - this.y = y; - } - - public Cell getNorth() { - return new Cell(x, y - 1); - } - - public Cell getSouth() { - return new Cell(x, y + 1); - } - - public Cell getEast() { - return new Cell(x + 1, y); - } - - public Cell getWest() { - return new Cell(x - 1, y); - } - - public Cell getNW() { - return new Cell(x - 1, y - 1); - } - - public Cell getNE() { - return new Cell(x + 1, y - 1); - } - - public Cell getSW() { - return new Cell(x - 1, y + 1); - } - - public Cell getSE() { - return new Cell(x + 1, y + 1); - } - - public CellSet getNeighbours4() { - CellSet result = new CellSet(); - - result.add(getNorth()); - result.add(getSouth()); - result.add(getWest()); - result.add(getEast()); - - return result; - } - - public CellSet getNeighbours8() { - CellSet result = new CellSet(); - - result.add(getNorth()); - result.add(getSouth()); - result.add(getWest()); - result.add(getEast()); - - result.add(getNW()); - result.add(getNE()); - result.add(getSW()); - result.add(getSE()); - - return result; - } - - - public boolean isNorthOf(Cell cell) { - if (this.y < cell.y) - return true; - return false; - } - - public boolean isSouthOf(Cell cell) { - if (this.y > cell.y) - return true; - return false; - } - - public boolean isWestOf(Cell cell) { - if (this.x < cell.x) - return true; - return false; - } - - public boolean isEastOf(Cell cell) { - if (this.x > cell.x) - return true; - return false; - } - - - public boolean equals(Object o) { - Cell cell = (Cell) o; - if (cell == null) - return false; - if (x == cell.x && y == cell.y) - return true; - else - return false; - } - - public int hashCode() { - return (x << 16) | y; - } - - public boolean isNextTo(int x2, int y2) { - if (Math.abs(x2 - x) == 1 && Math.abs(y2 - y) == 1) - return false; - if (Math.abs(x2 - x) == 1 && y2 == y) - return true; - if (Math.abs(y2 - y) == 1 && x2 == x) - return true; - return false; - } - - public boolean isNextTo(Cell cell) { - if (cell == null) - throw new IllegalArgumentException("cell cannot be null"); - return this.isNextTo(cell.x, cell.y); - } - - public String toString() { - return "(" + x + ", " + y + ")"; - } - - public void scale(int s) { - x = x * s; - y = y * s; - } - - } - - private class LineSegment { - int x1, x2, y, dy; - - public LineSegment(int x1, int x2, int y, int dy) { - this.x1 = x1; - this.x2 = x2; - this.y = y; - this.dy = dy; - } - } + private static final boolean DEBUG = false; + private static final char PLAIN_MODE = 'P'; + public static final char LATEX_MODE = 'L'; + + private ArrayList rows; + private List modeRows; + + private static char[] boundaries = {'/', '\\', '|', '-', '*', '=', ':'}; + private static char[] undisputableBoundaries = {'|', '-', '*', '=', ':'}; + private static char[] horizontalLines = {'-', '='}; + private static char[] verticalLines = {'|', ':'}; + private static char[] arrowHeads = {'<', '>', '^', 'v', 'V'}; + private static char[] cornerChars = {'\\', '/', '+'}; + private static char[] pointMarkers = {'*'}; + private static char[] dashedLines = {':', '~', '='}; + + private static char[] entryPoints1 = {'\\'}; + private static char[] entryPoints2 = {'|', ':', '+', '\\', '/'}; + private static char[] entryPoints3 = {'/'}; + private static char[] entryPoints4 = {'-', '=', '+', '\\', '/'}; + private static char[] entryPoints5 = {'\\'}; + private static char[] entryPoints6 = {'|', ':', '+', '\\', '/'}; + private static char[] entryPoints7 = {'/'}; + private static char[] entryPoints8 = {'-', '=', '+', '\\', '/'}; + + + + private static HashMap humanColorCodes = new HashMap(); + static { + humanColorCodes.put("GRE", "9D9"); + humanColorCodes.put("BLU", "55B"); + humanColorCodes.put("PNK", "FAA"); + humanColorCodes.put("RED", "E32"); + humanColorCodes.put("YEL", "FF3"); + humanColorCodes.put("BLK", "000"); + + } + + private static HashSet markupTags = + new HashSet(); + + static { + markupTags.add("d"); + markupTags.add("s"); + markupTags.add("io"); + markupTags.add("c"); + markupTags.add("mo"); + markupTags.add("tr"); + markupTags.add("o"); + } + + private void updateModeRows(){ + // Collectors.toList creates ArrayList and you see no performance penalty + // caused by using LinkedList here. + this.modeRows = this.rows.stream().map(TextGrid::transformRowToModeRow).collect(toList()); + } + + public static String transformRowToModeRow(CharSequence row){ + StringBuilder b = new StringBuilder(); + row.chars().map(new IntUnaryOperator(){ + /** + * This field hold current mode of a cell in the grid. + * Only 2 values can be assigned, which are 'P' and 'L'. + * They respectively represent 'Plain', which is normal ditaa mode, and 'LaTeX', which is LaTeX + * formula mode. + */ + int cur = PLAIN_MODE; + + @Override + public int applyAsInt(int operand){ + if(cur == PLAIN_MODE){ + if(operand == '$'){ + cur = LATEX_MODE; + return LATEX_MODE; + } + return PLAIN_MODE; + } else if(cur == LATEX_MODE){ + if(operand == '$'){ + cur = PLAIN_MODE; + return LATEX_MODE; + } + return this.cur; + } + throw new IllegalStateException(); + } + }).forEach(c->b.append((char) c)); + String ret = b.toString(); + if(ret.endsWith("L")) + if(row.charAt(row.length() - 1) != '$') + throw new IllegalArgumentException("LaTex mode was started but not finished."); + return ret; + } + + public void addToMarkupTags(Collection tags){ + markupTags.addAll(tags); + } + + public static void main(String[] args) throws Exception { + TextGrid grid = new TextGrid(); + grid.loadFrom("tests/text/art10.txt"); + + grid.writeStringTo(grid.new Cell(28, 1), "testing"); + + grid.findMarkupTags(); + + grid.printDebug(); + //System.out.println(grid.fillContinuousArea(0, 0, '-').size()+" cells filled"); + //grid.fillContinuousArea(4, 4, '-'); + //grid.getSubGrid(1,1,3,3).printDebug(); + //grid.printDebug(); + } + + + public TextGrid(){ + rows = new ArrayList(); + this.updateModeRows(); + } + + public TextGrid(int width, int height){ + String space = StringUtils.repeatString(" ", width); + rows = new ArrayList(); + for(int i = 0; i < height; i++) + rows.add(new StringBuilder(space)); + this.updateModeRows(); + } + + public static TextGrid makeSameSizeAs(TextGrid grid){ + return new TextGrid(grid.getWidth(), grid.getHeight()); + } + + + public TextGrid(TextGrid otherGrid){ + rows = new ArrayList(); + for(StringBuilder row : otherGrid.getRows()) { + rows.add(new StringBuilder(row)); + } + this.updateModeRows(); + } + + public void clear(){ + String blank = StringUtils.repeatString(" ", getWidth()); + int height = getHeight(); + rows.clear(); + for(int i = 0; i < height; i++) + rows.add(new StringBuilder(blank)); + } + +// duplicated code due to lots of hits to this function + public char get(int x, int y){ + if(x > getWidth() - 1 + || y > getHeight() - 1 + || x < 0 + || y < 0) return 0; + return rows.get(y).charAt(x); + } + + //duplicated code due to lots of hits to this function + public char get(Cell cell){ + if(cell.x > getWidth() - 1 + || cell.y > getHeight() - 1 + || cell.x < 0 + || cell.y < 0) return 0; + return rows.get(cell.y).charAt(cell.x); + } + + public StringBuilder getRow(int y){ + return rows.get(y); + } + + public TextGrid getSubGrid(int x, int y, int width, int height){ + TextGrid grid = new TextGrid(width, height); + for(int i = 0; i < height; i++){ + grid.setRow(i, new StringBuilder(getRow(y + i).subSequence(x, x + width))); + } + return grid; + } + + public TextGrid getTestingSubGrid(Cell cell){ + return getSubGrid(cell.x - 1, cell.y - 1, 3, 3); + } + + + public String getStringAt(int x, int y, int length){ + return getStringAt(new Cell(x, y), length); + } + + public String getStringAt(Cell cell, int length){ + int x = cell.x; + int y = cell.y; + if(x > getWidth() - 1 + || y > getHeight() - 1 + || x < 0 + || y < 0) return null; + return rows.get(y).substring(x, x + length); + } + + public char getNorthOf(int x, int y){ return get(x, y - 1); } + public char getSouthOf(int x, int y){ return get(x, y + 1); } + public char getEastOf(int x, int y){ return get(x + 1, y); } + public char getWestOf(int x, int y){ return get(x - 1, y); } + + public char getNorthOf(Cell cell){ return getNorthOf(cell.x, cell.y); } + public char getSouthOf(Cell cell){ return getSouthOf(cell.x, cell.y); } + public char getEastOf(Cell cell){ return getEastOf(cell.x, cell.y); } + public char getWestOf(Cell cell){ return getWestOf(cell.x, cell.y); } + + public void writeStringTo(int x, int y, String str){ + writeStringTo(new Cell(x, y), str); + } + + public void writeStringTo(Cell cell, String str){ + if(isOutOfBounds(cell)) return; + rows.get(cell.y).replace(cell.x, cell.x + str.length(), str); + } + + public void set(Cell cell, char c){ + set(cell.x, cell.y, c); + } + + public void set(int x, int y, char c){ + if(x > getWidth() - 1 || y > getHeight() - 1) return; + StringBuilder row = rows.get(y); + row.setCharAt(x, c); + } + + public void setRow(int y, String row){ + if(y > getHeight() || row.length() != getWidth()) + throw new IllegalArgumentException("setRow out of bounds or string wrong size"); + rows.set(y, new StringBuilder(row)); + } + + public void setRow(int y, StringBuilder row){ + if(y > getHeight() || row.length() != getWidth()) + throw new IllegalArgumentException("setRow out of bounds or string wrong size"); + rows.set(y, row); + } + + public int getWidth(){ + if(rows.size() == 0) return 0; //empty buffer + return rows.get(0).length(); + } + + public int getHeight(){ + return rows.size(); + } + + public void printDebug(){ + Iterator it = rows.iterator(); + int i = 0; + System.out.println( + " " + +StringUtils.repeatString("0123456789", (int) Math.floor(getWidth()/10)+1)); + while(it.hasNext()){ + String row = it.next().toString(); + String index = new Integer(i).toString(); + if(i < 10) index = " "+index; + System.out.println(index+" ("+row+")"); + i++; + } + } + + public String getDebugString(){ + StringBuilder buffer = new StringBuilder(); + Iterator it = rows.iterator(); + int i = 0; + buffer.append( + " " + +StringUtils.repeatString("0123456789", (int) Math.floor(getWidth()/10)+1)+"\n"); + while(it.hasNext()){ + String row = it.next().toString(); + String index = new Integer(i).toString(); + if(i < 10) index = " "+index; + row = row.replaceAll("\n", "\\\\n"); + row = row.replaceAll("\r", "\\\\r"); + buffer.append(index+" ("+row+")\n"); + i++; + } + return buffer.toString(); + } + + public String toString(){ + return getDebugString(); + } + + /** + * Adds grid to this. Space characters in this grid + * are replaced with the corresponding contents of + * grid, otherwise the contents are unchanged. + * + * @param grid + * @return false if the grids are of different size + */ + public boolean add(TextGrid grid){ + if(getWidth() != grid.getWidth() + || getHeight() != grid.getHeight()) return false; + int width = getWidth(); + int height = getHeight(); + for(int yi = 0; yi < height; yi++){ + for(int xi = 0; xi < width; xi++){ + if(get(xi, yi) == ' ') set(xi, yi, grid.get(xi, yi)); + } + } + return true; + } + + /** + * Replaces letters or numbers that are on horizontal or vertical + * lines, with the appropriate character that will make the line + * continuous (| for vertical and - for horizontal lines) + * + */ + public void replaceTypeOnLine(){ + int width = getWidth(); + int height = getHeight(); + for(int yi = 0; yi < height; yi++){ + for(int xi = 0; xi < width; xi++){ + char c = get(xi, yi); + if(Character.isLetterOrDigit(c)) { + boolean isOnHorizontalLine = isOnHorizontalLine(xi, yi); + boolean isOnVerticalLine = isOnVerticalLine(xi, yi); + if(isOnHorizontalLine && isOnVerticalLine){ + set(xi, yi, '+'); + if(DEBUG) System.out.println("replaced type on line '"+c+"' with +"); + } else if(isOnHorizontalLine){ + set(xi, yi, '-'); + if(DEBUG) System.out.println("replaced type on line '"+c+"' with -"); + } else if(isOnVerticalLine){ + set(xi, yi, '|'); + if(DEBUG) System.out.println("replaced type on line '"+c+"' with |"); + } + } + } + } + } + + public void replacePointMarkersOnLine(){ + int width = getWidth(); + int height = getHeight(); + for(int yi = 0; yi < height; yi++){ + for(int xi = 0; xi < width; xi++){ + char c = get(xi, yi); + Cell cell = new Cell(xi, yi); + if(StringUtils.isOneOf(c, pointMarkers) + && isStarOnLine(cell) && isInPlainMode(cell)){ + + boolean isOnHorizontalLine = false; + if(StringUtils.isOneOf(get(cell.getEast()), horizontalLines)) + isOnHorizontalLine = true; + if(StringUtils.isOneOf(get(cell.getWest()), horizontalLines)) + isOnHorizontalLine = true; + + boolean isOnVerticalLine = false; + if(StringUtils.isOneOf(get(cell.getNorth()), verticalLines)) + isOnVerticalLine = true; + if(StringUtils.isOneOf(get(cell.getSouth()), verticalLines)) + isOnVerticalLine = true; + + if(isOnHorizontalLine && isOnVerticalLine){ + set(xi, yi, '+'); + if(DEBUG) System.out.println("replaced marker on line '"+c+"' with +"); + } else if(isOnHorizontalLine){ + set(xi, yi, '-'); + if(DEBUG) System.out.println("replaced marker on line '"+c+"' with -"); + } else if(isOnVerticalLine){ + set(xi, yi, '|'); + if(DEBUG) System.out.println("replaced marker on line '"+c+"' with |"); + } + } + } + } + } + + public CellSet getPointMarkersOnLine(){ + CellSet result = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for(int yi = 0; yi < height; yi++){ + for(int xi = 0; xi < width; xi++){ + if(! isInPlainMode(xi, yi)) + continue; + char c = get(xi, yi); + if(StringUtils.isOneOf(c, pointMarkers) + && isStarOnLine(new Cell(xi, yi))){ + result.add(new Cell(xi, yi)); + } + } + } + return result; + } + + + public void replaceHumanColorCodes(){ + int height = getHeight(); + for(int y = 0; y < height; y++) + rows.set(y, replaceHumanColorCodes(y, rows.get(y))); + } + + private StringBuilder replaceHumanColorCodes(int rowIndex, StringBuilder in){ + Pattern p = Pattern.compile("(c.{0,3}|[^c]+)"); + StringBuilder ret = new StringBuilder(in.length()); + Iterator i = createTextSplitter(p, in); + while(i.hasNext()){ + String next = i.next(); + if(isInPlainMode(ret.length(), rowIndex) && looksColorCode(next)){ + String replacement = humanColorCodes.get(next.substring(1, 4)); + if(replacement != null) + ret.append(String.format("c%s", replacement)); + else + ret.append(next); + } else + ret.append(next); + } + return ret; + } + + private static boolean looksColorCode(String word){ + return word.length() >= 4 && word.startsWith("c"); + } + + + /** + * Replace all occurrences of c1 with c2 + * + * @param c1 + * @param c2 + */ + public void replaceAll(char c1, char c2){ + int width = getWidth(); + int height = getHeight(); + for(int yi = 0; yi < height; yi++){ + for(int xi = 0; xi < width; xi++){ + char c = get(xi, yi); + if(c == c1) set(xi, yi, c2); + } + } + } + + public boolean hasBlankCells(){ + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for(int y = 0; y < height; y++){ + for(int x = 0; x < width; x++){ + Cell cell = new Cell(x, y); + if(isBlank(cell)) return true; + } + } + return false; + } + + + public CellSet getAllNonBlank(){ + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for(int y = 0; y < height; y++){ + for(int x = 0; x < width; x++){ + Cell cell = new Cell(x, y); + if(!isBlank(cell)) set.add(cell); + } + } + return set; + } + + public CellSet getAllBoundaries(){ + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for(int y = 0; y < height; y++){ + for(int x = 0; x < width; x++){ + Cell cell = new Cell(x, y); + if(isBoundary(cell)) set.add(cell); + } + } + return set; + } + + + public CellSet getAllBlanksBetweenCharacters(){ + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for(int y = 0; y < height; y++){ + for(int x = 0; x < width; x++){ + Cell cell = new Cell(x, y); + if(isBlankBetweenCharacters(cell)) set.add(cell); + } + } + return set; + } + + + /** + * Returns an ArrayList of CellStringPairs that + * represents all the continuous (non-blank) Strings + * in the grid. Used on buffers that contain only + * type, in order to find the positions and the + * contents of the strings. + * + * @return + */ + public ArrayList findStrings(){ + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for(int y = 0; y < height; y++){ + for(int x = 0; x < width; x++){ + if(!isBlank(x, y)){ + Cell start = new Cell(x, y); + String str = String.valueOf(get(x,y)); + char c = get(++x, y); + boolean finished = false; + //while(c != ' '){ + while(!finished){ + str += String.valueOf(c); + c = get(++x, y); + char next = get(x + 1, y); + if((c == ' ' || c == 0) && (next == ' ' || next == 0)) + finished = true; + } + result.add(new CellStringPair(start, str)); + } + } + } + return result; + } + + /** + * This is done in a bit of a messy way, should be impossible + * to go out of sync with corresponding GridPatternGroup. + * + * @param cell + * @param entryPointId + * @return + */ + public boolean hasEntryPoint(Cell cell, int entryPointId){ + String result = ""; + char c = get(cell); + if(entryPointId == 1) { + return StringUtils.isOneOf(c, entryPoints1); + + } else if(entryPointId == 2) { + return StringUtils.isOneOf(c, entryPoints2); + + } else if(entryPointId == 3) { + return StringUtils.isOneOf(c, entryPoints3); + + } else if(entryPointId == 4) { + return StringUtils.isOneOf(c, entryPoints4); + + } else if(entryPointId == 5) { + return StringUtils.isOneOf(c, entryPoints5); + + } else if(entryPointId == 6) { + return StringUtils.isOneOf(c, entryPoints6); + + } else if(entryPointId == 7) { + return StringUtils.isOneOf(c, entryPoints7); + + } else if(entryPointId == 8) { + return StringUtils.isOneOf(c, entryPoints8); + } + return false; + } + + /** + * true if cell is blank and the east and west cells are not + * (used to find gaps between words) + * + * @param cell + * @return + */ + public boolean isBlankBetweenCharacters(Cell cell){ + return (isBlank(cell) + && !isBlank(cell.getEast()) + && !isBlank(cell.getWest())); + } + + /** + * Makes blank all the cells that contain non-text + * elements. + */ + public void removeNonText(){ + //the following order is significant + //since the south-pointing arrowheads + //are determined based on the surrounding boundaries + removeArrowheads(); + removeColorCodes(); + removeBoundaries(); + removeMarkupTags(); + } + + public void removeArrowheads(){ + int width = getWidth(); + int height = getHeight(); + for(int yi = 0; yi < height; yi++){ + for(int xi = 0; xi < width; xi++){ + Cell cell = new Cell(xi, yi); + if(isArrowhead(cell)) set(cell, ' '); + } + } + } + + public void removeColorCodes(){ + Iterator cells = findColorCodes().iterator(); + while(cells.hasNext()){ + Cell cell = ((CellColorPair) cells.next()).cell; + set(cell, ' '); + cell = cell.getEast(); set(cell, ' '); + cell = cell.getEast(); set(cell, ' '); + cell = cell.getEast(); set(cell, ' '); + } + } + + public void removeBoundaries(){ + ArrayList toBeRemoved = new ArrayList(); + + int width = getWidth(); + int height = getHeight(); + for(int yi = 0; yi < height; yi++){ + for(int xi = 0; xi < width; xi++){ + Cell cell = new Cell(xi, yi); + if(isBoundary(cell)) toBeRemoved.add(cell); + } + } + + //remove in two stages, because decision of + //isBoundary depends on content of surrounding + //cells + Iterator it = toBeRemoved.iterator(); + while(it.hasNext()){ + Cell cell = (Cell) it.next(); + if(isInPlainMode(cell)) + set(cell, ' '); + } + } + + public ArrayList findArrowheads(){ + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for(int yi = 0; yi < height; yi++){ + for(int xi = 0; xi < width; xi++){ + Cell cell = new Cell(xi, yi); + if(isArrowhead(cell)) result.add(cell); + } + } + if(DEBUG) System.out.println(result.size()+" arrowheads found"); + return result; + } + + + public ArrayList findColorCodes(){ + Pattern colorCodePattern = Pattern.compile("c[A-F0-9]{3}"); + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for(int yi = 0; yi < height; yi++){ + for(int xi = 0; xi < width - 3; xi++){ + Cell cell = new Cell(xi, yi); + if(! isInPlainMode(cell)) + continue; + String s = getStringAt(cell, 4); + Matcher matcher = colorCodePattern.matcher(s); + if(matcher.matches()){ + char cR = s.charAt(1); + char cG = s.charAt(2); + char cB = s.charAt(3); + int r = Integer.valueOf(String.valueOf(cR), 16).intValue() * 17; + int g = Integer.valueOf(String.valueOf(cG), 16).intValue() * 17; + int b = Integer.valueOf(String.valueOf(cB), 16).intValue() * 17; + result.add(new CellColorPair(cell, new Color(r, g, b))); + } + } + } + if(DEBUG) System.out.println(result.size()+" color codes found"); + return result; + } + + public ArrayList findMarkupTags(){ + Pattern tagPattern = Pattern.compile("\\{(.+?)\\}"); + ArrayList result = new ArrayList(); + + int width = getWidth(); + int height = getHeight(); + for(int y = 0; y < height; y++){ + for(int x = 0; x < width - 3; x++){ + if(! isInPlainMode(x, y)) + continue; + Cell cell = new Cell(x, y); + char c = get(cell); + if(c == '{'){ + String rowPart = rows.get(y).substring(x); + Matcher matcher = tagPattern.matcher(rowPart); + if(matcher.find()){ + String tagName = matcher.group(1); + if(markupTags.contains(tagName)){ + if(DEBUG) System.out.println("found tag "+tagName+" at "+x+", "+y); + result.add(new CellTagPair(new Cell(x, y), tagName)); + } + } + } + } + } + return result; + } + + public void removeMarkupTags(){ + Iterator it = findMarkupTags().iterator(); + while (it.hasNext()) { + CellTagPair pair = (CellTagPair) it.next(); + String tagName = pair.tag; + if(tagName == null) continue; + int length = 2 + tagName.length(); + writeStringTo(pair.cell, StringUtils.repeatString(" ", length)); + } + } + + + + public boolean matchesAny(GridPatternGroup criteria){ + return criteria.isAnyMatchedBy(this); + } + + public boolean matchesAll(GridPatternGroup criteria){ + return criteria.areAllMatchedBy(this); + } + + public boolean matches(GridPattern criteria){ + return criteria.isMatchedBy(this); + } + + + public boolean isOnHorizontalLine(Cell cell){ return isOnHorizontalLine(cell.x, cell.y); } + private boolean isOnHorizontalLine(int x, int y){ + char c1 = get(x - 1, y); + char c2 = get(x + 1, y); + if(isHorizontalLine(c1) && isHorizontalLine(c2)) return true; + return false; + } + + public boolean isOnVerticalLine(Cell cell){ return isOnVerticalLine(cell.x, cell.y); } + private boolean isOnVerticalLine(int x, int y){ + char c1 = get(x, y - 1); + char c2 = get(x, y + 1); + if(isVerticalLine(c1) && isVerticalLine(c2)) return true; + return false; + } + + + public static boolean isBoundary(char c){ + return StringUtils.isOneOf(c, boundaries); + } + public boolean isBoundary(int x, int y){ return isBoundary(new Cell(x, y)); } + public boolean isBoundary(Cell cell){ + if(! isInPlainMode(cell)) + return false; + char c = get(cell.x, cell.y); + if(0 == c) return false; + if('+' == c || '\\' == c || '/' == c){ + System.out.print(""); + if( + isIntersection(cell) + || isCorner(cell) + || isStub(cell) + || isCrossOnLine(cell)){ + return true; + } else return false; + } + //return StringUtils.isOneOf(c, undisputableBoundaries); + if(StringUtils.isOneOf(c, boundaries) && !isLoneDiagonal(cell)){ + return true; + } + return false; + } + + public boolean isLine(Cell cell){ + return isHorizontalLine(cell) || isVerticalLine(cell); + } + + public static boolean isHorizontalLine(char c){ + return StringUtils.isOneOf(c, horizontalLines); + } + + public boolean isHorizontalLine(Cell cell){ + return isHorizontalLine(cell.x, cell.y) && isInPlainMode(cell); + } + + public boolean isHorizontalLine(int x, int y){ + char c = get(x, y); + if(0 == c) + return false; + return StringUtils.isOneOf(c, horizontalLines) && isInPlainMode(x, y); + } + + public static boolean isVerticalLine(char c){ + return StringUtils.isOneOf(c, verticalLines); + } + + public boolean isVerticalLine(Cell cell){ + return isVerticalLine(cell.x, cell.y) && isInPlainMode(cell); + } + + public boolean isVerticalLine(int x, int y){ + char c = get(x, y); + if(0 == c) + return false; + return StringUtils.isOneOf(c, verticalLines); + } + + public boolean isLinesEnd(int x, int y){ + return isLinesEnd(new Cell(x, y)); + } + + /** + * Stubs are also considered end of lines + * + * @param cell + * @return + */ + public boolean isLinesEnd(Cell cell){ + return matchesAny(cell, GridPatternGroup.linesEndCriteria) && isInPlainMode(cell); + } + + public boolean isVerticalLinesEnd(Cell cell){ + return matchesAny(cell, GridPatternGroup.verticalLinesEndCriteria) && isInPlainMode(cell); + } + + public boolean isHorizontalLinesEnd(Cell cell){ + return matchesAny(cell, GridPatternGroup.horizontalLinesEndCriteria) && isInPlainMode(cell); + } + + + public boolean isPointCell(Cell cell){ + return ( + isCorner(cell) + || isIntersection(cell) + || isStub(cell) + || isLinesEnd(cell)) && isInPlainMode(cell); + } + + + public boolean containsAtLeastOneDashedLine(CellSet set){ + Iterator it = set.iterator(); + while(it.hasNext()) { + Cell cell = (Cell) it.next(); + if(StringUtils.isOneOf(get(cell), dashedLines)) return true; + } + return false; + } + + public boolean exactlyOneNeighbourIsBoundary(Cell cell) { + int howMany = 0; + if(isBoundary(cell.getNorth())) howMany++; + if(isBoundary(cell.getSouth())) howMany++; + if(isBoundary(cell.getEast())) howMany++; + if(isBoundary(cell.getWest())) howMany++; + return (howMany == 1); + } + + /** + * + * A stub looks like that: + * + *
+	 *
+	 * +- or -+ or + or + or /- or -/ or / (you get the point)
+	 *             |    |                |
+	 *
+	 * 
+ * + * @param cell + * @return + */ + + public boolean isStub(Cell cell){ + return matchesAny(cell, GridPatternGroup.stubCriteria); + } + + public boolean isCrossOnLine(Cell cell){ + return matchesAny(cell, GridPatternGroup.crossOnLineCriteria); + } + + public boolean isHorizontalCrossOnLine(Cell cell){ + return matchesAny(cell, GridPatternGroup.horizontalCrossOnLineCriteria); + } + + public boolean isVerticalCrossOnLine(Cell cell){ + return matchesAny(cell, GridPatternGroup.verticalCrossOnLineCriteria); + } + + public boolean isStarOnLine(Cell cell){ + return matchesAny(cell, GridPatternGroup.starOnLineCriteria); + } + + public boolean isLoneDiagonal(Cell cell){ + return matchesAny(cell, GridPatternGroup.loneDiagonalCriteria); + } + + + public boolean isHorizontalStarOnLine(Cell cell){ + return matchesAny(cell, GridPatternGroup.horizontalStarOnLineCriteria); + } + + public boolean isVerticalStarOnLine(Cell cell){ + return matchesAny(cell, GridPatternGroup.verticalStarOnLineCriteria); + } + + public boolean isArrowhead(Cell cell){ + return (isNorthArrowhead(cell) + || isSouthArrowhead(cell) + || isWestArrowhead(cell) + || isEastArrowhead(cell)); + } + + public boolean isNorthArrowhead(Cell cell){ + return get(cell) == '^' && isInPlainMode(cell); + } + + public boolean isEastArrowhead(Cell cell){ + return get(cell) == '>' && isInPlainMode(cell); + } + + public boolean isWestArrowhead(Cell cell){ + return get(cell) == '<' && isInPlainMode(cell); + } + + public boolean isSouthArrowhead(Cell cell){ + return (get(cell) == 'v' || get(cell) == 'V') + && isVerticalLine(cell.getNorth()) + && isInPlainMode(cell); + } + + +// unicode for bullets +// +// 2022 bullet +// 25CF black circle +// 25AA black circle (small) +// 25A0 black square +// 25A1 white square +// 25CB white circle +// 25BA black right-pointing pointer + + + public boolean isBullet(int x, int y){ + return isBullet(new Cell(x, y)); + } + + public boolean isBullet(Cell cell){ + char c = get(cell); + if((c == 'o' || c == '*') + && isBlank(cell.getEast()) + && isBlank(cell.getWest()) + && Character.isLetterOrDigit(get(cell.getEast().getEast())) + && isInPlainMode(cell)) + return true; + return false; + } + + public void replaceBullets(){ + int width = getWidth(); + int height = getHeight(); + for(int yi = 0; yi < height; yi++){ + for(int xi = 0; xi < width; xi++){ + Cell cell = new Cell(xi, yi); + if(isBullet(cell)){ + set(cell, ' '); + set(cell.getEast(), '\u2022'); + } + } + } + } + + /** + * true if the cell is not blank + * but the previous (west) is + * + * @param cell + * @return + */ + public boolean isStringsStart(Cell cell){ + return (!isBlank(cell) && isBlank(cell.getWest())); + } + + /** + * true if the cell is not blank + * but the next (east) is + * + * @param cell + * @return + */ + public boolean isStringsEnd(Cell cell){ + return (!isBlank(cell) + //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); + && isBlank(cell.getEast())); + } + + public int otherStringsStartInTheSameColumn(Cell cell){ + if(!isStringsStart(cell)) return 0; + int result = 0; + int height = getHeight(); + for(int y = 0; y < height; y++){ + Cell cCell = new Cell(cell.x, y); + if(!cCell.equals(cell) && isStringsStart(cCell)){ + result++; + } + } + return result; + } + + public int otherStringsEndInTheSameColumn(Cell cell){ + if(!isStringsEnd(cell)) return 0; + int result = 0; + int height = getHeight(); + for(int y = 0; y < height; y++){ + Cell cCell = new Cell(cell.x, y); + if(!cCell.equals(cell) && isStringsEnd(cCell)){ + result++; + } + } + return result; + } + + public boolean isColumnBlank(int x){ + int height = getHeight(); + for(int y = 0; y < height; y++){ + if(!isBlank(x, y)) return false; + } + return true; + } + + + public CellSet followLine(int x, int y){ + return followLine(new Cell(x, y)); + } + + public CellSet followIntersection(Cell cell){ + return followIntersection(cell, null); + } + + public CellSet followIntersection(Cell cell, Cell blocked){ + if(!isIntersection(cell)) return null; + CellSet result = new CellSet(); + Cell cN = cell.getNorth(); + Cell cS = cell.getSouth(); + Cell cE = cell.getEast(); + Cell cW = cell.getWest(); + if(hasEntryPoint(cN, 6)) result.add(cN); + if(hasEntryPoint(cS, 2)) result.add(cS); + if(hasEntryPoint(cE, 8)) result.add(cE); + if(hasEntryPoint(cW, 4)) result.add(cW); + if(result.contains(blocked)) result.remove(blocked); + return result; + } + + /** + * Returns the neighbours of a line-cell that are boundaries + * (0 to 2 cells are returned) + * + * @param cell + * @return null if the cell is not a line + */ + public CellSet followLine(Cell cell){ + if(isHorizontalLine(cell)){ + CellSet result = new CellSet(); + if(isBoundary(cell.getEast())) result.add(cell.getEast()); + if(isBoundary(cell.getWest())) result.add(cell.getWest()); + return result; + } else if (isVerticalLine(cell)){ + CellSet result = new CellSet(); + if(isBoundary(cell.getNorth())) result.add(cell.getNorth()); + if(isBoundary(cell.getSouth())) result.add(cell.getSouth()); + return result; + } + return null; + } + + public CellSet followLine(Cell cell, Cell blocked){ + CellSet nextCells = followLine(cell); + if(nextCells.contains(blocked)) nextCells.remove(blocked); + return nextCells; + } + + public CellSet followCorner(Cell cell){ + return followCorner(cell, null); + } + + public CellSet followCorner(Cell cell, Cell blocked){ + if(!isCorner(cell)) return null; + if(isCorner1(cell)) return followCorner1(cell, blocked); + if(isCorner2(cell)) return followCorner2(cell, blocked); + if(isCorner3(cell)) return followCorner3(cell, blocked); + if(isCorner4(cell)) return followCorner4(cell, blocked); + return null; + } + + public CellSet followCorner1(Cell cell){ + return followCorner1(cell, null); + } + public CellSet followCorner1(Cell cell, Cell blocked){ + if(!isCorner1(cell)) return null; + CellSet result = new CellSet(); + if(!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); + if(!cell.getEast().equals(blocked)) result.add(cell.getEast()); + return result; + } + + public CellSet followCorner2(Cell cell){ + return followCorner2(cell, null); + } + public CellSet followCorner2(Cell cell, Cell blocked){ + if(!isCorner2(cell)) return null; + CellSet result = new CellSet(); + if(!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); + if(!cell.getWest().equals(blocked)) result.add(cell.getWest()); + return result; + } + + public CellSet followCorner3(Cell cell){ + return followCorner3(cell, null); + } + public CellSet followCorner3(Cell cell, Cell blocked){ + if(!isCorner3(cell)) return null; + CellSet result = new CellSet(); + if(!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); + if(!cell.getWest().equals(blocked)) result.add(cell.getWest()); + return result; + } + + public CellSet followCorner4(Cell cell){ + return followCorner4(cell, null); + } + public CellSet followCorner4(Cell cell, Cell blocked){ + if(!isCorner4(cell)) return null; + CellSet result = new CellSet(); + if(!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); + if(!cell.getEast().equals(blocked)) result.add(cell.getEast()); + return result; + } + + + public CellSet followStub(Cell cell){ + return followStub(cell, null); + } + public CellSet followStub(Cell cell, Cell blocked){ + if(!isStub(cell)) return null; + CellSet result = new CellSet(); + if(isBoundary(cell.getEast())) result.add(cell.getEast()); + else if(isBoundary(cell.getWest())) result.add(cell.getWest()); + else if(isBoundary(cell.getNorth())) result.add(cell.getNorth()); + else if(isBoundary(cell.getSouth())) result.add(cell.getSouth()); + if(result.contains(blocked)) result.remove(blocked); + return result; + } + + public CellSet followCell(Cell cell){ + return followCell(cell, null); + } + + public CellSet followCell(Cell cell, Cell blocked){ + if(isIntersection(cell)) return followIntersection(cell, blocked); + if(isCorner(cell)) return followCorner(cell, blocked); + if(isLine(cell)) return followLine(cell, blocked); + if(isStub(cell)) return followStub(cell, blocked); + if(isCrossOnLine(cell)) return followCrossOnLine(cell, blocked); + System.err.println("Ambiguous input at position "+cell+":"); + TextGrid subGrid = getTestingSubGrid(cell); + subGrid.printDebug(); + throw new RuntimeException("Cannot follow cell "+cell+": cannot determine cell type"); + } + + public String getCellTypeAsString(Cell cell){ + if(isK(cell)) return "K"; + if(isT(cell)) return "T"; + if(isInverseK(cell)) return "inverse K"; + if(isInverseT(cell)) return "inverse T"; + if(isCorner1(cell)) return "corner 1"; + if(isCorner2(cell)) return "corner 2"; + if(isCorner3(cell)) return "corner 3"; + if(isCorner4(cell)) return "corner 4"; + if(isLine(cell)) return "line"; + if(isStub(cell)) return "stub"; + if(isCrossOnLine(cell)) return "crossOnLine"; + return "unrecognisable type"; + } + + + public CellSet followCrossOnLine(Cell cell, Cell blocked){ + CellSet result = new CellSet(); + if(isHorizontalCrossOnLine(cell)){ + result.add(cell.getEast()); + result.add(cell.getWest()); + } else if(isVerticalCrossOnLine(cell)){ + result.add(cell.getNorth()); + result.add(cell.getSouth()); + } + if(result.contains(blocked)) result.remove(blocked); + return result; + } + + public boolean isOutOfBounds(Cell cell){ + if(cell.x > getWidth() - 1 + || cell.y > getHeight() - 1 + || cell.x < 0 + || cell.y < 0) return true; + return false; + } + + public boolean isOutOfBounds(int x, int y){ + char c = get(x, y); + if(0 == c) return true; + return false; + } + + public boolean isBlank(Cell cell){ + char c = get(cell); + if(0 == c) return false; + return c == ' '; + } + + public boolean isBlank(int x, int y){ + char c = get(x, y); + if(0 == c) return true; + return c == ' '; + } + + public boolean isCorner(Cell cell){ + return isCorner(cell.x, cell.y); + } + public boolean isCorner(int x, int y){ + return (isNormalCorner(x,y) || isRoundCorner(x,y)); + } + + + public boolean matchesAny(Cell cell, GridPatternGroup criteria){ + TextGrid subGrid = getTestingSubGrid(cell); + return subGrid.matchesAny(criteria); + } + + public boolean isCorner1(Cell cell){ + return matchesAny(cell, GridPatternGroup.corner1Criteria); + } + + public boolean isCorner2(Cell cell){ + return matchesAny(cell, GridPatternGroup.corner2Criteria); + } + + public boolean isCorner3(Cell cell){ + return matchesAny(cell, GridPatternGroup.corner3Criteria); + } + + public boolean isCorner4(Cell cell){ + return matchesAny(cell, GridPatternGroup.corner4Criteria); + } + + public boolean isCross(Cell cell){ + return matchesAny(cell, GridPatternGroup.crossCriteria); + } + + public boolean isK(Cell cell){ + return matchesAny(cell, GridPatternGroup.KCriteria); + } + + public boolean isInverseK(Cell cell){ + return matchesAny(cell, GridPatternGroup.inverseKCriteria); + } + + public boolean isT(Cell cell){ + return matchesAny(cell, GridPatternGroup.TCriteria); + } + + public boolean isInverseT(Cell cell){ + return matchesAny(cell, GridPatternGroup.inverseTCriteria); + } + + public boolean isNormalCorner(Cell cell){ + return matchesAny(cell, GridPatternGroup.normalCornerCriteria); + } + public boolean isNormalCorner(int x, int y){ + return isNormalCorner(new Cell(x, y)); + } + + public boolean isRoundCorner(Cell cell){ + return matchesAny(cell, GridPatternGroup.roundCornerCriteria); + } + + public boolean isRoundCorner(int x, int y){ + return isRoundCorner(new Cell(x, y)); + } + + public boolean isIntersection(Cell cell){ + return matchesAny(cell, GridPatternGroup.intersectionCriteria); + } + public boolean isIntersection(int x, int y){ + return isIntersection(new Cell(x, y)); + } + + public void copyCellsTo(CellSet cells, TextGrid grid){ + Iterator it = cells.iterator(); + while(it.hasNext()){ + Cell cell = (Cell) it.next(); + grid.set(cell, this.get(cell)); + } + } + + public boolean equals(TextGrid grid){ + if(grid.getHeight() != this.getHeight() + || grid.getWidth() != this.getWidth() + ){ + return false; + } + int height = grid.getHeight(); + for(int i = 0; i < height; i++){ + String row1 = this.getRow(i).toString(); + String row2 = grid.getRow(i).toString(); + if(!row1.equals(row2)) return false; + } + return true; + } + + /** + * Fills all the cells in cells with c + * + * @param cells + * @param c + */ + public void fillCellsWith(Iterable cells, char c){ + Iterator it = cells.iterator(); + while(it.hasNext()){ + Cell cell = it.next(); + set(cell.x, cell.y, c); + } + } + + /** + * + * Fills the continuous area with if c1 characters with c2, + * flooding from cell x, y + * + * @param x + * @param y + * @param c1 the character to replace + * @param c2 the character to replace c1 with + * @return the list of cells filled + */ +// public CellSet fillContinuousArea(int x, int y, char c1, char c2){ +// CellSet cells = new CellSet(); +// //fillContinuousArea_internal(x, y, c1, c2, cells); +// seedFill(new Cell(x, y), c1, c2); +// return cells; +// } + + public CellSet fillContinuousArea(int x, int y, char c){ + return fillContinuousArea(new Cell(x, y), c); + } + + public CellSet fillContinuousArea(Cell cell, char c){ + if(isOutOfBounds(cell)) throw new IllegalArgumentException("Attempted to fill area out of bounds: "+cell); + return seedFillOld(cell, c); + } + + private CellSet seedFill(Cell seed, char newChar){ + CellSet cellsFilled = new CellSet(); + char oldChar = get(seed); + + if(oldChar == newChar) return cellsFilled; + if(isOutOfBounds(seed)) return cellsFilled; + + Stack stack = new Stack(); + + stack.push(seed); + + while(!stack.isEmpty()){ + Cell cell = (Cell) stack.pop(); + + //set(cell, newChar); + cellsFilled.add(cell); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if(get(nCell) == oldChar && !cellsFilled.contains(nCell)) stack.push(nCell); + if(get(sCell) == oldChar && !cellsFilled.contains(sCell)) stack.push(sCell); + if(get(eCell) == oldChar && !cellsFilled.contains(eCell)) stack.push(eCell); + if(get(wCell) == oldChar && !cellsFilled.contains(wCell)) stack.push(wCell); + } + + return cellsFilled; + } + + private CellSet seedFillOld(Cell seed, char newChar){ + CellSet cellsFilled = new CellSet(); + char oldChar = get(seed); + + if(oldChar == newChar) return cellsFilled; + if(isOutOfBounds(seed)) return cellsFilled; + + Stack stack = new Stack(); + + stack.push(seed); + + while(!stack.isEmpty()){ + Cell cell = (Cell) stack.pop(); + + set(cell, newChar); + cellsFilled.add(cell); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if(get(nCell) == oldChar) stack.push(nCell); + if(get(sCell) == oldChar) stack.push(sCell); + if(get(eCell) == oldChar) stack.push(eCell); + if(get(wCell) == oldChar) stack.push(wCell); + } + + return cellsFilled; + } + + + /** + * + * Locates and returns the '*' boundaries that we would + * encounter if we did a flood-fill at seed. + * + * @param seed + * @return + */ + public CellSet findBoundariesExpandingFrom(Cell seed){ + CellSet boundaries = new CellSet(); + char oldChar = get(seed); + + if(isOutOfBounds(seed)) return boundaries; + + char newChar = 1; //TODO: kludge + + Stack stack = new Stack(); + + stack.push(seed); + + while(!stack.isEmpty()){ + Cell cell = (Cell) stack.pop(); + + set(cell, newChar); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if(get(nCell) == oldChar) stack.push(nCell); + else if(get(nCell) == '*') boundaries.add(nCell); + + if(get(sCell) == oldChar) stack.push(sCell); + else if(get(sCell) == '*') boundaries.add(sCell); + + if(get(eCell) == oldChar) stack.push(eCell); + else if(get(eCell) == '*') boundaries.add(eCell); + + if(get(wCell) == oldChar) stack.push(wCell); + else if(get(wCell) == '*') boundaries.add(wCell); + } + + return boundaries; + } + + + //TODO: incomplete method seedFillLine() + private CellSet seedFillLine(Cell cell, char newChar){ + CellSet cellsFilled = new CellSet(); + + Stack stack = new Stack(); + + char oldChar = get(cell); + + if(oldChar == newChar) return cellsFilled; + if(isOutOfBounds(cell)) return cellsFilled; + + stack.push(new LineSegment(cell.x, cell.x, cell.y, 1)); + stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, -1)); + + int left; + while(!stack.isEmpty()){ + LineSegment segment = (LineSegment) stack.pop(); + int x; + //expand to the left + for( + x = segment.x1; + x >= 0 && get(x, segment.y) == oldChar; + --x){ + set(x, segment.y, newChar); + cellsFilled.add(new Cell(x, segment.y)); + } + + left = cell.getEast().x; + boolean skip = (x > segment.x1)? true : false; + + if(left < segment.x1){ //leak on left? + //TODO: i think the first param should be x + stack.push( + //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); + new LineSegment(x, left, segment.y - 1, -segment.dy)); + } + + x = segment.x1 + 1; + do { + if(!skip) { + for( ; x < getWidth() && get(x, segment.y) == oldChar; ++x){ + set(x, segment.y, newChar); + cellsFilled.add(new Cell(x, segment.y)); + } + + stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); + if(x > segment.x2 + 1) //leak on right? + stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, -segment.dy)); + } + skip = false; //skip only once + + for(++x; x <= segment.x2 && get(x, segment.y) != oldChar; ++x){;} + left = x; + } while( x < segment.x2); + } + + return cellsFilled; + } + + public boolean cellContainsDashedLineChar(Cell cell){ + char c = get(cell); + return StringUtils.isOneOf(c, dashedLines); + } + + public boolean loadFrom(String filename) + throws FileNotFoundException, IOException + { + return loadFrom(filename, null); + } + + public boolean loadFrom(String filename, ProcessingOptions options) + throws IOException + { + + String encoding = (options == null) ? null : options.getCharacterEncoding(); + ArrayList lines = new ArrayList(); + InputStream is; + if ("-".equals(filename)) + is = System.in; + else + is = new FileInputStream(filename); + String[] linesArray = FileUtils.readFile(is, filename, encoding).split("(\r)?\n"); + for(int i = 0; i < linesArray.length; i++) + lines.add(new StringBuilder(linesArray[i])); + + return initialiseWithLines(lines, options); + } + + public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException { + + ArrayList lines = new ArrayList(); + String[] linesArray = text.split("(\r)?\n"); + for(int i = 0; i < linesArray.length; i++) + lines.add(new StringBuilder(linesArray[i])); + + return initialiseWithLines(lines, options); + } + + public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException { + + //remove blank rows at the bottom + boolean done = false; + int i; + for(i = lines.size() - 1; i >= 0 && !done; i--){ + StringBuilder row = lines.get(i); + if(!StringUtils.isBlank(row.toString())) done = true; + } + rows = new ArrayList(lines.subList(0, i + 2)); + this.updateModeRows(); + try{ + + if(options != null) fixTabs(options.getTabSize()); + else fixTabs(options.DEFAULT_TAB_SIZE); + + + // make all lines of equal length + // add blank outline around the buffer to prevent fill glitch + // convert tabs to spaces (or remove them if setting is 0) + + int blankBorderSize = 2; + + int maxLength = 0; + int index = 0; + + String encoding = null; + if(options != null) encoding = options.getCharacterEncoding(); + + Iterator it = rows.iterator(); + while(it.hasNext()){ + String row = it.next().toString(); + if(encoding != null){ + byte[] bytes = row.getBytes(); + row = new String(bytes, encoding); + } + if(row.length() > maxLength) maxLength = row.length(); + rows.set(index, new StringBuilder(row)); + index++; + } + + it = rows.iterator(); + ArrayList newRows = new ArrayList(); + //TODO: make the following depend on blankBorderSize + + StringBuilder topBottomRow = + new StringBuilder(StringUtils.repeatString(" ", maxLength + blankBorderSize * 2)); + + newRows.add(topBottomRow); + newRows.add(topBottomRow); + while(it.hasNext()){ + StringBuilder row = it.next(); + + if(row.length() < maxLength){ + String borderString = StringUtils.repeatString(" ", blankBorderSize); + StringBuilder newRow = new StringBuilder(); + + newRow.append(borderString); + newRow.append(row); + newRow.append(StringUtils.repeatString(" ", maxLength - row.length())); + newRow.append(borderString); + + newRows.add(newRow); + } else{ //TODO: why is the following line like that? + newRows.add(new StringBuilder(" ").append(row).append(" ")); + } + } + //TODO: make the following depend on blankBorderSize + newRows.add(topBottomRow); + newRows.add(topBottomRow); + rows = newRows; + } finally{ + this.updateModeRows(); + } + + replaceBullets(); + replaceHumanColorCodes(); + + return true; + } + + private void fixTabs(int tabSize){ + + int rowIndex = 0; + Iterator it = rows.iterator(); + + while(it.hasNext()){ + String row = it.next().toString(); + StringBuilder newRow = new StringBuilder(); + + char[] chars = row.toCharArray(); + for(int i = 0; i < chars.length; i++){ + if(chars[i] == '\t'){ + int spacesLeft = tabSize - newRow.length() % tabSize; + if(DEBUG){ + System.out.println("Found tab. Spaces left: "+spacesLeft); + } + String spaces = StringUtils.repeatString(" ", spacesLeft); + newRow.append(spaces); + } else { + String character = Character.toString(chars[i]); + newRow.append(character); + } + } + rows.set(rowIndex, newRow); + rowIndex++; + } + } + + /** + * @return + */ + protected ArrayList getRows() { + return rows; + } + + private boolean isInPlainMode(Cell cell){ + return this.modeRows.get(cell.y).charAt(cell.x) == PLAIN_MODE; + } + + private boolean isInPlainMode(int x, int y){ + return this.modeRows.get(y).charAt(x) == PLAIN_MODE; + } + + public class CellColorPair{ + public CellColorPair(Cell cell, Color color){ + this.cell = cell; + this.color = color; + } + public Color color; + public Cell cell; + } + + public class CellStringPair{ + public CellStringPair(Cell cell, String string){ + this.cell = cell; + this.string = string; + } + public Cell cell; + public String string; + } + + public class CellTagPair{ + public CellTagPair(Cell cell, String tag){ + this.cell = cell; + this.tag = tag; + } + public Cell cell; + public String tag; + } + + + public class Cell{ + + public int x, y; + + public Cell(Cell cell){ + this(cell.x, cell.y); + } + + public Cell(int x, int y){ + this.x = x; + this.y = y; + } + + public Cell getNorth(){ return new Cell(x, y - 1); } + public Cell getSouth(){ return new Cell(x, y + 1); } + public Cell getEast(){ return new Cell(x + 1, y); } + public Cell getWest(){ return new Cell(x - 1, y); } + + public Cell getNW(){ return new Cell(x - 1, y - 1); } + public Cell getNE(){ return new Cell(x + 1, y - 1); } + public Cell getSW(){ return new Cell(x - 1, y + 1); } + public Cell getSE(){ return new Cell(x + 1, y + 1); } + + public CellSet getNeighbours4(){ + CellSet result = new CellSet(); + + result.add(getNorth()); + result.add(getSouth()); + result.add(getWest()); + result.add(getEast()); + + return result; + } + + public CellSet getNeighbours8(){ + CellSet result = new CellSet(); + + result.add(getNorth()); + result.add(getSouth()); + result.add(getWest()); + result.add(getEast()); + + result.add(getNW()); + result.add(getNE()); + result.add(getSW()); + result.add(getSE()); + + return result; + } + + + public boolean isNorthOf(Cell cell){ + if(this.y < cell.y) return true; + return false; + } + + public boolean isSouthOf(Cell cell){ + if(this.y > cell.y) return true; + return false; + } + + public boolean isWestOf(Cell cell){ + if(this.x < cell.x) return true; + return false; + } + + public boolean isEastOf(Cell cell){ + if(this.x > cell.x) return true; + return false; + } + + + public boolean equals(Object o){ + Cell cell = (Cell) o; + if(cell == null) return false; + if(x == cell.x && y == cell.y) return true; + else return false; + } + + public int hashCode() { + return (x << 16) | y; + } + + public boolean isNextTo(int x2, int y2){ + if(Math.abs(x2 - x) == 1 && Math.abs(y2 - y) == 1) return false; + if(Math.abs(x2 - x) == 1 && y2 == y) return true; + if(Math.abs(y2 - y) == 1 && x2 == x) return true; + return false; + } + + public boolean isNextTo(Cell cell){ + if(cell == null) throw new IllegalArgumentException("cell cannot be null"); + return this.isNextTo(cell.x, cell.y); + } + + public String toString(){ + return "("+x+", "+y+")"; + } + + public void scale(int s){ + x = x * s; + y = y * s; + } + + } + + private class LineSegment{ + int x1, x2, y, dy; + public LineSegment(int x1, int x2, int y, int dy){ + this.x1 = x1; + this.x2 = x2; + this.y = y; + this.dy = dy; + } + } } From 3ebd7f503ed24134757bd96e82d89eaeabc16af7 Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Sun, 28 Oct 2018 20:25:36 +0900 Subject: [PATCH 08/12] Do not use wildcard import. --- .../ascii2image/text/TextGrid.java | 455 ++++++++++-------- 1 file changed, 265 insertions(+), 190 deletions(-) diff --git a/src/java/org/stathissideris/ascii2image/text/TextGrid.java b/src/java/org/stathissideris/ascii2image/text/TextGrid.java index 082057c..d0cd93d 100644 --- a/src/java/org/stathissideris/ascii2image/text/TextGrid.java +++ b/src/java/org/stathissideris/ascii2image/text/TextGrid.java @@ -1,31 +1,39 @@ /** * ditaa - Diagrams Through Ascii Art - * + *

* Copyright (C) 2004-2011 Efstathios Sideris - * + *

* ditaa is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. - * + *

* ditaa is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + *

* You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.text; import org.stathissideris.ascii2image.core.FileUtils; import org.stathissideris.ascii2image.core.ProcessingOptions; -import java.awt.*; -import java.io.*; -import java.util.*; +import java.awt.Color; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Stack; import java.util.function.IntUnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -38,7 +46,7 @@ * * @author Efstathios Sideris */ -public class TextGrid { +public class TextGrid{ private static final boolean DEBUG = false; private static final char PLAIN_MODE = 'P'; @@ -66,9 +74,9 @@ public class TextGrid { private static char[] entryPoints8 = {'-', '=', '+', '\\', '/'}; - private static HashMap humanColorCodes = new HashMap(); - static { + + static{ humanColorCodes.put("GRE", "9D9"); humanColorCodes.put("BLU", "55B"); humanColorCodes.put("PNK", "FAA"); @@ -79,9 +87,9 @@ public class TextGrid { } private static HashSet markupTags = - new HashSet(); + new HashSet(); - static { + static{ markupTags.add("d"); markupTags.add("s"); markupTags.add("io"); @@ -137,7 +145,7 @@ public void addToMarkupTags(Collection tags){ markupTags.addAll(tags); } - public static void main(String[] args) throws Exception { + public static void main(String[] args) throws Exception{ TextGrid grid = new TextGrid(); grid.loadFrom("tests/text/art10.txt"); @@ -173,7 +181,7 @@ public static TextGrid makeSameSizeAs(TextGrid grid){ public TextGrid(TextGrid otherGrid){ rows = new ArrayList(); - for(StringBuilder row : otherGrid.getRows()) { + for(StringBuilder row : otherGrid.getRows()){ rows.add(new StringBuilder(row)); } this.updateModeRows(); @@ -187,21 +195,21 @@ public void clear(){ rows.add(new StringBuilder(blank)); } -// duplicated code due to lots of hits to this function + // duplicated code due to lots of hits to this function public char get(int x, int y){ if(x > getWidth() - 1 - || y > getHeight() - 1 - || x < 0 - || y < 0) return 0; + || y > getHeight() - 1 + || x < 0 + || y < 0) return 0; return rows.get(y).charAt(x); } //duplicated code due to lots of hits to this function public char get(Cell cell){ if(cell.x > getWidth() - 1 - || cell.y > getHeight() - 1 - || cell.x < 0 - || cell.y < 0) return 0; + || cell.y > getHeight() - 1 + || cell.x < 0 + || cell.y < 0) return 0; return rows.get(cell.y).charAt(cell.x); } @@ -230,21 +238,43 @@ public String getStringAt(Cell cell, int length){ int x = cell.x; int y = cell.y; if(x > getWidth() - 1 - || y > getHeight() - 1 - || x < 0 - || y < 0) return null; + || y > getHeight() - 1 + || x < 0 + || y < 0) return null; return rows.get(y).substring(x, x + length); } - public char getNorthOf(int x, int y){ return get(x, y - 1); } - public char getSouthOf(int x, int y){ return get(x, y + 1); } - public char getEastOf(int x, int y){ return get(x + 1, y); } - public char getWestOf(int x, int y){ return get(x - 1, y); } + public char getNorthOf(int x, int y){ + return get(x, y - 1); + } + + public char getSouthOf(int x, int y){ + return get(x, y + 1); + } + + public char getEastOf(int x, int y){ + return get(x + 1, y); + } + + public char getWestOf(int x, int y){ + return get(x - 1, y); + } + + public char getNorthOf(Cell cell){ + return getNorthOf(cell.x, cell.y); + } + + public char getSouthOf(Cell cell){ + return getSouthOf(cell.x, cell.y); + } + + public char getEastOf(Cell cell){ + return getEastOf(cell.x, cell.y); + } - public char getNorthOf(Cell cell){ return getNorthOf(cell.x, cell.y); } - public char getSouthOf(Cell cell){ return getSouthOf(cell.x, cell.y); } - public char getEastOf(Cell cell){ return getEastOf(cell.x, cell.y); } - public char getWestOf(Cell cell){ return getWestOf(cell.x, cell.y); } + public char getWestOf(Cell cell){ + return getWestOf(cell.x, cell.y); + } public void writeStringTo(int x, int y, String str){ writeStringTo(new Cell(x, y), str); @@ -290,13 +320,13 @@ public void printDebug(){ Iterator it = rows.iterator(); int i = 0; System.out.println( - " " - +StringUtils.repeatString("0123456789", (int) Math.floor(getWidth()/10)+1)); + " " + + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1)); while(it.hasNext()){ String row = it.next().toString(); String index = new Integer(i).toString(); - if(i < 10) index = " "+index; - System.out.println(index+" ("+row+")"); + if(i < 10) index = " " + index; + System.out.println(index + " (" + row + ")"); i++; } } @@ -306,15 +336,15 @@ public String getDebugString(){ Iterator it = rows.iterator(); int i = 0; buffer.append( - " " - +StringUtils.repeatString("0123456789", (int) Math.floor(getWidth()/10)+1)+"\n"); + " " + + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1) + "\n"); while(it.hasNext()){ String row = it.next().toString(); String index = new Integer(i).toString(); - if(i < 10) index = " "+index; + if(i < 10) index = " " + index; row = row.replaceAll("\n", "\\\\n"); row = row.replaceAll("\r", "\\\\r"); - buffer.append(index+" ("+row+")\n"); + buffer.append(index + " (" + row + ")\n"); i++; } return buffer.toString(); @@ -334,7 +364,7 @@ public String toString(){ */ public boolean add(TextGrid grid){ if(getWidth() != grid.getWidth() - || getHeight() != grid.getHeight()) return false; + || getHeight() != grid.getHeight()) return false; int width = getWidth(); int height = getHeight(); for(int yi = 0; yi < height; yi++){ @@ -357,18 +387,18 @@ public void replaceTypeOnLine(){ for(int yi = 0; yi < height; yi++){ for(int xi = 0; xi < width; xi++){ char c = get(xi, yi); - if(Character.isLetterOrDigit(c)) { + if(Character.isLetterOrDigit(c)){ boolean isOnHorizontalLine = isOnHorizontalLine(xi, yi); boolean isOnVerticalLine = isOnVerticalLine(xi, yi); if(isOnHorizontalLine && isOnVerticalLine){ set(xi, yi, '+'); - if(DEBUG) System.out.println("replaced type on line '"+c+"' with +"); + if(DEBUG) System.out.println("replaced type on line '" + c + "' with +"); } else if(isOnHorizontalLine){ set(xi, yi, '-'); - if(DEBUG) System.out.println("replaced type on line '"+c+"' with -"); + if(DEBUG) System.out.println("replaced type on line '" + c + "' with -"); } else if(isOnVerticalLine){ set(xi, yi, '|'); - if(DEBUG) System.out.println("replaced type on line '"+c+"' with |"); + if(DEBUG) System.out.println("replaced type on line '" + c + "' with |"); } } } @@ -399,13 +429,13 @@ && isStarOnLine(cell) && isInPlainMode(cell)){ if(isOnHorizontalLine && isOnVerticalLine){ set(xi, yi, '+'); - if(DEBUG) System.out.println("replaced marker on line '"+c+"' with +"); + if(DEBUG) System.out.println("replaced marker on line '" + c + "' with +"); } else if(isOnHorizontalLine){ set(xi, yi, '-'); - if(DEBUG) System.out.println("replaced marker on line '"+c+"' with -"); + if(DEBUG) System.out.println("replaced marker on line '" + c + "' with -"); } else if(isOnVerticalLine){ set(xi, yi, '|'); - if(DEBUG) System.out.println("replaced marker on line '"+c+"' with |"); + if(DEBUG) System.out.println("replaced marker on line '" + c + "' with |"); } } } @@ -498,7 +528,7 @@ public CellSet getAllNonBlank(){ for(int y = 0; y < height; y++){ for(int x = 0; x < width; x++){ Cell cell = new Cell(x, y); - if(!isBlank(cell)) set.add(cell); + if(! isBlank(cell)) set.add(cell); } } return set; @@ -547,15 +577,15 @@ public ArrayList findStrings(){ int height = getHeight(); for(int y = 0; y < height; y++){ for(int x = 0; x < width; x++){ - if(!isBlank(x, y)){ + if(! isBlank(x, y)){ Cell start = new Cell(x, y); - String str = String.valueOf(get(x,y)); - char c = get(++x, y); + String str = String.valueOf(get(x, y)); + char c = get(++ x, y); boolean finished = false; //while(c != ' '){ - while(!finished){ + while(! finished){ str += String.valueOf(c); - c = get(++x, y); + c = get(++ x, y); char next = get(x + 1, y); if((c == ' ' || c == 0) && (next == ' ' || next == 0)) finished = true; @@ -578,28 +608,28 @@ public ArrayList findStrings(){ public boolean hasEntryPoint(Cell cell, int entryPointId){ String result = ""; char c = get(cell); - if(entryPointId == 1) { + if(entryPointId == 1){ return StringUtils.isOneOf(c, entryPoints1); - } else if(entryPointId == 2) { + } else if(entryPointId == 2){ return StringUtils.isOneOf(c, entryPoints2); - } else if(entryPointId == 3) { + } else if(entryPointId == 3){ return StringUtils.isOneOf(c, entryPoints3); - } else if(entryPointId == 4) { + } else if(entryPointId == 4){ return StringUtils.isOneOf(c, entryPoints4); - } else if(entryPointId == 5) { + } else if(entryPointId == 5){ return StringUtils.isOneOf(c, entryPoints5); - } else if(entryPointId == 6) { + } else if(entryPointId == 6){ return StringUtils.isOneOf(c, entryPoints6); - } else if(entryPointId == 7) { + } else if(entryPointId == 7){ return StringUtils.isOneOf(c, entryPoints7); - } else if(entryPointId == 8) { + } else if(entryPointId == 8){ return StringUtils.isOneOf(c, entryPoints8); } return false; @@ -614,8 +644,8 @@ public boolean hasEntryPoint(Cell cell, int entryPointId){ */ public boolean isBlankBetweenCharacters(Cell cell){ return (isBlank(cell) - && !isBlank(cell.getEast()) - && !isBlank(cell.getWest())); + && ! isBlank(cell.getEast()) + && ! isBlank(cell.getWest())); } /** @@ -648,9 +678,12 @@ public void removeColorCodes(){ while(cells.hasNext()){ Cell cell = ((CellColorPair) cells.next()).cell; set(cell, ' '); - cell = cell.getEast(); set(cell, ' '); - cell = cell.getEast(); set(cell, ' '); - cell = cell.getEast(); set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); } } @@ -687,7 +720,7 @@ public ArrayList findArrowheads(){ if(isArrowhead(cell)) result.add(cell); } } - if(DEBUG) System.out.println(result.size()+" arrowheads found"); + if(DEBUG) System.out.println(result.size() + " arrowheads found"); return result; } @@ -715,7 +748,7 @@ public ArrayList findColorCodes(){ } } } - if(DEBUG) System.out.println(result.size()+" color codes found"); + if(DEBUG) System.out.println(result.size() + " color codes found"); return result; } @@ -737,7 +770,7 @@ public ArrayList findMarkupTags(){ if(matcher.find()){ String tagName = matcher.group(1); if(markupTags.contains(tagName)){ - if(DEBUG) System.out.println("found tag "+tagName+" at "+x+", "+y); + if(DEBUG) System.out.println("found tag " + tagName + " at " + x + ", " + y); result.add(new CellTagPair(new Cell(x, y), tagName)); } } @@ -749,7 +782,7 @@ public ArrayList findMarkupTags(){ public void removeMarkupTags(){ Iterator it = findMarkupTags().iterator(); - while (it.hasNext()) { + while(it.hasNext()){ CellTagPair pair = (CellTagPair) it.next(); String tagName = pair.tag; if(tagName == null) continue; @@ -759,7 +792,6 @@ public void removeMarkupTags(){ } - public boolean matchesAny(GridPatternGroup criteria){ return criteria.isAnyMatchedBy(this); } @@ -773,7 +805,10 @@ public boolean matches(GridPattern criteria){ } - public boolean isOnHorizontalLine(Cell cell){ return isOnHorizontalLine(cell.x, cell.y); } + public boolean isOnHorizontalLine(Cell cell){ + return isOnHorizontalLine(cell.x, cell.y); + } + private boolean isOnHorizontalLine(int x, int y){ char c1 = get(x - 1, y); char c2 = get(x + 1, y); @@ -781,7 +816,10 @@ private boolean isOnHorizontalLine(int x, int y){ return false; } - public boolean isOnVerticalLine(Cell cell){ return isOnVerticalLine(cell.x, cell.y); } + public boolean isOnVerticalLine(Cell cell){ + return isOnVerticalLine(cell.x, cell.y); + } + private boolean isOnVerticalLine(int x, int y){ char c1 = get(x, y - 1); char c2 = get(x, y + 1); @@ -793,7 +831,11 @@ private boolean isOnVerticalLine(int x, int y){ public static boolean isBoundary(char c){ return StringUtils.isOneOf(c, boundaries); } - public boolean isBoundary(int x, int y){ return isBoundary(new Cell(x, y)); } + + public boolean isBoundary(int x, int y){ + return isBoundary(new Cell(x, y)); + } + public boolean isBoundary(Cell cell){ if(! isInPlainMode(cell)) return false; @@ -802,15 +844,15 @@ public boolean isBoundary(Cell cell){ if('+' == c || '\\' == c || '/' == c){ System.out.print(""); if( - isIntersection(cell) - || isCorner(cell) - || isStub(cell) - || isCrossOnLine(cell)){ + isIntersection(cell) + || isCorner(cell) + || isStub(cell) + || isCrossOnLine(cell)){ return true; } else return false; } //return StringUtils.isOneOf(c, undisputableBoundaries); - if(StringUtils.isOneOf(c, boundaries) && !isLoneDiagonal(cell)){ + if(StringUtils.isOneOf(c, boundaries) && ! isLoneDiagonal(cell)){ return true; } return false; @@ -884,14 +926,14 @@ public boolean isPointCell(Cell cell){ public boolean containsAtLeastOneDashedLine(CellSet set){ Iterator it = set.iterator(); - while(it.hasNext()) { + while(it.hasNext()){ Cell cell = (Cell) it.next(); if(StringUtils.isOneOf(get(cell), dashedLines)) return true; } return false; } - public boolean exactlyOneNeighbourIsBoundary(Cell cell) { + public boolean exactlyOneNeighbourIsBoundary(Cell cell){ int howMany = 0; if(isBoundary(cell.getNorth())) howMany++; if(isBoundary(cell.getSouth())) howMany++; @@ -1022,7 +1064,7 @@ public void replaceBullets(){ * @return */ public boolean isStringsStart(Cell cell){ - return (!isBlank(cell) && isBlank(cell.getWest())); + return (! isBlank(cell) && isBlank(cell.getWest())); } /** @@ -1033,18 +1075,18 @@ public boolean isStringsStart(Cell cell){ * @return */ public boolean isStringsEnd(Cell cell){ - return (!isBlank(cell) - //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); - && isBlank(cell.getEast())); + return (! isBlank(cell) + //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); + && isBlank(cell.getEast())); } public int otherStringsStartInTheSameColumn(Cell cell){ - if(!isStringsStart(cell)) return 0; + if(! isStringsStart(cell)) return 0; int result = 0; int height = getHeight(); for(int y = 0; y < height; y++){ Cell cCell = new Cell(cell.x, y); - if(!cCell.equals(cell) && isStringsStart(cCell)){ + if(! cCell.equals(cell) && isStringsStart(cCell)){ result++; } } @@ -1052,12 +1094,12 @@ public int otherStringsStartInTheSameColumn(Cell cell){ } public int otherStringsEndInTheSameColumn(Cell cell){ - if(!isStringsEnd(cell)) return 0; + if(! isStringsEnd(cell)) return 0; int result = 0; int height = getHeight(); for(int y = 0; y < height; y++){ Cell cCell = new Cell(cell.x, y); - if(!cCell.equals(cell) && isStringsEnd(cCell)){ + if(! cCell.equals(cell) && isStringsEnd(cCell)){ result++; } } @@ -1067,7 +1109,7 @@ public int otherStringsEndInTheSameColumn(Cell cell){ public boolean isColumnBlank(int x){ int height = getHeight(); for(int y = 0; y < height; y++){ - if(!isBlank(x, y)) return false; + if(! isBlank(x, y)) return false; } return true; } @@ -1082,7 +1124,7 @@ public CellSet followIntersection(Cell cell){ } public CellSet followIntersection(Cell cell, Cell blocked){ - if(!isIntersection(cell)) return null; + if(! isIntersection(cell)) return null; CellSet result = new CellSet(); Cell cN = cell.getNorth(); Cell cS = cell.getSouth(); @@ -1109,7 +1151,7 @@ public CellSet followLine(Cell cell){ if(isBoundary(cell.getEast())) result.add(cell.getEast()); if(isBoundary(cell.getWest())) result.add(cell.getWest()); return result; - } else if (isVerticalLine(cell)){ + } else if(isVerticalLine(cell)){ CellSet result = new CellSet(); if(isBoundary(cell.getNorth())) result.add(cell.getNorth()); if(isBoundary(cell.getSouth())) result.add(cell.getSouth()); @@ -1129,7 +1171,7 @@ public CellSet followCorner(Cell cell){ } public CellSet followCorner(Cell cell, Cell blocked){ - if(!isCorner(cell)) return null; + if(! isCorner(cell)) return null; if(isCorner1(cell)) return followCorner1(cell, blocked); if(isCorner2(cell)) return followCorner2(cell, blocked); if(isCorner3(cell)) return followCorner3(cell, blocked); @@ -1140,44 +1182,48 @@ public CellSet followCorner(Cell cell, Cell blocked){ public CellSet followCorner1(Cell cell){ return followCorner1(cell, null); } + public CellSet followCorner1(Cell cell, Cell blocked){ - if(!isCorner1(cell)) return null; + if(! isCorner1(cell)) return null; CellSet result = new CellSet(); - if(!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); - if(!cell.getEast().equals(blocked)) result.add(cell.getEast()); + if(! cell.getSouth().equals(blocked)) result.add(cell.getSouth()); + if(! cell.getEast().equals(blocked)) result.add(cell.getEast()); return result; } public CellSet followCorner2(Cell cell){ return followCorner2(cell, null); } + public CellSet followCorner2(Cell cell, Cell blocked){ - if(!isCorner2(cell)) return null; + if(! isCorner2(cell)) return null; CellSet result = new CellSet(); - if(!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); - if(!cell.getWest().equals(blocked)) result.add(cell.getWest()); + if(! cell.getSouth().equals(blocked)) result.add(cell.getSouth()); + if(! cell.getWest().equals(blocked)) result.add(cell.getWest()); return result; } public CellSet followCorner3(Cell cell){ return followCorner3(cell, null); } + public CellSet followCorner3(Cell cell, Cell blocked){ - if(!isCorner3(cell)) return null; + if(! isCorner3(cell)) return null; CellSet result = new CellSet(); - if(!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); - if(!cell.getWest().equals(blocked)) result.add(cell.getWest()); + if(! cell.getNorth().equals(blocked)) result.add(cell.getNorth()); + if(! cell.getWest().equals(blocked)) result.add(cell.getWest()); return result; } public CellSet followCorner4(Cell cell){ return followCorner4(cell, null); } + public CellSet followCorner4(Cell cell, Cell blocked){ - if(!isCorner4(cell)) return null; + if(! isCorner4(cell)) return null; CellSet result = new CellSet(); - if(!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); - if(!cell.getEast().equals(blocked)) result.add(cell.getEast()); + if(! cell.getNorth().equals(blocked)) result.add(cell.getNorth()); + if(! cell.getEast().equals(blocked)) result.add(cell.getEast()); return result; } @@ -1185,8 +1231,9 @@ public CellSet followCorner4(Cell cell, Cell blocked){ public CellSet followStub(Cell cell){ return followStub(cell, null); } + public CellSet followStub(Cell cell, Cell blocked){ - if(!isStub(cell)) return null; + if(! isStub(cell)) return null; CellSet result = new CellSet(); if(isBoundary(cell.getEast())) result.add(cell.getEast()); else if(isBoundary(cell.getWest())) result.add(cell.getWest()); @@ -1206,10 +1253,10 @@ public CellSet followCell(Cell cell, Cell blocked){ if(isLine(cell)) return followLine(cell, blocked); if(isStub(cell)) return followStub(cell, blocked); if(isCrossOnLine(cell)) return followCrossOnLine(cell, blocked); - System.err.println("Ambiguous input at position "+cell+":"); + System.err.println("Ambiguous input at position " + cell + ":"); TextGrid subGrid = getTestingSubGrid(cell); subGrid.printDebug(); - throw new RuntimeException("Cannot follow cell "+cell+": cannot determine cell type"); + throw new RuntimeException("Cannot follow cell " + cell + ": cannot determine cell type"); } public String getCellTypeAsString(Cell cell){ @@ -1243,9 +1290,9 @@ public CellSet followCrossOnLine(Cell cell, Cell blocked){ public boolean isOutOfBounds(Cell cell){ if(cell.x > getWidth() - 1 - || cell.y > getHeight() - 1 - || cell.x < 0 - || cell.y < 0) return true; + || cell.y > getHeight() - 1 + || cell.x < 0 + || cell.y < 0) return true; return false; } @@ -1270,8 +1317,9 @@ public boolean isBlank(int x, int y){ public boolean isCorner(Cell cell){ return isCorner(cell.x, cell.y); } + public boolean isCorner(int x, int y){ - return (isNormalCorner(x,y) || isRoundCorner(x,y)); + return (isNormalCorner(x, y) || isRoundCorner(x, y)); } @@ -1319,6 +1367,7 @@ public boolean isInverseT(Cell cell){ public boolean isNormalCorner(Cell cell){ return matchesAny(cell, GridPatternGroup.normalCornerCriteria); } + public boolean isNormalCorner(int x, int y){ return isNormalCorner(new Cell(x, y)); } @@ -1334,6 +1383,7 @@ public boolean isRoundCorner(int x, int y){ public boolean isIntersection(Cell cell){ return matchesAny(cell, GridPatternGroup.intersectionCriteria); } + public boolean isIntersection(int x, int y){ return isIntersection(new Cell(x, y)); } @@ -1348,15 +1398,15 @@ public void copyCellsTo(CellSet cells, TextGrid grid){ public boolean equals(TextGrid grid){ if(grid.getHeight() != this.getHeight() - || grid.getWidth() != this.getWidth() - ){ + || grid.getWidth() != this.getWidth() + ){ return false; } int height = grid.getHeight(); for(int i = 0; i < height; i++){ String row1 = this.getRow(i).toString(); String row2 = grid.getRow(i).toString(); - if(!row1.equals(row2)) return false; + if(! row1.equals(row2)) return false; } return true; } @@ -1392,13 +1442,12 @@ public void fillCellsWith(Iterable cells, char c){ // seedFill(new Cell(x, y), c1, c2); // return cells; // } - public CellSet fillContinuousArea(int x, int y, char c){ return fillContinuousArea(new Cell(x, y), c); } public CellSet fillContinuousArea(Cell cell, char c){ - if(isOutOfBounds(cell)) throw new IllegalArgumentException("Attempted to fill area out of bounds: "+cell); + if(isOutOfBounds(cell)) throw new IllegalArgumentException("Attempted to fill area out of bounds: " + cell); return seedFillOld(cell, c); } @@ -1413,7 +1462,7 @@ private CellSet seedFill(Cell seed, char newChar){ stack.push(seed); - while(!stack.isEmpty()){ + while(! stack.isEmpty()){ Cell cell = (Cell) stack.pop(); //set(cell, newChar); @@ -1424,10 +1473,10 @@ private CellSet seedFill(Cell seed, char newChar){ Cell eCell = cell.getEast(); Cell wCell = cell.getWest(); - if(get(nCell) == oldChar && !cellsFilled.contains(nCell)) stack.push(nCell); - if(get(sCell) == oldChar && !cellsFilled.contains(sCell)) stack.push(sCell); - if(get(eCell) == oldChar && !cellsFilled.contains(eCell)) stack.push(eCell); - if(get(wCell) == oldChar && !cellsFilled.contains(wCell)) stack.push(wCell); + if(get(nCell) == oldChar && ! cellsFilled.contains(nCell)) stack.push(nCell); + if(get(sCell) == oldChar && ! cellsFilled.contains(sCell)) stack.push(sCell); + if(get(eCell) == oldChar && ! cellsFilled.contains(eCell)) stack.push(eCell); + if(get(wCell) == oldChar && ! cellsFilled.contains(wCell)) stack.push(wCell); } return cellsFilled; @@ -1444,7 +1493,7 @@ private CellSet seedFillOld(Cell seed, char newChar){ stack.push(seed); - while(!stack.isEmpty()){ + while(! stack.isEmpty()){ Cell cell = (Cell) stack.pop(); set(cell, newChar); @@ -1485,7 +1534,7 @@ public CellSet findBoundariesExpandingFrom(Cell seed){ stack.push(seed); - while(!stack.isEmpty()){ + while(! stack.isEmpty()){ Cell cell = (Cell) stack.pop(); set(cell, newChar); @@ -1524,48 +1573,50 @@ private CellSet seedFillLine(Cell cell, char newChar){ if(isOutOfBounds(cell)) return cellsFilled; stack.push(new LineSegment(cell.x, cell.x, cell.y, 1)); - stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, -1)); + stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, - 1)); int left; - while(!stack.isEmpty()){ + while(! stack.isEmpty()){ LineSegment segment = (LineSegment) stack.pop(); int x; //expand to the left for( x = segment.x1; x >= 0 && get(x, segment.y) == oldChar; - --x){ + -- x){ set(x, segment.y, newChar); cellsFilled.add(new Cell(x, segment.y)); } left = cell.getEast().x; - boolean skip = (x > segment.x1)? true : false; + boolean skip = (x > segment.x1) ? true : false; if(left < segment.x1){ //leak on left? //TODO: i think the first param should be x stack.push( - //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); - new LineSegment(x, left, segment.y - 1, -segment.dy)); + //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); + new LineSegment(x, left, segment.y - 1, - segment.dy)); } x = segment.x1 + 1; - do { - if(!skip) { - for( ; x < getWidth() && get(x, segment.y) == oldChar; ++x){ - set(x, segment.y, newChar); - cellsFilled.add(new Cell(x, segment.y)); - } - - stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); - if(x > segment.x2 + 1) //leak on right? - stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, -segment.dy)); - } + do{ + if(! skip){ + for(; x < getWidth() && get(x, segment.y) == oldChar; ++ x){ + set(x, segment.y, newChar); + cellsFilled.add(new Cell(x, segment.y)); + } + + stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); + if(x > segment.x2 + 1) //leak on right? + stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, - segment.dy)); + } skip = false; //skip only once - for(++x; x <= segment.x2 && get(x, segment.y) != oldChar; ++x){;} + for(++ x; x <= segment.x2 && get(x, segment.y) != oldChar; ++ x){ + ; + } left = x; - } while( x < segment.x2); + } while(x < segment.x2); } return cellsFilled; @@ -1577,30 +1628,28 @@ public boolean cellContainsDashedLineChar(Cell cell){ } public boolean loadFrom(String filename) - throws FileNotFoundException, IOException - { + throws FileNotFoundException, IOException{ return loadFrom(filename, null); } public boolean loadFrom(String filename, ProcessingOptions options) - throws IOException - { + throws IOException{ String encoding = (options == null) ? null : options.getCharacterEncoding(); ArrayList lines = new ArrayList(); InputStream is; - if ("-".equals(filename)) - is = System.in; + if("-".equals(filename)) + is = System.in; else - is = new FileInputStream(filename); + is = new FileInputStream(filename); String[] linesArray = FileUtils.readFile(is, filename, encoding).split("(\r)?\n"); - for(int i = 0; i < linesArray.length; i++) + for(int i = 0; i < linesArray.length; i++) lines.add(new StringBuilder(linesArray[i])); return initialiseWithLines(lines, options); } - public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException { + public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException{ ArrayList lines = new ArrayList(); String[] linesArray = text.split("(\r)?\n"); @@ -1610,46 +1659,46 @@ public boolean initialiseWithText(String text, ProcessingOptions options) throws return initialiseWithLines(lines, options); } - public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException { + public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException{ //remove blank rows at the bottom boolean done = false; int i; - for(i = lines.size() - 1; i >= 0 && !done; i--){ + for(i = lines.size() - 1; i >= 0 && ! done; i--){ StringBuilder row = lines.get(i); - if(!StringUtils.isBlank(row.toString())) done = true; + if(! StringUtils.isBlank(row.toString())) done = true; } rows = new ArrayList(lines.subList(0, i + 2)); this.updateModeRows(); try{ - if(options != null) fixTabs(options.getTabSize()); - else fixTabs(options.DEFAULT_TAB_SIZE); + if(options != null) fixTabs(options.getTabSize()); + else fixTabs(options.DEFAULT_TAB_SIZE); - // make all lines of equal length - // add blank outline around the buffer to prevent fill glitch - // convert tabs to spaces (or remove them if setting is 0) + // make all lines of equal length + // add blank outline around the buffer to prevent fill glitch + // convert tabs to spaces (or remove them if setting is 0) - int blankBorderSize = 2; + int blankBorderSize = 2; - int maxLength = 0; - int index = 0; + int maxLength = 0; + int index = 0; - String encoding = null; - if(options != null) encoding = options.getCharacterEncoding(); + String encoding = null; + if(options != null) encoding = options.getCharacterEncoding(); - Iterator it = rows.iterator(); - while(it.hasNext()){ - String row = it.next().toString(); - if(encoding != null){ - byte[] bytes = row.getBytes(); - row = new String(bytes, encoding); + Iterator it = rows.iterator(); + while(it.hasNext()){ + String row = it.next().toString(); + if(encoding != null){ + byte[] bytes = row.getBytes(); + row = new String(bytes, encoding); + } + if(row.length() > maxLength) maxLength = row.length(); + rows.set(index, new StringBuilder(row)); + index++; } - if(row.length() > maxLength) maxLength = row.length(); - rows.set(index, new StringBuilder(row)); - index++; - } it = rows.iterator(); ArrayList newRows = new ArrayList(); @@ -1705,11 +1754,11 @@ private void fixTabs(int tabSize){ if(chars[i] == '\t'){ int spacesLeft = tabSize - newRow.length() % tabSize; if(DEBUG){ - System.out.println("Found tab. Spaces left: "+spacesLeft); + System.out.println("Found tab. Spaces left: " + spacesLeft); } String spaces = StringUtils.repeatString(" ", spacesLeft); newRow.append(spaces); - } else { + } else{ String character = Character.toString(chars[i]); newRow.append(character); } @@ -1722,7 +1771,7 @@ private void fixTabs(int tabSize){ /** * @return */ - protected ArrayList getRows() { + protected ArrayList getRows(){ return rows; } @@ -1739,6 +1788,7 @@ public CellColorPair(Cell cell, Color color){ this.cell = cell; this.color = color; } + public Color color; public Cell cell; } @@ -1748,6 +1798,7 @@ public CellStringPair(Cell cell, String string){ this.cell = cell; this.string = string; } + public Cell cell; public String string; } @@ -1757,6 +1808,7 @@ public CellTagPair(Cell cell, String tag){ this.cell = cell; this.tag = tag; } + public Cell cell; public String tag; } @@ -1775,15 +1827,37 @@ public Cell(int x, int y){ this.y = y; } - public Cell getNorth(){ return new Cell(x, y - 1); } - public Cell getSouth(){ return new Cell(x, y + 1); } - public Cell getEast(){ return new Cell(x + 1, y); } - public Cell getWest(){ return new Cell(x - 1, y); } + public Cell getNorth(){ + return new Cell(x, y - 1); + } + + public Cell getSouth(){ + return new Cell(x, y + 1); + } + + public Cell getEast(){ + return new Cell(x + 1, y); + } + + public Cell getWest(){ + return new Cell(x - 1, y); + } - public Cell getNW(){ return new Cell(x - 1, y - 1); } - public Cell getNE(){ return new Cell(x + 1, y - 1); } - public Cell getSW(){ return new Cell(x - 1, y + 1); } - public Cell getSE(){ return new Cell(x + 1, y + 1); } + public Cell getNW(){ + return new Cell(x - 1, y - 1); + } + + public Cell getNE(){ + return new Cell(x + 1, y - 1); + } + + public Cell getSW(){ + return new Cell(x - 1, y + 1); + } + + public Cell getSE(){ + return new Cell(x + 1, y + 1); + } public CellSet getNeighbours4(){ CellSet result = new CellSet(); @@ -1841,7 +1915,7 @@ public boolean equals(Object o){ else return false; } - public int hashCode() { + public int hashCode(){ return (x << 16) | y; } @@ -1858,7 +1932,7 @@ public boolean isNextTo(Cell cell){ } public String toString(){ - return "("+x+", "+y+")"; + return "(" + x + ", " + y + ")"; } public void scale(int s){ @@ -1870,6 +1944,7 @@ public void scale(int s){ private class LineSegment{ int x1, x2, y, dy; + public LineSegment(int x1, int x2, int y, int dy){ this.x1 = x1; this.x2 = x2; From 725ddfdeee98fadc37682c8ead5b60ff1e9038a0 Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Mon, 29 Oct 2018 05:25:21 +0900 Subject: [PATCH 09/12] Reformat all source files based on discussion with Stathis Sideris. --- .../core/CommandLineConverter.java | 542 +-- .../ascii2image/core/ConfigurationParser.java | 312 +- .../ascii2image/core/ConversionOptions.java | 218 +- .../ascii2image/core/DebugUtils.java | 11 +- .../ascii2image/core/DocBookConverter.java | 79 +- .../ascii2image/core/FileUtils.java | 245 +- .../ascii2image/core/HTMLConverter.java | 397 +- .../stathissideris/ascii2image/core/Pair.java | 21 +- .../ascii2image/core/PerformanceTester.java | 71 +- .../ascii2image/core/ProcessingOptions.java | 428 +- .../ascii2image/core/RenderingOptions.java | 225 +- .../core/Shape3DOrderingComparator.java | 39 +- .../ascii2image/core/ShapeAreaComparator.java | 39 +- .../ascii2image/graphics/BitmapRenderer.java | 848 ++-- .../graphics/CompositeDiagramShape.java | 574 +-- .../graphics/CustomShapeDefinition.java | 127 +- .../ascii2image/graphics/Diagram.java | 1946 +++++---- .../graphics/DiagramComponent.java | 188 +- .../ascii2image/graphics/DiagramShape.java | 1909 ++++---- .../ascii2image/graphics/DiagramText.java | 432 +- .../ascii2image/graphics/FontMeasurer.java | 341 +- .../ascii2image/graphics/ImageHandler.java | 192 +- .../graphics/OffScreenSVGRenderer.java | 216 +- .../ascii2image/graphics/SVGBuilder.java | 548 +-- .../ascii2image/graphics/SVGRenderer.java | 8 +- .../ascii2image/graphics/ShapeEdge.java | 481 +- .../ascii2image/graphics/ShapePoint.java | 234 +- .../ascii2image/text/AbstractCell.java | 194 +- .../ascii2image/text/AbstractionGrid.java | 302 +- .../ascii2image/text/CellSet.java | 1327 +++--- .../ascii2image/text/GridPattern.java | 536 +-- .../ascii2image/text/GridPatternGroup.java | 809 ++-- .../ascii2image/text/StringUtils.java | 285 +- .../ascii2image/text/TextGrid.java | 3891 +++++++++-------- .../ascii2image/test/CellSetTest.java | 45 +- .../test/GenerateExpectedImages.java | 15 +- .../ascii2image/test/GridPatternTest.java | 44 +- .../ascii2image/test/TextGridTest.java | 560 +-- .../ascii2image/test/TrivialTest.java | 16 +- .../ascii2image/test/VisualTester.java | 575 +-- test/java/sandbox/Sandbox.java | 236 +- 41 files changed, 9944 insertions(+), 9562 deletions(-) diff --git a/src/java/org/stathissideris/ascii2image/core/CommandLineConverter.java b/src/java/org/stathissideris/ascii2image/core/CommandLineConverter.java index 937953a..2020be4 100644 --- a/src/java/org/stathissideris/ascii2image/core/CommandLineConverter.java +++ b/src/java/org/stathissideris/ascii2image/core/CommandLineConverter.java @@ -15,15 +15,9 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.core; -import java.awt.image.RenderedImage; -import java.io.*; - -import javax.imageio.ImageIO; - import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; @@ -36,272 +30,284 @@ import org.stathissideris.ascii2image.graphics.SVGRenderer; import org.stathissideris.ascii2image.text.TextGrid; +import javax.imageio.ImageIO; +import java.awt.image.RenderedImage; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; + /** * * @author Efstathios Sideris */ public class CommandLineConverter { - private static String notice = "ditaa version 0.11, Copyright (C) 2004--2017 Efstathios (Stathis) Sideris"; - - private static String[] markupModeAllowedValues = {"use", "ignore", "render"}; - - public static void main(String[] args){ - - long startTime = System.currentTimeMillis(); - - Options cmdLnOptions = new Options(); - cmdLnOptions.addOption( - OptionBuilder.withLongOpt("help") - .withDescription( "Prints usage help." ) - .create() ); - cmdLnOptions.addOption("v", "verbose", false, "Makes ditaa more verbose."); - cmdLnOptions.addOption("o", "overwrite", false, "If the filename of the destination image already exists, an alternative name is chosen. If the overwrite option is selected, the image file is instead overwriten."); - cmdLnOptions.addOption("S", "no-shadows", false, "Turns off the drop-shadow effect."); - cmdLnOptions.addOption("A", "no-antialias", false, "Turns anti-aliasing off."); - cmdLnOptions.addOption("W", "fixed-slope", false, "Makes sides of parallelograms and trapezoids fixed slope instead of fixed width."); - cmdLnOptions.addOption("d", "debug", false, "Renders the debug grid over the resulting image."); - cmdLnOptions.addOption("r", "round-corners", false, "Causes all corners to be rendered as round corners."); - cmdLnOptions.addOption("E", "no-separation", false, "Prevents the separation of common edges of shapes."); - cmdLnOptions.addOption("h", "html", false, "In this case the input is an HTML file. The contents of the

 tags are rendered as diagrams and saved in the images directory and a new HTML file is produced with the appropriate  tags.");
-		cmdLnOptions.addOption("T", "transparent", false, "Causes the diagram to be rendered on a transparent background. Overrides --background.");
-
-		cmdLnOptions.addOption(
-				OptionBuilder.withLongOpt("encoding")
-				.withDescription("The encoding of the input file.")
-				.hasArg()
-				.withArgName("ENCODING")
-				.create('e')
-				);
-
-		cmdLnOptions.addOption(
-				OptionBuilder.withLongOpt("scale")
-				.withDescription("A natural number that determines the size of the rendered image. The units are fractions of the default size (2.5 renders 1.5 times bigger than the default).")
-				.hasArg()
-				.withArgName("SCALE")
-				.create('s')
-				);
-
-		cmdLnOptions.addOption(
-				OptionBuilder.withLongOpt("tabs")
-				.withDescription("Tabs are normally interpreted as 8 spaces but it is possible to change that using this option. It is not advisable to use tabs in your diagrams.")
-				.hasArg()
-				.withArgName("TABS")
-				.create('t')
-				);
-
-		cmdLnOptions.addOption(
-				OptionBuilder.withLongOpt("background")
-				.withDescription("The background colour of the image. The format should be a six-digit hexadecimal number (as in HTML, FF0000 for red). Pass an eight-digit hex to define transparency. This is overridden by --transparent.")
-				.hasArg()
-				.withArgName("BACKGROUND")
-				.create('b')
-				);
-
-		cmdLnOptions.addOption(
-				OptionBuilder.withLongOpt("svg")
-				.withDescription( "Write an SVG image as destination file." )
-				.create()
-				);
-
-		cmdLnOptions.addOption(
-				OptionBuilder.withLongOpt("svg-font-url")
-				.withDescription( "SVG font URL." )
-				.hasArg()
-				.withArgName("FONT")
-				.create()
-				);
-
-//TODO: uncomment this for next version:
-//		cmdLnOptions.addOption(
-//				OptionBuilder.withLongOpt("config")
-//				.withDescription( "The shape configuration file." )
-//				.hasArg()
-//				.withArgName("CONFIG_FILE")
-//				.create('c') );
-
-		CommandLine cmdLine = null;
-
-
-
-		///// parse command line options
-		try {
-			// parse the command line arguments
-			CommandLineParser parser = new PosixParser();
-
-			cmdLine = parser.parse(cmdLnOptions, args);
-
-			// validate that block-size has been set
-			if( cmdLine.hasOption( "block-size" ) ) {
-				// print the value of block-size
-				System.out.println( cmdLine.getOptionValue( "block-size" ) );
-			}
-
-		} catch (org.apache.commons.cli.ParseException e) {
-			System.err.println(e.getMessage());
-			new HelpFormatter().printHelp("java -jar ditaa.jar  [OUTFILE]", cmdLnOptions, true);
-			System.exit(2);
-		}
-
-
-		if(cmdLine.hasOption("help") || args.length == 0 ){
-			new HelpFormatter().printHelp("java -jar ditaa.jar  [OUTFILE]", cmdLnOptions, true);
-			System.exit(0);
-		}
-
-		ConversionOptions options = null;
-		try {
-			options = new ConversionOptions(cmdLine);
-		} catch (UnsupportedEncodingException e2) {
-			System.err.println("Error: " + e2.getMessage());
-			System.exit(2);
-		} catch (IllegalArgumentException e2) {
-			System.err.println("Error: " + e2.getMessage());
-			new HelpFormatter().printHelp("java -jar ditaa.jar  [OUTFILE]", cmdLnOptions, true);
-			System.exit(2);
-		}
-
-		args = cmdLine.getArgs();
-
-		if(args.length == 0) {
-			System.err.println("Error: Please provide the input file filename");
-			new HelpFormatter().printHelp("java -jar ditaa.jar  [outfile]", cmdLnOptions, true);
-			System.exit(2);
-		}
-
-		if(cmdLine.hasOption("html")){
-			/////// print options before running
-			printRunInfo(cmdLine);
-			String filename = args[0];
-
-			boolean overwrite = false;
-			if(options.processingOptions.overwriteFiles()) overwrite = true;
-
-			String toFilename;
-			if(args.length == 1){
-				toFilename = FileUtils.makeTargetPathname(filename, "html", "_processed", true);
-			} else {
-				toFilename = args[1];
-			}
-			File target = new File(toFilename);
-			if(!overwrite && target.exists()) {
-				System.out.println("Error: File "+toFilename+" exists. If you would like to overwrite it, please use the --overwrite option.");
-				System.exit(0);
-			}
-
-			new HTMLConverter().convertHTMLFile(filename, toFilename, "ditaa_diagram", "images", options);
-			System.exit(0);
-
-		} else { //simple mode
-
-			TextGrid grid = new TextGrid();
-			if(options.processingOptions.getCustomShapes() != null){
-				grid.addToMarkupTags(options.processingOptions.getCustomShapes().keySet());
-			}
-
-			// "-" means stdin / stdout
-			String fromFilename = args[0];
-			boolean stdIn = "-".equals(fromFilename);
-
-			String toFilename;
-			boolean stdOut;
-
-			boolean overwrite = false;
-			if(options.processingOptions.overwriteFiles()) overwrite = true;
-
-			if(args.length == 1){
-				if (stdIn) { // if using stdin and no output specified, use stdout
-					stdOut = true;
-					toFilename = "-";
-				} else {
-					String ext = cmdLine.hasOption("svg") ? "svg" : "png";
-					toFilename = FileUtils.makeTargetPathname(fromFilename, ext, overwrite);
-					stdOut = false;
-				}
-			} else {
-				toFilename = args[1];
-				stdOut = "-".equals(toFilename);
-			}
-
-			if (!stdOut) {
-				/////// print options before running
-				printRunInfo(cmdLine);
-				System.out.println("Reading "+ (stdIn ? "standard input" : "file: " + fromFilename));
-			}
-
-			try {
-				if(!grid.loadFrom(fromFilename, options.processingOptions)){
-					System.err.println("Cannot open file "+fromFilename+" for reading");
-				}
-			} catch (UnsupportedEncodingException e1){
-				System.err.println("Error: "+e1.getMessage());
-				System.exit(1);
-			} catch (FileNotFoundException e1) {
-				System.err.println("Error: File "+fromFilename+" does not exist");
-				System.exit(1);
-			} catch (IOException e1) {
-				System.err.println("Error: Cannot open file "+fromFilename+" for reading");
-				System.exit(1);
-			}
-
-			if(options.processingOptions.printDebugOutput()){
-				if (!stdOut) System.out.println("Using grid:");
-				grid.printDebug();
-			}
-
-			Diagram diagram = new Diagram(grid, options);
-			if (!stdOut) System.out.println("Rendering to file: "+toFilename);
-
-			try {
-
-				if(cmdLine.hasOption("svg")){
-					String content = new SVGRenderer().renderToImage(diagram, options.renderingOptions);
-
-					PrintStream stream = stdOut ? System.out : new PrintStream(new FileOutputStream(toFilename));
-					stream.print(content);
-				} else {
-					RenderedImage image = new BitmapRenderer().renderToImage(diagram, options.renderingOptions);
-
-					OutputStream os = stdOut ? System.out : new FileOutputStream(toFilename);
-					ImageIO.write(image, "png", os);
-				}
-
-			} catch (IOException e) {
-				//e.printStackTrace();
-				System.err.println("Error: Cannot write to file "+toFilename);
-				System.exit(1);
-			}
-
-			//BitmapRenderer.renderToPNG(diagram, toFilename, options.renderingOptions);
-
-			long endTime = System.currentTimeMillis();
-			long totalTime  = (endTime - startTime) / 1000;
-			if (!stdOut) System.out.println("Done in "+totalTime+"sec");
-
-//			try {
-//			Thread.sleep(Long.MAX_VALUE);
-//			} catch (InterruptedException e) {
-//			e.printStackTrace();
-//			}
-
-		}
-	}
-
-	private static void printRunInfo(CommandLine cmdLine) {
-		System.out.println("\n"+notice+"\n");
-
-		System.out.println("Running with options:");
-		Option[] opts = cmdLine.getOptions();
-		for (Option option : opts) {
-			if(option.hasArgs()){
-				for(String value:option.getValues()){
-					System.out.println(option.getLongOpt()+" = "+value);
-				}
-			} else if(option.hasArg()){
-				System.out.println(option.getLongOpt()+" = "+option.getValue());
-			} else {
-				System.out.println(option.getLongOpt());
-			}
-		}
-	}
+  private static String notice = "ditaa version 0.11, Copyright (C) 2004--2017  Efstathios (Stathis) Sideris";
+
+  private static String[] markupModeAllowedValues = { "use", "ignore", "render" };
+
+  public static void main(String[] args) {
+
+    long startTime = System.currentTimeMillis();
+
+    Options cmdLnOptions = new Options();
+    cmdLnOptions.addOption(
+        OptionBuilder.withLongOpt("help")
+            .withDescription("Prints usage help.")
+            .create());
+    cmdLnOptions.addOption("v", "verbose", false, "Makes ditaa more verbose.");
+    cmdLnOptions.addOption("o", "overwrite", false, "If the filename of the destination image already exists, an alternative name is chosen. If the overwrite option is selected, the image file is instead overwriten.");
+    cmdLnOptions.addOption("S", "no-shadows", false, "Turns off the drop-shadow effect.");
+    cmdLnOptions.addOption("A", "no-antialias", false, "Turns anti-aliasing off.");
+    cmdLnOptions.addOption("W", "fixed-slope", false, "Makes sides of parallelograms and trapezoids fixed slope instead of fixed width.");
+    cmdLnOptions.addOption("d", "debug", false, "Renders the debug grid over the resulting image.");
+    cmdLnOptions.addOption("r", "round-corners", false, "Causes all corners to be rendered as round corners.");
+    cmdLnOptions.addOption("E", "no-separation", false, "Prevents the separation of common edges of shapes.");
+    cmdLnOptions.addOption("h", "html", false, "In this case the input is an HTML file. The contents of the 
 tags are rendered as diagrams and saved in the images directory and a new HTML file is produced with the appropriate  tags.");
+    cmdLnOptions.addOption("T", "transparent", false, "Causes the diagram to be rendered on a transparent background. Overrides --background.");
+
+    cmdLnOptions.addOption(
+        OptionBuilder.withLongOpt("encoding")
+            .withDescription("The encoding of the input file.")
+            .hasArg()
+            .withArgName("ENCODING")
+            .create('e')
+    );
+
+    cmdLnOptions.addOption(
+        OptionBuilder.withLongOpt("scale")
+            .withDescription("A natural number that determines the size of the rendered image. The units are fractions of the default size (2.5 renders 1.5 times bigger than the default).")
+            .hasArg()
+            .withArgName("SCALE")
+            .create('s')
+    );
+
+    cmdLnOptions.addOption(
+        OptionBuilder.withLongOpt("tabs")
+            .withDescription("Tabs are normally interpreted as 8 spaces but it is possible to change that using this option. It is not advisable to use tabs in your diagrams.")
+            .hasArg()
+            .withArgName("TABS")
+            .create('t')
+    );
+
+    cmdLnOptions.addOption(
+        OptionBuilder.withLongOpt("background")
+            .withDescription("The background colour of the image. The format should be a six-digit hexadecimal number (as in HTML, FF0000 for red). Pass an eight-digit hex to define transparency. This is overridden by --transparent.")
+            .hasArg()
+            .withArgName("BACKGROUND")
+            .create('b')
+    );
+
+    cmdLnOptions.addOption(
+        OptionBuilder.withLongOpt("svg")
+            .withDescription("Write an SVG image as destination file.")
+            .create()
+    );
+
+    cmdLnOptions.addOption(
+        OptionBuilder.withLongOpt("svg-font-url")
+            .withDescription("SVG font URL.")
+            .hasArg()
+            .withArgName("FONT")
+            .create()
+    );
+
+    //TODO: uncomment this for next version:
+    //		cmdLnOptions.addOption(
+    //				OptionBuilder.withLongOpt("config")
+    //				.withDescription( "The shape configuration file." )
+    //				.hasArg()
+    //				.withArgName("CONFIG_FILE")
+    //				.create('c') );
+
+    CommandLine cmdLine = null;
+
+    ///// parse command line options
+    try {
+      // parse the command line arguments
+      CommandLineParser parser = new PosixParser();
+
+      cmdLine = parser.parse(cmdLnOptions, args);
+
+      // validate that block-size has been set
+      if (cmdLine.hasOption("block-size")) {
+        // print the value of block-size
+        System.out.println(cmdLine.getOptionValue("block-size"));
+      }
+
+    } catch (org.apache.commons.cli.ParseException e) {
+      System.err.println(e.getMessage());
+      new HelpFormatter().printHelp("java -jar ditaa.jar  [OUTFILE]", cmdLnOptions, true);
+      System.exit(2);
+    }
+
+    if (cmdLine.hasOption("help") || args.length == 0) {
+      new HelpFormatter().printHelp("java -jar ditaa.jar  [OUTFILE]", cmdLnOptions, true);
+      System.exit(0);
+    }
+
+    ConversionOptions options = null;
+    try {
+      options = new ConversionOptions(cmdLine);
+    } catch (UnsupportedEncodingException e2) {
+      System.err.println("Error: " + e2.getMessage());
+      System.exit(2);
+    } catch (IllegalArgumentException e2) {
+      System.err.println("Error: " + e2.getMessage());
+      new HelpFormatter().printHelp("java -jar ditaa.jar  [OUTFILE]", cmdLnOptions, true);
+      System.exit(2);
+    }
+
+    args = cmdLine.getArgs();
+
+    if (args.length == 0) {
+      System.err.println("Error: Please provide the input file filename");
+      new HelpFormatter().printHelp("java -jar ditaa.jar  [outfile]", cmdLnOptions, true);
+      System.exit(2);
+    }
+
+    if (cmdLine.hasOption("html")) {
+      /////// print options before running
+      printRunInfo(cmdLine);
+      String filename = args[0];
+
+      boolean overwrite = false;
+      if (options.processingOptions.overwriteFiles())
+        overwrite = true;
+
+      String toFilename;
+      if (args.length == 1) {
+        toFilename = FileUtils.makeTargetPathname(filename, "html", "_processed", true);
+      } else {
+        toFilename = args[1];
+      }
+      File target = new File(toFilename);
+      if (!overwrite && target.exists()) {
+        System.out.println("Error: File " + toFilename + " exists. If you would like to overwrite it, please use the --overwrite option.");
+        System.exit(0);
+      }
+
+      new HTMLConverter().convertHTMLFile(filename, toFilename, "ditaa_diagram", "images", options);
+      System.exit(0);
+
+    } else { //simple mode
+
+      TextGrid grid = new TextGrid();
+      if (options.processingOptions.getCustomShapes() != null) {
+        grid.addToMarkupTags(options.processingOptions.getCustomShapes().keySet());
+      }
+
+      // "-" means stdin / stdout
+      String fromFilename = args[0];
+      boolean stdIn = "-".equals(fromFilename);
+
+      String toFilename;
+      boolean stdOut;
+
+      boolean overwrite = false;
+      if (options.processingOptions.overwriteFiles())
+        overwrite = true;
+
+      if (args.length == 1) {
+        if (stdIn) { // if using stdin and no output specified, use stdout
+          stdOut = true;
+          toFilename = "-";
+        } else {
+          String ext = cmdLine.hasOption("svg") ? "svg" : "png";
+          toFilename = FileUtils.makeTargetPathname(fromFilename, ext, overwrite);
+          stdOut = false;
+        }
+      } else {
+        toFilename = args[1];
+        stdOut = "-".equals(toFilename);
+      }
+
+      if (!stdOut) {
+        /////// print options before running
+        printRunInfo(cmdLine);
+        System.out.println("Reading " + (stdIn ? "standard input" : "file: " + fromFilename));
+      }
+
+      try {
+        if (!grid.loadFrom(fromFilename, options.processingOptions)) {
+          System.err.println("Cannot open file " + fromFilename + " for reading");
+        }
+      } catch (UnsupportedEncodingException e1) {
+        System.err.println("Error: " + e1.getMessage());
+        System.exit(1);
+      } catch (FileNotFoundException e1) {
+        System.err.println("Error: File " + fromFilename + " does not exist");
+        System.exit(1);
+      } catch (IOException e1) {
+        System.err.println("Error: Cannot open file " + fromFilename + " for reading");
+        System.exit(1);
+      }
+
+      if (options.processingOptions.printDebugOutput()) {
+        if (!stdOut)
+          System.out.println("Using grid:");
+        grid.printDebug();
+      }
+
+      Diagram diagram = new Diagram(grid, options);
+      if (!stdOut)
+        System.out.println("Rendering to file: " + toFilename);
+
+      try {
+
+        if (cmdLine.hasOption("svg")) {
+          String content = new SVGRenderer().renderToImage(diagram, options.renderingOptions);
+
+          PrintStream stream = stdOut ? System.out : new PrintStream(new FileOutputStream(toFilename));
+          stream.print(content);
+        } else {
+          RenderedImage image = new BitmapRenderer().renderToImage(diagram, options.renderingOptions);
+
+          OutputStream os = stdOut ? System.out : new FileOutputStream(toFilename);
+          ImageIO.write(image, "png", os);
+        }
+
+      } catch (IOException e) {
+        //e.printStackTrace();
+        System.err.println("Error: Cannot write to file " + toFilename);
+        System.exit(1);
+      }
+
+      //BitmapRenderer.renderToPNG(diagram, toFilename, options.renderingOptions);
+
+      long endTime = System.currentTimeMillis();
+      long totalTime = (endTime - startTime) / 1000;
+      if (!stdOut)
+        System.out.println("Done in " + totalTime + "sec");
+
+      //			try {
+      //			Thread.sleep(Long.MAX_VALUE);
+      //			} catch (InterruptedException e) {
+      //			e.printStackTrace();
+      //			}
+
+    }
+  }
+
+  private static void printRunInfo(CommandLine cmdLine) {
+    System.out.println("\n" + notice + "\n");
+
+    System.out.println("Running with options:");
+    Option[] opts = cmdLine.getOptions();
+    for (Option option : opts) {
+      if (option.hasArgs()) {
+        for (String value : option.getValues()) {
+          System.out.println(option.getLongOpt() + " = " + value);
+        }
+      } else if (option.hasArg()) {
+        System.out.println(option.getLongOpt() + " = " + option.getValue());
+      } else {
+        System.out.println(option.getLongOpt());
+      }
+    }
+  }
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/ConfigurationParser.java b/src/java/org/stathissideris/ascii2image/core/ConfigurationParser.java
index 96800b6..6dd4db2 100755
--- a/src/java/org/stathissideris/ascii2image/core/ConfigurationParser.java
+++ b/src/java/org/stathissideris/ascii2image/core/ConfigurationParser.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,190 +15,188 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.HashMap;
-
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
 import org.stathissideris.ascii2image.graphics.CustomShapeDefinition;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+
 public class ConfigurationParser {
 
-    private static final boolean DEBUG = false;
+  private static final boolean DEBUG = false;
 
-    private static final String INLCUDE_TAG_NAME = "include";
-    private static final String SHAPE_TAG_NAME = "shape";
-    private static final String SHAPE_GROUP_TAG_NAME = "shapes";
+  private static final String INLCUDE_TAG_NAME     = "include";
+  private static final String SHAPE_TAG_NAME       = "shape";
+  private static final String SHAPE_GROUP_TAG_NAME = "shapes";
 
-    private String currentDir = "";
-    private File configFile;
+  private String currentDir = "";
+  private File   configFile;
 
-    private HashMap shapeDefinitions = new HashMap();
+  private HashMap shapeDefinitions = new HashMap();
 
-    public Collection getShapeDefinitions() {
-        return shapeDefinitions.values();
-    }
+  public Collection getShapeDefinitions() {
+    return shapeDefinitions.values();
+  }
 
-    public HashMap getShapeDefinitionsHash() {
-        return shapeDefinitions;
-    }
+  public HashMap getShapeDefinitionsHash() {
+    return shapeDefinitions;
+  }
 
-    public void parseFile(File file) throws ParserConfigurationException,
-            SAXException, IOException {
-        configFile = file;
+  public void parseFile(File file) throws ParserConfigurationException,
+      SAXException, IOException {
+    configFile = file;
 
-        DefaultHandler handler = new XMLHandler();
+    DefaultHandler handler = new XMLHandler();
 
-        // Use the default (non-validating) parser
-        SAXParserFactory factory = SAXParserFactory.newInstance();
+    // Use the default (non-validating) parser
+    SAXParserFactory factory = SAXParserFactory.newInstance();
 
-        SAXParser saxParser = factory.newSAXParser();
-        saxParser.parse(file, handler);
-    }
+    SAXParser saxParser = factory.newSAXParser();
+    saxParser.parse(file, handler);
+  }
 
-    private class XMLHandler extends DefaultHandler {
-        public void startElement(String uri, String localName, String qName,
-                Attributes attributes) throws SAXException {
-            if (qName.equals(SHAPE_GROUP_TAG_NAME)) {
-                if (attributes.getLength() == 1) {
-                    currentDir = attributes.getValue(0).trim();
-                    if (currentDir.equals(""))
-                        currentDir = configFile.getParentFile()
-                                .getAbsolutePath();
-                } else {
-                    // the dir that contains the config file:
-                    currentDir = configFile.getParentFile().getAbsolutePath();
-                }
+  private class XMLHandler extends DefaultHandler {
+    public void startElement(String uri, String localName, String qName,
+        Attributes attributes) throws SAXException {
+      if (qName.equals(SHAPE_GROUP_TAG_NAME)) {
+        if (attributes.getLength() == 1) {
+          currentDir = attributes.getValue(0).trim();
+          if (currentDir.equals(""))
+            currentDir = configFile.getParentFile()
+                .getAbsolutePath();
+        } else {
+          // the dir that contains the config file:
+          currentDir = configFile.getParentFile().getAbsolutePath();
+        }
+      }
+      if (qName.equals(SHAPE_TAG_NAME)) {
+        CustomShapeDefinition definition = new CustomShapeDefinition();
+
+        int len = attributes.getLength();
+        for (int i = 0; i < len; i++) {
+          String name = attributes.getQName(i);
+          String value = attributes.getValue(i);
+
+          if (name.equals("tag")) {
+            definition.setTag(value);
+          } else if (name.equals("stretch")) {
+            definition
+                .setStretches(getBooleanFromAttributeValue(value));
+          } else if (name.equals("border")) {
+            definition
+                .setHasBorder(getBooleanFromAttributeValue(value));
+          } else if (name.equals("shadow")) {
+            definition
+                .setDropsShadow(getBooleanFromAttributeValue(value));
+          } else if (name.equals("comment")) {
+            definition.setComment(value);
+          } else if (name.equals("filename")) {
+            File file = new File(value);
+            if (file.isAbsolute()) {
+              definition.setFilename(value);
+            } else { // relative to the location of the config file
+              // or to the group's base dir
+              definition.setFilename(createFilename(currentDir,
+                  value));
             }
-            if (qName.equals(SHAPE_TAG_NAME)) {
-                CustomShapeDefinition definition = new CustomShapeDefinition();
-
-                int len = attributes.getLength();
-                for (int i = 0; i < len; i++) {
-                    String name = attributes.getQName(i);
-                    String value = attributes.getValue(i);
-
-                    if (name.equals("tag")) {
-                        definition.setTag(value);
-                    } else if (name.equals("stretch")) {
-                        definition
-                                .setStretches(getBooleanFromAttributeValue(value));
-                    } else if (name.equals("border")) {
-                        definition
-                                .setHasBorder(getBooleanFromAttributeValue(value));
-                    } else if (name.equals("shadow")) {
-                        definition
-                                .setDropsShadow(getBooleanFromAttributeValue(value));
-                    } else if (name.equals("comment")) {
-                        definition.setComment(value);
-                    } else if (name.equals("filename")) {
-                        File file = new File(value);
-                        if (file.isAbsolute()) {
-                            definition.setFilename(value);
-                        } else { // relative to the location of the config file
-                            // or to the group's base dir
-                            definition.setFilename(createFilename(currentDir,
-                                    value));
-                        }
-                    }
-                }
-
-                if (shapeDefinitions.containsKey(definition.getTag())) {
-                    CustomShapeDefinition oldDef = shapeDefinitions
-                            .get(definition.getTag());
-                    System.err.println("*** Warning: shape \""
-                            + oldDef.getTag() + "\" (file: "
-                            + oldDef.getFilename()
-                            + ") has been redefined as file: "
-                            + definition.getFilename());
-                }
-
-                File file = new File(definition.getFilename());
-                if (file.exists()) {
-                    shapeDefinitions.put(definition.getTag(), definition);
-                    if (DEBUG)
-                        System.out.println(definition);
-                } else {
-                    System.err.println("File " + file
-                            + " does not exist, skipping tag "
-                            + definition.getTag());
-                }
+          }
+        }
 
-            }
-            if (qName.equals(INLCUDE_TAG_NAME)) {
-                if (attributes.getLength() == 1) {
-                    File includedFile = new File(attributes.getValue(0).trim());
-
-                    if (!includedFile.isAbsolute()) {
-                        includedFile = new File(createFilename(configFile
-                                .getParentFile().getAbsolutePath(),
-                                includedFile.getPath()));
-                    }
-
-                    if (!includedFile.exists()) {
-                        System.err.println("Included file " + includedFile
-                                + " does not exist, skipping");
-                        return;
-                    }
-
-                    ConfigurationParser configParser = new ConfigurationParser();
-                    try {
-                        configParser.parseFile(includedFile);
-                    } catch (ParserConfigurationException e) {
-                        e.printStackTrace();
-                    } catch (SAXException e) {
-                        e.printStackTrace();
-                    } catch (IOException e) {
-                        e.printStackTrace();
-                    }
-                    HashMap shapes = configParser
-                            .getShapeDefinitionsHash();
-                    shapeDefinitions.putAll(shapes);
-                }
-            }
+        if (shapeDefinitions.containsKey(definition.getTag())) {
+          CustomShapeDefinition oldDef = shapeDefinitions
+              .get(definition.getTag());
+          System.err.println("*** Warning: shape \""
+              + oldDef.getTag() + "\" (file: "
+              + oldDef.getFilename()
+              + ") has been redefined as file: "
+              + definition.getFilename());
         }
-    }
 
-    private String createFilename(String baseDir, String filename) {
-        if (baseDir == null || baseDir.trim().equals("")) {
-            return filename;
+        File file = new File(definition.getFilename());
+        if (file.exists()) {
+          shapeDefinitions.put(definition.getTag(), definition);
+          if (DEBUG)
+            System.out.println(definition);
+        } else {
+          System.err.println("File " + file
+              + " does not exist, skipping tag "
+              + definition.getTag());
         }
-        if (baseDir.endsWith(File.separator)) {
-            return baseDir + filename;
+
+      }
+      if (qName.equals(INLCUDE_TAG_NAME)) {
+        if (attributes.getLength() == 1) {
+          File includedFile = new File(attributes.getValue(0).trim());
+
+          if (!includedFile.isAbsolute()) {
+            includedFile = new File(createFilename(configFile
+                    .getParentFile().getAbsolutePath(),
+                includedFile.getPath()));
+          }
+
+          if (!includedFile.exists()) {
+            System.err.println("Included file " + includedFile
+                + " does not exist, skipping");
+            return;
+          }
+
+          ConfigurationParser configParser = new ConfigurationParser();
+          try {
+            configParser.parseFile(includedFile);
+          } catch (ParserConfigurationException e) {
+            e.printStackTrace();
+          } catch (SAXException e) {
+            e.printStackTrace();
+          } catch (IOException e) {
+            e.printStackTrace();
+          }
+          HashMap shapes = configParser
+              .getShapeDefinitionsHash();
+          shapeDefinitions.putAll(shapes);
         }
-        return baseDir + File.separator + filename;
+      }
     }
+  }
 
-    private boolean getBooleanFromAttributeValue(String value) {
-        value = value.toLowerCase();
-        if ("no".equals(value))
-            return false;
-        if ("false".equals(value))
-            return false;
-        if ("yes".equals(value))
-            return true;
-        if ("true".equals(value))
-            return true;
-        throw new IllegalArgumentException("value " + value
-                + " cannot be interpreted as a boolean");
+  private String createFilename(String baseDir, String filename) {
+    if (baseDir == null || baseDir.trim().equals("")) {
+      return filename;
     }
-
-    public static void main(String argv[]) throws ParserConfigurationException,
-            SAXException, IOException {
-        ConfigurationParser parser = new ConfigurationParser();
-        parser.parseFile(new File("config.xml"));
-        parser.getShapeDefinitions();
+    if (baseDir.endsWith(File.separator)) {
+      return baseDir + filename;
     }
+    return baseDir + File.separator + filename;
+  }
+
+  private boolean getBooleanFromAttributeValue(String value) {
+    value = value.toLowerCase();
+    if ("no".equals(value))
+      return false;
+    if ("false".equals(value))
+      return false;
+    if ("yes".equals(value))
+      return true;
+    if ("true".equals(value))
+      return true;
+    throw new IllegalArgumentException("value " + value
+        + " cannot be interpreted as a boolean");
+  }
+
+  public static void main(String argv[]) throws ParserConfigurationException,
+      SAXException, IOException {
+    ConfigurationParser parser = new ConfigurationParser();
+    parser.parseFile(new File("config.xml"));
+    parser.getShapeDefinitions();
+  }
 
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java b/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java
index 5599f65..52a7eb1 100644
--- a/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java
+++ b/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,7 +15,6 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
@@ -25,125 +24,126 @@
 import org.xml.sax.SAXException;
 
 import javax.xml.parsers.ParserConfigurationException;
-import java.awt.*;
+import java.awt.Color;
 import java.io.File;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.util.HashMap;
 
 /**
- * 
+ *
  * @author Efstathios Sideris
  */
 public class ConversionOptions {
-	
-	public ProcessingOptions processingOptions =
-		new ProcessingOptions();
-	public RenderingOptions renderingOptions =
-		new RenderingOptions();
-		
-	public void setDebug(boolean value){
-		processingOptions.setPrintDebugOutput(value);
-		renderingOptions.setRenderDebugLines(value);
-	}
-	
-	public ConversionOptions(){}
-
-    /** Parse a color from a 6- or 8-digit hex string.  For example, FF0000 is red.
-     *  If eight digits, last two digits are alpha. */
-    public static Color parseColor(String hexString) {
-        if(hexString.length() == 6) {
-            return new Color(Integer.parseInt(hexString, 16));
-        } else if(hexString.length() == 8) {
-            return new Color(
-                Integer.parseInt(hexString.substring(0,2), 16),
-                Integer.parseInt(hexString.substring(2,4), 16),
-                Integer.parseInt(hexString.substring(4,6), 16),
-                Integer.parseInt(hexString.substring(6,8), 16)
-            );
-        } else {
-            throw new IllegalArgumentException("Cannot interpret \""+hexString+"\" as background colour. It needs to be a 6- or 8-digit hex number, depending on whether you have transparency or not (same as HTML).");
+
+  public ProcessingOptions processingOptions =
+      new ProcessingOptions();
+  public RenderingOptions  renderingOptions  =
+      new RenderingOptions();
+
+  public void setDebug(boolean value) {
+    processingOptions.setPrintDebugOutput(value);
+    renderingOptions.setRenderDebugLines(value);
+  }
+
+  public ConversionOptions() {
+  }
+
+  /** Parse a color from a 6- or 8-digit hex string.  For example, FF0000 is red.
+   *  If eight digits, last two digits are alpha. */
+  public static Color parseColor(String hexString) {
+    if (hexString.length() == 6) {
+      return new Color(Integer.parseInt(hexString, 16));
+    } else if (hexString.length() == 8) {
+      return new Color(
+          Integer.parseInt(hexString.substring(0, 2), 16),
+          Integer.parseInt(hexString.substring(2, 4), 16),
+          Integer.parseInt(hexString.substring(4, 6), 16),
+          Integer.parseInt(hexString.substring(6, 8), 16)
+      );
+    } else {
+      throw new IllegalArgumentException("Cannot interpret \"" + hexString + "\" as background colour. It needs to be a 6- or 8-digit hex number, depending on whether you have transparency or not (same as HTML).");
+    }
+  }
+
+  public ConversionOptions(CommandLine cmdLine) throws UnsupportedEncodingException {
+
+    processingOptions.setVerbose(cmdLine.hasOption("verbose"));
+    renderingOptions.setDropShadows(!cmdLine.hasOption("no-shadows"));
+    this.setDebug(cmdLine.hasOption("debug"));
+    processingOptions.setOverwriteFiles(cmdLine.hasOption("overwrite"));
+
+    if (cmdLine.hasOption("scale")) {
+      Float scale = Float.parseFloat(cmdLine.getOptionValue("scale"));
+      renderingOptions.setScale(scale.floatValue());
+    }
+
+    processingOptions.setAllCornersAreRound(cmdLine.hasOption("round-corners"));
+    processingOptions.setPerformSeparationOfCommonEdges(!cmdLine.hasOption("no-separation"));
+    renderingOptions.setAntialias(!cmdLine.hasOption("no-antialias"));
+    renderingOptions.setFixedSlope(cmdLine.hasOption("fixed-slope"));
+
+    if (cmdLine.hasOption("background")) {
+      String b = cmdLine.getOptionValue("background");
+      Color background = parseColor(b);
+      renderingOptions.setBackgroundColor(background);
+    }
+
+    if (cmdLine.hasOption("transparent")) {
+      renderingOptions.setBackgroundColor(new Color(0, 0, 0, 0));
+    }
+
+    if (cmdLine.hasOption("tabs")) {
+      Integer tabSize = Integer.parseInt(cmdLine.getOptionValue("tabs"));
+      int tabSizeValue = tabSize.intValue();
+      if (tabSizeValue < 0)
+        tabSizeValue = 0;
+      processingOptions.setTabSize(tabSizeValue);
+    }
+
+    String encoding = (String) cmdLine.getOptionValue("encoding");
+    if (encoding != null) {
+      new String(new byte[2], encoding);
+      processingOptions.setCharacterEncoding(encoding);
+    }
+
+    if (cmdLine.hasOption("svg")) {
+      renderingOptions.setImageType(RenderingOptions.ImageType.SVG);
+    }
+
+    if (cmdLine.hasOption("svg-font-url")) {
+      renderingOptions.setFontURL(cmdLine.getOptionValue("svg-font-url"));
+    }
+
+    ConfigurationParser configParser = new ConfigurationParser();
+    try {
+      for (Option curOption : cmdLine.getOptions()) {
+        if (curOption.getLongOpt().equals("config")) {
+          String configFilename = curOption.getValue();
+          System.out.println("Parsing configuration file " + configFilename);
+          File file = new File(configFilename);
+          if (file.exists()) {
+            configParser.parseFile(file);
+            HashMap shapes = configParser.getShapeDefinitionsHash();
+            processingOptions.putAllInCustomShapes(shapes);
+          } else {
+            System.err.println("File " + file + " does not exist, skipping");
+          }
         }
+      }
+    } catch (ParserConfigurationException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    } catch (SAXException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
+    } catch (IOException e) {
+      // TODO Auto-generated catch block
+      e.printStackTrace();
     }
-	
-	public ConversionOptions(CommandLine cmdLine) throws UnsupportedEncodingException{
-		
-		processingOptions.setVerbose(cmdLine.hasOption("verbose"));
-		renderingOptions.setDropShadows(!cmdLine.hasOption("no-shadows"));
-		this.setDebug(cmdLine.hasOption("debug"));
-		processingOptions.setOverwriteFiles(cmdLine.hasOption("overwrite"));
-		
-		if(cmdLine.hasOption("scale")){
-			Float scale = Float.parseFloat(cmdLine.getOptionValue("scale"));
-			renderingOptions.setScale(scale.floatValue());
-		}
-		
-		processingOptions.setAllCornersAreRound(cmdLine.hasOption("round-corners"));
-		processingOptions.setPerformSeparationOfCommonEdges(!cmdLine.hasOption("no-separation"));
-		renderingOptions.setAntialias(!cmdLine.hasOption("no-antialias"));
-		renderingOptions.setFixedSlope(cmdLine.hasOption("fixed-slope"));
-
-		if(cmdLine.hasOption("background")) {
-			String b = cmdLine.getOptionValue("background");
-            Color background = parseColor(b);
-			renderingOptions.setBackgroundColor(background);
-		}
-		
-		if(cmdLine.hasOption("transparent")) {
-			renderingOptions.setBackgroundColor(new Color(0,0,0,0));
-		}
-
-		if(cmdLine.hasOption("tabs")){
-			Integer tabSize = Integer.parseInt(cmdLine.getOptionValue("tabs"));
-			int tabSizeValue = tabSize.intValue();
-			if(tabSizeValue < 0) tabSizeValue = 0;
-			processingOptions.setTabSize(tabSizeValue);
-		}
-
-		String encoding = (String) cmdLine.getOptionValue("encoding");
-		if(encoding != null){
-			new String(new byte[2], encoding);
-			processingOptions.setCharacterEncoding(encoding);
-		}
-		
-		if (cmdLine.hasOption("svg")){
-			renderingOptions.setImageType(RenderingOptions.ImageType.SVG);
-		}
-
-		if (cmdLine.hasOption("svg-font-url")){
-			renderingOptions.setFontURL(cmdLine.getOptionValue("svg-font-url"));
-		}
-
-		ConfigurationParser configParser = new ConfigurationParser();
-		try {
-			for (Option curOption : cmdLine.getOptions()) {
-				if(curOption.getLongOpt().equals("config")) {
-					String configFilename = curOption.getValue();
-					System.out.println("Parsing configuration file "+configFilename);
-					File file = new File(configFilename);
-					if(file.exists()){
-						configParser.parseFile(file);
-						HashMap shapes = configParser.getShapeDefinitionsHash();
-						processingOptions.putAllInCustomShapes(shapes);
-					} else {
-						System.err.println("File "+file+" does not exist, skipping");
-					}
-				}
-			}
-		} catch (ParserConfigurationException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		} catch (SAXException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		} catch (IOException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
-	}
+  }
 }
 
-
 // may be supported at a later date:
 //String exportFormat = (String) cmdLine.getOptionValue("format");
 //if(exportFormat != null){
diff --git a/src/java/org/stathissideris/ascii2image/core/DebugUtils.java b/src/java/org/stathissideris/ascii2image/core/DebugUtils.java
index 62c9276..3ef4715 100644
--- a/src/java/org/stathissideris/ascii2image/core/DebugUtils.java
+++ b/src/java/org/stathissideris/ascii2image/core/DebugUtils.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,12 +15,11 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
 public class DebugUtils {
-	public static int getLineNumber() {
-		return Thread.currentThread().getStackTrace()[2].getLineNumber();
-	}
+  public static int getLineNumber() {
+    return Thread.currentThread().getStackTrace()[2].getLineNumber();
+  }
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/DocBookConverter.java b/src/java/org/stathissideris/ascii2image/core/DocBookConverter.java
index 9df1d41..6a135bd 100644
--- a/src/java/org/stathissideris/ascii2image/core/DocBookConverter.java
+++ b/src/java/org/stathissideris/ascii2image/core/DocBookConverter.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,53 +15,54 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
-import java.io.*;
-import org.xml.sax.*;
-import org.xml.sax.helpers.*;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.helpers.XMLReaderFactory;
 
 // using SAX
 public class DocBookConverter {
 
-	class HowToHandler extends DefaultHandler {
-    	boolean title = false;
-    	boolean url   = false;
+  class HowToHandler extends DefaultHandler {
+    boolean title = false;
+    boolean url   = false;
 
-    	public void startElement(
-    		String nsURI,
-    		String strippedName,
-			String tagName,
-			Attributes attributes)
-       			throws SAXException {
-     		if (tagName.equalsIgnoreCase("title"))
-        	title = true;
-     		if (tagName.equalsIgnoreCase("url"))
-        		url = true;
-    		}
+    public void startElement(
+        String nsURI,
+        String strippedName,
+        String tagName,
+        Attributes attributes)
+        throws SAXException {
+      if (tagName.equalsIgnoreCase("title"))
+        title = true;
+      if (tagName.equalsIgnoreCase("url"))
+        url = true;
+    }
 
-    	public void characters(char[] ch, int start, int length) {
-     		if (title) {
-       			System.out.println("Title: " + new String(ch, start, length));
-       			title = false;
-       		} else if (url) {
-       			System.out.println("Url: " + new String(ch, start,length));
-       			url = false;
-			}
-		}
+    public void characters(char[] ch, int start, int length) {
+      if (title) {
+        System.out.println("Title: " + new String(ch, start, length));
+        title = false;
+      } else if (url) {
+        System.out.println("Url: " + new String(ch, start, length));
+        url = false;
+      }
     }
+  }
 
-    public void list( ) throws Exception {
-		XMLReader parser =
-			XMLReaderFactory.createXMLReader
-            	("org.apache.crimson.parser.XMLReaderImpl");
-		parser.setContentHandler(new HowToHandler( ));
-		parser.parse("howto.xml");
-	}
+  public void list() throws Exception {
+    XMLReader parser =
+        XMLReaderFactory.createXMLReader
+            ("org.apache.crimson.parser.XMLReaderImpl");
+    parser.setContentHandler(new HowToHandler());
+    parser.parse("howto.xml");
+  }
 
-	public static void main(String[] args) throws Exception {
-		new DocBookConverter().list( );
-	}
+  public static void main(String[] args) throws Exception {
+    new DocBookConverter().list();
+  }
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/FileUtils.java b/src/java/org/stathissideris/ascii2image/core/FileUtils.java
index 4570a53..ed36bc5 100644
--- a/src/java/org/stathissideris/ascii2image/core/FileUtils.java
+++ b/src/java/org/stathissideris/ascii2image/core/FileUtils.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,137 +15,140 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.LineNumberReader;
 
 /**
- * 
+ *
  * @author Efstathios Sideris
  */
 public class FileUtils {
-	
-	//private static final 
-	
-	public static String makeTargetPathname(String sourcePathname, String extension, boolean overwrite){
-		return makeTargetPathname(sourcePathname, extension, "", overwrite);
-	}
-	
-	public static String makeTargetPathname(String sourcePathname, String extension, String postfix, boolean overwrite){
-		File sourceFile =
-			new File(sourcePathname);
-		
-		String path = "";
-		if(sourceFile.getParentFile() != null){
-			path = sourceFile.getParentFile().getAbsolutePath();
-			if(!path.endsWith(File.separator)) path += File.separator;
-		}
-		String baseName = getBaseName(sourceFile.getName());
-		
-		String targetName = path + baseName + postfix + "." + extension;
-		if(new File(targetName).exists() && !overwrite)
-			targetName = makeAlternativePathname(targetName);
-		return targetName;
-	}
-	
-	public static String makeAlternativePathname(String pathName){
-		int limit = 100;
-		
-		for(int i = 2; i <= limit; i++){
-			String alternative = getBaseName(pathName)+"_"+i;
-			String extension = getExtension(pathName);
-			if(extension != null) alternative += "."+extension;
-			if(!(new File(alternative).exists())) return alternative; 
-		}
-		return null;
-	}
-
-	public static String getExtension(String pathName){
-		if(pathName.lastIndexOf('.') == -1) return null;
-		return pathName.substring(pathName.lastIndexOf('.') + 1);
-	}
-	
-	public static String getBaseName(String pathName){
-		if(pathName.lastIndexOf('.') == -1) return pathName;
-		return pathName.substring(0, pathName.lastIndexOf('.'));
-	}
-	
-	public static String readFile(File file) throws IOException {
-		return readFile(file, null);
-	}
-	
-	public static String readFile(File file, String encoding) throws IOException {
-        long length = file.length();
-        
-        if (length > Integer.MAX_VALUE) {
-            // File is too large
-        	// TODO: we need some feedback for the case of the file being too large
-        }
-
-		return readFile(new FileInputStream(file), file.getName(), encoding, length);
-	}
-
-	public static String readFile(InputStream is, String name, String encoding) throws IOException {
-		return readFile(is, name, encoding, -1);
-	}
-
-	public static String readFile(InputStream is, String name, String encoding, long length) throws IOException {
-
-		if (length < 0) {
-			LineNumberReader reader = new LineNumberReader(new InputStreamReader(is));
-			StringBuilder builder = new StringBuilder();
-			while (true) {
-				String line = reader.readLine();
-				if (line == null) break;
-				else builder.append(line).append("\n");
-			}
-			return builder.toString();
-		}
-
-		else {
-			// Create the byte array to hold the data
-			byte[] bytes = new byte[(int)length];
-    
-			// Read in the bytes
-			int offset = 0;
-			int numRead = 0;
-			while (offset < bytes.length
-				   && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
-				offset += numRead;
-			}
-    
-			// Ensure all the bytes have been read in
-			if (offset < bytes.length) {
-				throw new IOException("Could not completely read file "+name);
-			}
-    
-			// Close the input stream and return bytes
-			is.close();
-			if(encoding == null){
-        		return new String(bytes);
-			} else {
-        		return new String(bytes, encoding);
-			}
-		}
-	}
-		
-	public static void main(String[] args){
-		System.out.println(makeTargetPathname("C:\\Files\\papar.txt", "jpg", false));
-		System.out.println(makeTargetPathname("C:\\Files\\papar", "jpg", false));
-		System.out.println(makeTargetPathname("papar.txt", "jpg", false));
-		System.out.println(makeTargetPathname("/home/sideris/tsourekia/papar.txt", "jpg", false));
-		System.out.println(makeTargetPathname("D:\\diagram.max", "jpg", false));
-		System.out.println(makeAlternativePathname("C:\\Files\\papar.txt"));
-		System.out.println(makeAlternativePathname("C:\\Files\\papar"));
-		System.out.println(getExtension("pipi.jpeg"));
-		System.out.println(getExtension("pipi"));
-	}
+
+  //private static final
+
+  public static String makeTargetPathname(String sourcePathname, String extension, boolean overwrite) {
+    return makeTargetPathname(sourcePathname, extension, "", overwrite);
+  }
+
+  public static String makeTargetPathname(String sourcePathname, String extension, String postfix, boolean overwrite) {
+    File sourceFile =
+        new File(sourcePathname);
+
+    String path = "";
+    if (sourceFile.getParentFile() != null) {
+      path = sourceFile.getParentFile().getAbsolutePath();
+      if (!path.endsWith(File.separator))
+        path += File.separator;
+    }
+    String baseName = getBaseName(sourceFile.getName());
+
+    String targetName = path + baseName + postfix + "." + extension;
+    if (new File(targetName).exists() && !overwrite)
+      targetName = makeAlternativePathname(targetName);
+    return targetName;
+  }
+
+  public static String makeAlternativePathname(String pathName) {
+    int limit = 100;
+
+    for (int i = 2; i <= limit; i++) {
+      String alternative = getBaseName(pathName) + "_" + i;
+      String extension = getExtension(pathName);
+      if (extension != null)
+        alternative += "." + extension;
+      if (!(new File(alternative).exists()))
+        return alternative;
+    }
+    return null;
+  }
+
+  public static String getExtension(String pathName) {
+    if (pathName.lastIndexOf('.') == -1)
+      return null;
+    return pathName.substring(pathName.lastIndexOf('.') + 1);
+  }
+
+  public static String getBaseName(String pathName) {
+    if (pathName.lastIndexOf('.') == -1)
+      return pathName;
+    return pathName.substring(0, pathName.lastIndexOf('.'));
+  }
+
+  public static String readFile(File file) throws IOException {
+    return readFile(file, null);
+  }
+
+  public static String readFile(File file, String encoding) throws IOException {
+    long length = file.length();
+
+    if (length > Integer.MAX_VALUE) {
+      // File is too large
+      // TODO: we need some feedback for the case of the file being too large
+    }
+
+    return readFile(new FileInputStream(file), file.getName(), encoding, length);
+  }
+
+  public static String readFile(InputStream is, String name, String encoding) throws IOException {
+    return readFile(is, name, encoding, -1);
+  }
+
+  public static String readFile(InputStream is, String name, String encoding, long length) throws IOException {
+
+    if (length < 0) {
+      LineNumberReader reader = new LineNumberReader(new InputStreamReader(is));
+      StringBuilder builder = new StringBuilder();
+      while (true) {
+        String line = reader.readLine();
+        if (line == null)
+          break;
+        else
+          builder.append(line).append("\n");
+      }
+      return builder.toString();
+    } else {
+      // Create the byte array to hold the data
+      byte[] bytes = new byte[(int) length];
+
+      // Read in the bytes
+      int offset = 0;
+      int numRead = 0;
+      while (offset < bytes.length
+          && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
+        offset += numRead;
+      }
+
+      // Ensure all the bytes have been read in
+      if (offset < bytes.length) {
+        throw new IOException("Could not completely read file " + name);
+      }
+
+      // Close the input stream and return bytes
+      is.close();
+      if (encoding == null) {
+        return new String(bytes);
+      } else {
+        return new String(bytes, encoding);
+      }
+    }
+  }
+
+  public static void main(String[] args) {
+    System.out.println(makeTargetPathname("C:\\Files\\papar.txt", "jpg", false));
+    System.out.println(makeTargetPathname("C:\\Files\\papar", "jpg", false));
+    System.out.println(makeTargetPathname("papar.txt", "jpg", false));
+    System.out.println(makeTargetPathname("/home/sideris/tsourekia/papar.txt", "jpg", false));
+    System.out.println(makeTargetPathname("D:\\diagram.max", "jpg", false));
+    System.out.println(makeAlternativePathname("C:\\Files\\papar.txt"));
+    System.out.println(makeAlternativePathname("C:\\Files\\papar"));
+    System.out.println(getExtension("pipi.jpeg"));
+    System.out.println(getExtension("pipi"));
+  }
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/HTMLConverter.java b/src/java/org/stathissideris/ascii2image/core/HTMLConverter.java
index feb89e7..1b38e8f 100644
--- a/src/java/org/stathissideris/ascii2image/core/HTMLConverter.java
+++ b/src/java/org/stathissideris/ascii2image/core/HTMLConverter.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,224 +15,227 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
-import java.awt.image.RenderedImage;
-import java.io.*;
-import java.util.HashMap;
-
-import javax.imageio.ImageIO;
-import javax.swing.text.html.HTMLEditorKit;
-
 import net.htmlparser.jericho.Attribute;
 import net.htmlparser.jericho.Element;
 import net.htmlparser.jericho.OutputDocument;
 import net.htmlparser.jericho.Source;
 import net.htmlparser.jericho.StartTag;
-
 import org.stathissideris.ascii2image.graphics.BitmapRenderer;
 import org.stathissideris.ascii2image.graphics.Diagram;
 import org.stathissideris.ascii2image.graphics.SVGRenderer;
 import org.stathissideris.ascii2image.text.TextGrid;
 
+import javax.imageio.ImageIO;
+import javax.swing.text.html.HTMLEditorKit;
+import java.awt.image.RenderedImage;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+
 /**
- * 
+ *
  * TODO: incomplete class
- * 
+ *
  * @author Efstathios Sideris
  */
 public class HTMLConverter extends HTMLEditorKit {
 
-	private static final String TAG_CLASS = "textdiagram";
-	private static final String testDir = "tests/html-converter/";
-	
-	
-	public static void main(String[] args){		
-		new HTMLConverter().convertHTMLFile(
-			testDir + "index.html", 
-			testDir + "index2.html", 
-			"ditaa_diagram", 
-			"images", 
-			null);
-	}
-
-	/**
-	 * 
-	 * @param filename
-	 * @param targetFilename
-	 * @param imageBaseFilename
-	 * @param imageDirName relative to the location of the target HTML document
-	 * @param options
-	 * @return
-	 */
-	public boolean convertHTMLFile(
-			String filename,
-			String targetFilename,
-			String imageBaseFilename,
-			String imageDirName,
-			ConversionOptions options){
-		
-		if(options == null){
-			options = new ConversionOptions();
-		}
-				
-		BufferedReader in = null;
-		try {
-			in = new BufferedReader(new FileReader(filename));
-		} catch (FileNotFoundException e) {
-			//e.printStackTrace();
-			System.err.println("Error: cannot read file " + filename);
-			return false;
-		}
-		
-		String htmlText = "";
-		
-		try {
-			while(in.ready()){
-				htmlText += in.readLine()+"\n";
-			}
-			in.close();
-		} catch (IOException e1) {
-			//e1.printStackTrace();
-			System.err.println("Error while reading file " + filename);
-			return false;
-		}
-		
-		System.out.print("Converting HTML file ("+filename+" -> "+targetFilename+")... ");
-		
-		Source source = new Source(htmlText);
-		OutputDocument outputDocument = new OutputDocument(source);
-		
-		int index = 1;
-		HashMap diagramList = new HashMap();
-		for(Element element : source.getAllElements("pre")) {
-			StartTag tag = element.getStartTag();
-			Attribute classAttr = tag.getAttributes().get("class");
-			if(classAttr != null
-					&& classAttr.hasValue()
-					&& classAttr.getValue().equals(TAG_CLASS)) {
-				
-				String baseFilename = imageBaseFilename;
-				
-				String ext = options.renderingOptions.getImageType() == RenderingOptions.ImageType.SVG ? ".svg" : ".png";
-				
-				String URL;
-				Attribute nameAttr = tag.getAttributes().get("id");
-				if(nameAttr != null
-						&& nameAttr.hasValue()) {
-					baseFilename = makeFilenameFromTagName(nameAttr.getValue());
-					URL = imageDirName + "/" + baseFilename + ext;
-				} else {
-					URL = imageDirName + "/" + baseFilename + "_" + index + ext;
-					index++;
-				}
-
-				outputDocument.replace(element, "");
-				diagramList.put(URL, element.getContent().toString());
-			}
-		}
-		
-		if(diagramList.isEmpty()){
-			System.out.println("\nHTML document does not contain any " +
-				"
 tags with their class attribute set to \""+TAG_CLASS+"\". Nothing to do.");
-			
-			//TODO: should return the method with appropriate exit code instead
-			System.exit(0);
-		}
-		
-		FileWriter out;
-		try {
-			out = new FileWriter(targetFilename);
-			outputDocument.writeTo(out);
-			//out.flush();
-			//out.close();
-		} catch (IOException e2) {
-			System.err.println("Error while writing to file " + targetFilename);
-			return false;
-		} 
-
-		
-		System.out.println("done");
-		
-		
-		System.out.println("Generating diagrams... ");
-		
-		File imageDir = new File(new File(targetFilename).getParent() + File.separator + imageDirName);
-		if(!imageDir.exists()){
-			if(!imageDir.mkdir()){
-				System.err.println("Could not create directory " + imageDirName);
-				return false;
-			}
-		}
-		
-		for(String URL : diagramList.keySet()) {
-			String text = (String) diagramList.get(URL);
-			String imageFilename = new File(targetFilename).getParent() + File.separator + URL;
-			if(new File(imageFilename).exists() && !options.processingOptions.overwriteFiles()){
-				System.out.println("Error: Cannot overwrite file "+URL+", file already exists." +
-					" Use the --overwrite option if you would like to allow file overwrite.");
-				continue;
-			}
-	
-			TextGrid grid = new TextGrid();
-			grid.addToMarkupTags(options.processingOptions.getCustomShapes().keySet());
-
-			try {
-				grid.initialiseWithText(text, options.processingOptions);
-			} catch (UnsupportedEncodingException e1) {
-				System.err.println("Error: "+e1.getMessage());
-				System.exit(1);
-			}
-
-			Diagram diagram = new Diagram(grid, options);
-
-			if(options.renderingOptions.getImageType() == RenderingOptions.ImageType.SVG){
-
-				String content = new SVGRenderer().renderToImage(diagram, options.renderingOptions);
-
-				try {
-
-					PrintStream stream = new PrintStream(new FileOutputStream(imageFilename));
-
-					stream.print(content);
-
-				} catch (IOException e) {
-					System.err.println("Error: Cannot write to file "+filename+" -- skipping");
-					continue;
-				}
-
-			} else {
-				RenderedImage image = new BitmapRenderer().renderToImage(diagram, options.renderingOptions);
-
-				try {
-					File file = new File(imageFilename);
-					ImageIO.write(image, "png", file);
-				} catch (IOException e) {
-					//e.printStackTrace();
-					System.err.println("Error: Cannot write to file "+filename+" -- skipping");
-					continue;
-				}
-			}
-			
-			System.out.println("\t"+imageFilename);
-		}
-		
-		System.out.println("\n...done");
-		
-		return true;
-	}
+  private static final String TAG_CLASS = "textdiagram";
+  private static final String testDir   = "tests/html-converter/";
+
+
+  public static void main(String[] args) {
+    new HTMLConverter().convertHTMLFile(
+        testDir + "index.html",
+        testDir + "index2.html",
+        "ditaa_diagram",
+        "images",
+        null);
+  }
+
+  /**
+   *
+   * @param filename
+   * @param targetFilename
+   * @param imageBaseFilename
+   * @param imageDirName relative to the location of the target HTML document
+   * @param options
+   * @return
+   */
+  public boolean convertHTMLFile(
+      String filename,
+      String targetFilename,
+      String imageBaseFilename,
+      String imageDirName,
+      ConversionOptions options) {
+
+    if (options == null) {
+      options = new ConversionOptions();
+    }
+
+    BufferedReader in = null;
+    try {
+      in = new BufferedReader(new FileReader(filename));
+    } catch (FileNotFoundException e) {
+      //e.printStackTrace();
+      System.err.println("Error: cannot read file " + filename);
+      return false;
+    }
+
+    String htmlText = "";
+
+    try {
+      while (in.ready()) {
+        htmlText += in.readLine() + "\n";
+      }
+      in.close();
+    } catch (IOException e1) {
+      //e1.printStackTrace();
+      System.err.println("Error while reading file " + filename);
+      return false;
+    }
+
+    System.out.print("Converting HTML file (" + filename + " -> " + targetFilename + ")... ");
+
+    Source source = new Source(htmlText);
+    OutputDocument outputDocument = new OutputDocument(source);
+
+    int index = 1;
+    HashMap diagramList = new HashMap();
+    for (Element element : source.getAllElements("pre")) {
+      StartTag tag = element.getStartTag();
+      Attribute classAttr = tag.getAttributes().get("class");
+      if (classAttr != null
+          && classAttr.hasValue()
+          && classAttr.getValue().equals(TAG_CLASS)) {
+
+        String baseFilename = imageBaseFilename;
+
+        String ext = options.renderingOptions.getImageType() == RenderingOptions.ImageType.SVG ? ".svg" : ".png";
+
+        String URL;
+        Attribute nameAttr = tag.getAttributes().get("id");
+        if (nameAttr != null
+            && nameAttr.hasValue()) {
+          baseFilename = makeFilenameFromTagName(nameAttr.getValue());
+          URL = imageDirName + "/" + baseFilename + ext;
+        } else {
+          URL = imageDirName + "/" + baseFilename + "_" + index + ext;
+          index++;
+        }
+
+        outputDocument.replace(element, "");
+        diagramList.put(URL, element.getContent().toString());
+      }
+    }
+
+    if (diagramList.isEmpty()) {
+      System.out.println("\nHTML document does not contain any " +
+          "
 tags with their class attribute set to \"" + TAG_CLASS + "\". Nothing to do.");
+
+      //TODO: should return the method with appropriate exit code instead
+      System.exit(0);
+    }
+
+    FileWriter out;
+    try {
+      out = new FileWriter(targetFilename);
+      outputDocument.writeTo(out);
+      //out.flush();
+      //out.close();
+    } catch (IOException e2) {
+      System.err.println("Error while writing to file " + targetFilename);
+      return false;
+    }
+
+    System.out.println("done");
+
+    System.out.println("Generating diagrams... ");
+
+    File imageDir = new File(new File(targetFilename).getParent() + File.separator + imageDirName);
+    if (!imageDir.exists()) {
+      if (!imageDir.mkdir()) {
+        System.err.println("Could not create directory " + imageDirName);
+        return false;
+      }
+    }
+
+    for (String URL : diagramList.keySet()) {
+      String text = (String) diagramList.get(URL);
+      String imageFilename = new File(targetFilename).getParent() + File.separator + URL;
+      if (new File(imageFilename).exists() && !options.processingOptions.overwriteFiles()) {
+        System.out.println("Error: Cannot overwrite file " + URL + ", file already exists." +
+            " Use the --overwrite option if you would like to allow file overwrite.");
+        continue;
+      }
+
+      TextGrid grid = new TextGrid();
+      grid.addToMarkupTags(options.processingOptions.getCustomShapes().keySet());
+
+      try {
+        grid.initialiseWithText(text, options.processingOptions);
+      } catch (UnsupportedEncodingException e1) {
+        System.err.println("Error: " + e1.getMessage());
+        System.exit(1);
+      }
+
+      Diagram diagram = new Diagram(grid, options);
+
+      if (options.renderingOptions.getImageType() == RenderingOptions.ImageType.SVG) {
+
+        String content = new SVGRenderer().renderToImage(diagram, options.renderingOptions);
+
+        try {
+
+          PrintStream stream = new PrintStream(new FileOutputStream(imageFilename));
+
+          stream.print(content);
+
+        } catch (IOException e) {
+          System.err.println("Error: Cannot write to file " + filename + " -- skipping");
+          continue;
+        }
+
+      } else {
+        RenderedImage image = new BitmapRenderer().renderToImage(diagram, options.renderingOptions);
+
+        try {
+          File file = new File(imageFilename);
+          ImageIO.write(image, "png", file);
+        } catch (IOException e) {
+          //e.printStackTrace();
+          System.err.println("Error: Cannot write to file " + filename + " -- skipping");
+          continue;
+        }
+      }
+
+      System.out.println("\t" + imageFilename);
+    }
+
+    System.out.println("\n...done");
+
+    return true;
+  }
 	
 	/*
 	private static String relativizePath(String base, String path) {
 		return new File(base).toURI().relativize(new File(path).toURI()).getPath();
 	}
 	*/
-	
-	private String makeFilenameFromTagName(String tagName){
-		tagName = tagName.replace(' ', '_');
-		return tagName;
-	}
-	
+
+  private String makeFilenameFromTagName(String tagName) {
+    tagName = tagName.replace(' ', '_');
+    return tagName;
+  }
+
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/Pair.java b/src/java/org/stathissideris/ascii2image/core/Pair.java
index 0468633..eefc39f 100644
--- a/src/java/org/stathissideris/ascii2image/core/Pair.java
+++ b/src/java/org/stathissideris/ascii2image/core/Pair.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,16 +15,15 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
-public class Pair {
-	public T first;
-	public K second;
-	
-	public Pair(T first, K second) {
-		this.first = first;
-		this.second = second;
-	}
+public class Pair {
+  public T first;
+  public K second;
+
+  public Pair(T first, K second) {
+    this.first = first;
+    this.second = second;
+  }
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/PerformanceTester.java b/src/java/org/stathissideris/ascii2image/core/PerformanceTester.java
index fbeb8ad..a9a05a6 100644
--- a/src/java/org/stathissideris/ascii2image/core/PerformanceTester.java
+++ b/src/java/org/stathissideris/ascii2image/core/PerformanceTester.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,50 +15,49 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
 import org.stathissideris.ascii2image.graphics.Diagram;
 import org.stathissideris.ascii2image.text.TextGrid;
 
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
 /**
- * 
+ *
  * @author Efstathios Sideris
  */
 public class PerformanceTester {
 
-	public static void main(String[] args){
-		
-		String inputFilename = "tests/text/ditaa_bug.txt";
-		ConversionOptions options = new ConversionOptions();
+  public static void main(String[] args) {
+
+    String inputFilename = "tests/text/ditaa_bug.txt";
+    ConversionOptions options = new ConversionOptions();
+
+    int iterations = 30;
+
+    try {
+      long a = java.lang.System.currentTimeMillis();
+
+      for (int i = 0; i < iterations; i++) {
+        System.out.println("iteration " + i);
+
+        TextGrid grid = new TextGrid();
+        grid.loadFrom(inputFilename);
+        new Diagram(grid, options);
+      }
+
+      long b = java.lang.System.currentTimeMillis();
+
+      System.out.println((b - a) + "msec for " + iterations + " iterations on " + inputFilename);
+
+    } catch (FileNotFoundException e) {
+      e.printStackTrace();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
 
-		int iterations = 30;
-		
-		try {
-			long a = java.lang.System.currentTimeMillis();
-			
-			for(int i = 0; i < iterations; i++) {
-				System.out.println("iteration "+i);
-				
-				TextGrid grid = new TextGrid();
-				grid.loadFrom(inputFilename);
-				new Diagram(grid, options);
-			}
-			
-			long b = java.lang.System.currentTimeMillis();
-			
-			System.out.println((b-a) + "msec for " + iterations + " iterations on "+inputFilename);
-			
-		} catch (FileNotFoundException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
-		
-		System.out.println("Tests completed");
-	}
+    System.out.println("Tests completed");
+  }
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/ProcessingOptions.java b/src/java/org/stathissideris/ascii2image/core/ProcessingOptions.java
index 1833e9f..6d56f9f 100644
--- a/src/java/org/stathissideris/ascii2image/core/ProcessingOptions.java
+++ b/src/java/org/stathissideris/ascii2image/core/ProcessingOptions.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,229 +15,227 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
-import java.util.HashMap;
-
 import org.stathissideris.ascii2image.graphics.CustomShapeDefinition;
 
+import java.util.HashMap;
+
 /**
  * @author Efstathios Sideris
  *
  */
 public class ProcessingOptions {
 
-	private HashMap customShapes = new HashMap();
-	
-	private boolean beVerbose = false;
-	private boolean printDebugOutput = false;
-	private boolean overwriteFiles = false;
-	private boolean performSeparationOfCommonEdges = true;
-	private boolean allCornersAreRound = false;
-
-	public static final int USE_TAGS = 0;
-	public static final int RENDER_TAGS = 1;
-	public static final int IGNORE_TAGS = 2;
-	private int tagProcessingMode = USE_TAGS;
-
-	public static final int USE_COLOR_CODES = 0;
-	public static final int RENDER_COLOR_CODES = 1;
-	public static final int IGNORE_COLOR_CODES = 2;
-	private int colorCodesProcessingMode = USE_COLOR_CODES;
-
-	public static final int FORMAT_JPEG = 0;
-	public static final int FORMAT_PNG = 1;
-	public static final int FORMAT_GIF = 2;
-	private int exportFormat = FORMAT_PNG;
-
-	public static final int DEFAULT_TAB_SIZE = 8;
-	private int tabSize = DEFAULT_TAB_SIZE;
-
-	private String inputFilename;
-	private String outputFilename;
-	
-	private String characterEncoding = null;
-	
-	/**
-	 * @return
-	 */
-	public boolean areAllCornersRound() {
-		return allCornersAreRound;
-	}
-
-	/**
-	 * @return
-	 */
-	public int getColorCodesProcessingMode() {
-		return colorCodesProcessingMode;
-	}
-
-	/**
-	 * @return
-	 */
-	public int getExportFormat() {
-		return exportFormat;
-	}
-
-	/**
-	 * @return
-	 */
-	public boolean performSeparationOfCommonEdges() {
-		return performSeparationOfCommonEdges;
-	}
-
-    /**
-	 * @return
-	 */
-	public int getTagProcessingMode() {
-		return tagProcessingMode;
-	}
-
-	/**
-	 * @param b
-	 */
-	public void setAllCornersAreRound(boolean b) {
-		allCornersAreRound = b;
-	}
-
-	/**
-	 * @param i
-	 */
-	public void setColorCodesProcessingMode(int i) {
-		colorCodesProcessingMode = i;
-	}
-
-	/**
-	 * @param i
-	 */
-	public void setExportFormat(int i) {
-		exportFormat = i;
-	}
-
-	/**
-	 * @param b
-	 */
-	public void setPerformSeparationOfCommonEdges(boolean b) {
-		performSeparationOfCommonEdges = b;
-	}
-
-    /**
-	 * @param i
-	 */
-	public void setTagProcessingMode(int i) {
-		tagProcessingMode = i;
-	}
-
-	/**
-	 * @return
-	 */
-	public String getInputFilename() {
-		return inputFilename;
-	}
-
-	/**
-	 * @return
-	 */
-	public String getOutputFilename() {
-		return outputFilename;
-	}
-
-	/**
-	 * @param string
-	 */
-	public void setInputFilename(String string) {
-		inputFilename = string;
-	}
-
-	/**
-	 * @param string
-	 */
-	public void setOutputFilename(String string) {
-		outputFilename = string;
-	}
-
-	/**
-	 * @return
-	 */
-	public boolean verbose() {
-		return beVerbose;
-	}
-
-	/**
-	 * @return
-	 */
-	public boolean printDebugOutput() {
-		return printDebugOutput;
-	}
-
-	/**
-	 * @param b
-	 */
-	public void setVerbose(boolean b) {
-		beVerbose = b;
-	}
-
-	/**
-	 * @param b
-	 */
-	public void setPrintDebugOutput(boolean b) {
-		printDebugOutput = b;
-	}
-
-	/**
-	 * @return
-	 */
-	public boolean overwriteFiles() {
-		return overwriteFiles;
-	}
-
-	/**
-	 * @param b
-	 */
-	public void setOverwriteFiles(boolean b) {
-		overwriteFiles = b;
-	}
-
-	/**
-	 * @return
-	 */
-	public int getTabSize() {
-		return tabSize;
-	}
-
-	/**
-	 * @param i
-	 */
-	public void setTabSize(int i) {
-		tabSize = i;
-	}
-
-	public String getCharacterEncoding() {
-		return characterEncoding;
-	}
-
-	public void setCharacterEncoding(String characterEncoding) {
-		this.characterEncoding = characterEncoding;
-	}
-
-	public HashMap getCustomShapes() {
-		return customShapes;
-	}
-
-	public void setCustomShapes(HashMap customShapes) {
-		this.customShapes = customShapes;
-	}
-
-	public void putAllInCustomShapes(HashMap customShapes) {
-		this.customShapes.putAll(customShapes);
-	}
-	
-	public CustomShapeDefinition getFromCustomShapes(String tagName){
-		return customShapes.get(tagName);
-	}
-	
-	
+  private HashMap customShapes = new HashMap();
+
+  private boolean beVerbose                      = false;
+  private boolean printDebugOutput               = false;
+  private boolean overwriteFiles                 = false;
+  private boolean performSeparationOfCommonEdges = true;
+  private boolean allCornersAreRound             = false;
+
+  public static final int USE_TAGS          = 0;
+  public static final int RENDER_TAGS       = 1;
+  public static final int IGNORE_TAGS       = 2;
+  private             int tagProcessingMode = USE_TAGS;
+
+  public static final int USE_COLOR_CODES          = 0;
+  public static final int RENDER_COLOR_CODES       = 1;
+  public static final int IGNORE_COLOR_CODES       = 2;
+  private             int colorCodesProcessingMode = USE_COLOR_CODES;
+
+  public static final int FORMAT_JPEG  = 0;
+  public static final int FORMAT_PNG   = 1;
+  public static final int FORMAT_GIF   = 2;
+  private             int exportFormat = FORMAT_PNG;
+
+  public static final int DEFAULT_TAB_SIZE = 8;
+  private             int tabSize          = DEFAULT_TAB_SIZE;
+
+  private String inputFilename;
+  private String outputFilename;
+
+  private String characterEncoding = null;
+
+  /**
+   * @return
+   */
+  public boolean areAllCornersRound() {
+    return allCornersAreRound;
+  }
+
+  /**
+   * @return
+   */
+  public int getColorCodesProcessingMode() {
+    return colorCodesProcessingMode;
+  }
+
+  /**
+   * @return
+   */
+  public int getExportFormat() {
+    return exportFormat;
+  }
+
+  /**
+   * @return
+   */
+  public boolean performSeparationOfCommonEdges() {
+    return performSeparationOfCommonEdges;
+  }
+
+  /**
+   * @return
+   */
+  public int getTagProcessingMode() {
+    return tagProcessingMode;
+  }
+
+  /**
+   * @param b
+   */
+  public void setAllCornersAreRound(boolean b) {
+    allCornersAreRound = b;
+  }
+
+  /**
+   * @param i
+   */
+  public void setColorCodesProcessingMode(int i) {
+    colorCodesProcessingMode = i;
+  }
+
+  /**
+   * @param i
+   */
+  public void setExportFormat(int i) {
+    exportFormat = i;
+  }
+
+  /**
+   * @param b
+   */
+  public void setPerformSeparationOfCommonEdges(boolean b) {
+    performSeparationOfCommonEdges = b;
+  }
+
+  /**
+   * @param i
+   */
+  public void setTagProcessingMode(int i) {
+    tagProcessingMode = i;
+  }
+
+  /**
+   * @return
+   */
+  public String getInputFilename() {
+    return inputFilename;
+  }
+
+  /**
+   * @return
+   */
+  public String getOutputFilename() {
+    return outputFilename;
+  }
+
+  /**
+   * @param string
+   */
+  public void setInputFilename(String string) {
+    inputFilename = string;
+  }
+
+  /**
+   * @param string
+   */
+  public void setOutputFilename(String string) {
+    outputFilename = string;
+  }
+
+  /**
+   * @return
+   */
+  public boolean verbose() {
+    return beVerbose;
+  }
+
+  /**
+   * @return
+   */
+  public boolean printDebugOutput() {
+    return printDebugOutput;
+  }
+
+  /**
+   * @param b
+   */
+  public void setVerbose(boolean b) {
+    beVerbose = b;
+  }
+
+  /**
+   * @param b
+   */
+  public void setPrintDebugOutput(boolean b) {
+    printDebugOutput = b;
+  }
+
+  /**
+   * @return
+   */
+  public boolean overwriteFiles() {
+    return overwriteFiles;
+  }
+
+  /**
+   * @param b
+   */
+  public void setOverwriteFiles(boolean b) {
+    overwriteFiles = b;
+  }
+
+  /**
+   * @return
+   */
+  public int getTabSize() {
+    return tabSize;
+  }
+
+  /**
+   * @param i
+   */
+  public void setTabSize(int i) {
+    tabSize = i;
+  }
+
+  public String getCharacterEncoding() {
+    return characterEncoding;
+  }
+
+  public void setCharacterEncoding(String characterEncoding) {
+    this.characterEncoding = characterEncoding;
+  }
+
+  public HashMap getCustomShapes() {
+    return customShapes;
+  }
+
+  public void setCustomShapes(HashMap customShapes) {
+    this.customShapes = customShapes;
+  }
+
+  public void putAllInCustomShapes(HashMap customShapes) {
+    this.customShapes.putAll(customShapes);
+  }
+
+  public CustomShapeDefinition getFromCustomShapes(String tagName) {
+    return customShapes.get(tagName);
+  }
+
 
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/RenderingOptions.java b/src/java/org/stathissideris/ascii2image/core/RenderingOptions.java
index 02af946..d05541b 100644
--- a/src/java/org/stathissideris/ascii2image/core/RenderingOptions.java
+++ b/src/java/org/stathissideris/ascii2image/core/RenderingOptions.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,118 +15,133 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
+import org.stathissideris.ascii2image.graphics.CustomShapeDefinition;
+
 import java.awt.Color;
 import java.util.HashMap;
 
-import org.stathissideris.ascii2image.graphics.CustomShapeDefinition;
-
 /**
- * 
+ *
  * @author Efstathios Sideris
  */
 public class RenderingOptions {
 
-	private HashMap customShapes;
-	
-	private boolean dropShadows = true;
-	private boolean renderDebugLines = false;
-	private boolean antialias = true;
-    private boolean fixedSlope = false;
-
-	private int cellWidth = 10;
-	private int cellHeight = 14;
-	
-	private float scale = 1;
-	
-	private Color backgroundColor = Color.white;
-
-	public enum ImageType { PNG, SVG };
-
-	private ImageType imageType = ImageType.PNG;
-
-	public ImageType getImageType() { return imageType; }
-	public void setImageType(ImageType type) { imageType = type; }
-
-	private String fontFamily = "Courier";
-	private String fontURL = null;
-
-	public String getFontFamily() { return fontFamily; }
-	public String getFontURL() { return fontURL; }
-	public void setFontURL(String url) { fontFamily = "Custom"; fontURL = url; }
-
-	public int getCellHeight() {
-		return cellHeight;
-	}
-
-	public int getCellWidth() {
-		return cellWidth;
-	}
-
-	public boolean dropShadows() {
-		return dropShadows;
-	}
-
-	public boolean renderDebugLines() {
-		return renderDebugLines;
-	}
-
-	public float getScale() {
-		return scale;
-	}
-
-	public void setDropShadows(boolean b) {
-		dropShadows = b;
-	}
-
-	public void setRenderDebugLines(boolean b) {
-		renderDebugLines = b;
-	}
-
-	public void setScale(float f) {
-		scale = f;
-		cellWidth *= scale;
-		cellHeight *= scale;
-	}
-
-	public boolean performAntialias() {
-		return antialias;
-	}
-
-	public void setAntialias(boolean b) {
-		antialias = b;
-	}
-
-	public Color getBackgroundColor() {
-		return backgroundColor;
-	}
-
-	public void setBackgroundColor(Color backgroundColor) {
-		this.backgroundColor = backgroundColor;
-	}
-	
-	public boolean needsTransparency() {
-		return backgroundColor.getAlpha() < 255;
-	}
-
-	/**
-     * Should the sides of trapezoids and parallelograms have fixed width (false, default)
-     * or fixed slope (true)?
-     * @return true for fixed slope, false for fixed width
-     */
-    public boolean isFixedSlope() {
-        return fixedSlope;
-    }
-
-    /**
-     * Should the sides of trapezoids and parallelograms have fixed width (false, default)
-     * or fixed slope (true)?
-     * @param b true for fixed slope, false for fixed width
-     */
-    public void setFixedSlope(boolean b) {
-        this.fixedSlope = b;
-    }
+  private HashMap customShapes;
+
+  private boolean dropShadows      = true;
+  private boolean renderDebugLines = false;
+  private boolean antialias        = true;
+  private boolean fixedSlope       = false;
+
+  private int cellWidth  = 10;
+  private int cellHeight = 14;
+
+  private float scale = 1;
+
+  private Color backgroundColor = Color.white;
+
+  public enum ImageType {PNG, SVG}
+
+  ;
+
+  private ImageType imageType = ImageType.PNG;
+
+  public ImageType getImageType() {
+    return imageType;
+  }
+
+  public void setImageType(ImageType type) {
+    imageType = type;
+  }
+
+  private String fontFamily = "Courier";
+  private String fontURL    = null;
+
+  public String getFontFamily() {
+    return fontFamily;
+  }
+
+  public String getFontURL() {
+    return fontURL;
+  }
+
+  public void setFontURL(String url) {
+    fontFamily = "Custom";
+    fontURL = url;
+  }
+
+  public int getCellHeight() {
+    return cellHeight;
+  }
+
+  public int getCellWidth() {
+    return cellWidth;
+  }
+
+  public boolean dropShadows() {
+    return dropShadows;
+  }
+
+  public boolean renderDebugLines() {
+    return renderDebugLines;
+  }
+
+  public float getScale() {
+    return scale;
+  }
+
+  public void setDropShadows(boolean b) {
+    dropShadows = b;
+  }
+
+  public void setRenderDebugLines(boolean b) {
+    renderDebugLines = b;
+  }
+
+  public void setScale(float f) {
+    scale = f;
+    cellWidth *= scale;
+    cellHeight *= scale;
+  }
+
+  public boolean performAntialias() {
+    return antialias;
+  }
+
+  public void setAntialias(boolean b) {
+    antialias = b;
+  }
+
+  public Color getBackgroundColor() {
+    return backgroundColor;
+  }
+
+  public void setBackgroundColor(Color backgroundColor) {
+    this.backgroundColor = backgroundColor;
+  }
+
+  public boolean needsTransparency() {
+    return backgroundColor.getAlpha() < 255;
+  }
+
+  /**
+   * Should the sides of trapezoids and parallelograms have fixed width (false, default)
+   * or fixed slope (true)?
+   * @return true for fixed slope, false for fixed width
+   */
+  public boolean isFixedSlope() {
+    return fixedSlope;
+  }
+
+  /**
+   * Should the sides of trapezoids and parallelograms have fixed width (false, default)
+   * or fixed slope (true)?
+   * @param b true for fixed slope, false for fixed width
+   */
+  public void setFixedSlope(boolean b) {
+    this.fixedSlope = b;
+  }
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/Shape3DOrderingComparator.java b/src/java/org/stathissideris/ascii2image/core/Shape3DOrderingComparator.java
index f78e9c2..b6af120 100644
--- a/src/java/org/stathissideris/ascii2image/core/Shape3DOrderingComparator.java
+++ b/src/java/org/stathissideris/ascii2image/core/Shape3DOrderingComparator.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,32 +15,33 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
-import java.util.Comparator;
-
 import org.stathissideris.ascii2image.graphics.DiagramShape;
 
+import java.util.Comparator;
+
 /**
- * 
+ *
  * @author Efstathios Sideris
  */
 public class Shape3DOrderingComparator implements Comparator {
 
-	/**
-	 * Puts diagram shapes in pseudo-3d order starting from back to front
-	 * 
-	 */
-	public int compare(DiagramShape shape1, DiagramShape shape2) {		
-		double y1 = shape1.makeIntoPath().getBounds().getCenterY();
-		double y2 = shape2.makeIntoPath().getBounds().getCenterY();
-		
-		if(y1 > y2) return -1;
-		if(y1 < y2) return 1;
-		
-		return 0;
-	}
+  /**
+   * Puts diagram shapes in pseudo-3d order starting from back to front
+   *
+   */
+  public int compare(DiagramShape shape1, DiagramShape shape2) {
+    double y1 = shape1.makeIntoPath().getBounds().getCenterY();
+    double y2 = shape2.makeIntoPath().getBounds().getCenterY();
+
+    if (y1 > y2)
+      return -1;
+    if (y1 < y2)
+      return 1;
+
+    return 0;
+  }
 
 }
diff --git a/src/java/org/stathissideris/ascii2image/core/ShapeAreaComparator.java b/src/java/org/stathissideris/ascii2image/core/ShapeAreaComparator.java
index 162ca59..c685a3f 100644
--- a/src/java/org/stathissideris/ascii2image/core/ShapeAreaComparator.java
+++ b/src/java/org/stathissideris/ascii2image/core/ShapeAreaComparator.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,32 +15,33 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.core;
 
-import java.util.Comparator;
-
 import org.stathissideris.ascii2image.graphics.DiagramShape;
 
+import java.util.Comparator;
+
 /**
- * 
+ *
  * @author Efstathios Sideris
  */
 public class ShapeAreaComparator implements Comparator {
 
-	/**
-	 * Puts diagram shapes in order or area starting from largest to smallest
-	 * 
-	 */
-	public int compare(DiagramShape shape1, DiagramShape shape2) {
-		double y1 = shape1.calculateArea();
-		double y2 = shape2.calculateArea();
-		
-		if(y1 > y2) return -1;
-		if(y1 < y2) return 1;
-		
-		return 0;
-	}
+  /**
+   * Puts diagram shapes in order or area starting from largest to smallest
+   *
+   */
+  public int compare(DiagramShape shape1, DiagramShape shape2) {
+    double y1 = shape1.calculateArea();
+    double y2 = shape2.calculateArea();
+
+    if (y1 > y2)
+      return -1;
+    if (y1 < y2)
+      return 1;
+
+    return 0;
+  }
 
 }
diff --git a/src/java/org/stathissideris/ascii2image/graphics/BitmapRenderer.java b/src/java/org/stathissideris/ascii2image/graphics/BitmapRenderer.java
index 5f40976..28b13d6 100644
--- a/src/java/org/stathissideris/ascii2image/graphics/BitmapRenderer.java
+++ b/src/java/org/stathissideris/ascii2image/graphics/BitmapRenderer.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,7 +15,6 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.graphics;
 
@@ -26,7 +25,15 @@
 import org.stathissideris.ascii2image.text.TextGrid;
 
 import javax.imageio.ImageIO;
-import java.awt.*;
+import java.awt.BasicStroke;
+import java.awt.Canvas;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Stroke;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.GeneralPath;
 import java.awt.image.BufferedImage;
@@ -40,300 +47,298 @@
 import java.util.Iterator;
 
 /**
- * 
+ *
  * @author Efstathios Sideris
  */
 public class BitmapRenderer {
 
-	private static final boolean DEBUG = false;
-	private static final boolean DEBUG_LINES = false;
-
-	private static final String IDREGEX = "^.+_vfill$";
-	
-	Stroke normalStroke;
-	Stroke dashStroke; 
-	
-	public static void main(String[] args) throws Exception {
-		
-		
-		long startTime = System.currentTimeMillis();
-		
-		ConversionOptions options = new ConversionOptions();
-		
-		TextGrid grid = new TextGrid();
-		
-		String filename = "bug18.txt";
-		
-		grid.loadFrom("tests/text/"+filename);
-		
-		Diagram diagram = new Diagram(grid, options);
-		new BitmapRenderer().renderToPNG(diagram, "tests/images/"+filename+".png", options.renderingOptions);
-		long endTime = System.currentTimeMillis();
-		long totalTime  = (endTime - startTime) / 1000;
-		System.out.println("Done in "+totalTime+"sec");
-		
-		File workDir = new File("tests/images");
-		//Process p = Runtime.getRuntime().exec("display "+filename+".png", null, workDir);
-	}
-
-	private boolean renderToPNG(Diagram diagram, String filename, RenderingOptions options){	
-		RenderedImage image = renderToImage(diagram, options);
-		
-		try {
-			File file = new File(filename);
-			ImageIO.write(image, "png", file);
-		} catch (IOException e) {
-			//e.printStackTrace();
-			System.err.println("Error: Cannot write to file "+filename);
-			return false;
-		}
-		return true;
-	}
-	
-	public RenderedImage renderToImage(Diagram diagram, RenderingOptions options){
-		BufferedImage image;
-		if(options.needsTransparency()) {
-			image = new BufferedImage(
-					diagram.getWidth(),
-					diagram.getHeight(),
-					BufferedImage.TYPE_INT_ARGB);
-		} else {
-			image = new BufferedImage(
-					diagram.getWidth(),
-					diagram.getHeight(),
-					BufferedImage.TYPE_INT_RGB);
-		}
-		
-		return render(diagram, image, options);
-	}
-	
-	public RenderedImage render(Diagram diagram, BufferedImage image,  RenderingOptions options){
-		RenderedImage renderedImage = image;
-		Graphics2D g2 = image.createGraphics();
-
-		Object antialiasSetting = RenderingHints.VALUE_ANTIALIAS_OFF;
-		if(options.performAntialias())
-			antialiasSetting = RenderingHints.VALUE_ANTIALIAS_ON;
-		
-		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasSetting);
-
-		g2.setColor(options.getBackgroundColor());
-		//TODO: find out why the next line does not work
-		g2.fillRect(0, 0, image.getWidth()+10, image.getHeight()+10);
+  private static final boolean DEBUG       = false;
+  private static final boolean DEBUG_LINES = false;
+
+  private static final String IDREGEX = "^.+_vfill$";
+
+  Stroke normalStroke;
+  Stroke dashStroke;
+
+  public static void main(String[] args) throws Exception {
+
+    long startTime = System.currentTimeMillis();
+
+    ConversionOptions options = new ConversionOptions();
+
+    TextGrid grid = new TextGrid();
+
+    String filename = "bug18.txt";
+
+    grid.loadFrom("tests/text/" + filename);
+
+    Diagram diagram = new Diagram(grid, options);
+    new BitmapRenderer().renderToPNG(diagram, "tests/images/" + filename + ".png", options.renderingOptions);
+    long endTime = System.currentTimeMillis();
+    long totalTime = (endTime - startTime) / 1000;
+    System.out.println("Done in " + totalTime + "sec");
+
+    File workDir = new File("tests/images");
+    //Process p = Runtime.getRuntime().exec("display "+filename+".png", null, workDir);
+  }
+
+  private boolean renderToPNG(Diagram diagram, String filename, RenderingOptions options) {
+    RenderedImage image = renderToImage(diagram, options);
+
+    try {
+      File file = new File(filename);
+      ImageIO.write(image, "png", file);
+    } catch (IOException e) {
+      //e.printStackTrace();
+      System.err.println("Error: Cannot write to file " + filename);
+      return false;
+    }
+    return true;
+  }
+
+  public RenderedImage renderToImage(Diagram diagram, RenderingOptions options) {
+    BufferedImage image;
+    if (options.needsTransparency()) {
+      image = new BufferedImage(
+          diagram.getWidth(),
+          diagram.getHeight(),
+          BufferedImage.TYPE_INT_ARGB);
+    } else {
+      image = new BufferedImage(
+          diagram.getWidth(),
+          diagram.getHeight(),
+          BufferedImage.TYPE_INT_RGB);
+    }
+
+    return render(diagram, image, options);
+  }
+
+  public RenderedImage render(Diagram diagram, BufferedImage image, RenderingOptions options) {
+    RenderedImage renderedImage = image;
+    Graphics2D g2 = image.createGraphics();
+
+    Object antialiasSetting = RenderingHints.VALUE_ANTIALIAS_OFF;
+    if (options.performAntialias())
+      antialiasSetting = RenderingHints.VALUE_ANTIALIAS_ON;
+
+    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasSetting);
+
+    g2.setColor(options.getBackgroundColor());
+    //TODO: find out why the next line does not work
+    g2.fillRect(0, 0, image.getWidth() + 10, image.getHeight() + 10);
 		/*for(int y = 0; y < diagram.getHeight(); y ++)
 			g2.drawLine(0, y, diagram.getWidth(), y);*/
-		
-		g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND));
-
-		ArrayList shapes = diagram.getAllDiagramShapes();
-
-		if(DEBUG) System.out.println("Rendering "+shapes.size()+" shapes (groups flattened)");
-
-		Iterator shapesIt;
-		if(options.dropShadows()){
-			//render shadows
-			shapesIt = shapes.iterator();
-			while(shapesIt.hasNext()){
-				DiagramShape shape = shapesIt.next();
-
-				if(shape.getPoints().isEmpty()) continue;
-
-				//GeneralPath path = shape.makeIntoPath();
-				GeneralPath path;
-				path = shape.makeIntoRenderPath(diagram, options);			
-							
-				float offset = diagram.getMinimumOfCellDimension() / 3.333f;
-			
-				if(path != null
-						&& shape.dropsShadow()
-						&& shape.getType() != DiagramShape.TYPE_CUSTOM){
-					GeneralPath shadow = new GeneralPath(path);
-					AffineTransform translate = new AffineTransform();
-					translate.setToTranslation(offset, offset);
-					shadow.transform(translate);
-					g2.setColor(new Color(150,150,150));
-					g2.fill(shadow);
-				
-				}
-			}
 
-		
-			//blur shadows
-		
-			if(true) {
-				int blurRadius = 6;
-				int blurRadius2 = blurRadius * blurRadius;
-				float blurRadius2F = blurRadius2;
-				float weight = 1.0f / blurRadius2F;
-				float[] elements = new float[blurRadius2];
-				for (int k = 0; k < blurRadius2; k++)
-					elements[k] = weight;
-				Kernel myKernel = new Kernel(blurRadius, blurRadius, elements);
-
-				//if EDGE_NO_OP is not selected, EDGE_ZERO_FILL is the default which creates a black border 
-				ConvolveOp simpleBlur =
-					new ConvolveOp(myKernel, ConvolveOp.EDGE_NO_OP, null);
-								
-				BufferedImage destination =
-					new BufferedImage(
-						image.getWidth(),
-						image.getHeight(),
-						image.getType());
-
-				simpleBlur.filter(image, (BufferedImage) destination);
-
-				//destination = destination.getSubimage(blurRadius/2, blurRadius/2, image.getWidth(), image.getHeight()); 
-				g2 = (Graphics2D) destination.getGraphics();
-				g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasSetting);
-				renderedImage = (RenderedImage) destination;
-			}
-		}
-
-		
-		//fill and stroke
-		
-		float dashInterval = Math.min(diagram.getCellWidth(), diagram.getCellHeight()) / 2;
-		//Stroke normalStroke = g2.getStroke();
-		
-		float strokeWeight = diagram.getMinimumOfCellDimension() / 10;
-		
-		normalStroke =
-		  new BasicStroke(
-			strokeWeight,
-			//10,
-			BasicStroke.CAP_ROUND,
-			BasicStroke.JOIN_ROUND
-		  );
-
-		dashStroke = 
-		  new BasicStroke(
-			strokeWeight,
-			BasicStroke.CAP_BUTT,
-			BasicStroke.JOIN_ROUND,
-			0,
-			new float[] {dashInterval}, 
-			0
-		  );
-		
-		//TODO: at this stage we should draw the open shapes first in order to make sure they are at the bottom (this is useful for the {mo} shape) 
-		
-		
-		//find storage shapes
-		ArrayList storageShapes = new ArrayList();
-		shapesIt = shapes.iterator();
-		while(shapesIt.hasNext()){
-			DiagramShape shape = (DiagramShape) shapesIt.next();
-			if(shape.getType() == DiagramShape.TYPE_STORAGE) {
-				storageShapes.add(shape);
-				continue;
-			} 
-		}
-
-		//render storage shapes
-		//special case since they are '3d' and should be
-		//rendered bottom to top
-		//TODO: known bug: if a storage object is within a bigger normal box, it will be overwritten in the main drawing loop
-		//(BUT this is not possible since tags are applied to all shapes overlaping shapes)
-
-		
-		Collections.sort(storageShapes, new Shape3DOrderingComparator());
-		
-		g2.setStroke(normalStroke);
-		shapesIt = storageShapes.iterator();
-		while(shapesIt.hasNext()){
-			DiagramShape shape = (DiagramShape) shapesIt.next();
-
-			GeneralPath path;
-			path = shape.makeIntoRenderPath(diagram, options);
-			
-			if(!shape.isStrokeDashed()) {
-				if(shape.getFillColor() != null)
-					g2.setColor(shape.getFillColor());
-				else
-					g2.setColor(Color.white);
-				g2.fill(path);
-			}
+    g2.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND));
 
-			if(shape.isStrokeDashed())
-				g2.setStroke(dashStroke);
-			else
-				g2.setStroke(normalStroke);
-			g2.setColor(shape.getStrokeColor());
-			g2.draw(path);
-		}
-
-		//sort so that the largest shapes are rendered first
-		Collections.sort(shapes, new ShapeAreaComparator());
-		
-		//render the rest of the shapes
-		ArrayList pointMarkers = new ArrayList();
-		shapesIt = shapes.iterator();
-		while(shapesIt.hasNext()){
-			DiagramShape shape = (DiagramShape) shapesIt.next();
-			if(shape.getType() == DiagramShape.TYPE_POINT_MARKER) {
-				pointMarkers.add(shape);
-				continue;
-			} 
-			if(shape.getType() == DiagramShape.TYPE_STORAGE) {
-				continue;
-			} 
-			if(shape.getType() == DiagramShape.TYPE_CUSTOM){
-				renderCustomShape(shape, g2);
-				continue;
-			}
+    ArrayList shapes = diagram.getAllDiagramShapes();
 
-			if(shape.getPoints().isEmpty()) continue;
-
-			int size = shape.getPoints().size();
-			
-			GeneralPath path;
-			path = shape.makeIntoRenderPath(diagram, options);
-			
-			//fill
-			if(path != null && shape.isClosed() && !shape.isStrokeDashed()){
-				if(shape.getFillColor() != null)
-					g2.setColor(shape.getFillColor());
-				else
-					g2.setColor(Color.white);
-				g2.fill(path);
-			}
-			
-			//draw
-			if(shape.getType() != DiagramShape.TYPE_ARROWHEAD){
-				g2.setColor(shape.getStrokeColor());
-				if(shape.isStrokeDashed())
-					g2.setStroke(dashStroke);
-				else
-					g2.setStroke(normalStroke);
-				g2.draw(path);
-			}
-		}
-		
-		//render point markers
-		
-		g2.setStroke(normalStroke);
-		shapesIt = pointMarkers.iterator();
-		while(shapesIt.hasNext()){
-			DiagramShape shape = (DiagramShape) shapesIt.next();
-			//if(shape.getType() != DiagramShape.TYPE_POINT_MARKER) continue;
-
-			GeneralPath path;
-			path = shape.makeIntoRenderPath(diagram, options);
-			
-			g2.setColor(Color.white);
-			g2.fill(path);
-			g2.setColor(shape.getStrokeColor());
-			g2.draw(path);
-		}		
-		
-		//handle text
-		//g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-		//renderTextLayer(diagram.getTextObjects().iterator());
-		
-		Iterator textIt = diagram.getTextObjects().iterator();
-		while(textIt.hasNext()){
-			DiagramText text = textIt.next();
-			text.drawOn(g2);
+    if (DEBUG)
+      System.out.println("Rendering " + shapes.size() + " shapes (groups flattened)");
+
+    Iterator shapesIt;
+    if (options.dropShadows()) {
+      //render shadows
+      shapesIt = shapes.iterator();
+      while (shapesIt.hasNext()) {
+        DiagramShape shape = shapesIt.next();
+
+        if (shape.getPoints().isEmpty())
+          continue;
+
+        //GeneralPath path = shape.makeIntoPath();
+        GeneralPath path;
+        path = shape.makeIntoRenderPath(diagram, options);
+
+        float offset = diagram.getMinimumOfCellDimension() / 3.333f;
+
+        if (path != null
+            && shape.dropsShadow()
+            && shape.getType() != DiagramShape.TYPE_CUSTOM) {
+          GeneralPath shadow = new GeneralPath(path);
+          AffineTransform translate = new AffineTransform();
+          translate.setToTranslation(offset, offset);
+          shadow.transform(translate);
+          g2.setColor(new Color(150, 150, 150));
+          g2.fill(shadow);
+
+        }
+      }
+
+      //blur shadows
+
+      if (true) {
+        int blurRadius = 6;
+        int blurRadius2 = blurRadius * blurRadius;
+        float blurRadius2F = blurRadius2;
+        float weight = 1.0f / blurRadius2F;
+        float[] elements = new float[blurRadius2];
+        for (int k = 0; k < blurRadius2; k++)
+          elements[k] = weight;
+        Kernel myKernel = new Kernel(blurRadius, blurRadius, elements);
+
+        //if EDGE_NO_OP is not selected, EDGE_ZERO_FILL is the default which creates a black border
+        ConvolveOp simpleBlur =
+            new ConvolveOp(myKernel, ConvolveOp.EDGE_NO_OP, null);
+
+        BufferedImage destination =
+            new BufferedImage(
+                image.getWidth(),
+                image.getHeight(),
+                image.getType());
+
+        simpleBlur.filter(image, (BufferedImage) destination);
+
+        //destination = destination.getSubimage(blurRadius/2, blurRadius/2, image.getWidth(), image.getHeight());
+        g2 = (Graphics2D) destination.getGraphics();
+        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasSetting);
+        renderedImage = (RenderedImage) destination;
+      }
+    }
+
+    //fill and stroke
+
+    float dashInterval = Math.min(diagram.getCellWidth(), diagram.getCellHeight()) / 2;
+    //Stroke normalStroke = g2.getStroke();
+
+    float strokeWeight = diagram.getMinimumOfCellDimension() / 10;
+
+    normalStroke =
+        new BasicStroke(
+            strokeWeight,
+            //10,
+            BasicStroke.CAP_ROUND,
+            BasicStroke.JOIN_ROUND
+        );
+
+    dashStroke =
+        new BasicStroke(
+            strokeWeight,
+            BasicStroke.CAP_BUTT,
+            BasicStroke.JOIN_ROUND,
+            0,
+            new float[] { dashInterval },
+            0
+        );
+
+    //TODO: at this stage we should draw the open shapes first in order to make sure they are at the bottom (this is useful for the {mo} shape)
+
+    //find storage shapes
+    ArrayList storageShapes = new ArrayList();
+    shapesIt = shapes.iterator();
+    while (shapesIt.hasNext()) {
+      DiagramShape shape = (DiagramShape) shapesIt.next();
+      if (shape.getType() == DiagramShape.TYPE_STORAGE) {
+        storageShapes.add(shape);
+        continue;
+      }
+    }
+
+    //render storage shapes
+    //special case since they are '3d' and should be
+    //rendered bottom to top
+    //TODO: known bug: if a storage object is within a bigger normal box, it will be overwritten in the main drawing loop
+    //(BUT this is not possible since tags are applied to all shapes overlaping shapes)
+
+    Collections.sort(storageShapes, new Shape3DOrderingComparator());
+
+    g2.setStroke(normalStroke);
+    shapesIt = storageShapes.iterator();
+    while (shapesIt.hasNext()) {
+      DiagramShape shape = (DiagramShape) shapesIt.next();
+
+      GeneralPath path;
+      path = shape.makeIntoRenderPath(diagram, options);
+
+      if (!shape.isStrokeDashed()) {
+        if (shape.getFillColor() != null)
+          g2.setColor(shape.getFillColor());
+        else
+          g2.setColor(Color.white);
+        g2.fill(path);
+      }
+
+      if (shape.isStrokeDashed())
+        g2.setStroke(dashStroke);
+      else
+        g2.setStroke(normalStroke);
+      g2.setColor(shape.getStrokeColor());
+      g2.draw(path);
+    }
+
+    //sort so that the largest shapes are rendered first
+    Collections.sort(shapes, new ShapeAreaComparator());
+
+    //render the rest of the shapes
+    ArrayList pointMarkers = new ArrayList();
+    shapesIt = shapes.iterator();
+    while (shapesIt.hasNext()) {
+      DiagramShape shape = (DiagramShape) shapesIt.next();
+      if (shape.getType() == DiagramShape.TYPE_POINT_MARKER) {
+        pointMarkers.add(shape);
+        continue;
+      }
+      if (shape.getType() == DiagramShape.TYPE_STORAGE) {
+        continue;
+      }
+      if (shape.getType() == DiagramShape.TYPE_CUSTOM) {
+        renderCustomShape(shape, g2);
+        continue;
+      }
+
+      if (shape.getPoints().isEmpty())
+        continue;
+
+      int size = shape.getPoints().size();
+
+      GeneralPath path;
+      path = shape.makeIntoRenderPath(diagram, options);
+
+      //fill
+      if (path != null && shape.isClosed() && !shape.isStrokeDashed()) {
+        if (shape.getFillColor() != null)
+          g2.setColor(shape.getFillColor());
+        else
+          g2.setColor(Color.white);
+        g2.fill(path);
+      }
+
+      //draw
+      if (shape.getType() != DiagramShape.TYPE_ARROWHEAD) {
+        g2.setColor(shape.getStrokeColor());
+        if (shape.isStrokeDashed())
+          g2.setStroke(dashStroke);
+        else
+          g2.setStroke(normalStroke);
+        g2.draw(path);
+      }
+    }
+
+    //render point markers
+
+    g2.setStroke(normalStroke);
+    shapesIt = pointMarkers.iterator();
+    while (shapesIt.hasNext()) {
+      DiagramShape shape = (DiagramShape) shapesIt.next();
+      //if(shape.getType() != DiagramShape.TYPE_POINT_MARKER) continue;
+
+      GeneralPath path;
+      path = shape.makeIntoRenderPath(diagram, options);
+
+      g2.setColor(Color.white);
+      g2.fill(path);
+      g2.setColor(shape.getStrokeColor());
+      g2.draw(path);
+    }
+
+    //handle text
+    //g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+    //renderTextLayer(diagram.getTextObjects().iterator());
+
+    Iterator textIt = diagram.getTextObjects().iterator();
+    while (textIt.hasNext()) {
+      DiagramText text = textIt.next();
+      text.drawOn(g2);
 			/*
 			g2.setFont(text.getFont());
 			if(text.hasOutline()){
@@ -346,135 +351,138 @@ public RenderedImage render(Diagram diagram, BufferedImage image,  RenderingOpti
 			g2.setColor(text.getColor());
 			g2.drawString(text.getText(), text.getXPos(), text.getYPos());
 			*/
-		}
-		
-		if(options.renderDebugLines() || DEBUG_LINES){
-			Stroke debugStroke =
-			  new BasicStroke(
-				1,
-				BasicStroke.CAP_ROUND,
-				BasicStroke.JOIN_ROUND
-			  );
-			g2.setStroke(debugStroke);
-			g2.setColor(new Color(170, 170, 170));
-			g2.setXORMode(Color.white);
-			for(int x = 0; x < diagram.getWidth(); x += diagram.getCellWidth())
-				g2.drawLine(x, 0, x, diagram.getHeight());
-			for(int y = 0; y < diagram.getHeight(); y += diagram.getCellHeight())
-				g2.drawLine(0, y, diagram.getWidth(), y);
-		}
-		
-
-		g2.dispose();
-		
-		return renderedImage;
-	}
-	
-	private RenderedImage renderTextLayer(ArrayList textObjects, int width, int height){
-		TextCanvas canvas = new TextCanvas(textObjects);
-		Image image = canvas.createImage(width, height);
-		Graphics g = image.getGraphics();
-		canvas.paint(g);
-		return (RenderedImage) image;
-	}
-	
-	private class TextCanvas extends Canvas {
-		ArrayList textObjects;
-		
-		public TextCanvas(ArrayList textObjects){
-			this.textObjects = textObjects;
-		}
-		
-		public void paint(Graphics g){
-			Graphics2D g2 = (Graphics2D) g;
-			Iterator textIt = textObjects.iterator();
-			while(textIt.hasNext()){
-				textIt.next().drawOn(g2);
-			}
-		}
-	}
-	
-	private void renderCustomShape(DiagramShape shape, Graphics2D g2){
-		CustomShapeDefinition definition = shape.getDefinition();
-		
-		Rectangle bounds = shape.getBounds();
-		
-		if(definition.hasBorder()){
-			g2.setColor(shape.getStrokeColor());
-			if(shape.isStrokeDashed())
-				g2.setStroke(dashStroke);
-			else
-				g2.setStroke(normalStroke);
-			g2.drawLine(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y);
-			g2.drawLine(bounds.x + bounds.width, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height);
-			g2.drawLine(bounds.x, bounds.y + bounds.height, bounds.x + bounds.width, bounds.y + bounds.height);
-			g2.drawLine(bounds.x, bounds.y, bounds.x, bounds.y + bounds.height);
-			
-//			g2.drawRect(bounds.x, bounds.y, bounds.width, bounds.height); //looks different!			
-		}
-		
-		//TODO: custom shape distinction relies on filename extension. Make this more intelligent
-		if(definition.getFilename().endsWith(".png")){
-			renderCustomPNGShape(shape, g2);
-		} else if(definition.getFilename().endsWith(".svg")){
-			renderCustomSVGShape(shape, g2);
-		}
-	}
-	
-	private void renderCustomSVGShape(DiagramShape shape, Graphics2D g2){
-		CustomShapeDefinition definition = shape.getDefinition();
-		Rectangle bounds = shape.getBounds();
-		Image graphic;
-		try {
-			if(shape.getFillColor() == null) {
-				graphic = ImageHandler.instance().renderSVG(
-						definition.getFilename(), bounds.width, bounds.height, definition.stretches());
-			} else {
-				graphic = ImageHandler.instance().renderSVG(
-						definition.getFilename(), bounds.width, bounds.height, definition.stretches(), IDREGEX, shape.getFillColor());				
-			}
-			g2.drawImage(graphic, bounds.x, bounds.y, null);
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
-	}
-	
-	private void renderCustomPNGShape(DiagramShape shape, Graphics2D g2){
-		CustomShapeDefinition definition = shape.getDefinition();
-		Rectangle bounds = shape.getBounds();
-		Image graphic = ImageHandler.instance().loadImage(definition.getFilename());
-		
-		int xPos, yPos, width, height;
-		
-		if(definition.stretches()){ //occupy all available space
-			xPos = bounds.x; yPos = bounds.y;
-			width = bounds.width; height = bounds.height;
-		} else { //decide how to fit
-			int newHeight = bounds.width * graphic.getHeight(null) / graphic.getWidth(null);
-			if(newHeight < bounds.height){ //expand to fit width
-				height = newHeight;
-				width = bounds.width;
-				xPos = bounds.x;
-				yPos = bounds.y + bounds.height / 2 - graphic.getHeight(null) / 2;
-			} else { //expand to fit height
-				width = graphic.getWidth(null) * bounds.height / graphic.getHeight(null);
-				height = bounds.height;
-				xPos = bounds.x + bounds.width / 2 - graphic.getWidth(null) / 2;
-				yPos = bounds.y;
-			}
-		}
-		
-		g2.drawImage(graphic, xPos, yPos, width, height, null);		
-	}
-	
-	public static boolean isColorDark(Color color){
-		int brightness = Math.max(color.getRed(), color.getGreen());
-		brightness = Math.max(color.getBlue(), brightness);
-		if(brightness < 200) {
-			if(DEBUG) System.out.println("Color "+color+" is dark");
-			return true;
-		}
-		if(DEBUG) System.out.println("Color "+color+" is not dark");
-		return false;
-	}
+    }
+
+    if (options.renderDebugLines() || DEBUG_LINES) {
+      Stroke debugStroke =
+          new BasicStroke(
+              1,
+              BasicStroke.CAP_ROUND,
+              BasicStroke.JOIN_ROUND
+          );
+      g2.setStroke(debugStroke);
+      g2.setColor(new Color(170, 170, 170));
+      g2.setXORMode(Color.white);
+      for (int x = 0; x < diagram.getWidth(); x += diagram.getCellWidth())
+        g2.drawLine(x, 0, x, diagram.getHeight());
+      for (int y = 0; y < diagram.getHeight(); y += diagram.getCellHeight())
+        g2.drawLine(0, y, diagram.getWidth(), y);
+    }
+
+    g2.dispose();
+
+    return renderedImage;
+  }
+
+  private RenderedImage renderTextLayer(ArrayList textObjects, int width, int height) {
+    TextCanvas canvas = new TextCanvas(textObjects);
+    Image image = canvas.createImage(width, height);
+    Graphics g = image.getGraphics();
+    canvas.paint(g);
+    return (RenderedImage) image;
+  }
+
+  private class TextCanvas extends Canvas {
+    ArrayList textObjects;
+
+    public TextCanvas(ArrayList textObjects) {
+      this.textObjects = textObjects;
+    }
+
+    public void paint(Graphics g) {
+      Graphics2D g2 = (Graphics2D) g;
+      Iterator textIt = textObjects.iterator();
+      while (textIt.hasNext()) {
+        textIt.next().drawOn(g2);
+      }
+    }
+  }
+
+  private void renderCustomShape(DiagramShape shape, Graphics2D g2) {
+    CustomShapeDefinition definition = shape.getDefinition();
+
+    Rectangle bounds = shape.getBounds();
+
+    if (definition.hasBorder()) {
+      g2.setColor(shape.getStrokeColor());
+      if (shape.isStrokeDashed())
+        g2.setStroke(dashStroke);
+      else
+        g2.setStroke(normalStroke);
+      g2.drawLine(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y);
+      g2.drawLine(bounds.x + bounds.width, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height);
+      g2.drawLine(bounds.x, bounds.y + bounds.height, bounds.x + bounds.width, bounds.y + bounds.height);
+      g2.drawLine(bounds.x, bounds.y, bounds.x, bounds.y + bounds.height);
+
+      //			g2.drawRect(bounds.x, bounds.y, bounds.width, bounds.height); //looks different!
+    }
+
+    //TODO: custom shape distinction relies on filename extension. Make this more intelligent
+    if (definition.getFilename().endsWith(".png")) {
+      renderCustomPNGShape(shape, g2);
+    } else if (definition.getFilename().endsWith(".svg")) {
+      renderCustomSVGShape(shape, g2);
+    }
+  }
+
+  private void renderCustomSVGShape(DiagramShape shape, Graphics2D g2) {
+    CustomShapeDefinition definition = shape.getDefinition();
+    Rectangle bounds = shape.getBounds();
+    Image graphic;
+    try {
+      if (shape.getFillColor() == null) {
+        graphic = ImageHandler.instance().renderSVG(
+            definition.getFilename(), bounds.width, bounds.height, definition.stretches());
+      } else {
+        graphic = ImageHandler.instance().renderSVG(
+            definition.getFilename(), bounds.width, bounds.height, definition.stretches(), IDREGEX, shape.getFillColor());
+      }
+      g2.drawImage(graphic, bounds.x, bounds.y, null);
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+  }
+
+  private void renderCustomPNGShape(DiagramShape shape, Graphics2D g2) {
+    CustomShapeDefinition definition = shape.getDefinition();
+    Rectangle bounds = shape.getBounds();
+    Image graphic = ImageHandler.instance().loadImage(definition.getFilename());
+
+    int xPos, yPos, width, height;
+
+    if (definition.stretches()) { //occupy all available space
+      xPos = bounds.x;
+      yPos = bounds.y;
+      width = bounds.width;
+      height = bounds.height;
+    } else { //decide how to fit
+      int newHeight = bounds.width * graphic.getHeight(null) / graphic.getWidth(null);
+      if (newHeight < bounds.height) { //expand to fit width
+        height = newHeight;
+        width = bounds.width;
+        xPos = bounds.x;
+        yPos = bounds.y + bounds.height / 2 - graphic.getHeight(null) / 2;
+      } else { //expand to fit height
+        width = graphic.getWidth(null) * bounds.height / graphic.getHeight(null);
+        height = bounds.height;
+        xPos = bounds.x + bounds.width / 2 - graphic.getWidth(null) / 2;
+        yPos = bounds.y;
+      }
+    }
+
+    g2.drawImage(graphic, xPos, yPos, width, height, null);
+  }
+
+  public static boolean isColorDark(Color color) {
+    int brightness = Math.max(color.getRed(), color.getGreen());
+    brightness = Math.max(color.getBlue(), brightness);
+    if (brightness < 200) {
+      if (DEBUG)
+        System.out.println("Color " + color + " is dark");
+      return true;
+    }
+    if (DEBUG)
+      System.out.println("Color " + color + " is not dark");
+    return false;
+  }
 }
diff --git a/src/java/org/stathissideris/ascii2image/graphics/CompositeDiagramShape.java b/src/java/org/stathissideris/ascii2image/graphics/CompositeDiagramShape.java
index 0677966..2bd9397 100644
--- a/src/java/org/stathissideris/ascii2image/graphics/CompositeDiagramShape.java
+++ b/src/java/org/stathissideris/ascii2image/graphics/CompositeDiagramShape.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,298 +15,306 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.graphics;
 
+import org.stathissideris.ascii2image.core.DebugUtils;
+import org.stathissideris.ascii2image.text.CellSet;
+import org.stathissideris.ascii2image.text.TextGrid;
+
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Iterator;
-
-
-import org.stathissideris.ascii2image.core.DebugUtils;
-import org.stathissideris.ascii2image.text.*;
+import java.util.List;
 
 /**
- * 
+ *
  * @author Efstathios Sideris
  */
 public class CompositeDiagramShape extends DiagramComponent {
 
-	private static final boolean DEBUG = false;
-
-	private ArrayList shapes = new ArrayList();
-
-	public static void main(String[] args) {
-	}
-
-	public static DiagramComponent createFromBoundaryCells(
-			final TextGrid grid,
-			final CellSet boundaryCells,
-			final int cellWidth,
-			final int cellHeight) {
-				return createOpenFromBoundaryCells(
-						grid,
-						boundaryCells,
-						cellWidth, cellHeight,
-						false);
-	}
-
-
-	public static DiagramComponent createOpenFromBoundaryCells(
-			final TextGrid grid,
-			final CellSet boundaryCells,
-			final int cellWidth,
-			final int cellHeight,
-			boolean allRound) {
-		
-		if(boundaryCells.getType(grid) != CellSet.TYPE_OPEN) throw new IllegalArgumentException("This shape is closed and cannot be handled by this method");
-		if(boundaryCells.size() == 0) return null;
-
-		
-		CompositeDiagramShape compositeShape = new CompositeDiagramShape();
-		TextGrid workGrid = new TextGrid(grid.getWidth(), grid.getHeight());
-		grid.copyCellsTo(boundaryCells, workGrid);
-
-		if(DEBUG) {
-			System.out.println("Making composite shape from grid:");
-			workGrid.printDebug();
-		}
-		
-		
-		CellSet visitedCells = new CellSet();
-		
-		List shapes = new ArrayList(100);
-		
-		for(TextGrid.Cell cell : boundaryCells) {
-			if(workGrid.isLinesEnd(cell)) {
-				CellSet nextCells = workGrid.followCell(cell);
-				shapes.addAll(growEdgesFromCell(workGrid, cellWidth, cellHeight, allRound, nextCells.getFirst(), cell, visitedCells));
-				break;
-			}
-		}
-		
-		//dashed shapes should "infect" the rest of the shapes
-		boolean dashedShapeExists = false;
-		for(DiagramShape shape : shapes)
-			if(shape.isStrokeDashed())
-				dashedShapeExists = true;
-		
-		for(DiagramShape shape : shapes) {
-			if(dashedShapeExists) shape.setStrokeDashed(true);
-			compositeShape.addToShapes(shape);
-		}
-		
-		return compositeShape;
-	}
-	
-	
-	private static List growEdgesFromCell(
-			TextGrid workGrid,
-			final int cellWidth,
-			final int cellHeight,
-			boolean allRound,
-			TextGrid.Cell cell, 
-			TextGrid.Cell previousCell, 
-			CellSet visitedCells) {
-		
-		List result = new ArrayList(50); 
-		
-		visitedCells.add(previousCell);
-		
-		DiagramShape shape = new DiagramShape();
-		
-		shape.addToPoints(makePointForCell(previousCell, workGrid, cellWidth, cellHeight, allRound));
-		if(DEBUG) System.out.println("point at "+previousCell+" (call from line: "+DebugUtils.getLineNumber()+")");
-		if(workGrid.cellContainsDashedLineChar(previousCell)) shape.setStrokeDashed(true);
-
-		boolean finished = false;
-		while(!finished) {
-			visitedCells.add(cell);
-			if(workGrid.isPointCell(cell)) {
-				if(DEBUG) System.out.println("point at "+cell+" (call from line: "+DebugUtils.getLineNumber()+")");
-				shape.addToPoints(makePointForCell(cell, workGrid, cellWidth, cellHeight, allRound));
-			}
-			
-			if(workGrid.cellContainsDashedLineChar(cell)) shape.setStrokeDashed(true);
-
-			if(workGrid.isLinesEnd(cell)){
-				finished = true;
-				if(DEBUG) System.out.println("finished shape");
-			}
-			
-			CellSet nextCells = workGrid.followCell(cell, previousCell);
-			if(nextCells.size() == 1) {
-				previousCell = cell;
-				cell = (TextGrid.Cell) nextCells.getFirst();
-				if(DEBUG) System.out.println("tracing at "+cell+" (call from line: "+DebugUtils.getLineNumber()+")");
-			} else if(nextCells.size() > 1 || nextCells.size() == 0) {//3- or 4- way intersection
-				finished = true;
-				for(TextGrid.Cell nextCell : nextCells)
-					result.addAll(growEdgesFromCell(workGrid, cellWidth, cellHeight, allRound, nextCell, cell, visitedCells));
-			}
-		}
-		
-		result.add(shape);
-		return result;
-	}
-
-	/**
-	 * Returns a new diagram component with the lines of
-	 * this CompositeDiagramShape connected. It can a composite
-	 * or simple shape
-	 * 
-	 * @return
-	 */
-	public DiagramComponent connectLines(){
-		CompositeDiagramShape result = new CompositeDiagramShape();
-
-		//find all lines
-		ArrayList lines = new ArrayList();
-		Iterator it = shapes.iterator();
-		while(it.hasNext()){
-			DiagramShape shape = (DiagramShape) it.next();
-			if(shape.getPoints().size() == 2){
-				lines.add(shape);
-			}
-		}
-		
-		it = lines.iterator();
-		while(it.hasNext()){
-			DiagramShape line1 = (DiagramShape) it.next();
-			Iterator it2 = lines.iterator();
-			while(it2.hasNext()){
-				DiagramShape line2 = (DiagramShape) it.next();
-				ShapePoint commonPoint = null;
-				ShapePoint line1UncommonPoint = null;
-				ShapePoint line2UncommonPoint = null;
-				if(line1.getPoint(0).equals(line2.getPoint(0))){
-					commonPoint = line1.getPoint(0);
-					line1UncommonPoint = line1.getPoint(1);
-					line2UncommonPoint = line2.getPoint(1);
-				}
-				if(line1.getPoint(0).equals(line2.getPoint(1))){
-					commonPoint = line1.getPoint(0);
-					line1UncommonPoint = line1.getPoint(1);
-					line2UncommonPoint = line2.getPoint(0);
-				}
-				if(line1.getPoint(1).equals(line2.getPoint(0))){
-					commonPoint = line1.getPoint(1);
-					line1UncommonPoint = line1.getPoint(0);
-					line2UncommonPoint = line2.getPoint(1);
-				}
-				if(line1.getPoint(1).equals(line2.getPoint(1))){
-					commonPoint = line1.getPoint(1);
-					line1UncommonPoint = line1.getPoint(0);
-					line2UncommonPoint = line2.getPoint(0);
-				}
-				if(commonPoint != null){
-					
-				}
-			}
-		}
-		
-		return result;
-	}
-
-	public void connectEndsToAnchors(TextGrid grid, Diagram diagram){
-		Iterator it = shapes.iterator();
-		while (it.hasNext()) {
-			DiagramShape shape = (DiagramShape) it.next();
-			if(!shape.isClosed()){
-				shape.connectEndsToAnchors(grid, diagram);
-			}
-		}
-	}
-
-	private static DiagramShape makeLine(TextGrid grid, TextGrid.Cell start, TextGrid.Cell end, int cellWidth, int cellHeight){
-		DiagramShape line = new DiagramShape();
-		
-		if(grid.isHorizontalLine(start)){
-			if(start.isWestOf(end)){
-				line.addToPoints(new ShapePoint(
-							Diagram.getCellMinX(start, cellWidth),
-							Diagram.getCellMidY(start, cellHeight)));
-			} else {
-				line.addToPoints(new ShapePoint(
-							Diagram.getCellMaxX(start, cellWidth),
-							Diagram.getCellMidY(start, cellHeight)));
-			}
-		} else if(grid.isVerticalLine(start)){
-			if(start.isNorthOf(end)){
-				line.addToPoints(new ShapePoint(
-							Diagram.getCellMidX(start, cellWidth),
-							Diagram.getCellMinY(start, cellHeight)));
-			} else {
-				line.addToPoints(new ShapePoint(
-							Diagram.getCellMidX(start, cellWidth),
-							Diagram.getCellMaxY(start, cellHeight)));
-			}			
-		} else { //corner
-			if(DEBUG) System.out.println("Corner");
-			int type = (grid.isRoundCorner(start))?ShapePoint.TYPE_ROUND:ShapePoint.TYPE_NORMAL;
-			line.addToPoints(new ShapePoint(
-						Diagram.getCellMidX(start, cellWidth),
-						Diagram.getCellMidY(start, cellHeight),
-						type));
-			
-		}
-
-		if(grid.isHorizontalLine(end)){
-			if(start.isWestOf(start)){
-				line.addToPoints(new ShapePoint(
-							Diagram.getCellMinX(end, cellWidth),
-							Diagram.getCellMidY(end, cellHeight)));
-			} else {
-				line.addToPoints(new ShapePoint(
-							Diagram.getCellMaxX(end, cellWidth),
-							Diagram.getCellMidY(end, cellHeight)));
-			}
-		} else if(grid.isVerticalLine(end)){
-			if(start.isNorthOf(start)){
-				line.addToPoints(new ShapePoint(
-							Diagram.getCellMidX(end, cellWidth),
-							Diagram.getCellMinY(end, cellHeight)));
-			} else {
-				line.addToPoints(new ShapePoint(
-							Diagram.getCellMidX(end, cellWidth),
-							Diagram.getCellMaxY(end, cellHeight)));
-			}			
-		} else { //corner
-			int type = (grid.isRoundCorner(end))?ShapePoint.TYPE_ROUND:ShapePoint.TYPE_NORMAL;
-			if(DEBUG) System.out.println("Corner");
-			line.addToPoints(new ShapePoint(
-						Diagram.getCellMidX(end, cellWidth),
-						Diagram.getCellMidY(end, cellHeight),
-						type));
-			
-		}
-
-		
-		return line;
-	}
-
-	public void addToShapes(DiagramShape shape){
-		shapes.add(shape);
-	}
-	
-	private Iterator getShapesIterator(){
-		return shapes.iterator();
-	}
-	
-	public void scale(float factor){
-		Iterator it = getShapesIterator();
-		while(it.hasNext()){
-			DiagramShape shape = (DiagramShape) it.next();
-			shape.scale(factor);
-		}
-	}
-	/**
-	 * @return
-	 */
-	public ArrayList getShapes() {
-		return shapes;
-	}
+  private static final boolean DEBUG = false;
+
+  private ArrayList shapes = new ArrayList();
+
+  public static void main(String[] args) {
+  }
+
+  public static DiagramComponent createFromBoundaryCells(
+      final TextGrid grid,
+      final CellSet boundaryCells,
+      final int cellWidth,
+      final int cellHeight) {
+    return createOpenFromBoundaryCells(
+        grid,
+        boundaryCells,
+        cellWidth, cellHeight,
+        false);
+  }
+
+
+  public static DiagramComponent createOpenFromBoundaryCells(
+      final TextGrid grid,
+      final CellSet boundaryCells,
+      final int cellWidth,
+      final int cellHeight,
+      boolean allRound) {
+
+    if (boundaryCells.getType(grid) != CellSet.TYPE_OPEN)
+      throw new IllegalArgumentException("This shape is closed and cannot be handled by this method");
+    if (boundaryCells.size() == 0)
+      return null;
+
+    CompositeDiagramShape compositeShape = new CompositeDiagramShape();
+    TextGrid workGrid = new TextGrid(grid.getWidth(), grid.getHeight());
+    grid.copyCellsTo(boundaryCells, workGrid);
+
+    if (DEBUG) {
+      System.out.println("Making composite shape from grid:");
+      workGrid.printDebug();
+    }
+
+    CellSet visitedCells = new CellSet();
+
+    List shapes = new ArrayList(100);
+
+    for (TextGrid.Cell cell : boundaryCells) {
+      if (workGrid.isLinesEnd(cell)) {
+        CellSet nextCells = workGrid.followCell(cell);
+        shapes.addAll(growEdgesFromCell(workGrid, cellWidth, cellHeight, allRound, nextCells.getFirst(), cell, visitedCells));
+        break;
+      }
+    }
+
+    //dashed shapes should "infect" the rest of the shapes
+    boolean dashedShapeExists = false;
+    for (DiagramShape shape : shapes)
+      if (shape.isStrokeDashed())
+        dashedShapeExists = true;
+
+    for (DiagramShape shape : shapes) {
+      if (dashedShapeExists)
+        shape.setStrokeDashed(true);
+      compositeShape.addToShapes(shape);
+    }
+
+    return compositeShape;
+  }
+
+
+  private static List growEdgesFromCell(
+      TextGrid workGrid,
+      final int cellWidth,
+      final int cellHeight,
+      boolean allRound,
+      TextGrid.Cell cell,
+      TextGrid.Cell previousCell,
+      CellSet visitedCells) {
+
+    List result = new ArrayList(50);
+
+    visitedCells.add(previousCell);
+
+    DiagramShape shape = new DiagramShape();
+
+    shape.addToPoints(makePointForCell(previousCell, workGrid, cellWidth, cellHeight, allRound));
+    if (DEBUG)
+      System.out.println("point at " + previousCell + " (call from line: " + DebugUtils.getLineNumber() + ")");
+    if (workGrid.cellContainsDashedLineChar(previousCell))
+      shape.setStrokeDashed(true);
+
+    boolean finished = false;
+    while (!finished) {
+      visitedCells.add(cell);
+      if (workGrid.isPointCell(cell)) {
+        if (DEBUG)
+          System.out.println("point at " + cell + " (call from line: " + DebugUtils.getLineNumber() + ")");
+        shape.addToPoints(makePointForCell(cell, workGrid, cellWidth, cellHeight, allRound));
+      }
+
+      if (workGrid.cellContainsDashedLineChar(cell))
+        shape.setStrokeDashed(true);
+
+      if (workGrid.isLinesEnd(cell)) {
+        finished = true;
+        if (DEBUG)
+          System.out.println("finished shape");
+      }
+
+      CellSet nextCells = workGrid.followCell(cell, previousCell);
+      if (nextCells.size() == 1) {
+        previousCell = cell;
+        cell = (TextGrid.Cell) nextCells.getFirst();
+        if (DEBUG)
+          System.out.println("tracing at " + cell + " (call from line: " + DebugUtils.getLineNumber() + ")");
+      } else if (nextCells.size() > 1 || nextCells.size() == 0) {//3- or 4- way intersection
+        finished = true;
+        for (TextGrid.Cell nextCell : nextCells)
+          result.addAll(growEdgesFromCell(workGrid, cellWidth, cellHeight, allRound, nextCell, cell, visitedCells));
+      }
+    }
+
+    result.add(shape);
+    return result;
+  }
+
+  /**
+   * Returns a new diagram component with the lines of
+   * this CompositeDiagramShape connected. It can a composite
+   * or simple shape
+   *
+   * @return
+   */
+  public DiagramComponent connectLines() {
+    CompositeDiagramShape result = new CompositeDiagramShape();
+
+    //find all lines
+    ArrayList lines = new ArrayList();
+    Iterator it = shapes.iterator();
+    while (it.hasNext()) {
+      DiagramShape shape = (DiagramShape) it.next();
+      if (shape.getPoints().size() == 2) {
+        lines.add(shape);
+      }
+    }
+
+    it = lines.iterator();
+    while (it.hasNext()) {
+      DiagramShape line1 = (DiagramShape) it.next();
+      Iterator it2 = lines.iterator();
+      while (it2.hasNext()) {
+        DiagramShape line2 = (DiagramShape) it.next();
+        ShapePoint commonPoint = null;
+        ShapePoint line1UncommonPoint = null;
+        ShapePoint line2UncommonPoint = null;
+        if (line1.getPoint(0).equals(line2.getPoint(0))) {
+          commonPoint = line1.getPoint(0);
+          line1UncommonPoint = line1.getPoint(1);
+          line2UncommonPoint = line2.getPoint(1);
+        }
+        if (line1.getPoint(0).equals(line2.getPoint(1))) {
+          commonPoint = line1.getPoint(0);
+          line1UncommonPoint = line1.getPoint(1);
+          line2UncommonPoint = line2.getPoint(0);
+        }
+        if (line1.getPoint(1).equals(line2.getPoint(0))) {
+          commonPoint = line1.getPoint(1);
+          line1UncommonPoint = line1.getPoint(0);
+          line2UncommonPoint = line2.getPoint(1);
+        }
+        if (line1.getPoint(1).equals(line2.getPoint(1))) {
+          commonPoint = line1.getPoint(1);
+          line1UncommonPoint = line1.getPoint(0);
+          line2UncommonPoint = line2.getPoint(0);
+        }
+        if (commonPoint != null) {
+
+        }
+      }
+    }
+
+    return result;
+  }
+
+  public void connectEndsToAnchors(TextGrid grid, Diagram diagram) {
+    Iterator it = shapes.iterator();
+    while (it.hasNext()) {
+      DiagramShape shape = (DiagramShape) it.next();
+      if (!shape.isClosed()) {
+        shape.connectEndsToAnchors(grid, diagram);
+      }
+    }
+  }
+
+  private static DiagramShape makeLine(TextGrid grid, TextGrid.Cell start, TextGrid.Cell end, int cellWidth, int cellHeight) {
+    DiagramShape line = new DiagramShape();
+
+    if (grid.isHorizontalLine(start)) {
+      if (start.isWestOf(end)) {
+        line.addToPoints(new ShapePoint(
+            Diagram.getCellMinX(start, cellWidth),
+            Diagram.getCellMidY(start, cellHeight)));
+      } else {
+        line.addToPoints(new ShapePoint(
+            Diagram.getCellMaxX(start, cellWidth),
+            Diagram.getCellMidY(start, cellHeight)));
+      }
+    } else if (grid.isVerticalLine(start)) {
+      if (start.isNorthOf(end)) {
+        line.addToPoints(new ShapePoint(
+            Diagram.getCellMidX(start, cellWidth),
+            Diagram.getCellMinY(start, cellHeight)));
+      } else {
+        line.addToPoints(new ShapePoint(
+            Diagram.getCellMidX(start, cellWidth),
+            Diagram.getCellMaxY(start, cellHeight)));
+      }
+    } else { //corner
+      if (DEBUG)
+        System.out.println("Corner");
+      int type = (grid.isRoundCorner(start)) ? ShapePoint.TYPE_ROUND : ShapePoint.TYPE_NORMAL;
+      line.addToPoints(new ShapePoint(
+          Diagram.getCellMidX(start, cellWidth),
+          Diagram.getCellMidY(start, cellHeight),
+          type));
+
+    }
+
+    if (grid.isHorizontalLine(end)) {
+      if (start.isWestOf(start)) {
+        line.addToPoints(new ShapePoint(
+            Diagram.getCellMinX(end, cellWidth),
+            Diagram.getCellMidY(end, cellHeight)));
+      } else {
+        line.addToPoints(new ShapePoint(
+            Diagram.getCellMaxX(end, cellWidth),
+            Diagram.getCellMidY(end, cellHeight)));
+      }
+    } else if (grid.isVerticalLine(end)) {
+      if (start.isNorthOf(start)) {
+        line.addToPoints(new ShapePoint(
+            Diagram.getCellMidX(end, cellWidth),
+            Diagram.getCellMinY(end, cellHeight)));
+      } else {
+        line.addToPoints(new ShapePoint(
+            Diagram.getCellMidX(end, cellWidth),
+            Diagram.getCellMaxY(end, cellHeight)));
+      }
+    } else { //corner
+      int type = (grid.isRoundCorner(end)) ? ShapePoint.TYPE_ROUND : ShapePoint.TYPE_NORMAL;
+      if (DEBUG)
+        System.out.println("Corner");
+      line.addToPoints(new ShapePoint(
+          Diagram.getCellMidX(end, cellWidth),
+          Diagram.getCellMidY(end, cellHeight),
+          type));
+
+    }
+
+    return line;
+  }
+
+  public void addToShapes(DiagramShape shape) {
+    shapes.add(shape);
+  }
+
+  private Iterator getShapesIterator() {
+    return shapes.iterator();
+  }
+
+  public void scale(float factor) {
+    Iterator it = getShapesIterator();
+    while (it.hasNext()) {
+      DiagramShape shape = (DiagramShape) it.next();
+      shape.scale(factor);
+    }
+  }
+
+  /**
+   * @return
+   */
+  public ArrayList getShapes() {
+    return shapes;
+  }
 
 }
 
diff --git a/src/java/org/stathissideris/ascii2image/graphics/CustomShapeDefinition.java b/src/java/org/stathissideris/ascii2image/graphics/CustomShapeDefinition.java
index db513d9..47fcf9c 100755
--- a/src/java/org/stathissideris/ascii2image/graphics/CustomShapeDefinition.java
+++ b/src/java/org/stathissideris/ascii2image/graphics/CustomShapeDefinition.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,65 +15,74 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.graphics;
 
 public class CustomShapeDefinition {
-	private String tag;
-	private boolean stretch = false;
-	private boolean dropShadow = true;
-	private boolean hasBorder = false;
-	private String filename;
-	private String comment;
-	
-	public boolean dropsShadow() {
-		return dropShadow;
-	}
-	public void setDropsShadow(boolean dropShadow) {
-		this.dropShadow = dropShadow;
-	}
-	public String getFilename() {
-		return filename;
-	}
-	public void setFilename(String filename) {
-		this.filename = filename;
-	}
-	public boolean stretches() {
-		return stretch;
-	}
-	public void setStretches(boolean stretch) {
-		this.stretch = stretch;
-	}
-	public boolean hasBorder() {
-		return hasBorder;
-	}
-	public void setHasBorder(boolean hasBorder) {
-		this.hasBorder = hasBorder;
-	}
-	public String getTag() {
-		return tag;
-	}
-	public void setTag(String tag) {
-		this.tag = tag;
-	}
-	
-	public String getComment() {
-		return comment;
-	}
-	public void setComment(String comment) {
-		this.comment = comment;
-	}
-	
-	public String toString(){
-		return
-			"Custom shape: \""+getTag()+"\":\n"
-			+"\tfile: "+getFilename()+"\n"
-			+"\tstretches: "+stretches()+"\n"
-			+"\thas border: "+hasBorder()+"\n"
-			+"\tdrops shadow: "+dropsShadow()+"\n"
-			+"\tcomment: "+getComment()+"\n"
-			;
-	}
-	
+  private String  tag;
+  private boolean stretch    = false;
+  private boolean dropShadow = true;
+  private boolean hasBorder  = false;
+  private String  filename;
+  private String  comment;
+
+  public boolean dropsShadow() {
+    return dropShadow;
+  }
+
+  public void setDropsShadow(boolean dropShadow) {
+    this.dropShadow = dropShadow;
+  }
+
+  public String getFilename() {
+    return filename;
+  }
+
+  public void setFilename(String filename) {
+    this.filename = filename;
+  }
+
+  public boolean stretches() {
+    return stretch;
+  }
+
+  public void setStretches(boolean stretch) {
+    this.stretch = stretch;
+  }
+
+  public boolean hasBorder() {
+    return hasBorder;
+  }
+
+  public void setHasBorder(boolean hasBorder) {
+    this.hasBorder = hasBorder;
+  }
+
+  public String getTag() {
+    return tag;
+  }
+
+  public void setTag(String tag) {
+    this.tag = tag;
+  }
+
+  public String getComment() {
+    return comment;
+  }
+
+  public void setComment(String comment) {
+    this.comment = comment;
+  }
+
+  public String toString() {
+    return
+        "Custom shape: \"" + getTag() + "\":\n"
+            + "\tfile: " + getFilename() + "\n"
+            + "\tstretches: " + stretches() + "\n"
+            + "\thas border: " + hasBorder() + "\n"
+            + "\tdrops shadow: " + dropsShadow() + "\n"
+            + "\tcomment: " + getComment() + "\n"
+        ;
+  }
+
 }
diff --git a/src/java/org/stathissideris/ascii2image/graphics/Diagram.java b/src/java/org/stathissideris/ascii2image/graphics/Diagram.java
index ef3d66b..80cd53c 100644
--- a/src/java/org/stathissideris/ascii2image/graphics/Diagram.java
+++ b/src/java/org/stathissideris/ascii2image/graphics/Diagram.java
@@ -1,10 +1,10 @@
 /**
  * ditaa - Diagrams Through Ascii Art
- * 
+ *
  * Copyright (C) 2004-2011 Efstathios Sideris
  *
  * ditaa is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as 
+ * it under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation, either version 3 of
  * the License, or (at your option) any later version.
  *
@@ -15,16 +15,9 @@
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with ditaa.  If not, see .
- *   
  */
 package org.stathissideris.ascii2image.graphics;
 
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.geom.Rectangle2D;
-import java.util.ArrayList;
-import java.util.Iterator;
-
 import org.stathissideris.ascii2image.core.ConversionOptions;
 import org.stathissideris.ascii2image.core.Pair;
 import org.stathissideris.ascii2image.text.AbstractionGrid;
@@ -35,964 +28,997 @@
 import org.stathissideris.ascii2image.text.TextGrid.CellStringPair;
 import org.stathissideris.ascii2image.text.TextGrid.CellTagPair;
 
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Iterator;
+
 /**
- * 
+ *
  * @author Efstathios Sideris
  */
 public class Diagram {
 
-	private static final boolean DEBUG = false;
-	private static final boolean DEBUG_VERBOSE = false;
-	private static final boolean DEBUG_MAKE_SHAPES = false;
-
-	private ArrayList shapes = new ArrayList();
-	private ArrayList compositeShapes = new ArrayList();
-	private ArrayList textObjects = new ArrayList();
-	
-	private int width, height;
-	private int cellWidth, cellHeight;
-	
-	
-	/**
-	 * 
-	 * 

An outline of the inner workings of this very important (and monstrous) - * constructor is presented here. Boundary processing is the first step - * of the process:

- * - *
    - *
  1. Copy the grid into a work grid and remove all type-on-line - * and point markers from the work grid
  2. - *
  3. Split grid into distinct shapes by plotting the grid - * onto an AbstractionGrid and its getDistinctShapes() method.
  4. - *
  5. Find all the possible boundary sets of each of the - * distinct shapes. This can produce duplicate shapes (if the boundaries - * are the same when filling from the inside and the outside).
  6. - *
  7. Remove duplicate boundaries.
  8. - *
  9. Remove obsolete boundaries. Obsolete boundaries are the ones that are - * the sum of their parts when plotted as filled shapes. (see method - * removeObsoleteShapes())
  10. - *
  11. Separate the found boundary sets to open, closed or mixed - * (See CellSet class on how its done).
  12. - *
  13. Are there any closed boundaries? - *
      - *
    • YES. Subtract all the closed boundaries from each of the - * open ones. That should convert the mixed shapes into open.
    • - *
    • NO. In this (harder) case, we use the method - * breakTrulyMixedBoundaries() of CellSet to break boundaries - * into open and closed shapes (would work in any case, but it's - * probably slower than the other method). This method is based - * on tracing from the lines' ends and splitting when we get to - * an intersection.
    • - *
    - *
  14. - *
  15. If we had to eliminate any mixed shapes, we separate the found - * boundary sets again to open, closed or mixed.
  16. - *
- * - *

At this stage, the boundary processing is all complete and we - * proceed with using those boundaries to create the shapes:

- * - *
    - *
  1. Create closed shapes.
  2. - *
  3. Create open shapes. That's when the line end corrections are - * also applied, concerning the positioning of the ends of lines - * see methods connectEndsToAnchors() and moveEndsToCellEdges() of - * DiagramShape.
  4. - *
  5. Assign color codes to closed shapes.
  6. - *
  7. Assign extended markup tags to closed shapes.

    - *
  8. Create arrowheads.

    - *
  9. Create point markers.

    - *
- * - *

Finally, the text processing occurs: [pending]

- * - * @param grid - * @param cellWidth - * @param cellHeight - */ - public Diagram(TextGrid grid, ConversionOptions options) { - - this.cellWidth = options.renderingOptions.getCellWidth(); - this.cellHeight = options.renderingOptions.getCellHeight(); - - width = grid.getWidth() * cellWidth; - height = grid.getHeight() * cellHeight; - - TextGrid workGrid = new TextGrid(grid); - workGrid.replaceTypeOnLine(); - workGrid.replacePointMarkersOnLine(); - if(DEBUG) workGrid.printDebug(); - - int width = grid.getWidth(); - int height = grid.getHeight(); - - - //split distinct shapes using AbstractionGrid - AbstractionGrid temp = new AbstractionGrid(workGrid, workGrid.getAllBoundaries()); - ArrayList boundarySetsStep1 = temp.getDistinctShapes(); - - if(DEBUG){ - System.out.println("******* Distinct shapes found using AbstractionGrid *******"); - Iterator dit = boundarySetsStep1.iterator(); - while (dit.hasNext()) { - CellSet set = dit.next(); - set.printAsGrid(); - } - System.out.println("******* Same set of shapes after processing them by filling *******"); - } - - - //Find all the boundaries by using the special version of the filling method - //(fills in a different buffer than the buffer it reads from) - ArrayList boundarySetsStep2 = new ArrayList(); - for(CellSet set : boundarySetsStep1) { - //the fill buffer keeps track of which cells have been - //filled already - TextGrid fillBuffer = new TextGrid(width * 3, height * 3); - - for(int yi = 0; yi < height * 3; yi++){ - for(int xi = 0; xi < width * 3; xi++){ - if(fillBuffer.isBlank(xi, yi)){ - - TextGrid copyGrid = new AbstractionGrid(workGrid, set).getCopyOfInternalBuffer(); - - CellSet boundaries = - copyGrid - .findBoundariesExpandingFrom(copyGrid.new Cell(xi, yi)); - if(boundaries.size() == 0) continue; //i'm not sure why these occur - boundarySetsStep2.add(boundaries.makeScaledOneThirdEquivalent()); - - copyGrid = new AbstractionGrid(workGrid, set).getCopyOfInternalBuffer(); - CellSet filled = - copyGrid - .fillContinuousArea(copyGrid.new Cell(xi, yi), '*'); - fillBuffer.fillCellsWith(filled, '*'); - fillBuffer.fillCellsWith(boundaries, '-'); - - if(DEBUG){ - //System.out.println("Fill buffer:"); - //fillBuffer.printDebug(); - boundaries.makeScaledOneThirdEquivalent().printAsGrid(); - System.out.println("-----------------------------------"); - } - - } - } - } - } - - if (DEBUG) - System.out.println("******* Removed duplicates *******"); - - boundarySetsStep2 = CellSet.removeDuplicateSets(boundarySetsStep2); - - if(DEBUG){ - Iterator dit = boundarySetsStep2.iterator(); - while (dit.hasNext()) { - CellSet set = dit.next(); - set.printAsGrid(); - } - } - - int originalSize = boundarySetsStep2.size(); - boundarySetsStep2 = CellSet.removeDuplicateSets(boundarySetsStep2); - if(DEBUG) { - System.out.println( - "******* Removed duplicates: there were " - +originalSize - +" shapes and now there are " - +boundarySetsStep2.size()); - } - - - //split boundaries to open, closed and mixed - - if (DEBUG) - System.out.println("******* First evaluation of openess *******"); - - ArrayList open = new ArrayList(); - ArrayList closed = new ArrayList(); - ArrayList mixed = new ArrayList(); - - Iterator sets = boundarySetsStep2.iterator(); - while(sets.hasNext()){ - CellSet set = (CellSet) sets.next(); - int type = set.getType(workGrid); - if(type == CellSet.TYPE_CLOSED) closed.add(set); - else if(type == CellSet.TYPE_OPEN) open.add(set); - else if(type == CellSet.TYPE_MIXED) mixed.add(set); - if(DEBUG){ - if(type == CellSet.TYPE_CLOSED) System.out.println("Closed boundaries:"); - else if(type == CellSet.TYPE_OPEN) System.out.println("Open boundaries:"); - else if(type == CellSet.TYPE_MIXED) System.out.println("Mixed boundaries:"); - set.printAsGrid(); - } - } - - boolean hadToEliminateMixed = false; - - if(mixed.size() > 0 && closed.size() > 0) { - // mixed shapes can be eliminated by - // subtracting all the closed shapes from them - if (DEBUG) - System.out.println("******* Eliminating mixed shapes (basic algorithm) *******"); - - hadToEliminateMixed = true; - - //subtract from each of the mixed sets all the closed sets - sets = mixed.iterator(); - while(sets.hasNext()){ - CellSet set = (CellSet) sets.next(); - Iterator closedSets = closed.iterator(); - while(closedSets.hasNext()){ - CellSet closedSet = closedSets.next(); - set.subtractSet(closedSet); - } - // this is necessary because some mixed sets produce - // several distinct open sets after you subtract the - // closed sets from them - if(set.getType(workGrid) == CellSet.TYPE_OPEN) { - boundarySetsStep2.remove(set); - boundarySetsStep2.addAll(set.breakIntoDistinctBoundaries(workGrid)); - } - } - - } else if(mixed.size() > 0 && closed.size() == 0) { - // no closed shape exists, will have to - // handle mixed shape on its own - // an example of this case is the following: - // +-----+ - // | A |C B - // + ---+------------------- - // | | - // +-----+ - - hadToEliminateMixed = true; - - if (DEBUG) - System.out.println("******* Eliminating mixed shapes (advanced algorithm for truly mixed shapes) *******"); - - sets = mixed.iterator(); - while(sets.hasNext()){ - CellSet set = (CellSet) sets.next(); - boundarySetsStep2.remove(set); - boundarySetsStep2.addAll(set.breakTrulyMixedBoundaries(workGrid)); - } - - } else { - if (DEBUG) - System.out.println("No mixed shapes found. Skipped mixed shape elimination step"); - } - - - if(hadToEliminateMixed){ - if (DEBUG) - System.out.println("******* Second evaluation of openess *******"); - - //split boundaries again to open, closed and mixed - open = new ArrayList(); - closed = new ArrayList(); - mixed = new ArrayList(); - - sets = boundarySetsStep2.iterator(); - while(sets.hasNext()){ - CellSet set = (CellSet) sets.next(); - int type = set.getType(workGrid); - if(type == CellSet.TYPE_CLOSED) closed.add(set); - else if(type == CellSet.TYPE_OPEN) open.add(set); - else if(type == CellSet.TYPE_MIXED) mixed.add(set); - if(DEBUG){ - if(type == CellSet.TYPE_CLOSED) System.out.println("Closed boundaries:"); - else if(type == CellSet.TYPE_OPEN) System.out.println("Open boundaries:"); - else if(type == CellSet.TYPE_MIXED) System.out.println("Mixed boundaries:"); - set.printAsGrid(); - } - } - } - - boolean removedAnyObsolete = removeObsoleteShapes(workGrid, closed); - - boolean allCornersRound = false; - if(options.processingOptions.areAllCornersRound()) allCornersRound = true; - - //make shapes from the boundary sets - //make closed shapes - if(DEBUG_MAKE_SHAPES) { - System.out.println("***** MAKING SHAPES FROM BOUNDARY SETS *****"); - System.out.println("***** CLOSED: *****"); - } - - ArrayList closedShapes = new ArrayList(); - sets = closed.iterator(); - while(sets.hasNext()){ - CellSet set = (CellSet) sets.next(); - - if(DEBUG_MAKE_SHAPES) { - set.printAsGrid(); - } - - DiagramComponent shape = DiagramComponent.createClosedFromBoundaryCells(workGrid, set, cellWidth, cellHeight, allCornersRound); - if(shape != null){ - if(shape instanceof DiagramShape){ - addToShapes((DiagramShape) shape); - closedShapes.add(shape); - } else if(shape instanceof CompositeDiagramShape) - addToCompositeShapes((CompositeDiagramShape) shape); - } - } - - if(options.processingOptions.performSeparationOfCommonEdges()) - separateCommonEdges(closedShapes); - - //make open shapes - sets = open.iterator(); - while(sets.hasNext()){ - CellSet set = (CellSet) sets.next(); - if(set.size() == 1){ //single cell "shape" - TextGrid.Cell cell = (TextGrid.Cell) set.getFirst(); - if(!grid.cellContainsDashedLineChar(cell)) { - DiagramShape shape = DiagramShape.createSmallLine(workGrid, cell, cellWidth, cellHeight); - if(shape != null) { - addToShapes(shape); - shape.connectEndsToAnchors(workGrid, this); - } - } - } else { //normal shape - if (DEBUG) - System.out.println(set.getCellsAsString()); - - DiagramComponent shape = - CompositeDiagramShape - .createOpenFromBoundaryCells( - workGrid, set, cellWidth, cellHeight, allCornersRound); - - if(shape != null){ - if(shape instanceof CompositeDiagramShape){ - addToCompositeShapes((CompositeDiagramShape) shape); - ((CompositeDiagramShape) shape).connectEndsToAnchors(workGrid, this); - } else if(shape instanceof DiagramShape) { - addToShapes((DiagramShape) shape); - ((DiagramShape) shape).connectEndsToAnchors(workGrid, this); - ((DiagramShape) shape).moveEndsToCellEdges(grid, this); - } - } - - } - } - - //assign color codes to shapes - //TODO: text on line should not change its color - - Iterator cellColorPairs = grid.findColorCodes().iterator(); - while(cellColorPairs.hasNext()){ - TextGrid.CellColorPair pair = - (TextGrid.CellColorPair) cellColorPairs.next(); - - ShapePoint point = - new ShapePoint(getCellMidX(pair.cell), getCellMidY(pair.cell)); - DiagramShape containingShape = findSmallestShapeContaining(point); - - if(containingShape != null) - containingShape.setFillColor(pair.color); - } - - //assign markup to shapes - Iterator cellTagPairs = grid.findMarkupTags().iterator(); - while(cellTagPairs.hasNext()){ - TextGrid.CellTagPair pair = - (TextGrid.CellTagPair) cellTagPairs.next(); - - ShapePoint point = - new ShapePoint(getCellMidX(pair.cell), getCellMidY(pair.cell)); - - DiagramShape containingShape = findSmallestShapeContaining(point); - - //this tag is not within a shape, skip - if(containingShape == null) continue; - - //TODO: the code below could be a lot more concise - if(pair.tag.equals("d")){ - CustomShapeDefinition def = - options.processingOptions.getFromCustomShapes("d"); - if(def == null) - containingShape.setType(DiagramShape.TYPE_DOCUMENT); - else { - containingShape.setType(DiagramShape.TYPE_CUSTOM); - containingShape.setDefinition(def); - } - } else if(pair.tag.equals("s")){ - CustomShapeDefinition def = - options.processingOptions.getFromCustomShapes("s"); - if(def == null) - containingShape.setType(DiagramShape.TYPE_STORAGE); - else { - containingShape.setType(DiagramShape.TYPE_CUSTOM); - containingShape.setDefinition(def); - } - } else if(pair.tag.equals("io")){ - CustomShapeDefinition def = - options.processingOptions.getFromCustomShapes("io"); - if(def == null) - containingShape.setType(DiagramShape.TYPE_IO); - else { - containingShape.setType(DiagramShape.TYPE_CUSTOM); - containingShape.setDefinition(def); - } - } else if(pair.tag.equals("c")){ - CustomShapeDefinition def = - options.processingOptions.getFromCustomShapes("c"); - if(def == null) - containingShape.setType(DiagramShape.TYPE_DECISION); - else { - containingShape.setType(DiagramShape.TYPE_CUSTOM); - containingShape.setDefinition(def); - } - } else if(pair.tag.equals("mo")){ - CustomShapeDefinition def = - options.processingOptions.getFromCustomShapes("mo"); - if(def == null) - containingShape.setType(DiagramShape.TYPE_MANUAL_OPERATION); - else { - containingShape.setType(DiagramShape.TYPE_CUSTOM); - containingShape.setDefinition(def); - } - } else if(pair.tag.equals("tr")){ - CustomShapeDefinition def = - options.processingOptions.getFromCustomShapes("tr"); - if(def == null) - containingShape.setType(DiagramShape.TYPE_TRAPEZOID); - else { - containingShape.setType(DiagramShape.TYPE_CUSTOM); - containingShape.setDefinition(def); - } - } else if(pair.tag.equals("o")){ - CustomShapeDefinition def = - options.processingOptions.getFromCustomShapes("o"); - if(def == null) - containingShape.setType(DiagramShape.TYPE_ELLIPSE); - else { - containingShape.setType(DiagramShape.TYPE_CUSTOM); - containingShape.setDefinition(def); - } - } else { - CustomShapeDefinition def = - options.processingOptions.getFromCustomShapes(pair.tag); - containingShape.setType(DiagramShape.TYPE_CUSTOM); - containingShape.setDefinition(def); - } - } - - //make arrowheads - Iterator arrowheadCells = workGrid.findArrowheads().iterator(); - while(arrowheadCells.hasNext()){ - TextGrid.Cell cell = arrowheadCells.next(); - DiagramShape arrowhead = DiagramShape.createArrowhead(workGrid, cell, cellWidth, cellHeight); - if(arrowhead != null) addToShapes(arrowhead); - else System.err.println("Could not create arrowhead shape. Unexpected error."); - } - - //make point markers - Iterator markersIt = grid.getPointMarkersOnLine().iterator(); - while (markersIt.hasNext()) { - TextGrid.Cell cell = markersIt.next(); - - DiagramShape mark = new DiagramShape(); - mark.addToPoints(new ShapePoint( - getCellMidX(cell), - getCellMidY(cell) - )); - mark.setType(DiagramShape.TYPE_POINT_MARKER); - mark.setFillColor(Color.white); - shapes.add(mark); - } - - removeDuplicateShapes(); - - if(DEBUG) System.out.println("Shape count: "+shapes.size()); - if(DEBUG) System.out.println("Composite shape count: "+compositeShapes.size()); - - //copy again - workGrid = new TextGrid(grid); - workGrid.removeNonText(); - - - // ****** handle text ******* - //break up text into groups - TextGrid textGroupGrid = new TextGrid(workGrid); - CellSet gaps = textGroupGrid.getAllBlanksBetweenCharacters(); - //kludge - textGroupGrid.fillCellsWith(gaps, '|'); - CellSet nonBlank = textGroupGrid.getAllNonBlank(); - ArrayList textGroups = nonBlank.breakIntoDistinctBoundaries(); - if(DEBUG) System.out.println(textGroups.size()+" text groups found"); - - Font font = FontMeasurer.instance().getFontFor(cellHeight); - - Iterator textGroupIt = textGroups.iterator(); - while(textGroupIt.hasNext()){ - CellSet textGroupCellSet = (CellSet) textGroupIt.next(); - - TextGrid isolationGrid = new TextGrid(width, height); - workGrid.copyCellsTo(textGroupCellSet, isolationGrid); - - ArrayList strings = isolationGrid.findStrings(); - Iterator it = strings.iterator(); - while(it.hasNext()){ - TextGrid.CellStringPair pair = it.next(); - TextGrid.Cell cell = pair.cell; - String string = pair.string; - if (DEBUG) - System.out.println("Found string "+string); - TextGrid.Cell lastCell = isolationGrid.new Cell(cell.x + string.length() - 1, cell.y); - - int minX = getCellMinX(cell); - int y = getCellMaxY(cell); - int maxX = getCellMaxX(lastCell); - - DiagramText textObject; - if(FontMeasurer.instance().getWidthFor(string, font) > maxX - minX){ //does not fit horizontally - Font lessWideFont = FontMeasurer.instance().getFontFor(maxX - minX, string); - textObject = new DiagramText(minX, y, string, lessWideFont); - } else textObject = new DiagramText(minX, y, string, font); - - textObject.centerVerticallyBetween(getCellMinY(cell), getCellMaxY(cell)); - - //TODO: if the strings start with bullets they should be aligned to the left - - //position text correctly - int otherStart = isolationGrid.otherStringsStartInTheSameColumn(cell); - int otherEnd = isolationGrid.otherStringsEndInTheSameColumn(lastCell); - if(0 == otherStart && 0 == otherEnd) { - textObject.centerHorizontallyBetween(minX, maxX); - } else if(otherEnd > 0 && otherStart == 0) { - textObject.alignRightEdgeTo(maxX); - } else if(otherEnd > 0 && otherStart > 0){ - if(otherEnd > otherStart){ - textObject.alignRightEdgeTo(maxX); - } else if(otherEnd == otherStart){ - textObject.centerHorizontallyBetween(minX, maxX); - } - } - - addToTextObjects(textObject); - } - } - - if (DEBUG) - System.out.println("Positioned text"); - - //correct the color of the text objects according - //to the underlying color - for(DiagramText textObject : getTextObjects()) { - DiagramShape shape = findSmallestShapeIntersecting(textObject.getBounds()); - if(shape != null - && shape.getFillColor() != null - && BitmapRenderer.isColorDark(shape.getFillColor())) { - textObject.setColor(Color.white); - } - } - - //set outline to true for test within custom shapes - Iterator shapes = this.getAllDiagramShapes().iterator(); - while(shapes.hasNext()){ - DiagramShape shape = (DiagramShape) shapes.next(); - if(shape.getType() == DiagramShape.TYPE_CUSTOM){ - Iterator textObjects = getTextObjects().iterator(); - while(textObjects.hasNext()){ - DiagramText textObject = (DiagramText) textObjects.next(); - textObject.setHasOutline(true); - textObject.setColor(DiagramText.DEFAULT_COLOR); - } - } - } - - if (DEBUG) - System.out.println("Corrected color of text according to underlying color"); - - } - - /** - * Returns a list of all DiagramShapes in the Diagram, including - * the ones within CompositeDiagramShapes - * - * @return - */ - public ArrayList getAllDiagramShapes(){ - ArrayList shapes = new ArrayList(); - shapes.addAll(this.getShapes()); - - for(CompositeDiagramShape compShape : getCompositeShapes()) { - shapes.addAll(compShape.getShapes()); - } - return shapes; - } - - /** - * Removes the sets from setsthat are the sum of their parts - * when plotted as filled shapes. - * - * @return true if it removed any obsolete. - * - */ - private boolean removeObsoleteShapes(TextGrid grid, ArrayList sets){ - if (DEBUG) - System.out.println("******* Removing obsolete shapes *******"); - - boolean removedAny = false; - - ArrayList filledSets = new ArrayList(); - - Iterator it; - - if(DEBUG_VERBOSE) { - System.out.println("******* Sets before *******"); - it = sets.iterator(); - while(it.hasNext()){ - CellSet set = (CellSet) it.next(); - set.printAsGrid(); - } - } - - //make filled versions of all the boundary sets - it = sets.iterator(); - while(it.hasNext()){ - CellSet set = (CellSet) it.next(); - set = set.getFilledEquivalent(grid); - if(set == null){ - return false; - } else filledSets.add(set); - } - - ArrayList toBeRemovedIndices = new ArrayList(); - it = filledSets.iterator(); - while(it.hasNext()){ - CellSet set = (CellSet) it.next(); - - if(DEBUG_VERBOSE){ - System.out.println("*** Deciding if the following should be removed:"); - set.printAsGrid(); - } - - //find the other sets that have common cells with set - ArrayList common = new ArrayList(); - common.add(set); - Iterator it2 = filledSets.iterator(); - while(it2.hasNext()){ - CellSet set2 = (CellSet) it2.next(); - if(set != set2 && set.hasCommonCells(set2)){ - common.add(set2); - } - } - //it only makes sense for more than 2 sets - if(common.size() == 2) continue; - - //find largest set - CellSet largest = set; - it2 = common.iterator(); - while(it2.hasNext()){ - CellSet set2 = (CellSet) it2.next(); - if(set2.size() > largest.size()){ - largest = set2; - } - } - - if(DEBUG_VERBOSE){ - System.out.println("Largest:"); - largest.printAsGrid(); - } - - //see if largest is sum of others - common.remove(largest); - - //make the sum set of the small sets on a grid - TextGrid gridOfSmalls = new TextGrid(largest.getMaxX() + 2, largest.getMaxY() + 2); - CellSet sumOfSmall = new CellSet(); - it2 = common.iterator(); - while(it2.hasNext()){ - CellSet set2 = (CellSet) it2.next(); - if(DEBUG_VERBOSE){ - System.out.println("One of smalls:"); - set2.printAsGrid(); - } - gridOfSmalls.fillCellsWith(set2, '*'); - } - if(DEBUG_VERBOSE){ - System.out.println("Sum of smalls:"); - gridOfSmalls.printDebug(); - } - TextGrid gridLargest = new TextGrid(largest.getMaxX() + 2, largest.getMaxY() + 2); - gridLargest.fillCellsWith(largest, '*'); - - int index = filledSets.indexOf(largest); - if(gridLargest.equals(gridOfSmalls) - && !toBeRemovedIndices.contains(new Integer(index))) { - toBeRemovedIndices.add(new Integer(index)); - if (DEBUG){ - System.out.println("Decided to remove set:"); - largest.printAsGrid(); - } - } /*else if (DEBUG){ + private static final boolean DEBUG = false; + private static final boolean DEBUG_VERBOSE = false; + private static final boolean DEBUG_MAKE_SHAPES = false; + + private ArrayList shapes = new ArrayList(); + private ArrayList compositeShapes = new ArrayList(); + private ArrayList textObjects = new ArrayList(); + + private int width, height; + private int cellWidth, cellHeight; + + + /** + * + *

An outline of the inner workings of this very important (and monstrous) + * constructor is presented here. Boundary processing is the first step + * of the process:

+ * + *
    + *
  1. Copy the grid into a work grid and remove all type-on-line + * and point markers from the work grid
  2. + *
  3. Split grid into distinct shapes by plotting the grid + * onto an AbstractionGrid and its getDistinctShapes() method.
  4. + *
  5. Find all the possible boundary sets of each of the + * distinct shapes. This can produce duplicate shapes (if the boundaries + * are the same when filling from the inside and the outside).
  6. + *
  7. Remove duplicate boundaries.
  8. + *
  9. Remove obsolete boundaries. Obsolete boundaries are the ones that are + * the sum of their parts when plotted as filled shapes. (see method + * removeObsoleteShapes())
  10. + *
  11. Separate the found boundary sets to open, closed or mixed + * (See CellSet class on how its done).
  12. + *
  13. Are there any closed boundaries? + *
      + *
    • YES. Subtract all the closed boundaries from each of the + * open ones. That should convert the mixed shapes into open.
    • + *
    • NO. In this (harder) case, we use the method + * breakTrulyMixedBoundaries() of CellSet to break boundaries + * into open and closed shapes (would work in any case, but it's + * probably slower than the other method). This method is based + * on tracing from the lines' ends and splitting when we get to + * an intersection.
    • + *
    + *
  14. + *
  15. If we had to eliminate any mixed shapes, we separate the found + * boundary sets again to open, closed or mixed.
  16. + *
+ * + *

At this stage, the boundary processing is all complete and we + * proceed with using those boundaries to create the shapes:

+ * + *
    + *
  1. Create closed shapes.
  2. + *
  3. Create open shapes. That's when the line end corrections are + * also applied, concerning the positioning of the ends of lines + * see methods connectEndsToAnchors() and moveEndsToCellEdges() of + * DiagramShape.
  4. + *
  5. Assign color codes to closed shapes.
  6. + *
  7. Assign extended markup tags to closed shapes.

    + *
  8. Create arrowheads.

    + *
  9. Create point markers.

    + *
+ * + *

Finally, the text processing occurs: [pending]

+ * + * @param grid + * @param cellWidth + * @param cellHeight + */ + public Diagram(TextGrid grid, ConversionOptions options) { + + this.cellWidth = options.renderingOptions.getCellWidth(); + this.cellHeight = options.renderingOptions.getCellHeight(); + + width = grid.getWidth() * cellWidth; + height = grid.getHeight() * cellHeight; + + TextGrid workGrid = new TextGrid(grid); + workGrid.replaceTypeOnLine(); + workGrid.replacePointMarkersOnLine(); + if (DEBUG) + workGrid.printDebug(); + + int width = grid.getWidth(); + int height = grid.getHeight(); + + //split distinct shapes using AbstractionGrid + AbstractionGrid temp = new AbstractionGrid(workGrid, workGrid.getAllBoundaries()); + ArrayList boundarySetsStep1 = temp.getDistinctShapes(); + + if (DEBUG) { + System.out.println("******* Distinct shapes found using AbstractionGrid *******"); + Iterator dit = boundarySetsStep1.iterator(); + while (dit.hasNext()) { + CellSet set = dit.next(); + set.printAsGrid(); + } + System.out.println("******* Same set of shapes after processing them by filling *******"); + } + + //Find all the boundaries by using the special version of the filling method + //(fills in a different buffer than the buffer it reads from) + ArrayList boundarySetsStep2 = new ArrayList(); + for (CellSet set : boundarySetsStep1) { + //the fill buffer keeps track of which cells have been + //filled already + TextGrid fillBuffer = new TextGrid(width * 3, height * 3); + + for (int yi = 0; yi < height * 3; yi++) { + for (int xi = 0; xi < width * 3; xi++) { + if (fillBuffer.isBlank(xi, yi)) { + + TextGrid copyGrid = new AbstractionGrid(workGrid, set).getCopyOfInternalBuffer(); + + CellSet boundaries = + copyGrid + .findBoundariesExpandingFrom(copyGrid.new Cell(xi, yi)); + if (boundaries.size() == 0) + continue; //i'm not sure why these occur + boundarySetsStep2.add(boundaries.makeScaledOneThirdEquivalent()); + + copyGrid = new AbstractionGrid(workGrid, set).getCopyOfInternalBuffer(); + CellSet filled = + copyGrid + .fillContinuousArea(copyGrid.new Cell(xi, yi), '*'); + fillBuffer.fillCellsWith(filled, '*'); + fillBuffer.fillCellsWith(boundaries, '-'); + + if (DEBUG) { + //System.out.println("Fill buffer:"); + //fillBuffer.printDebug(); + boundaries.makeScaledOneThirdEquivalent().printAsGrid(); + System.out.println("-----------------------------------"); + } + + } + } + } + } + + if (DEBUG) + System.out.println("******* Removed duplicates *******"); + + boundarySetsStep2 = CellSet.removeDuplicateSets(boundarySetsStep2); + + if (DEBUG) { + Iterator dit = boundarySetsStep2.iterator(); + while (dit.hasNext()) { + CellSet set = dit.next(); + set.printAsGrid(); + } + } + + int originalSize = boundarySetsStep2.size(); + boundarySetsStep2 = CellSet.removeDuplicateSets(boundarySetsStep2); + if (DEBUG) { + System.out.println( + "******* Removed duplicates: there were " + + originalSize + + " shapes and now there are " + + boundarySetsStep2.size()); + } + + //split boundaries to open, closed and mixed + + if (DEBUG) + System.out.println("******* First evaluation of openess *******"); + + ArrayList open = new ArrayList(); + ArrayList closed = new ArrayList(); + ArrayList mixed = new ArrayList(); + + Iterator sets = boundarySetsStep2.iterator(); + while (sets.hasNext()) { + CellSet set = (CellSet) sets.next(); + int type = set.getType(workGrid); + if (type == CellSet.TYPE_CLOSED) + closed.add(set); + else if (type == CellSet.TYPE_OPEN) + open.add(set); + else if (type == CellSet.TYPE_MIXED) + mixed.add(set); + if (DEBUG) { + if (type == CellSet.TYPE_CLOSED) + System.out.println("Closed boundaries:"); + else if (type == CellSet.TYPE_OPEN) + System.out.println("Open boundaries:"); + else if (type == CellSet.TYPE_MIXED) + System.out.println("Mixed boundaries:"); + set.printAsGrid(); + } + } + + boolean hadToEliminateMixed = false; + + if (mixed.size() > 0 && closed.size() > 0) { + // mixed shapes can be eliminated by + // subtracting all the closed shapes from them + if (DEBUG) + System.out.println("******* Eliminating mixed shapes (basic algorithm) *******"); + + hadToEliminateMixed = true; + + //subtract from each of the mixed sets all the closed sets + sets = mixed.iterator(); + while (sets.hasNext()) { + CellSet set = (CellSet) sets.next(); + Iterator closedSets = closed.iterator(); + while (closedSets.hasNext()) { + CellSet closedSet = closedSets.next(); + set.subtractSet(closedSet); + } + // this is necessary because some mixed sets produce + // several distinct open sets after you subtract the + // closed sets from them + if (set.getType(workGrid) == CellSet.TYPE_OPEN) { + boundarySetsStep2.remove(set); + boundarySetsStep2.addAll(set.breakIntoDistinctBoundaries(workGrid)); + } + } + + } else if (mixed.size() > 0 && closed.size() == 0) { + // no closed shape exists, will have to + // handle mixed shape on its own + // an example of this case is the following: + // +-----+ + // | A |C B + // + ---+------------------- + // | | + // +-----+ + + hadToEliminateMixed = true; + + if (DEBUG) + System.out.println("******* Eliminating mixed shapes (advanced algorithm for truly mixed shapes) *******"); + + sets = mixed.iterator(); + while (sets.hasNext()) { + CellSet set = (CellSet) sets.next(); + boundarySetsStep2.remove(set); + boundarySetsStep2.addAll(set.breakTrulyMixedBoundaries(workGrid)); + } + + } else { + if (DEBUG) + System.out.println("No mixed shapes found. Skipped mixed shape elimination step"); + } + + if (hadToEliminateMixed) { + if (DEBUG) + System.out.println("******* Second evaluation of openess *******"); + + //split boundaries again to open, closed and mixed + open = new ArrayList(); + closed = new ArrayList(); + mixed = new ArrayList(); + + sets = boundarySetsStep2.iterator(); + while (sets.hasNext()) { + CellSet set = (CellSet) sets.next(); + int type = set.getType(workGrid); + if (type == CellSet.TYPE_CLOSED) + closed.add(set); + else if (type == CellSet.TYPE_OPEN) + open.add(set); + else if (type == CellSet.TYPE_MIXED) + mixed.add(set); + if (DEBUG) { + if (type == CellSet.TYPE_CLOSED) + System.out.println("Closed boundaries:"); + else if (type == CellSet.TYPE_OPEN) + System.out.println("Open boundaries:"); + else if (type == CellSet.TYPE_MIXED) + System.out.println("Mixed boundaries:"); + set.printAsGrid(); + } + } + } + + boolean removedAnyObsolete = removeObsoleteShapes(workGrid, closed); + + boolean allCornersRound = false; + if (options.processingOptions.areAllCornersRound()) + allCornersRound = true; + + //make shapes from the boundary sets + //make closed shapes + if (DEBUG_MAKE_SHAPES) { + System.out.println("***** MAKING SHAPES FROM BOUNDARY SETS *****"); + System.out.println("***** CLOSED: *****"); + } + + ArrayList closedShapes = new ArrayList(); + sets = closed.iterator(); + while (sets.hasNext()) { + CellSet set = (CellSet) sets.next(); + + if (DEBUG_MAKE_SHAPES) { + set.printAsGrid(); + } + + DiagramComponent shape = DiagramComponent.createClosedFromBoundaryCells(workGrid, set, cellWidth, cellHeight, allCornersRound); + if (shape != null) { + if (shape instanceof DiagramShape) { + addToShapes((DiagramShape) shape); + closedShapes.add(shape); + } else if (shape instanceof CompositeDiagramShape) + addToCompositeShapes((CompositeDiagramShape) shape); + } + } + + if (options.processingOptions.performSeparationOfCommonEdges()) + separateCommonEdges(closedShapes); + + //make open shapes + sets = open.iterator(); + while (sets.hasNext()) { + CellSet set = (CellSet) sets.next(); + if (set.size() == 1) { //single cell "shape" + TextGrid.Cell cell = (TextGrid.Cell) set.getFirst(); + if (!grid.cellContainsDashedLineChar(cell)) { + DiagramShape shape = DiagramShape.createSmallLine(workGrid, cell, cellWidth, cellHeight); + if (shape != null) { + addToShapes(shape); + shape.connectEndsToAnchors(workGrid, this); + } + } + } else { //normal shape + if (DEBUG) + System.out.println(set.getCellsAsString()); + + DiagramComponent shape = + CompositeDiagramShape + .createOpenFromBoundaryCells( + workGrid, set, cellWidth, cellHeight, allCornersRound); + + if (shape != null) { + if (shape instanceof CompositeDiagramShape) { + addToCompositeShapes((CompositeDiagramShape) shape); + ((CompositeDiagramShape) shape).connectEndsToAnchors(workGrid, this); + } else if (shape instanceof DiagramShape) { + addToShapes((DiagramShape) shape); + ((DiagramShape) shape).connectEndsToAnchors(workGrid, this); + ((DiagramShape) shape).moveEndsToCellEdges(grid, this); + } + } + + } + } + + //assign color codes to shapes + //TODO: text on line should not change its color + + Iterator cellColorPairs = grid.findColorCodes().iterator(); + while (cellColorPairs.hasNext()) { + TextGrid.CellColorPair pair = + (TextGrid.CellColorPair) cellColorPairs.next(); + + ShapePoint point = + new ShapePoint(getCellMidX(pair.cell), getCellMidY(pair.cell)); + DiagramShape containingShape = findSmallestShapeContaining(point); + + if (containingShape != null) + containingShape.setFillColor(pair.color); + } + + //assign markup to shapes + Iterator cellTagPairs = grid.findMarkupTags().iterator(); + while (cellTagPairs.hasNext()) { + TextGrid.CellTagPair pair = + (TextGrid.CellTagPair) cellTagPairs.next(); + + ShapePoint point = + new ShapePoint(getCellMidX(pair.cell), getCellMidY(pair.cell)); + + DiagramShape containingShape = findSmallestShapeContaining(point); + + //this tag is not within a shape, skip + if (containingShape == null) + continue; + + //TODO: the code below could be a lot more concise + if (pair.tag.equals("d")) { + CustomShapeDefinition def = + options.processingOptions.getFromCustomShapes("d"); + if (def == null) + containingShape.setType(DiagramShape.TYPE_DOCUMENT); + else { + containingShape.setType(DiagramShape.TYPE_CUSTOM); + containingShape.setDefinition(def); + } + } else if (pair.tag.equals("s")) { + CustomShapeDefinition def = + options.processingOptions.getFromCustomShapes("s"); + if (def == null) + containingShape.setType(DiagramShape.TYPE_STORAGE); + else { + containingShape.setType(DiagramShape.TYPE_CUSTOM); + containingShape.setDefinition(def); + } + } else if (pair.tag.equals("io")) { + CustomShapeDefinition def = + options.processingOptions.getFromCustomShapes("io"); + if (def == null) + containingShape.setType(DiagramShape.TYPE_IO); + else { + containingShape.setType(DiagramShape.TYPE_CUSTOM); + containingShape.setDefinition(def); + } + } else if (pair.tag.equals("c")) { + CustomShapeDefinition def = + options.processingOptions.getFromCustomShapes("c"); + if (def == null) + containingShape.setType(DiagramShape.TYPE_DECISION); + else { + containingShape.setType(DiagramShape.TYPE_CUSTOM); + containingShape.setDefinition(def); + } + } else if (pair.tag.equals("mo")) { + CustomShapeDefinition def = + options.processingOptions.getFromCustomShapes("mo"); + if (def == null) + containingShape.setType(DiagramShape.TYPE_MANUAL_OPERATION); + else { + containingShape.setType(DiagramShape.TYPE_CUSTOM); + containingShape.setDefinition(def); + } + } else if (pair.tag.equals("tr")) { + CustomShapeDefinition def = + options.processingOptions.getFromCustomShapes("tr"); + if (def == null) + containingShape.setType(DiagramShape.TYPE_TRAPEZOID); + else { + containingShape.setType(DiagramShape.TYPE_CUSTOM); + containingShape.setDefinition(def); + } + } else if (pair.tag.equals("o")) { + CustomShapeDefinition def = + options.processingOptions.getFromCustomShapes("o"); + if (def == null) + containingShape.setType(DiagramShape.TYPE_ELLIPSE); + else { + containingShape.setType(DiagramShape.TYPE_CUSTOM); + containingShape.setDefinition(def); + } + } else { + CustomShapeDefinition def = + options.processingOptions.getFromCustomShapes(pair.tag); + containingShape.setType(DiagramShape.TYPE_CUSTOM); + containingShape.setDefinition(def); + } + } + + //make arrowheads + Iterator arrowheadCells = workGrid.findArrowheads().iterator(); + while (arrowheadCells.hasNext()) { + TextGrid.Cell cell = arrowheadCells.next(); + DiagramShape arrowhead = DiagramShape.createArrowhead(workGrid, cell, cellWidth, cellHeight); + if (arrowhead != null) + addToShapes(arrowhead); + else + System.err.println("Could not create arrowhead shape. Unexpected error."); + } + + //make point markers + Iterator markersIt = grid.getPointMarkersOnLine().iterator(); + while (markersIt.hasNext()) { + TextGrid.Cell cell = markersIt.next(); + + DiagramShape mark = new DiagramShape(); + mark.addToPoints(new ShapePoint( + getCellMidX(cell), + getCellMidY(cell) + )); + mark.setType(DiagramShape.TYPE_POINT_MARKER); + mark.setFillColor(Color.white); + shapes.add(mark); + } + + removeDuplicateShapes(); + + if (DEBUG) + System.out.println("Shape count: " + shapes.size()); + if (DEBUG) + System.out.println("Composite shape count: " + compositeShapes.size()); + + //copy again + workGrid = new TextGrid(grid); + workGrid.removeNonText(); + + // ****** handle text ******* + //break up text into groups + TextGrid textGroupGrid = new TextGrid(workGrid); + CellSet gaps = textGroupGrid.getAllBlanksBetweenCharacters(); + //kludge + textGroupGrid.fillCellsWith(gaps, '|'); + CellSet nonBlank = textGroupGrid.getAllNonBlank(); + ArrayList textGroups = nonBlank.breakIntoDistinctBoundaries(); + if (DEBUG) + System.out.println(textGroups.size() + " text groups found"); + + Font font = FontMeasurer.instance().getFontFor(cellHeight); + + Iterator textGroupIt = textGroups.iterator(); + while (textGroupIt.hasNext()) { + CellSet textGroupCellSet = (CellSet) textGroupIt.next(); + + TextGrid isolationGrid = new TextGrid(width, height); + workGrid.copyCellsTo(textGroupCellSet, isolationGrid); + + ArrayList strings = isolationGrid.findStrings(); + Iterator it = strings.iterator(); + while (it.hasNext()) { + TextGrid.CellStringPair pair = it.next(); + TextGrid.Cell cell = pair.cell; + String string = pair.string; + if (DEBUG) + System.out.println("Found string " + string); + TextGrid.Cell lastCell = isolationGrid.new Cell(cell.x + string.length() - 1, cell.y); + + int minX = getCellMinX(cell); + int y = getCellMaxY(cell); + int maxX = getCellMaxX(lastCell); + + DiagramText textObject; + if (FontMeasurer.instance().getWidthFor(string, font) > maxX - minX) { //does not fit horizontally + Font lessWideFont = FontMeasurer.instance().getFontFor(maxX - minX, string); + textObject = new DiagramText(minX, y, string, lessWideFont); + } else + textObject = new DiagramText(minX, y, string, font); + + textObject.centerVerticallyBetween(getCellMinY(cell), getCellMaxY(cell)); + + //TODO: if the strings start with bullets they should be aligned to the left + + //position text correctly + int otherStart = isolationGrid.otherStringsStartInTheSameColumn(cell); + int otherEnd = isolationGrid.otherStringsEndInTheSameColumn(lastCell); + if (0 == otherStart && 0 == otherEnd) { + textObject.centerHorizontallyBetween(minX, maxX); + } else if (otherEnd > 0 && otherStart == 0) { + textObject.alignRightEdgeTo(maxX); + } else if (otherEnd > 0 && otherStart > 0) { + if (otherEnd > otherStart) { + textObject.alignRightEdgeTo(maxX); + } else if (otherEnd == otherStart) { + textObject.centerHorizontallyBetween(minX, maxX); + } + } + + addToTextObjects(textObject); + } + } + + if (DEBUG) + System.out.println("Positioned text"); + + //correct the color of the text objects according + //to the underlying color + for (DiagramText textObject : getTextObjects()) { + DiagramShape shape = findSmallestShapeIntersecting(textObject.getBounds()); + if (shape != null + && shape.getFillColor() != null + && BitmapRenderer.isColorDark(shape.getFillColor())) { + textObject.setColor(Color.white); + } + } + + //set outline to true for test within custom shapes + Iterator shapes = this.getAllDiagramShapes().iterator(); + while (shapes.hasNext()) { + DiagramShape shape = (DiagramShape) shapes.next(); + if (shape.getType() == DiagramShape.TYPE_CUSTOM) { + Iterator textObjects = getTextObjects().iterator(); + while (textObjects.hasNext()) { + DiagramText textObject = (DiagramText) textObjects.next(); + textObject.setHasOutline(true); + textObject.setColor(DiagramText.DEFAULT_COLOR); + } + } + } + + if (DEBUG) + System.out.println("Corrected color of text according to underlying color"); + + } + + /** + * Returns a list of all DiagramShapes in the Diagram, including + * the ones within CompositeDiagramShapes + * + * @return + */ + public ArrayList getAllDiagramShapes() { + ArrayList shapes = new ArrayList(); + shapes.addAll(this.getShapes()); + + for (CompositeDiagramShape compShape : getCompositeShapes()) { + shapes.addAll(compShape.getShapes()); + } + return shapes; + } + + /** + * Removes the sets from setsthat are the sum of their parts + * when plotted as filled shapes. + * + * @return true if it removed any obsolete. + * + */ + private boolean removeObsoleteShapes(TextGrid grid, ArrayList sets) { + if (DEBUG) + System.out.println("******* Removing obsolete shapes *******"); + + boolean removedAny = false; + + ArrayList filledSets = new ArrayList(); + + Iterator it; + + if (DEBUG_VERBOSE) { + System.out.println("******* Sets before *******"); + it = sets.iterator(); + while (it.hasNext()) { + CellSet set = (CellSet) it.next(); + set.printAsGrid(); + } + } + + //make filled versions of all the boundary sets + it = sets.iterator(); + while (it.hasNext()) { + CellSet set = (CellSet) it.next(); + set = set.getFilledEquivalent(grid); + if (set == null) { + return false; + } else + filledSets.add(set); + } + + ArrayList toBeRemovedIndices = new ArrayList(); + it = filledSets.iterator(); + while (it.hasNext()) { + CellSet set = (CellSet) it.next(); + + if (DEBUG_VERBOSE) { + System.out.println("*** Deciding if the following should be removed:"); + set.printAsGrid(); + } + + //find the other sets that have common cells with set + ArrayList common = new ArrayList(); + common.add(set); + Iterator it2 = filledSets.iterator(); + while (it2.hasNext()) { + CellSet set2 = (CellSet) it2.next(); + if (set != set2 && set.hasCommonCells(set2)) { + common.add(set2); + } + } + //it only makes sense for more than 2 sets + if (common.size() == 2) + continue; + + //find largest set + CellSet largest = set; + it2 = common.iterator(); + while (it2.hasNext()) { + CellSet set2 = (CellSet) it2.next(); + if (set2.size() > largest.size()) { + largest = set2; + } + } + + if (DEBUG_VERBOSE) { + System.out.println("Largest:"); + largest.printAsGrid(); + } + + //see if largest is sum of others + common.remove(largest); + + //make the sum set of the small sets on a grid + TextGrid gridOfSmalls = new TextGrid(largest.getMaxX() + 2, largest.getMaxY() + 2); + CellSet sumOfSmall = new CellSet(); + it2 = common.iterator(); + while (it2.hasNext()) { + CellSet set2 = (CellSet) it2.next(); + if (DEBUG_VERBOSE) { + System.out.println("One of smalls:"); + set2.printAsGrid(); + } + gridOfSmalls.fillCellsWith(set2, '*'); + } + if (DEBUG_VERBOSE) { + System.out.println("Sum of smalls:"); + gridOfSmalls.printDebug(); + } + TextGrid gridLargest = new TextGrid(largest.getMaxX() + 2, largest.getMaxY() + 2); + gridLargest.fillCellsWith(largest, '*'); + + int index = filledSets.indexOf(largest); + if (gridLargest.equals(gridOfSmalls) + && !toBeRemovedIndices.contains(new Integer(index))) { + toBeRemovedIndices.add(new Integer(index)); + if (DEBUG) { + System.out.println("Decided to remove set:"); + largest.printAsGrid(); + } + } /*else if (DEBUG){ System.out.println("This set WILL NOT be removed:"); largest.printAsGrid(); }*/ - //if(gridLargest.equals(gridOfSmalls)) toBeRemovedIndices.add(new Integer(index)); - } - - ArrayList setsToBeRemoved = new ArrayList(); - it = toBeRemovedIndices.iterator(); - while(it.hasNext()){ - int i = ((Integer) it.next()).intValue(); - setsToBeRemoved.add(sets.get(i)); - } - - it = setsToBeRemoved.iterator(); - while(it.hasNext()){ - CellSet set = (CellSet) it.next(); - removedAny = true; - sets.remove(set); - } - - if(DEBUG_VERBOSE) { - System.out.println("******* Sets after *******"); - it = sets.iterator(); - while(it.hasNext()){ - CellSet set = (CellSet) it.next(); - set.printAsGrid(); - } - } - - return removedAny; - } - - public float getMinimumOfCellDimension(){ - return Math.min(getCellWidth(), getCellHeight()); - } - - private void separateCommonEdges(ArrayList shapes){ - - float offset = getMinimumOfCellDimension() / 5; - - ArrayList edges = new ArrayList(); - - //get all adges - Iterator it = shapes.iterator(); - while (it.hasNext()) { - DiagramShape shape = (DiagramShape) it.next(); - edges.addAll(shape.getEdges()); - } - - //group edges into pairs of touching edges - ArrayList> listOfPairs = new ArrayList>(); - it = edges.iterator(); - - //all-against-all touching test for the edges - int startIndex = 1; //skip some to avoid duplicate comparisons and self-to-self comparisons - - while(it.hasNext()){ - ShapeEdge edge1 = (ShapeEdge) it.next(); - - for(int k = startIndex; k < edges.size(); k++) { - ShapeEdge edge2 = edges.get(k); - - if(edge1.touchesWith(edge2)) { - listOfPairs.add(new Pair(edge1, edge2)); - } - } - startIndex++; - } - - ArrayList movedEdges = new ArrayList(); - - //move equivalent edges inwards - it = listOfPairs.iterator(); - while(it.hasNext()){ - Pair pair = (Pair) it.next(); - if(!movedEdges.contains(pair.first)) { - pair.first.moveInwardsBy(offset); - movedEdges.add(pair.first); - } - if(!movedEdges.contains(pair.second)) { - pair.second.moveInwardsBy(offset); - movedEdges.add(pair.second); - } - } - - } - - - //TODO: removes more than it should - private void removeDuplicateShapes() { - ArrayList originalShapes = new ArrayList(); - - Iterator shapesIt = getShapesIterator(); - while(shapesIt.hasNext()){ - DiagramShape shape = (DiagramShape) shapesIt.next(); - boolean isOriginal = true; - Iterator originals = originalShapes.iterator(); - while(originals.hasNext()){ - DiagramShape originalShape = (DiagramShape) originals.next(); - if(shape.equals(originalShape)){ - isOriginal = false; - } - } - if(isOriginal) originalShapes.add(shape); - } - - shapes.clear(); - shapes.addAll(originalShapes); - } - - private DiagramShape findSmallestShapeContaining(ShapePoint point) { - DiagramShape containingShape = null; - Iterator shapes = getShapes().iterator(); - while(shapes.hasNext()){ - DiagramShape shape = shapes.next(); - if(shape.contains(point)){ - if(containingShape == null){ - containingShape = shape; - } else { - if(shape.isSmallerThan(containingShape)){ - containingShape = shape; - } - } - } - } - return containingShape; - } - - private DiagramShape findSmallestShapeIntersecting(Rectangle2D rect) { - DiagramShape intersectingShape = null; - Iterator shapes = getShapes().iterator(); - while(shapes.hasNext()){ - DiagramShape shape = shapes.next(); - if(shape.intersects(rect)){ - if(intersectingShape == null){ - intersectingShape = shape; - } else { - if(shape.isSmallerThan(intersectingShape)){ - intersectingShape = shape; - } - } - } - } - return intersectingShape; - } - - private void addToTextObjects(DiagramText shape){ - textObjects.add(shape); - } - - private void addToCompositeShapes(CompositeDiagramShape shape){ - compositeShapes.add(shape); - } - - - private void addToShapes(DiagramShape shape){ - shapes.add(shape); - } - - public Iterator getShapesIterator(){ - return shapes.iterator(); - } - - /** - * @return - */ - public int getHeight() { - return height; - } - - /** - * @return - */ - public int getWidth() { - return width; - } - - /** - * @return - */ - public int getCellWidth() { - return cellWidth; - } - - /** - * @return - */ - public int getCellHeight() { - return cellHeight; - } - - /** - * @return - */ - public ArrayList getCompositeShapes() { - return compositeShapes; - } - - /** - * @return - */ - public ArrayList getShapes() { - return shapes; - } - - public int getCellMinX(TextGrid.Cell cell){ - return getCellMinX(cell, cellWidth); - } - public static int getCellMinX(TextGrid.Cell cell, int cellXSize){ - return cell.x * cellXSize; - } - - public int getCellMidX(TextGrid.Cell cell){ - return getCellMidX(cell, cellWidth); - } - public static int getCellMidX(TextGrid.Cell cell, int cellXSize){ - return cell.x * cellXSize + cellXSize / 2; - } - - public int getCellMaxX(TextGrid.Cell cell){ - return getCellMaxX(cell, cellWidth); - } - public static int getCellMaxX(TextGrid.Cell cell, int cellXSize){ - return cell.x * cellXSize + cellXSize; - } - - public int getCellMinY(TextGrid.Cell cell){ - return getCellMinY(cell, cellHeight); - } - public static int getCellMinY(TextGrid.Cell cell, int cellYSize){ - return cell.y * cellYSize; - } - - public int getCellMidY(TextGrid.Cell cell){ - return getCellMidY(cell, cellHeight); - } - public static int getCellMidY(TextGrid.Cell cell, int cellYSize){ - return cell.y * cellYSize + cellYSize / 2; - } - - public int getCellMaxY(TextGrid.Cell cell){ - return getCellMaxY(cell, cellHeight); - } - public static int getCellMaxY(TextGrid.Cell cell, int cellYSize){ - return cell.y * cellYSize + cellYSize; - } - - public TextGrid.Cell getCellFor(ShapePoint point){ - if(point == null) throw new IllegalArgumentException("ShapePoint cannot be null"); - //TODO: the fake grid is a problem - TextGrid g = new TextGrid(); - return g.new Cell((int) point.x / cellWidth, - (int) point.y / cellHeight); - } - - - /** - * @return - */ - public ArrayList getTextObjects() { - return textObjects; - } + //if(gridLargest.equals(gridOfSmalls)) toBeRemovedIndices.add(new Integer(index)); + } + + ArrayList setsToBeRemoved = new ArrayList(); + it = toBeRemovedIndices.iterator(); + while (it.hasNext()) { + int i = ((Integer) it.next()).intValue(); + setsToBeRemoved.add(sets.get(i)); + } + + it = setsToBeRemoved.iterator(); + while (it.hasNext()) { + CellSet set = (CellSet) it.next(); + removedAny = true; + sets.remove(set); + } + + if (DEBUG_VERBOSE) { + System.out.println("******* Sets after *******"); + it = sets.iterator(); + while (it.hasNext()) { + CellSet set = (CellSet) it.next(); + set.printAsGrid(); + } + } + + return removedAny; + } + + public float getMinimumOfCellDimension() { + return Math.min(getCellWidth(), getCellHeight()); + } + + private void separateCommonEdges(ArrayList shapes) { + + float offset = getMinimumOfCellDimension() / 5; + + ArrayList edges = new ArrayList(); + + //get all adges + Iterator it = shapes.iterator(); + while (it.hasNext()) { + DiagramShape shape = (DiagramShape) it.next(); + edges.addAll(shape.getEdges()); + } + + //group edges into pairs of touching edges + ArrayList> listOfPairs = new ArrayList>(); + it = edges.iterator(); + + //all-against-all touching test for the edges + int startIndex = 1; //skip some to avoid duplicate comparisons and self-to-self comparisons + + while (it.hasNext()) { + ShapeEdge edge1 = (ShapeEdge) it.next(); + + for (int k = startIndex; k < edges.size(); k++) { + ShapeEdge edge2 = edges.get(k); + + if (edge1.touchesWith(edge2)) { + listOfPairs.add(new Pair(edge1, edge2)); + } + } + startIndex++; + } + + ArrayList movedEdges = new ArrayList(); + + //move equivalent edges inwards + it = listOfPairs.iterator(); + while (it.hasNext()) { + Pair pair = (Pair) it.next(); + if (!movedEdges.contains(pair.first)) { + pair.first.moveInwardsBy(offset); + movedEdges.add(pair.first); + } + if (!movedEdges.contains(pair.second)) { + pair.second.moveInwardsBy(offset); + movedEdges.add(pair.second); + } + } + + } + + + //TODO: removes more than it should + private void removeDuplicateShapes() { + ArrayList originalShapes = new ArrayList(); + + Iterator shapesIt = getShapesIterator(); + while (shapesIt.hasNext()) { + DiagramShape shape = (DiagramShape) shapesIt.next(); + boolean isOriginal = true; + Iterator originals = originalShapes.iterator(); + while (originals.hasNext()) { + DiagramShape originalShape = (DiagramShape) originals.next(); + if (shape.equals(originalShape)) { + isOriginal = false; + } + } + if (isOriginal) + originalShapes.add(shape); + } + + shapes.clear(); + shapes.addAll(originalShapes); + } + + private DiagramShape findSmallestShapeContaining(ShapePoint point) { + DiagramShape containingShape = null; + Iterator shapes = getShapes().iterator(); + while (shapes.hasNext()) { + DiagramShape shape = shapes.next(); + if (shape.contains(point)) { + if (containingShape == null) { + containingShape = shape; + } else { + if (shape.isSmallerThan(containingShape)) { + containingShape = shape; + } + } + } + } + return containingShape; + } + + private DiagramShape findSmallestShapeIntersecting(Rectangle2D rect) { + DiagramShape intersectingShape = null; + Iterator shapes = getShapes().iterator(); + while (shapes.hasNext()) { + DiagramShape shape = shapes.next(); + if (shape.intersects(rect)) { + if (intersectingShape == null) { + intersectingShape = shape; + } else { + if (shape.isSmallerThan(intersectingShape)) { + intersectingShape = shape; + } + } + } + } + return intersectingShape; + } + + private void addToTextObjects(DiagramText shape) { + textObjects.add(shape); + } + + private void addToCompositeShapes(CompositeDiagramShape shape) { + compositeShapes.add(shape); + } + + + private void addToShapes(DiagramShape shape) { + shapes.add(shape); + } + + public Iterator getShapesIterator() { + return shapes.iterator(); + } + + /** + * @return + */ + public int getHeight() { + return height; + } + + /** + * @return + */ + public int getWidth() { + return width; + } + + /** + * @return + */ + public int getCellWidth() { + return cellWidth; + } + + /** + * @return + */ + public int getCellHeight() { + return cellHeight; + } + + /** + * @return + */ + public ArrayList getCompositeShapes() { + return compositeShapes; + } + + /** + * @return + */ + public ArrayList getShapes() { + return shapes; + } + + public int getCellMinX(TextGrid.Cell cell) { + return getCellMinX(cell, cellWidth); + } + + public static int getCellMinX(TextGrid.Cell cell, int cellXSize) { + return cell.x * cellXSize; + } + + public int getCellMidX(TextGrid.Cell cell) { + return getCellMidX(cell, cellWidth); + } + + public static int getCellMidX(TextGrid.Cell cell, int cellXSize) { + return cell.x * cellXSize + cellXSize / 2; + } + + public int getCellMaxX(TextGrid.Cell cell) { + return getCellMaxX(cell, cellWidth); + } + + public static int getCellMaxX(TextGrid.Cell cell, int cellXSize) { + return cell.x * cellXSize + cellXSize; + } + + public int getCellMinY(TextGrid.Cell cell) { + return getCellMinY(cell, cellHeight); + } + + public static int getCellMinY(TextGrid.Cell cell, int cellYSize) { + return cell.y * cellYSize; + } + + public int getCellMidY(TextGrid.Cell cell) { + return getCellMidY(cell, cellHeight); + } + + public static int getCellMidY(TextGrid.Cell cell, int cellYSize) { + return cell.y * cellYSize + cellYSize / 2; + } + + public int getCellMaxY(TextGrid.Cell cell) { + return getCellMaxY(cell, cellHeight); + } + + public static int getCellMaxY(TextGrid.Cell cell, int cellYSize) { + return cell.y * cellYSize + cellYSize; + } + + public TextGrid.Cell getCellFor(ShapePoint point) { + if (point == null) + throw new IllegalArgumentException("ShapePoint cannot be null"); + //TODO: the fake grid is a problem + TextGrid g = new TextGrid(); + return g.new Cell((int) point.x / cellWidth, + (int) point.y / cellHeight); + } + + + /** + * @return + */ + public ArrayList getTextObjects() { + return textObjects; + } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/DiagramComponent.java b/src/java/org/stathissideris/ascii2image/graphics/DiagramComponent.java index efaee3a..caa3ecd 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/DiagramComponent.java +++ b/src/java/org/stathissideris/ascii2image/graphics/DiagramComponent.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,107 +15,109 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.graphics; -import java.util.ArrayList; -import java.util.Iterator; - import org.stathissideris.ascii2image.text.CellSet; import org.stathissideris.ascii2image.text.TextGrid; /** - * + * * @author Efstathios Sideris */ public abstract class DiagramComponent { - - private static final boolean DEBUG = false; - - protected static ShapePoint makePointForCell(TextGrid.Cell cell, TextGrid grid, int cellWidth, int cellHeight, boolean allRound){ - if (DEBUG) - System.out.println("Found point at cell "+cell); - if(grid.isCorner(cell) && allRound){ - return new ShapePoint( - cell.x * cellWidth + cellWidth/2, - cell.y * cellHeight + cellHeight/2, - ShapePoint.TYPE_ROUND - ); - } else if(grid.isNormalCorner(cell)){ - return new ShapePoint( - cell.x * cellWidth + cellWidth/2, - cell.y * cellHeight + cellHeight/2, - ShapePoint.TYPE_NORMAL - ); - } else if(grid.isRoundCorner(cell)){ - return new ShapePoint( - cell.x * cellWidth + cellWidth/2, - cell.y * cellHeight + cellHeight/2, - ShapePoint.TYPE_ROUND - ); - } else if(grid.isLinesEnd(cell)){ - return new ShapePoint( - cell.x * cellWidth + cellWidth/2, - cell.y * cellHeight + cellHeight/2, - ShapePoint.TYPE_NORMAL - ); - } else if(grid.isIntersection(cell)){ - return new ShapePoint( - cell.x * cellWidth + cellWidth/2, - cell.y * cellHeight + cellHeight/2, - ShapePoint.TYPE_NORMAL - ); - } - throw new RuntimeException("Cannot make point for cell "+cell); - } - public static DiagramComponent createClosedFromBoundaryCells(TextGrid grid, CellSet cells, int cellWidth, int cellHeight){ - return createClosedFromBoundaryCells(grid, cells, cellWidth, cellHeight, false); - } + private static final boolean DEBUG = false; + + protected static ShapePoint makePointForCell(TextGrid.Cell cell, TextGrid grid, int cellWidth, int cellHeight, boolean allRound) { + if (DEBUG) + System.out.println("Found point at cell " + cell); + if (grid.isCorner(cell) && allRound) { + return new ShapePoint( + cell.x * cellWidth + cellWidth / 2, + cell.y * cellHeight + cellHeight / 2, + ShapePoint.TYPE_ROUND + ); + } else if (grid.isNormalCorner(cell)) { + return new ShapePoint( + cell.x * cellWidth + cellWidth / 2, + cell.y * cellHeight + cellHeight / 2, + ShapePoint.TYPE_NORMAL + ); + } else if (grid.isRoundCorner(cell)) { + return new ShapePoint( + cell.x * cellWidth + cellWidth / 2, + cell.y * cellHeight + cellHeight / 2, + ShapePoint.TYPE_ROUND + ); + } else if (grid.isLinesEnd(cell)) { + return new ShapePoint( + cell.x * cellWidth + cellWidth / 2, + cell.y * cellHeight + cellHeight / 2, + ShapePoint.TYPE_NORMAL + ); + } else if (grid.isIntersection(cell)) { + return new ShapePoint( + cell.x * cellWidth + cellWidth / 2, + cell.y * cellHeight + cellHeight / 2, + ShapePoint.TYPE_NORMAL + ); + } + throw new RuntimeException("Cannot make point for cell " + cell); + } + + public static DiagramComponent createClosedFromBoundaryCells(TextGrid grid, CellSet cells, int cellWidth, int cellHeight) { + return createClosedFromBoundaryCells(grid, cells, cellWidth, cellHeight, false); + } + + public static DiagramComponent createClosedFromBoundaryCells(TextGrid grid, CellSet cells, int cellWidth, int cellHeight, boolean allRound) { + if (cells.getType(grid) == CellSet.TYPE_OPEN) + throw new IllegalArgumentException("CellSet is closed and cannot be handled by this method"); + if (cells.size() < 2) + return null; + + DiagramShape shape = new DiagramShape(); + shape.setClosed(true); + if (grid.containsAtLeastOneDashedLine(cells)) + shape.setStrokeDashed(true); + + TextGrid workGrid = new TextGrid(grid.getWidth(), grid.getHeight()); + grid.copyCellsTo(cells, workGrid); + + if (DEBUG) { + System.out.println("Making closed shape from buffer:"); + workGrid.printDebug(); + } + + TextGrid.Cell start = (TextGrid.Cell) cells.getFirst(); + if (workGrid.isCorner(start)) + shape.addToPoints(makePointForCell(start, workGrid, cellWidth, cellHeight, allRound)); + TextGrid.Cell previous = start; + TextGrid.Cell cell = null; + CellSet nextCells = workGrid.followCell(previous); + if (nextCells.size() == 0) + return null; + cell = (TextGrid.Cell) nextCells.getFirst(); + if (workGrid.isCorner(cell)) + shape.addToPoints(makePointForCell(cell, workGrid, cellWidth, cellHeight, allRound)); + + while (!cell.equals(start)) { + nextCells = workGrid.followCell(cell, previous); + if (nextCells.size() == 1) { + previous = cell; + cell = (TextGrid.Cell) nextCells.getFirst(); + if (!cell.equals(start) && workGrid.isCorner(cell)) + shape.addToPoints(makePointForCell(cell, workGrid, cellWidth, cellHeight, allRound)); + } else if (nextCells.size() > 1) { + return null; + } else { + throw new RuntimeException("cannot create closed shape from boundary cells, nowhere to go from " + + cell + " coming from " + previous + " in grid:\n" + grid + + "\nmaybe you have an edge pointing nowhere?"); + } + } + + return shape; - public static DiagramComponent createClosedFromBoundaryCells(TextGrid grid, CellSet cells, int cellWidth, int cellHeight, boolean allRound){ - if(cells.getType(grid) == CellSet.TYPE_OPEN) throw new IllegalArgumentException("CellSet is closed and cannot be handled by this method"); - if(cells.size() < 2) return null; - - DiagramShape shape = new DiagramShape(); - shape.setClosed(true); - if(grid.containsAtLeastOneDashedLine(cells)) shape.setStrokeDashed(true); - - TextGrid workGrid = new TextGrid(grid.getWidth(), grid.getHeight()); - grid.copyCellsTo(cells, workGrid); - - if (DEBUG){ - System.out.println("Making closed shape from buffer:"); - workGrid.printDebug(); - } - - TextGrid.Cell start = (TextGrid.Cell) cells.getFirst(); - if(workGrid.isCorner(start)) shape.addToPoints(makePointForCell(start, workGrid, cellWidth, cellHeight, allRound)); - TextGrid.Cell previous = start; - TextGrid.Cell cell = null; - CellSet nextCells = workGrid.followCell(previous); - if(nextCells.size() == 0) return null; - cell = (TextGrid.Cell) nextCells.getFirst(); - if(workGrid.isCorner(cell)) shape.addToPoints(makePointForCell(cell, workGrid, cellWidth, cellHeight, allRound)); - - while(!cell.equals(start)){ - nextCells = workGrid.followCell(cell, previous); - if(nextCells.size() == 1) { - previous = cell; - cell = (TextGrid.Cell) nextCells.getFirst(); - if(!cell.equals(start) && workGrid.isCorner(cell)) - shape.addToPoints(makePointForCell(cell, workGrid, cellWidth, cellHeight, allRound)); - } else if(nextCells.size() > 1) { - return null; - } else { - throw new RuntimeException("cannot create closed shape from boundary cells, nowhere to go from " - + cell + " coming from " + previous + " in grid:\n" + grid - +"\nmaybe you have an edge pointing nowhere?"); - } - } - - return shape; - - } + } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/DiagramShape.java b/src/java/org/stathissideris/ascii2image/graphics/DiagramShape.java index a96f166..bd5674e 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/DiagramShape.java +++ b/src/java/org/stathissideris/ascii2image/graphics/DiagramShape.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,964 +15,1003 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.graphics; +import org.stathissideris.ascii2image.core.RenderingOptions; +import org.stathissideris.ascii2image.text.TextGrid; + import java.awt.Color; import java.awt.Rectangle; +import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import java.awt.geom.Rectangle2D; -import java.awt.geom.Ellipse2D; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; -import org.stathissideris.ascii2image.core.RenderingOptions; -import org.stathissideris.ascii2image.text.*; - /** - * + * * @author Efstathios Sideris */ public class DiagramShape extends DiagramComponent { - - private static final boolean DEBUG = false; - - public static final int TYPE_SIMPLE = 0; - public static final int TYPE_ARROWHEAD = 1; - public static final int TYPE_POINT_MARKER = 2; - public static final int TYPE_DOCUMENT = 3; - public static final int TYPE_STORAGE = 4; - public static final int TYPE_IO = 5; - public static final int TYPE_DECISION = 6; - public static final int TYPE_MANUAL_OPERATION = 7; // upside-down trapezoid - public static final int TYPE_TRAPEZOID = 8; // rightside-up trapezoid - public static final int TYPE_ELLIPSE = 9; - public static final int TYPE_CUSTOM = 9999; - - /** The slope of side lines on trapezoids (mo, tr) and parallelograms (io). */ - public static final float SHAPE_SLOPE = 8; - - protected int type = TYPE_SIMPLE; - - private Color fillColor = null; - private Color strokeColor = Color.black; - - private boolean isClosed = false; - private boolean isStrokeDashed = false; - - protected ArrayList points = new ArrayList(); - - CustomShapeDefinition definition = null; - - public static void main(String[] args) { - } - - public static DiagramShape createArrowhead(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { - if(!grid.isArrowhead(cell)) return null; - if(grid.isNorthArrowhead(cell)) return createNorthArrowhead(grid, cell, cellXSize, cellYSize); - if(grid.isSouthArrowhead(cell)) return createSouthArrowhead(grid, cell, cellXSize, cellYSize); - if(grid.isWestArrowhead(cell)) return createWestArrowhead(grid, cell, cellXSize, cellYSize); - if(grid.isEastArrowhead(cell)) return createEastArrowhead(grid, cell, cellXSize, cellYSize); - return null; - } - - private static DiagramShape createNorthArrowhead(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { - if(!grid.isNorthArrowhead(cell)) return null; - DiagramShape shape = new DiagramShape(); - shape.addToPoints(new ShapePoint( - Diagram.getCellMidX(cell,cellXSize), - Diagram.getCellMinY(cell,cellYSize))); - shape.addToPoints(new ShapePoint( - Diagram.getCellMinX(cell,cellXSize), - Diagram.getCellMaxY(cell,cellYSize))); - shape.addToPoints(new ShapePoint( - Diagram.getCellMaxX(cell,cellXSize), - Diagram.getCellMaxY(cell,cellYSize))); - shape.setClosed(true); - shape.setFillColor(Color.black); - shape.setStrokeColor(Color.black); - shape.setType(TYPE_ARROWHEAD); - return shape; - } - - private static DiagramShape createSouthArrowhead(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { - if(!grid.isSouthArrowhead(cell)) return null; - DiagramShape shape = new DiagramShape(); - shape.addToPoints(new ShapePoint( - Diagram.getCellMinX(cell,cellXSize), - Diagram.getCellMinY(cell,cellYSize))); - shape.addToPoints(new ShapePoint( - Diagram.getCellMidX(cell,cellXSize), - Diagram.getCellMaxY(cell,cellYSize))); - shape.addToPoints(new ShapePoint( - Diagram.getCellMaxX(cell,cellXSize), - Diagram.getCellMinY(cell,cellYSize))); - shape.setClosed(true); - shape.setFillColor(Color.black); - shape.setStrokeColor(Color.black); - shape.setType(TYPE_ARROWHEAD); - return shape; - } - - private static DiagramShape createWestArrowhead(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { - if(!grid.isWestArrowhead(cell)) return null; - DiagramShape shape = new DiagramShape(); - shape.addToPoints(new ShapePoint( - Diagram.getCellMaxX(cell,cellXSize), - Diagram.getCellMinY(cell,cellYSize))); - shape.addToPoints(new ShapePoint( - Diagram.getCellMinX(cell,cellXSize), - Diagram.getCellMidY(cell,cellYSize))); - shape.addToPoints(new ShapePoint( - Diagram.getCellMaxX(cell,cellXSize), - Diagram.getCellMaxY(cell,cellYSize))); - shape.setClosed(true); - shape.setFillColor(Color.black); - shape.setStrokeColor(Color.black); - shape.setType(TYPE_ARROWHEAD); - return shape; - } - - private static DiagramShape createEastArrowhead(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { - if(!grid.isEastArrowhead(cell)) return null; - DiagramShape shape = new DiagramShape(); - shape.addToPoints(new ShapePoint( - Diagram.getCellMinX(cell,cellXSize), - Diagram.getCellMinY(cell,cellYSize))); - shape.addToPoints(new ShapePoint( - Diagram.getCellMaxX(cell,cellXSize), - Diagram.getCellMidY(cell,cellYSize))); - shape.addToPoints(new ShapePoint( - Diagram.getCellMinX(cell,cellXSize), - Diagram.getCellMaxY(cell,cellYSize))); - shape.setClosed(true); - shape.setFillColor(Color.black); - shape.setStrokeColor(Color.black); - shape.setType(TYPE_ARROWHEAD); - return shape; - } - - public static DiagramShape createSmallLine(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { - if (grid.isLine(cell)) { - DiagramShape shape = new DiagramShape(); - if (grid.isHorizontalLine(cell)) { - shape.addToPoints( - new ShapePoint( - cell.x * cellXSize, - cell.y * cellYSize + cellYSize / 2)); - shape.addToPoints( - new ShapePoint( - cell.x * cellXSize + cellXSize - 1, - cell.y * cellYSize + cellYSize / 2)); - } else if (grid.isVerticalLine(cell)) { - shape.addToPoints( - new ShapePoint( - cell.x * cellXSize + cellXSize / 2, - cell.y * cellYSize)); - shape.addToPoints( - new ShapePoint( - cell.x * cellXSize + cellXSize / 2, - cell.y * cellYSize + cellYSize - 1)); - } - - //the -1 above, make a difference: the second point - //should not fall into the next cell, because this - //results in a failure of a proper end-of-line - //plotting correction - return shape; - } - return null; - } - - public void addToPoints(ShapePoint point){ - points.add(point); - } - - public Iterator getPointsIterator(){ - return points.iterator(); - } - - public void scale(float factor){ - Iterator it = getPointsIterator(); - while(it.hasNext()){ - ShapePoint point = (ShapePoint) it.next(); - point.x *= factor; - point.y *= factor; - } - } - - public boolean isEmpty(){ - return points.isEmpty(); - } - - public boolean isFilled(){ - return (fillColor != null); - } - - public void setIsNotFilled(){ - fillColor = null; - } - - public boolean isPointLinesEnd(ShapePoint point){ - if(isClosed()) return false; //no line-ends in closed shapes! - if(point == points.get(0)) return true; - if(point == points.get(points.size() - 1)) return true; - return false; - } - - //TODO: method in development: isRectangle() - public boolean isRectangle(){ - if(points.size() != 4) return false; - ShapePoint p1 = (ShapePoint) points.get(0); - ShapePoint p2 = (ShapePoint) points.get(1); - ShapePoint p3 = (ShapePoint) points.get(2); - ShapePoint p4 = (ShapePoint) points.get(3); - if(p1.isInLineWith(p2) - && p2.isInLineWith(p3) - && p3.isInLineWith(p4) - && p4.isInLineWith(p1)) return true; - return false; - } - - /** - * Crude way to determine which of the two shapes is smaller, - * based just on their bounding boxes. Used in markup - * assignment precendence. - * - * @param other - * @return - */ - public boolean isSmallerThan(DiagramShape other){ - Rectangle bounds = getBounds(); - Rectangle otherBounds = other.getBounds(); - - int area = bounds.height * bounds.width; - int otherArea = otherBounds.height * otherBounds.width; - - if(area < otherArea) { - return true; - } - return false; - } - - /** - * @return - */ - public Color getFillColor() { - return fillColor; - } - - /** - * @return - */ - public Color getStrokeColor() { - return strokeColor; - } - - /** - * @param color - */ - public void setFillColor(Color color) { - fillColor = color; - } - - /** - * @param color - */ - public void setStrokeColor(Color color) { - strokeColor = color; - } - - /** - * @return - */ - public boolean isClosed() { - return isClosed; - } - - /** - * @param b - */ - public void setClosed(boolean b) { - isClosed = b; - } - - public void printDebug(){ - System.out.print("DiagramShape: "); - System.out.println(points.size()+" points"); - } - - /** - * @return - */ - public ArrayList getPoints() { - return points; - } - - public ShapePoint getPoint(int i) { - return (ShapePoint) points.get(i); - } - - public void setPoint(int i, ShapePoint point) { - points.set(i, point); - } - - - public boolean equals(Object object){ - DiagramShape shape = null; - if(!(object instanceof DiagramShape)) { return false; } - else shape = (DiagramShape) object; - if(getPoints().size() != shape.getPoints().size()) return false; - - if(DEBUG) System.out.println("comparing shapes:"); - - if(DEBUG) System.out.println("points1: "); - HashMap points1 = new HashMap(); - Iterator it = getPointsIterator(); - while(it.hasNext()){ - ShapePoint point = (ShapePoint) it.next(); - points1.put( ""+((int) point.x)+","+((int) point.y), null); - if(DEBUG) System.out.println(((int) point.x)+", "+((int) point.y)); - } - - if(DEBUG) System.out.println("points2: "); - HashMap points2 = new HashMap(); - it = shape.getPointsIterator(); - while(it.hasNext()){ - ShapePoint point = (ShapePoint) it.next(); - points2.put( ""+((int) point.x)+","+((int) point.y), null); - if(DEBUG) System.out.println(((int) point.x)+", "+((int) point.y)); - } - - it = points1.keySet().iterator(); - while(it.hasNext()){ - String key = (String) it.next(); - if(!points2.containsKey(key)) { - if (DEBUG) - System.out.println("\tare not equal"); - return false; - } - } - if (DEBUG) - System.out.println("\tare equal"); - return true; - } - - public GeneralPath makeIntoPath() { - int size = getPoints().size(); - - if(size < 2) return null; - - GeneralPath path = new GeneralPath(); - ShapePoint point = (ShapePoint) getPoints().get(0); - path.moveTo((int) point.x, (int) point.y); - for(int i = 1; i < size; i++){ - point = (ShapePoint) getPoints().get(i); - path.lineTo((int) point.x, (int) point.y); - } - if(isClosed() && size > 2){ - path.closePath(); - } - return path; - } - - public GeneralPath makeMarkerPath(Diagram diagram){ - if(points.size() != 1) return null; - ShapePoint center = (ShapePoint) this.getPoint(0); - float diameter = - (float) 0.7 * Math.min(diagram.getCellWidth(), diagram.getCellHeight()); - return new GeneralPath(new Ellipse2D.Float( - center.x - diameter/2, - center.y - diameter/2, - diameter, - diameter)); - } - - public Rectangle getBounds(){ - Rectangle bounds = makeIntoPath().getBounds(); - return bounds; - } - - public GeneralPath makeIntoRenderPath(Diagram diagram, RenderingOptions options) { - int size = getPoints().size(); - - if(getType() == TYPE_POINT_MARKER){ - return makeMarkerPath(diagram); - } - - if(getType() == TYPE_DOCUMENT && points.size() == 4){ - return makeDocumentPath(diagram); - } - - if(getType() == TYPE_STORAGE && points.size() == 4){ - return makeStoragePath(diagram); - } - - if(getType() == TYPE_IO && points.size() == 4){ - return makeIOPath(diagram, options); - } - - if(getType() == TYPE_DECISION && points.size() == 4){ - return makeDecisionPath(diagram); - } - - if(getType() == TYPE_MANUAL_OPERATION && points.size() == 4){ - return makeTrapezoidPath(diagram, options, true); - } - - if(getType() == TYPE_TRAPEZOID && points.size() == 4){ - return makeTrapezoidPath(diagram, options, false); - } - - if(getType() == TYPE_ELLIPSE && points.size() == 4){ - return makeEllipsePath(diagram); - } - - if(size < 2) return null; - - GeneralPath path = new GeneralPath(); - ShapePoint point = (ShapePoint) getPoints().get(0); - TextGrid.Cell cell = diagram.getCellFor(point); - //path.moveTo((int) point.x, (int) point.y); - ShapePoint previous = (ShapePoint) getPoints().get(size - 1); - ShapePoint next = (ShapePoint) getPoints().get(1); - ShapePoint entryPoint; - ShapePoint exitPoint; - - if(point.getType() == ShapePoint.TYPE_NORMAL){ - //if(isClosed()){ - path.moveTo((int) point.x, (int) point.y); + + private static final boolean DEBUG = false; + + public static final int TYPE_SIMPLE = 0; + public static final int TYPE_ARROWHEAD = 1; + public static final int TYPE_POINT_MARKER = 2; + public static final int TYPE_DOCUMENT = 3; + public static final int TYPE_STORAGE = 4; + public static final int TYPE_IO = 5; + public static final int TYPE_DECISION = 6; + public static final int TYPE_MANUAL_OPERATION = 7; // upside-down trapezoid + public static final int TYPE_TRAPEZOID = 8; // rightside-up trapezoid + public static final int TYPE_ELLIPSE = 9; + public static final int TYPE_CUSTOM = 9999; + + /** The slope of side lines on trapezoids (mo, tr) and parallelograms (io). */ + public static final float SHAPE_SLOPE = 8; + + protected int type = TYPE_SIMPLE; + + private Color fillColor = null; + private Color strokeColor = Color.black; + + private boolean isClosed = false; + private boolean isStrokeDashed = false; + + protected ArrayList points = new ArrayList(); + + CustomShapeDefinition definition = null; + + public static void main(String[] args) { + } + + public static DiagramShape createArrowhead(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { + if (!grid.isArrowhead(cell)) + return null; + if (grid.isNorthArrowhead(cell)) + return createNorthArrowhead(grid, cell, cellXSize, cellYSize); + if (grid.isSouthArrowhead(cell)) + return createSouthArrowhead(grid, cell, cellXSize, cellYSize); + if (grid.isWestArrowhead(cell)) + return createWestArrowhead(grid, cell, cellXSize, cellYSize); + if (grid.isEastArrowhead(cell)) + return createEastArrowhead(grid, cell, cellXSize, cellYSize); + return null; + } + + private static DiagramShape createNorthArrowhead(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { + if (!grid.isNorthArrowhead(cell)) + return null; + DiagramShape shape = new DiagramShape(); + shape.addToPoints(new ShapePoint( + Diagram.getCellMidX(cell, cellXSize), + Diagram.getCellMinY(cell, cellYSize))); + shape.addToPoints(new ShapePoint( + Diagram.getCellMinX(cell, cellXSize), + Diagram.getCellMaxY(cell, cellYSize))); + shape.addToPoints(new ShapePoint( + Diagram.getCellMaxX(cell, cellXSize), + Diagram.getCellMaxY(cell, cellYSize))); + shape.setClosed(true); + shape.setFillColor(Color.black); + shape.setStrokeColor(Color.black); + shape.setType(TYPE_ARROWHEAD); + return shape; + } + + private static DiagramShape createSouthArrowhead(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { + if (!grid.isSouthArrowhead(cell)) + return null; + DiagramShape shape = new DiagramShape(); + shape.addToPoints(new ShapePoint( + Diagram.getCellMinX(cell, cellXSize), + Diagram.getCellMinY(cell, cellYSize))); + shape.addToPoints(new ShapePoint( + Diagram.getCellMidX(cell, cellXSize), + Diagram.getCellMaxY(cell, cellYSize))); + shape.addToPoints(new ShapePoint( + Diagram.getCellMaxX(cell, cellXSize), + Diagram.getCellMinY(cell, cellYSize))); + shape.setClosed(true); + shape.setFillColor(Color.black); + shape.setStrokeColor(Color.black); + shape.setType(TYPE_ARROWHEAD); + return shape; + } + + private static DiagramShape createWestArrowhead(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { + if (!grid.isWestArrowhead(cell)) + return null; + DiagramShape shape = new DiagramShape(); + shape.addToPoints(new ShapePoint( + Diagram.getCellMaxX(cell, cellXSize), + Diagram.getCellMinY(cell, cellYSize))); + shape.addToPoints(new ShapePoint( + Diagram.getCellMinX(cell, cellXSize), + Diagram.getCellMidY(cell, cellYSize))); + shape.addToPoints(new ShapePoint( + Diagram.getCellMaxX(cell, cellXSize), + Diagram.getCellMaxY(cell, cellYSize))); + shape.setClosed(true); + shape.setFillColor(Color.black); + shape.setStrokeColor(Color.black); + shape.setType(TYPE_ARROWHEAD); + return shape; + } + + private static DiagramShape createEastArrowhead(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { + if (!grid.isEastArrowhead(cell)) + return null; + DiagramShape shape = new DiagramShape(); + shape.addToPoints(new ShapePoint( + Diagram.getCellMinX(cell, cellXSize), + Diagram.getCellMinY(cell, cellYSize))); + shape.addToPoints(new ShapePoint( + Diagram.getCellMaxX(cell, cellXSize), + Diagram.getCellMidY(cell, cellYSize))); + shape.addToPoints(new ShapePoint( + Diagram.getCellMinX(cell, cellXSize), + Diagram.getCellMaxY(cell, cellYSize))); + shape.setClosed(true); + shape.setFillColor(Color.black); + shape.setStrokeColor(Color.black); + shape.setType(TYPE_ARROWHEAD); + return shape; + } + + public static DiagramShape createSmallLine(TextGrid grid, TextGrid.Cell cell, int cellXSize, int cellYSize) { + if (grid.isLine(cell)) { + DiagramShape shape = new DiagramShape(); + if (grid.isHorizontalLine(cell)) { + shape.addToPoints( + new ShapePoint( + cell.x * cellXSize, + cell.y * cellYSize + cellYSize / 2)); + shape.addToPoints( + new ShapePoint( + cell.x * cellXSize + cellXSize - 1, + cell.y * cellYSize + cellYSize / 2)); + } else if (grid.isVerticalLine(cell)) { + shape.addToPoints( + new ShapePoint( + cell.x * cellXSize + cellXSize / 2, + cell.y * cellYSize)); + shape.addToPoints( + new ShapePoint( + cell.x * cellXSize + cellXSize / 2, + cell.y * cellYSize + cellYSize - 1)); + } + + //the -1 above, make a difference: the second point + //should not fall into the next cell, because this + //results in a failure of a proper end-of-line + //plotting correction + return shape; + } + return null; + } + + public void addToPoints(ShapePoint point) { + points.add(point); + } + + public Iterator getPointsIterator() { + return points.iterator(); + } + + public void scale(float factor) { + Iterator it = getPointsIterator(); + while (it.hasNext()) { + ShapePoint point = (ShapePoint) it.next(); + point.x *= factor; + point.y *= factor; + } + } + + public boolean isEmpty() { + return points.isEmpty(); + } + + public boolean isFilled() { + return (fillColor != null); + } + + public void setIsNotFilled() { + fillColor = null; + } + + public boolean isPointLinesEnd(ShapePoint point) { + if (isClosed()) + return false; //no line-ends in closed shapes! + if (point == points.get(0)) + return true; + if (point == points.get(points.size() - 1)) + return true; + return false; + } + + //TODO: method in development: isRectangle() + public boolean isRectangle() { + if (points.size() != 4) + return false; + ShapePoint p1 = (ShapePoint) points.get(0); + ShapePoint p2 = (ShapePoint) points.get(1); + ShapePoint p3 = (ShapePoint) points.get(2); + ShapePoint p4 = (ShapePoint) points.get(3); + if (p1.isInLineWith(p2) + && p2.isInLineWith(p3) + && p3.isInLineWith(p4) + && p4.isInLineWith(p1)) + return true; + return false; + } + + /** + * Crude way to determine which of the two shapes is smaller, + * based just on their bounding boxes. Used in markup + * assignment precendence. + * + * @param other + * @return + */ + public boolean isSmallerThan(DiagramShape other) { + Rectangle bounds = getBounds(); + Rectangle otherBounds = other.getBounds(); + + int area = bounds.height * bounds.width; + int otherArea = otherBounds.height * otherBounds.width; + + if (area < otherArea) { + return true; + } + return false; + } + + /** + * @return + */ + public Color getFillColor() { + return fillColor; + } + + /** + * @return + */ + public Color getStrokeColor() { + return strokeColor; + } + + /** + * @param color + */ + public void setFillColor(Color color) { + fillColor = color; + } + + /** + * @param color + */ + public void setStrokeColor(Color color) { + strokeColor = color; + } + + /** + * @return + */ + public boolean isClosed() { + return isClosed; + } + + /** + * @param b + */ + public void setClosed(boolean b) { + isClosed = b; + } + + public void printDebug() { + System.out.print("DiagramShape: "); + System.out.println(points.size() + " points"); + } + + /** + * @return + */ + public ArrayList getPoints() { + return points; + } + + public ShapePoint getPoint(int i) { + return (ShapePoint) points.get(i); + } + + public void setPoint(int i, ShapePoint point) { + points.set(i, point); + } + + + public boolean equals(Object object) { + DiagramShape shape = null; + if (!(object instanceof DiagramShape)) { + return false; + } else + shape = (DiagramShape) object; + if (getPoints().size() != shape.getPoints().size()) + return false; + + if (DEBUG) + System.out.println("comparing shapes:"); + + if (DEBUG) + System.out.println("points1: "); + HashMap points1 = new HashMap(); + Iterator it = getPointsIterator(); + while (it.hasNext()) { + ShapePoint point = (ShapePoint) it.next(); + points1.put("" + ((int) point.x) + "," + ((int) point.y), null); + if (DEBUG) + System.out.println(((int) point.x) + ", " + ((int) point.y)); + } + + if (DEBUG) + System.out.println("points2: "); + HashMap points2 = new HashMap(); + it = shape.getPointsIterator(); + while (it.hasNext()) { + ShapePoint point = (ShapePoint) it.next(); + points2.put("" + ((int) point.x) + "," + ((int) point.y), null); + if (DEBUG) + System.out.println(((int) point.x) + ", " + ((int) point.y)); + } + + it = points1.keySet().iterator(); + while (it.hasNext()) { + String key = (String) it.next(); + if (!points2.containsKey(key)) { + if (DEBUG) + System.out.println("\tare not equal"); + return false; + } + } + if (DEBUG) + System.out.println("\tare equal"); + return true; + } + + public GeneralPath makeIntoPath() { + int size = getPoints().size(); + + if (size < 2) + return null; + + GeneralPath path = new GeneralPath(); + ShapePoint point = (ShapePoint) getPoints().get(0); + path.moveTo((int) point.x, (int) point.y); + for (int i = 1; i < size; i++) { + point = (ShapePoint) getPoints().get(i); + path.lineTo((int) point.x, (int) point.y); + } + if (isClosed() && size > 2) { + path.closePath(); + } + return path; + } + + public GeneralPath makeMarkerPath(Diagram diagram) { + if (points.size() != 1) + return null; + ShapePoint center = (ShapePoint) this.getPoint(0); + float diameter = + (float) 0.7 * Math.min(diagram.getCellWidth(), diagram.getCellHeight()); + return new GeneralPath(new Ellipse2D.Float( + center.x - diameter / 2, + center.y - diameter / 2, + diameter, + diameter)); + } + + public Rectangle getBounds() { + Rectangle bounds = makeIntoPath().getBounds(); + return bounds; + } + + public GeneralPath makeIntoRenderPath(Diagram diagram, RenderingOptions options) { + int size = getPoints().size(); + + if (getType() == TYPE_POINT_MARKER) { + return makeMarkerPath(diagram); + } + + if (getType() == TYPE_DOCUMENT && points.size() == 4) { + return makeDocumentPath(diagram); + } + + if (getType() == TYPE_STORAGE && points.size() == 4) { + return makeStoragePath(diagram); + } + + if (getType() == TYPE_IO && points.size() == 4) { + return makeIOPath(diagram, options); + } + + if (getType() == TYPE_DECISION && points.size() == 4) { + return makeDecisionPath(diagram); + } + + if (getType() == TYPE_MANUAL_OPERATION && points.size() == 4) { + return makeTrapezoidPath(diagram, options, true); + } + + if (getType() == TYPE_TRAPEZOID && points.size() == 4) { + return makeTrapezoidPath(diagram, options, false); + } + + if (getType() == TYPE_ELLIPSE && points.size() == 4) { + return makeEllipsePath(diagram); + } + + if (size < 2) + return null; + + GeneralPath path = new GeneralPath(); + ShapePoint point = (ShapePoint) getPoints().get(0); + TextGrid.Cell cell = diagram.getCellFor(point); + //path.moveTo((int) point.x, (int) point.y); + ShapePoint previous = (ShapePoint) getPoints().get(size - 1); + ShapePoint next = (ShapePoint) getPoints().get(1); + ShapePoint entryPoint; + ShapePoint exitPoint; + + if (point.getType() == ShapePoint.TYPE_NORMAL) { + //if(isClosed()){ + path.moveTo((int) point.x, (int) point.y); /*} else { ShapePoint projectionPoint = getCellEdgeProjectionPointBetween(point, next, diagram); path.moveTo((int) projectionPoint.x, (int) projectionPoint.y); }*/ - } else if(point.getType() == ShapePoint.TYPE_ROUND){ - entryPoint = getCellEdgePointBetween(point, previous, diagram); - exitPoint = getCellEdgePointBetween(point, next, diagram); - path.moveTo(entryPoint.x, entryPoint.y); - path.quadTo(point.x, point.y, exitPoint.x, exitPoint.y); - } - - for(int i = 1; i < size; i++){ - previous = point; - point = (ShapePoint) getPoints().get(i); - if(i < size - 1) - next = (ShapePoint) getPoints().get(i + 1); - else next = (ShapePoint) getPoints().get(0); - - cell = diagram.getCellFor(point); - - if(point.getType() == ShapePoint.TYPE_NORMAL) - //if(!isPointLinesEnd(point)) - path.lineTo((int) point.x, (int) point.y); + } else if (point.getType() == ShapePoint.TYPE_ROUND) { + entryPoint = getCellEdgePointBetween(point, previous, diagram); + exitPoint = getCellEdgePointBetween(point, next, diagram); + path.moveTo(entryPoint.x, entryPoint.y); + path.quadTo(point.x, point.y, exitPoint.x, exitPoint.y); + } + + for (int i = 1; i < size; i++) { + previous = point; + point = (ShapePoint) getPoints().get(i); + if (i < size - 1) + next = (ShapePoint) getPoints().get(i + 1); + else + next = (ShapePoint) getPoints().get(0); + + cell = diagram.getCellFor(point); + + if (point.getType() == ShapePoint.TYPE_NORMAL) + //if(!isPointLinesEnd(point)) + path.lineTo((int) point.x, (int) point.y); /*else { //it is line's end, so we plot it at the projected intersection of the line with the cell's edge ShapePoint projectionPoint = getCellEdgeProjectionPointBetween(point, previous, diagram); path.lineTo((int) projectionPoint.x, (int) projectionPoint.y); }*/ - else if(point.getType() == ShapePoint.TYPE_ROUND){ - entryPoint = getCellEdgePointBetween(point, previous, diagram); - exitPoint = getCellEdgePointBetween(point, next, diagram); - - path.lineTo(entryPoint.x, entryPoint.y); - path.quadTo(point.x, point.y, exitPoint.x, exitPoint.y); - //if(!isPointLinesEnd(next)){ - if(next.getType() == ShapePoint.TYPE_NORMAL) - path.lineTo(next.x, next.y); - else if(next.getType() == ShapePoint.TYPE_ROUND){ - entryPoint = getCellEdgePointBetween(next, point, diagram); - path.lineTo(entryPoint.x, entryPoint.y); - } + else if (point.getType() == ShapePoint.TYPE_ROUND) { + entryPoint = getCellEdgePointBetween(point, previous, diagram); + exitPoint = getCellEdgePointBetween(point, next, diagram); + + path.lineTo(entryPoint.x, entryPoint.y); + path.quadTo(point.x, point.y, exitPoint.x, exitPoint.y); + //if(!isPointLinesEnd(next)){ + if (next.getType() == ShapePoint.TYPE_NORMAL) + path.lineTo(next.x, next.y); + else if (next.getType() == ShapePoint.TYPE_ROUND) { + entryPoint = getCellEdgePointBetween(next, point, diagram); + path.lineTo(entryPoint.x, entryPoint.y); + } /*} else { entryPoint = getCellEdgeProjectionPointBetween(next, point, diagram); path.lineTo(entryPoint.x, entryPoint.y); }*/ - } - } - //TODO: this shouldn't be needed, but it is! - if(isClosed() && size > 2){ - path.closePath(); - } - return path; - } - - public ArrayList getEdges(){ - ArrayList edges = new ArrayList(); - if(this.points.size() == 1) return edges; - int noOfPoints = points.size(); - for(int i = 0; i < noOfPoints - 1; i++){ - ShapePoint startPoint = (ShapePoint) points.get(i); - ShapePoint endPoint = (ShapePoint) points.get(i + 1); - ShapeEdge edge = new ShapeEdge(startPoint, endPoint, this); - edges.add(edge); - } - //if it is closed return edge that connects the - //last point to the first - if(this.isClosed()){ - ShapePoint firstPoint = (ShapePoint) points.get(0); - ShapePoint lastPoint = (ShapePoint) points.get(points.size() - 1); - ShapeEdge edge = new ShapeEdge(lastPoint, firstPoint, this); - edges.add(edge); - } - return edges; - } - - /** - * Finds the point that represents the intersection between the cell edge - * that contains pointInCell and the line connecting pointInCell and - * otherPoint. - * - * Returns C, if A is point in cell and B is otherPoint: - *
-	 *     Cell
-	 *    +-----+
-	 *    |  A  |C                 B
-	 *    |  *--*------------------*
-	 *    |     |
-	 *    +-----+
-	 *
- * - * @param pointInCell - * @param otherPoint - * @return - */ - public ShapePoint getCellEdgePointBetween(ShapePoint pointInCell, ShapePoint otherPoint, Diagram diagram){ - if(pointInCell == null || otherPoint == null || diagram == null) - throw new IllegalArgumentException("None of the parameters can be null"); - if(pointInCell.equals(otherPoint)) - throw new IllegalArgumentException("The two points cannot be the same"); - - ShapePoint result = null; - TextGrid.Cell cell = diagram.getCellFor(pointInCell); - - if(cell == null) - throw new RuntimeException("Upexpected error, cannot find cell corresponding to point "+pointInCell+" for diagram "+diagram); - - if(otherPoint.isNorthOf(pointInCell)) - result = new ShapePoint(pointInCell.x, - diagram.getCellMinY(cell)); - else if(otherPoint.isSouthOf(pointInCell)) - result = new ShapePoint(pointInCell.x, - diagram.getCellMaxY(cell)); - else if(otherPoint.isWestOf(pointInCell)) - result = new ShapePoint(diagram.getCellMinX(cell), - pointInCell.y); - else if(otherPoint.isEastOf(pointInCell)) - result = new ShapePoint(diagram.getCellMaxX(cell), - pointInCell.y); - - if(result == null) - throw new RuntimeException("Upexpected error, cannot find cell edge point for points "+pointInCell+" and "+otherPoint+" for diagram "+diagram); - - - return result; - } - - - /** - * - * Returns C, if A is point in cell and B is otherPoint: - * - *
-	 *     Cell
-	 *    +-----+
-	 *    |  A  |                  B
-	 *  C *--*--+------------------*
-	 *    |     |
-	 *    +-----+
-	 * 
- * - * @param pointInCell - * @param otherPoint - * @param diagram - * @return - */ - - public ShapePoint getCellEdgeProjectionPointBetween(ShapePoint pointInCell, ShapePoint otherPoint, Diagram diagram){ - if(pointInCell == null || otherPoint == null || diagram == null) - throw new IllegalArgumentException("None of the parameters can be null"); - if(pointInCell.equals(otherPoint)) - throw new IllegalArgumentException("The two points cannot be the same: "+pointInCell+" and "+otherPoint+" passed"); - - ShapePoint result = null; - TextGrid.Cell cell = diagram.getCellFor(pointInCell); - - if(cell == null) - throw new RuntimeException("Unexpected error, cannot find cell corresponding to point "+pointInCell+" for diagram "+diagram); - - if(otherPoint.isNorthOf(pointInCell)) - result = new ShapePoint(pointInCell.x, - diagram.getCellMaxY(cell)); - else if(otherPoint.isSouthOf(pointInCell)) - result = new ShapePoint(pointInCell.x, - diagram.getCellMinY(cell)); - else if(otherPoint.isWestOf(pointInCell)) - result = new ShapePoint(diagram.getCellMaxX(cell), - pointInCell.y); - else if(otherPoint.isEastOf(pointInCell)) - result = new ShapePoint(diagram.getCellMinX(cell), - pointInCell.y); - - if(result == null) - throw new RuntimeException("Unexpected error, cannot find cell edge point for points "+pointInCell+" and "+otherPoint+" for diagram "+diagram); - - - return result; - } - - public boolean contains(ShapePoint point){ - GeneralPath path = makeIntoPath(); - if(path != null) return path.contains(point); - return false; - } - - public boolean contains(Rectangle2D rect){ - GeneralPath path = makeIntoPath(); - if(path != null) return path.contains(rect); - return false; - } - - public boolean intersects(Rectangle2D rect){ - GeneralPath path = makeIntoPath(); - if(path != null) return path.intersects(rect); - return false; - } - - public boolean dropsShadow(){ - return (isClosed() - && getType() != DiagramShape.TYPE_ARROWHEAD - && getType() != DiagramShape.TYPE_POINT_MARKER - && !isStrokeDashed()); - } - - /** - * @return - */ - public int getType() { - return type; - } - - /** - * @param i - */ - public void setType(int i) { - type = i; - } - - public void moveEndsToCellEdges(TextGrid grid, Diagram diagram){ - if(isClosed()) return; - - ShapePoint linesEnd = (ShapePoint) points.get(0); - ShapePoint nextPoint = (ShapePoint) points.get(1); - - ShapePoint projectionPoint = getCellEdgeProjectionPointBetween(linesEnd, nextPoint, diagram); - - linesEnd.moveTo(projectionPoint); - - linesEnd = (ShapePoint) points.get(points.size() - 1); - nextPoint = (ShapePoint) points.get(points.size() - 2); - - projectionPoint = getCellEdgeProjectionPointBetween(linesEnd, nextPoint, diagram); - - linesEnd.moveTo(projectionPoint); - } - - public void connectEndsToAnchors(TextGrid grid, Diagram diagram){ - if(isClosed()) return; - - ShapePoint linesEnd; - ShapePoint nextPoint; - - linesEnd = (ShapePoint) points.get(0); - nextPoint = (ShapePoint) points.get(1); - - connectEndToAnchors(grid, diagram, nextPoint, linesEnd); - - linesEnd = (ShapePoint) points.get(points.size() - 1); - nextPoint = (ShapePoint) points.get(points.size() - 2); - - connectEndToAnchors(grid, diagram, nextPoint, linesEnd); - - } - - - //TODO: improve connect Ends To Arrowheads to take direction into account - private void connectEndToAnchors( - TextGrid grid, - Diagram diagram, - ShapePoint nextPoint, - ShapePoint linesEnd){ - - if(isClosed()) return; - - TextGrid.Cell anchorCell; - anchorCell = getPossibleAnchorCell(linesEnd, nextPoint, diagram); - - if(grid.isArrowhead(anchorCell)){ - linesEnd.x = diagram.getCellMidX(anchorCell); - linesEnd.y = diagram.getCellMidY(anchorCell); - linesEnd.setLocked(true); - } else if (grid.isCorner(anchorCell) || grid.isIntersection(anchorCell)){ - linesEnd.x = diagram.getCellMidX(anchorCell); - linesEnd.y = diagram.getCellMidY(anchorCell); - linesEnd.setLocked(true); - } - } - - /** - * Given the end of a line, the next point and a Diagram, it - * returns the cell that may contain intersections or arrowheads - * to which the line's end should be connected - * - * @param linesEnd - * @param nextPoint - * @param diagram - * @return - */ - private static TextGrid.Cell getPossibleAnchorCell( - ShapePoint linesEnd, - ShapePoint nextPoint, - Diagram diagram - ){ - ShapePoint cellPoint = null; - - if(nextPoint.isNorthOf(linesEnd)) - cellPoint = new ShapePoint(linesEnd.x, linesEnd.y + diagram.getCellHeight()); - if(nextPoint.isSouthOf(linesEnd)) - cellPoint = new ShapePoint(linesEnd.x, linesEnd.y - diagram.getCellHeight()); - if(nextPoint.isWestOf(linesEnd)) - cellPoint = new ShapePoint(linesEnd.x + diagram.getCellWidth(), linesEnd.y); - if(nextPoint.isEastOf(linesEnd)) - cellPoint = new ShapePoint(linesEnd.x - diagram.getCellWidth(), linesEnd.y); - - return diagram.getCellFor(cellPoint); - } - - - public String toString(){ - String s = "DiagramShape, "+points.size()+" points: "; - Iterator it = getPointsIterator(); - while(it.hasNext()){ - ShapePoint point = (ShapePoint) it.next(); - s += point; - if(it.hasNext()) s += " "; - } - return s; - } - - /** - * @return - */ - public boolean isStrokeDashed() { - return isStrokeDashed; - } - - /** - * @param b - */ - public void setStrokeDashed(boolean b) { - isStrokeDashed = b; - } - - private GeneralPath makeStoragePath(Diagram diagram) { - if(points.size() != 4) return null; - Rectangle bounds = makeIntoPath().getBounds(); - ShapePoint point1 = new ShapePoint((float)bounds.getMinX(), (float)bounds.getMinY()); - ShapePoint point2 = new ShapePoint((float)bounds.getMaxX(), (float)bounds.getMinY()); - ShapePoint point3 = new ShapePoint((float)bounds.getMaxX(), (float)bounds.getMaxY()); - ShapePoint point4 = new ShapePoint((float)bounds.getMinX(), (float)bounds.getMaxY()); - - ShapePoint pointMidTop = new ShapePoint((float)bounds.getCenterX(), (float)bounds.getMinY()); - ShapePoint pointMidBottom = new ShapePoint((float)bounds.getCenterX(), (float)bounds.getMaxY()); - - float diameterX = bounds.width; - float diameterY = 0.75f * diagram.getCellHeight(); - - //control point offset X, and Y - float cpOffsetX = bounds.width / 6; - float cpOffsetYTop = diagram.getCellHeight() / 2; - float cpOffsetYBottom = 10 * diagram.getCellHeight() / 14; - //float cpOffsetYBottom = cpOffsetYTop; - - GeneralPath path = new GeneralPath(); - - //top of cylinder - path.moveTo(point1.x, point1.y); - path.curveTo( - point1.x + cpOffsetX, point1.y + cpOffsetYTop, - point2.x - cpOffsetX, point2.y + cpOffsetYTop, - point2.x, point2.y - ); - path.curveTo( - point2.x - cpOffsetX, point2.y - cpOffsetYTop, - point1.x + cpOffsetX, point1.y - cpOffsetYTop, - point1.x, point1.y - ); - - //side of cylinder - path.moveTo(point1.x, point1.y); - path.lineTo(point4.x, point4.y); - - path.curveTo( - point4.x + cpOffsetX, point4.y + cpOffsetYBottom, - point3.x - cpOffsetX, point3.y + cpOffsetYBottom, - point3.x, point3.y - ); - - path.lineTo(point2.x, point2.y); - - return path; - } - - private GeneralPath makeDocumentPath(Diagram diagram) { - if(points.size() != 4) return null; - Rectangle bounds = makeIntoPath().getBounds(); - ShapePoint point1 = new ShapePoint((float)bounds.getMinX(), (float)bounds.getMinY()); - ShapePoint point2 = new ShapePoint((float)bounds.getMaxX(), (float)bounds.getMinY()); - ShapePoint point3 = new ShapePoint((float)bounds.getMaxX(), (float)bounds.getMaxY()); - ShapePoint point4 = new ShapePoint((float)bounds.getMinX(), (float)bounds.getMaxY()); - - ShapePoint pointMid = new ShapePoint((float)bounds.getCenterX(), (float)bounds.getMaxY()); - - GeneralPath path = new GeneralPath(); - path.moveTo(point1.x, point1.y); - path.lineTo(point2.x, point2.y); - path.lineTo(point3.x, point3.y); - - //int controlDX = diagram.getCellWidth(); - //int controlDY = diagram.getCellHeight() / 2; - - int controlDX = bounds.width / 6; - int controlDY = bounds.height / 8; - - path.quadTo(pointMid.x + controlDX, pointMid.y - controlDY, pointMid.x, pointMid.y); - path.quadTo(pointMid.x - controlDX, pointMid.y + controlDY, point4.x, point4.y); - path.closePath(); - - return path; - } - - // to draw a circle with 4 Bezier curves, set the control points at this ratio of - // the radius above & below the side points - // thanks to G. Adam Stanislav, http://whizkidtech.redprince.net/bezier/circle/ - private static final float KAPPA = 4f * ((float) Math.sqrt(2) - 1) / 3f; - - private GeneralPath makeEllipsePath(Diagram diagram) { - if(points.size() != 4) return null; - Rectangle bounds = makeIntoPath().getBounds(); - float xOff = (float) bounds.getWidth() * 0.5f * KAPPA; - float yOff = (float) bounds.getHeight() * 0.5f * KAPPA; - ShapePoint pointMid = new ShapePoint((float)bounds.getCenterX(), (float)bounds.getCenterY()); - - ShapePoint left = new ShapePoint((float)bounds.getMinX(), (float)pointMid.getY()); - ShapePoint right = new ShapePoint((float)bounds.getMaxX(), (float)pointMid.getY()); - ShapePoint top = new ShapePoint((float)pointMid.getX(), (float)bounds.getMinY()); - ShapePoint bottom = new ShapePoint((float)pointMid.getX(), (float)bounds.getMaxY()); - - GeneralPath path = new GeneralPath(); - path.moveTo(top.x, top.y); - path.curveTo(top.x + xOff, top.y, right.x, right.y - yOff, right.x, right.y); - path.curveTo(right.x, right.y + yOff, bottom.x + xOff, bottom.y, bottom.x, bottom.y); - path.curveTo(bottom.x - xOff, bottom.y, left.x, left.y + yOff, left.x, left.y); - path.curveTo(left.x, left.y - yOff, top.x - xOff, top.y, top.x, top.y); - path.closePath(); - - return path; - } - - private GeneralPath makeTrapezoidPath(Diagram diagram, RenderingOptions options, boolean inverted) { - if(points.size() != 4) return null; - Rectangle bounds = makeIntoPath().getBounds(); - float offset = options.isFixedSlope() ? bounds.height / SHAPE_SLOPE : diagram.getCellWidth() * 0.5f; - if (inverted) offset = -offset; - ShapePoint ul = new ShapePoint((float)bounds.getMinX() + offset, (float)bounds.getMinY()); - ShapePoint ur = new ShapePoint((float)bounds.getMaxX() - offset, (float)bounds.getMinY()); - ShapePoint br = new ShapePoint((float)bounds.getMaxX() + offset, (float)bounds.getMaxY()); - ShapePoint bl = new ShapePoint((float)bounds.getMinX() - offset, (float)bounds.getMaxY()); - - ShapePoint pointMid = new ShapePoint((float)bounds.getCenterX(), (float)bounds.getMaxY()); - - GeneralPath path = new GeneralPath(); - path.moveTo(ul.x, ul.y); - path.lineTo(ur.x, ur.y); - path.lineTo(br.x, br.y); - path.lineTo(bl.x, bl.y); - path.closePath(); - - return path; - } - - private GeneralPath makeDecisionPath(Diagram diagram) { - if(points.size() != 4) return null; - Rectangle bounds = makeIntoPath().getBounds(); - ShapePoint pointMid = new ShapePoint((float)bounds.getCenterX(), (float)bounds.getCenterY()); - ShapePoint left = new ShapePoint((float)bounds.getMinX(), (float)pointMid.getY()); - ShapePoint right = new ShapePoint((float)bounds.getMaxX(), (float)pointMid.getY()); - ShapePoint top = new ShapePoint((float)pointMid.getX(), (float)bounds.getMinY()); - ShapePoint bottom = new ShapePoint((float)pointMid.getX(), (float)bounds.getMaxY()); - - GeneralPath path = new GeneralPath(); - path.moveTo(left.x, left.y); - path.lineTo(top.x, top.y); - path.lineTo(right.x, right.y); - path.lineTo(bottom.x, bottom.y); - - path.closePath(); - - return path; - } - - private GeneralPath makeIOPath(Diagram diagram, RenderingOptions options) { - if(points.size() != 4) return null; - Rectangle bounds = makeIntoPath().getBounds(); - ShapePoint point1 = new ShapePoint((float)bounds.getMinX(), (float)bounds.getMinY()); - ShapePoint point2 = new ShapePoint((float)bounds.getMaxX(), (float)bounds.getMinY()); - ShapePoint point3 = new ShapePoint((float)bounds.getMaxX(), (float)bounds.getMaxY()); - ShapePoint point4 = new ShapePoint((float)bounds.getMinX(), (float)bounds.getMaxY()); - - float offset = options.isFixedSlope() ? bounds.height / SHAPE_SLOPE : diagram.getCellWidth() * 0.5f; - - GeneralPath path = new GeneralPath(); - path.moveTo(point1.x + offset, point1.y); - path.lineTo(point2.x + offset, point2.y); - path.lineTo(point3.x - offset, point3.y); - path.lineTo(point4.x - offset, point4.y); - path.closePath(); - - return path; - } - - public CustomShapeDefinition getDefinition() { - return definition; - } - - public void setDefinition(CustomShapeDefinition definition) { - this.definition = definition; - } - - /** - * See http://mathworld.wolfram.com/PolygonArea.html - * - * @return the overall area of the shape - */ - public double calculateArea() { - if(points.size() == 0) return 0; - - double area = 0; - - for(int i = 0; i < points.size() - 1; i++){ - ShapePoint point1 = points.get(i); - ShapePoint point2 = points.get(i + 1); - area += point1.x * point2.y; - area -= point2.x * point1.y; - } - ShapePoint point1 = points.get(points.size() - 1); - ShapePoint point2 = points.get(0); - area += point1.x * point2.y; - area -= point2.x * point1.y; - - return Math.abs(area / 2); - } - + } + } + //TODO: this shouldn't be needed, but it is! + if (isClosed() && size > 2) { + path.closePath(); + } + return path; + } + + public ArrayList getEdges() { + ArrayList edges = new ArrayList(); + if (this.points.size() == 1) + return edges; + int noOfPoints = points.size(); + for (int i = 0; i < noOfPoints - 1; i++) { + ShapePoint startPoint = (ShapePoint) points.get(i); + ShapePoint endPoint = (ShapePoint) points.get(i + 1); + ShapeEdge edge = new ShapeEdge(startPoint, endPoint, this); + edges.add(edge); + } + //if it is closed return edge that connects the + //last point to the first + if (this.isClosed()) { + ShapePoint firstPoint = (ShapePoint) points.get(0); + ShapePoint lastPoint = (ShapePoint) points.get(points.size() - 1); + ShapeEdge edge = new ShapeEdge(lastPoint, firstPoint, this); + edges.add(edge); + } + return edges; + } + + /** + * Finds the point that represents the intersection between the cell edge + * that contains pointInCell and the line connecting pointInCell and + * otherPoint. + * + * Returns C, if A is point in cell and B is otherPoint: + *
+   *     Cell
+   *    +-----+
+   *    |  A  |C                 B
+   *    |  *--*------------------*
+   *    |     |
+   *    +-----+
+   *
+ * + * @param pointInCell + * @param otherPoint + * @return + */ + public ShapePoint getCellEdgePointBetween(ShapePoint pointInCell, ShapePoint otherPoint, Diagram diagram) { + if (pointInCell == null || otherPoint == null || diagram == null) + throw new IllegalArgumentException("None of the parameters can be null"); + if (pointInCell.equals(otherPoint)) + throw new IllegalArgumentException("The two points cannot be the same"); + + ShapePoint result = null; + TextGrid.Cell cell = diagram.getCellFor(pointInCell); + + if (cell == null) + throw new RuntimeException("Upexpected error, cannot find cell corresponding to point " + pointInCell + " for diagram " + diagram); + + if (otherPoint.isNorthOf(pointInCell)) + result = new ShapePoint(pointInCell.x, + diagram.getCellMinY(cell)); + else if (otherPoint.isSouthOf(pointInCell)) + result = new ShapePoint(pointInCell.x, + diagram.getCellMaxY(cell)); + else if (otherPoint.isWestOf(pointInCell)) + result = new ShapePoint(diagram.getCellMinX(cell), + pointInCell.y); + else if (otherPoint.isEastOf(pointInCell)) + result = new ShapePoint(diagram.getCellMaxX(cell), + pointInCell.y); + + if (result == null) + throw new RuntimeException("Upexpected error, cannot find cell edge point for points " + pointInCell + " and " + otherPoint + " for diagram " + diagram); + + return result; + } + + + /** + * + * Returns C, if A is point in cell and B is otherPoint: + * + *
+   *     Cell
+   *    +-----+
+   *    |  A  |                  B
+   *  C *--*--+------------------*
+   *    |     |
+   *    +-----+
+   * 
+ * + * @param pointInCell + * @param otherPoint + * @param diagram + * @return + */ + + public ShapePoint getCellEdgeProjectionPointBetween(ShapePoint pointInCell, ShapePoint otherPoint, Diagram diagram) { + if (pointInCell == null || otherPoint == null || diagram == null) + throw new IllegalArgumentException("None of the parameters can be null"); + if (pointInCell.equals(otherPoint)) + throw new IllegalArgumentException("The two points cannot be the same: " + pointInCell + " and " + otherPoint + " passed"); + + ShapePoint result = null; + TextGrid.Cell cell = diagram.getCellFor(pointInCell); + + if (cell == null) + throw new RuntimeException("Unexpected error, cannot find cell corresponding to point " + pointInCell + " for diagram " + diagram); + + if (otherPoint.isNorthOf(pointInCell)) + result = new ShapePoint(pointInCell.x, + diagram.getCellMaxY(cell)); + else if (otherPoint.isSouthOf(pointInCell)) + result = new ShapePoint(pointInCell.x, + diagram.getCellMinY(cell)); + else if (otherPoint.isWestOf(pointInCell)) + result = new ShapePoint(diagram.getCellMaxX(cell), + pointInCell.y); + else if (otherPoint.isEastOf(pointInCell)) + result = new ShapePoint(diagram.getCellMinX(cell), + pointInCell.y); + + if (result == null) + throw new RuntimeException("Unexpected error, cannot find cell edge point for points " + pointInCell + " and " + otherPoint + " for diagram " + diagram); + + return result; + } + + public boolean contains(ShapePoint point) { + GeneralPath path = makeIntoPath(); + if (path != null) + return path.contains(point); + return false; + } + + public boolean contains(Rectangle2D rect) { + GeneralPath path = makeIntoPath(); + if (path != null) + return path.contains(rect); + return false; + } + + public boolean intersects(Rectangle2D rect) { + GeneralPath path = makeIntoPath(); + if (path != null) + return path.intersects(rect); + return false; + } + + public boolean dropsShadow() { + return (isClosed() + && getType() != DiagramShape.TYPE_ARROWHEAD + && getType() != DiagramShape.TYPE_POINT_MARKER + && !isStrokeDashed()); + } + + /** + * @return + */ + public int getType() { + return type; + } + + /** + * @param i + */ + public void setType(int i) { + type = i; + } + + public void moveEndsToCellEdges(TextGrid grid, Diagram diagram) { + if (isClosed()) + return; + + ShapePoint linesEnd = (ShapePoint) points.get(0); + ShapePoint nextPoint = (ShapePoint) points.get(1); + + ShapePoint projectionPoint = getCellEdgeProjectionPointBetween(linesEnd, nextPoint, diagram); + + linesEnd.moveTo(projectionPoint); + + linesEnd = (ShapePoint) points.get(points.size() - 1); + nextPoint = (ShapePoint) points.get(points.size() - 2); + + projectionPoint = getCellEdgeProjectionPointBetween(linesEnd, nextPoint, diagram); + + linesEnd.moveTo(projectionPoint); + } + + public void connectEndsToAnchors(TextGrid grid, Diagram diagram) { + if (isClosed()) + return; + + ShapePoint linesEnd; + ShapePoint nextPoint; + + linesEnd = (ShapePoint) points.get(0); + nextPoint = (ShapePoint) points.get(1); + + connectEndToAnchors(grid, diagram, nextPoint, linesEnd); + + linesEnd = (ShapePoint) points.get(points.size() - 1); + nextPoint = (ShapePoint) points.get(points.size() - 2); + + connectEndToAnchors(grid, diagram, nextPoint, linesEnd); + + } + + + //TODO: improve connect Ends To Arrowheads to take direction into account + private void connectEndToAnchors( + TextGrid grid, + Diagram diagram, + ShapePoint nextPoint, + ShapePoint linesEnd) { + + if (isClosed()) + return; + + TextGrid.Cell anchorCell; + anchorCell = getPossibleAnchorCell(linesEnd, nextPoint, diagram); + + if (grid.isArrowhead(anchorCell)) { + linesEnd.x = diagram.getCellMidX(anchorCell); + linesEnd.y = diagram.getCellMidY(anchorCell); + linesEnd.setLocked(true); + } else if (grid.isCorner(anchorCell) || grid.isIntersection(anchorCell)) { + linesEnd.x = diagram.getCellMidX(anchorCell); + linesEnd.y = diagram.getCellMidY(anchorCell); + linesEnd.setLocked(true); + } + } + + /** + * Given the end of a line, the next point and a Diagram, it + * returns the cell that may contain intersections or arrowheads + * to which the line's end should be connected + * + * @param linesEnd + * @param nextPoint + * @param diagram + * @return + */ + private static TextGrid.Cell getPossibleAnchorCell( + ShapePoint linesEnd, + ShapePoint nextPoint, + Diagram diagram + ) { + ShapePoint cellPoint = null; + + if (nextPoint.isNorthOf(linesEnd)) + cellPoint = new ShapePoint(linesEnd.x, linesEnd.y + diagram.getCellHeight()); + if (nextPoint.isSouthOf(linesEnd)) + cellPoint = new ShapePoint(linesEnd.x, linesEnd.y - diagram.getCellHeight()); + if (nextPoint.isWestOf(linesEnd)) + cellPoint = new ShapePoint(linesEnd.x + diagram.getCellWidth(), linesEnd.y); + if (nextPoint.isEastOf(linesEnd)) + cellPoint = new ShapePoint(linesEnd.x - diagram.getCellWidth(), linesEnd.y); + + return diagram.getCellFor(cellPoint); + } + + + public String toString() { + String s = "DiagramShape, " + points.size() + " points: "; + Iterator it = getPointsIterator(); + while (it.hasNext()) { + ShapePoint point = (ShapePoint) it.next(); + s += point; + if (it.hasNext()) + s += " "; + } + return s; + } + + /** + * @return + */ + public boolean isStrokeDashed() { + return isStrokeDashed; + } + + /** + * @param b + */ + public void setStrokeDashed(boolean b) { + isStrokeDashed = b; + } + + private GeneralPath makeStoragePath(Diagram diagram) { + if (points.size() != 4) + return null; + Rectangle bounds = makeIntoPath().getBounds(); + ShapePoint point1 = new ShapePoint((float) bounds.getMinX(), (float) bounds.getMinY()); + ShapePoint point2 = new ShapePoint((float) bounds.getMaxX(), (float) bounds.getMinY()); + ShapePoint point3 = new ShapePoint((float) bounds.getMaxX(), (float) bounds.getMaxY()); + ShapePoint point4 = new ShapePoint((float) bounds.getMinX(), (float) bounds.getMaxY()); + + ShapePoint pointMidTop = new ShapePoint((float) bounds.getCenterX(), (float) bounds.getMinY()); + ShapePoint pointMidBottom = new ShapePoint((float) bounds.getCenterX(), (float) bounds.getMaxY()); + + float diameterX = bounds.width; + float diameterY = 0.75f * diagram.getCellHeight(); + + //control point offset X, and Y + float cpOffsetX = bounds.width / 6; + float cpOffsetYTop = diagram.getCellHeight() / 2; + float cpOffsetYBottom = 10 * diagram.getCellHeight() / 14; + //float cpOffsetYBottom = cpOffsetYTop; + + GeneralPath path = new GeneralPath(); + + //top of cylinder + path.moveTo(point1.x, point1.y); + path.curveTo( + point1.x + cpOffsetX, point1.y + cpOffsetYTop, + point2.x - cpOffsetX, point2.y + cpOffsetYTop, + point2.x, point2.y + ); + path.curveTo( + point2.x - cpOffsetX, point2.y - cpOffsetYTop, + point1.x + cpOffsetX, point1.y - cpOffsetYTop, + point1.x, point1.y + ); + + //side of cylinder + path.moveTo(point1.x, point1.y); + path.lineTo(point4.x, point4.y); + + path.curveTo( + point4.x + cpOffsetX, point4.y + cpOffsetYBottom, + point3.x - cpOffsetX, point3.y + cpOffsetYBottom, + point3.x, point3.y + ); + + path.lineTo(point2.x, point2.y); + + return path; + } + + private GeneralPath makeDocumentPath(Diagram diagram) { + if (points.size() != 4) + return null; + Rectangle bounds = makeIntoPath().getBounds(); + ShapePoint point1 = new ShapePoint((float) bounds.getMinX(), (float) bounds.getMinY()); + ShapePoint point2 = new ShapePoint((float) bounds.getMaxX(), (float) bounds.getMinY()); + ShapePoint point3 = new ShapePoint((float) bounds.getMaxX(), (float) bounds.getMaxY()); + ShapePoint point4 = new ShapePoint((float) bounds.getMinX(), (float) bounds.getMaxY()); + + ShapePoint pointMid = new ShapePoint((float) bounds.getCenterX(), (float) bounds.getMaxY()); + + GeneralPath path = new GeneralPath(); + path.moveTo(point1.x, point1.y); + path.lineTo(point2.x, point2.y); + path.lineTo(point3.x, point3.y); + + //int controlDX = diagram.getCellWidth(); + //int controlDY = diagram.getCellHeight() / 2; + + int controlDX = bounds.width / 6; + int controlDY = bounds.height / 8; + + path.quadTo(pointMid.x + controlDX, pointMid.y - controlDY, pointMid.x, pointMid.y); + path.quadTo(pointMid.x - controlDX, pointMid.y + controlDY, point4.x, point4.y); + path.closePath(); + + return path; + } + + // to draw a circle with 4 Bezier curves, set the control points at this ratio of + // the radius above & below the side points + // thanks to G. Adam Stanislav, http://whizkidtech.redprince.net/bezier/circle/ + private static final float KAPPA = 4f * ((float) Math.sqrt(2) - 1) / 3f; + + private GeneralPath makeEllipsePath(Diagram diagram) { + if (points.size() != 4) + return null; + Rectangle bounds = makeIntoPath().getBounds(); + float xOff = (float) bounds.getWidth() * 0.5f * KAPPA; + float yOff = (float) bounds.getHeight() * 0.5f * KAPPA; + ShapePoint pointMid = new ShapePoint((float) bounds.getCenterX(), (float) bounds.getCenterY()); + + ShapePoint left = new ShapePoint((float) bounds.getMinX(), (float) pointMid.getY()); + ShapePoint right = new ShapePoint((float) bounds.getMaxX(), (float) pointMid.getY()); + ShapePoint top = new ShapePoint((float) pointMid.getX(), (float) bounds.getMinY()); + ShapePoint bottom = new ShapePoint((float) pointMid.getX(), (float) bounds.getMaxY()); + + GeneralPath path = new GeneralPath(); + path.moveTo(top.x, top.y); + path.curveTo(top.x + xOff, top.y, right.x, right.y - yOff, right.x, right.y); + path.curveTo(right.x, right.y + yOff, bottom.x + xOff, bottom.y, bottom.x, bottom.y); + path.curveTo(bottom.x - xOff, bottom.y, left.x, left.y + yOff, left.x, left.y); + path.curveTo(left.x, left.y - yOff, top.x - xOff, top.y, top.x, top.y); + path.closePath(); + + return path; + } + + private GeneralPath makeTrapezoidPath(Diagram diagram, RenderingOptions options, boolean inverted) { + if (points.size() != 4) + return null; + Rectangle bounds = makeIntoPath().getBounds(); + float offset = options.isFixedSlope() ? bounds.height / SHAPE_SLOPE : diagram.getCellWidth() * 0.5f; + if (inverted) + offset = -offset; + ShapePoint ul = new ShapePoint((float) bounds.getMinX() + offset, (float) bounds.getMinY()); + ShapePoint ur = new ShapePoint((float) bounds.getMaxX() - offset, (float) bounds.getMinY()); + ShapePoint br = new ShapePoint((float) bounds.getMaxX() + offset, (float) bounds.getMaxY()); + ShapePoint bl = new ShapePoint((float) bounds.getMinX() - offset, (float) bounds.getMaxY()); + + ShapePoint pointMid = new ShapePoint((float) bounds.getCenterX(), (float) bounds.getMaxY()); + + GeneralPath path = new GeneralPath(); + path.moveTo(ul.x, ul.y); + path.lineTo(ur.x, ur.y); + path.lineTo(br.x, br.y); + path.lineTo(bl.x, bl.y); + path.closePath(); + + return path; + } + + private GeneralPath makeDecisionPath(Diagram diagram) { + if (points.size() != 4) + return null; + Rectangle bounds = makeIntoPath().getBounds(); + ShapePoint pointMid = new ShapePoint((float) bounds.getCenterX(), (float) bounds.getCenterY()); + ShapePoint left = new ShapePoint((float) bounds.getMinX(), (float) pointMid.getY()); + ShapePoint right = new ShapePoint((float) bounds.getMaxX(), (float) pointMid.getY()); + ShapePoint top = new ShapePoint((float) pointMid.getX(), (float) bounds.getMinY()); + ShapePoint bottom = new ShapePoint((float) pointMid.getX(), (float) bounds.getMaxY()); + + GeneralPath path = new GeneralPath(); + path.moveTo(left.x, left.y); + path.lineTo(top.x, top.y); + path.lineTo(right.x, right.y); + path.lineTo(bottom.x, bottom.y); + + path.closePath(); + + return path; + } + + private GeneralPath makeIOPath(Diagram diagram, RenderingOptions options) { + if (points.size() != 4) + return null; + Rectangle bounds = makeIntoPath().getBounds(); + ShapePoint point1 = new ShapePoint((float) bounds.getMinX(), (float) bounds.getMinY()); + ShapePoint point2 = new ShapePoint((float) bounds.getMaxX(), (float) bounds.getMinY()); + ShapePoint point3 = new ShapePoint((float) bounds.getMaxX(), (float) bounds.getMaxY()); + ShapePoint point4 = new ShapePoint((float) bounds.getMinX(), (float) bounds.getMaxY()); + + float offset = options.isFixedSlope() ? bounds.height / SHAPE_SLOPE : diagram.getCellWidth() * 0.5f; + + GeneralPath path = new GeneralPath(); + path.moveTo(point1.x + offset, point1.y); + path.lineTo(point2.x + offset, point2.y); + path.lineTo(point3.x - offset, point3.y); + path.lineTo(point4.x - offset, point4.y); + path.closePath(); + + return path; + } + + public CustomShapeDefinition getDefinition() { + return definition; + } + + public void setDefinition(CustomShapeDefinition definition) { + this.definition = definition; + } + + /** + * See http://mathworld.wolfram.com/PolygonArea.html + * + * @return the overall area of the shape + */ + public double calculateArea() { + if (points.size() == 0) + return 0; + + double area = 0; + + for (int i = 0; i < points.size() - 1; i++) { + ShapePoint point1 = points.get(i); + ShapePoint point2 = points.get(i + 1); + area += point1.x * point2.y; + area -= point2.x * point1.y; + } + ShapePoint point1 = points.get(points.size() - 1); + ShapePoint point2 = points.get(0); + area += point1.x * point2.y; + area -= point2.x * point1.y; + + return Math.abs(area / 2); + } + } diff --git a/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java b/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java index b22d5ce..2cef56f 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java +++ b/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java @@ -23,8 +23,10 @@ import org.scilab.forge.jlatexmath.TeXIcon; import org.stathissideris.ascii2image.text.StringUtils; -import javax.swing.*; -import java.awt.*; +import javax.swing.JLabel; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; import java.util.Iterator; import java.util.regex.Pattern; @@ -35,217 +37,219 @@ */ public class DiagramText extends DiagramComponent { - public static final Color DEFAULT_COLOR = Color.black; - public static final Pattern TEXT_SPLITTING_REGEX = Pattern.compile("([^$]+|\\$[^$]*\\$)"); - - private String text; - private Font font; - private int xPos, yPos; - private Color color = Color.black; - private boolean isTextOnLine = false; - private boolean hasOutline = false; - private Color outlineColor = Color.white; - - public DiagramText(int x, int y, String text, Font font) { - if (text == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null string"); - if (font == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null font"); - - this.xPos = x; - this.yPos = y; - this.text = text; - this.font = font; - } - - public void centerInBounds(Rectangle2D bounds) { - centerHorizontallyBetween((int) bounds.getMinX(), (int) bounds.getMaxX()); - centerVerticallyBetween((int) bounds.getMinY(), (int) bounds.getMaxY()); - } - - public void centerHorizontallyBetween(int minX, int maxX) { - int width = FontMeasurer.instance().getWidthFor(text, font); - int center = Math.abs(maxX - minX) / 2; - xPos += Math.abs(center - width / 2); - - } - - public void centerVerticallyBetween(int minY, int maxY) { - int zHeight = FontMeasurer.instance().getZHeight(font); - int center = Math.abs(maxY - minY) / 2; - yPos -= Math.abs(center - zHeight / 2); - } - - public void alignRightEdgeTo(int x) { - int width = FontMeasurer.instance().getWidthFor(text, font); - xPos = x - width; - } - - - /** - * @return - */ - public Color getColor() { - return color; - } - - /** - * @return - */ - public Font getFont() { - return font; - } - - /** - * @return - */ - public String getText() { - return text; - } - - public void drawOn(Graphics2D g2) { - g2.setFont(this.getFont()); - if (this.hasOutline()) { - g2.setColor(this.getOutlineColor()); - Stream.of(1, -1) - .peek(d -> draw(g2, this.getXPos() + d, this.getYPos())) - .peek(d -> draw(g2, this.getXPos(), this.getYPos() + d)) - .forEach(d -> { - }); - } - g2.setColor(this.getColor()); - draw(g2, this.getXPos(), this.getYPos()); - } - - private void draw(Graphics2D g2, int xPos, int yPos) { - Iterator i = StringUtils.createTextSplitter(TEXT_SPLITTING_REGEX, getText()); - int x = xPos; - while (i.hasNext()) { - String text = i.next(); - if (text.startsWith("$")) - x += drawTeXFormula( - g2, - text, - x, yPos, this.getColor(), - this.getFont().getSize()); - else - x += drawString( - g2, - text, - x, yPos, this.getColor(), - this.getFont()); - } - } - - /** - * @return - */ - public int getXPos() { - return xPos; - } - - /** - * @return - */ - public int getYPos() { - return yPos; - } - - /** - * @param color - */ - public void setColor(Color color) { - this.color = color; - } - - /** - * @param font - */ - public void setFont(Font font) { - this.font = font; - } - - /** - * @param string - */ - public void setText(String string) { - text = string; - } - - /** - * @param i - */ - public void setXPos(int i) { - xPos = i; - } - - /** - * @param i - */ - public void setYPos(int i) { - yPos = i; - } - - public Rectangle2D getBounds() { - Rectangle2D bounds = FontMeasurer.instance().getBoundsFor(text, font); - bounds.setRect( - bounds.getMinX() + xPos, - bounds.getMinY() + yPos, - bounds.getWidth(), - bounds.getHeight()); - return bounds; - } - - public String toString() { - return "DiagramText, at (" + xPos + ", " + yPos + "), within " + getBounds() + " '" + text + "', " + color + " " + font; - } - - /** - * @return - */ - public boolean isTextOnLine() { - return isTextOnLine; - } - - /** - * @param b - */ - public void setTextOnLine(boolean b) { - isTextOnLine = b; - } - - public boolean hasOutline() { - return hasOutline; - } - - public void setHasOutline(boolean hasOutline) { - this.hasOutline = hasOutline; - } - - public Color getOutlineColor() { - return outlineColor; - } - - public void setOutlineColor(Color outlineColor) { - this.outlineColor = outlineColor; - } - - public static int drawTeXFormula(Graphics2D g2, String text, int x, int y, Color color, float fontSize) { - TeXFormula formula = new TeXFormula(text); - TeXIcon icon = formula.new TeXIconBuilder() - .setStyle(TeXConstants.STYLE_DISPLAY) - .setSize(fontSize) - .build(); - /* 12 is a magic number to adjust vertical position */ - icon.paintIcon(new JLabel() {{ - setForeground(color); - }}, g2, x, y - 12); - return icon.getIconWidth(); - } - - private static int drawString(Graphics2D g2, String text, int xPos, int yPos, Color color, Font font) { - g2.setColor(color); - g2.setFont(font); - g2.drawString(text, xPos, yPos); - return FontMeasurer.instance().getWidthFor(text, font); - } + public static final Color DEFAULT_COLOR = Color.black; + public static final Pattern TEXT_SPLITTING_REGEX = Pattern.compile("([^$]+|\\$[^$]*\\$)"); + + private String text; + private Font font; + private int xPos, yPos; + private Color color = Color.black; + private boolean isTextOnLine = false; + private boolean hasOutline = false; + private Color outlineColor = Color.white; + + public DiagramText(int x, int y, String text, Font font) { + if (text == null) + throw new IllegalArgumentException("DiagramText cannot be initialised with a null string"); + if (font == null) + throw new IllegalArgumentException("DiagramText cannot be initialised with a null font"); + + this.xPos = x; + this.yPos = y; + this.text = text; + this.font = font; + } + + public void centerInBounds(Rectangle2D bounds) { + centerHorizontallyBetween((int) bounds.getMinX(), (int) bounds.getMaxX()); + centerVerticallyBetween((int) bounds.getMinY(), (int) bounds.getMaxY()); + } + + public void centerHorizontallyBetween(int minX, int maxX) { + int width = FontMeasurer.instance().getWidthFor(text, font); + int center = Math.abs(maxX - minX) / 2; + xPos += Math.abs(center - width / 2); + + } + + public void centerVerticallyBetween(int minY, int maxY) { + int zHeight = FontMeasurer.instance().getZHeight(font); + int center = Math.abs(maxY - minY) / 2; + yPos -= Math.abs(center - zHeight / 2); + } + + public void alignRightEdgeTo(int x) { + int width = FontMeasurer.instance().getWidthFor(text, font); + xPos = x - width; + } + + + /** + * @return + */ + public Color getColor() { + return color; + } + + /** + * @return + */ + public Font getFont() { + return font; + } + + /** + * @return + */ + public String getText() { + return text; + } + + public void drawOn(Graphics2D g2) { + g2.setFont(this.getFont()); + if (this.hasOutline()) { + g2.setColor(this.getOutlineColor()); + Stream.of(1, -1) + .peek(d -> draw(g2, this.getXPos() + d, this.getYPos())) + .peek(d -> draw(g2, this.getXPos(), this.getYPos() + d)) + .forEach(d -> { + }); + } + g2.setColor(this.getColor()); + draw(g2, this.getXPos(), this.getYPos()); + } + + private void draw(Graphics2D g2, int xPos, int yPos) { + Iterator i = StringUtils.createTextSplitter(TEXT_SPLITTING_REGEX, getText()); + int x = xPos; + while (i.hasNext()) { + String text = i.next(); + if (text.startsWith("$")) + x += drawTeXFormula( + g2, + text, + x, yPos, this.getColor(), + this.getFont().getSize()); + else + x += drawString( + g2, + text, + x, yPos, this.getColor(), + this.getFont()); + } + } + + /** + * @return + */ + public int getXPos() { + return xPos; + } + + /** + * @return + */ + public int getYPos() { + return yPos; + } + + /** + * @param color + */ + public void setColor(Color color) { + this.color = color; + } + + /** + * @param font + */ + public void setFont(Font font) { + this.font = font; + } + + /** + * @param string + */ + public void setText(String string) { + text = string; + } + + /** + * @param i + */ + public void setXPos(int i) { + xPos = i; + } + + /** + * @param i + */ + public void setYPos(int i) { + yPos = i; + } + + public Rectangle2D getBounds() { + Rectangle2D bounds = FontMeasurer.instance().getBoundsFor(text, font); + bounds.setRect( + bounds.getMinX() + xPos, + bounds.getMinY() + yPos, + bounds.getWidth(), + bounds.getHeight()); + return bounds; + } + + public String toString() { + return "DiagramText, at (" + xPos + ", " + yPos + "), within " + getBounds() + " '" + text + "', " + color + " " + font; + } + + /** + * @return + */ + public boolean isTextOnLine() { + return isTextOnLine; + } + + /** + * @param b + */ + public void setTextOnLine(boolean b) { + isTextOnLine = b; + } + + public boolean hasOutline() { + return hasOutline; + } + + public void setHasOutline(boolean hasOutline) { + this.hasOutline = hasOutline; + } + + public Color getOutlineColor() { + return outlineColor; + } + + public void setOutlineColor(Color outlineColor) { + this.outlineColor = outlineColor; + } + + public static int drawTeXFormula(Graphics2D g2, String text, int x, int y, Color color, float fontSize) { + TeXFormula formula = new TeXFormula(text); + TeXIcon icon = formula.new TeXIconBuilder() + .setStyle(TeXConstants.STYLE_DISPLAY) + .setSize(fontSize) + .build(); + /* 12 is a magic number to adjust vertical position */ + icon.paintIcon(new JLabel() {{ + setForeground(color); + }}, g2, x, y - 12); + return icon.getIconWidth(); + } + + private static int drawString(Graphics2D g2, String text, int xPos, int yPos, Color color, Font font) { + g2.setColor(color); + g2.setFont(font); + g2.drawString(text, xPos, yPos); + return FontMeasurer.instance().getWidthFor(text, font); + } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/FontMeasurer.java b/src/java/org/stathissideris/ascii2image/graphics/FontMeasurer.java index 6cdac3e..62556a9 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/FontMeasurer.java +++ b/src/java/org/stathissideris/ascii2image/graphics/FontMeasurer.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,187 +15,188 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.graphics; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; -import java.awt.GraphicsEnvironment; -import java.awt.Rectangle; import java.awt.font.FontRenderContext; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.util.Locale; -import javax.swing.JOptionPane; - /** - * + * * @author Efstathios Sideris */ public class FontMeasurer { - - private static final String fontFamilyName = "Dialog"; - //private static final String fontFamilyName = "Helvetica"; - - private static final boolean DEBUG = false; - - private static final FontMeasurer instance = new FontMeasurer(); - FontRenderContext fakeRenderContext; - Graphics2D fakeGraphics; - - { - BufferedImage image = new BufferedImage(1,1, BufferedImage.TYPE_INT_RGB); - fakeGraphics = image.createGraphics(); - - if(DEBUG) System.out.println("Locale: "+Locale.getDefault()); - - fakeRenderContext = fakeGraphics.getFontRenderContext(); - } - - - public int getWidthFor(String str, int pixelHeight){ - Font font = getFontFor(pixelHeight); - Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext); - return (int) rectangle.getWidth(); - } - - public int getHeightFor(String str, int pixelHeight){ - Font font = getFontFor(pixelHeight); - Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext); - return (int) rectangle.getHeight(); - } - - public int getWidthFor(String str, Font font){ - Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext); - return (int) rectangle.getWidth(); - } - - public int getHeightFor(String str, Font font){ - Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext); - return (int) rectangle.getHeight(); - } - - public Rectangle2D getBoundsFor(String str, Font font){ - return font.getStringBounds(str, fakeRenderContext); - } - - public Font getFontFor(int pixelHeight){ - BufferedImage image = new BufferedImage(1,1, BufferedImage.TYPE_INT_RGB); - Graphics2D g2 = image.createGraphics(); - return getFontFor(pixelHeight, fakeRenderContext); - } - - public int getAscent(Font font){ - fakeGraphics.setFont(font); - FontMetrics metrics = fakeGraphics.getFontMetrics(); - if(DEBUG) System.out.println("Ascent: "+metrics.getAscent()); - return metrics.getAscent(); - } - - public int getZHeight(Font font){ - int height = (int) font.createGlyphVector(fakeRenderContext, "Z").getOutline().getBounds().getHeight(); - if(DEBUG) System.out.println("Z height: "+height); - return height; - } - - public Font getFontFor(int maxWidth, String string){ - float size = 12; - Font currentFont = new Font(fontFamilyName, Font.BOLD, (int) size); - //ascent is the distance between the baseline and the tallest character - int width = getWidthFor(string, currentFont); - - int direction; //direction of size change (towards smaller or bigger) - if(width > maxWidth){ - currentFont = currentFont.deriveFont(size - 1); - size--; - direction = -1; - } else { - currentFont = currentFont.deriveFont(size + 1); - size++; - direction = 1; - } - while(size > 0){ - currentFont = currentFont.deriveFont(size); - //rectangle = currentFont.getStringBounds(testString, frc); - width = getWidthFor(string, currentFont); - if(direction == 1){ - if(width > maxWidth){ - size = size - 1; - return currentFont.deriveFont(size); - } - else size = size + 1; - } else { - if(width < maxWidth) - return currentFont; - else size = size - 1; - } - } - return null; - } - - - - public Font getFontFor(int pixelHeight, FontRenderContext frc){ - float size = 12; - Font currentFont = new Font(fontFamilyName, Font.BOLD, (int) size); -// Font currentFont = new Font("Times", Font.BOLD, (int) size); - if(DEBUG) System.out.println(currentFont.getFontName()); - //ascent is the distance between the baseline and the tallest character - int ascent = getAscent(currentFont); - - int direction; //direction of size change (towards smaller or bigger) - if(ascent > pixelHeight){ - currentFont = currentFont.deriveFont(size - 1); - size--; - direction = -1; - } else { - currentFont = currentFont.deriveFont(size + 1); - size++; - direction = 1; - } - while(size > 0){ - currentFont = currentFont.deriveFont(size); - //rectangle = currentFont.getStringBounds(testString, frc); - ascent = getAscent(currentFont); - if(direction == 1){ - if(ascent > pixelHeight){ - size = size - 0.5f; - return currentFont.deriveFont(size); - } - else size = size + 0.5f; - } else { - if(ascent < pixelHeight) - return currentFont; - else size = size - 0.5f; - } - } - return null; - } - - public static FontMeasurer instance(){ - return instance; - } - - public FontMeasurer(){ - } - - public static void main(String[] args) { - //FontMeasurer.instance().getFontFor(7); - float size = 12; - Font currentFont = new Font("Sans", Font.BOLD, (int) size); - if(DEBUG) System.out.println(currentFont.getSize()); - currentFont = currentFont.deriveFont(--size); - System.out.println(currentFont.getSize()); - currentFont = currentFont.deriveFont(--size); - System.out.println(currentFont.getSize()); - currentFont = currentFont.deriveFont(--size); - System.out.println(currentFont.getSize()); - currentFont = currentFont.deriveFont(--size); - System.out.println(currentFont.getSize()); - currentFont = currentFont.deriveFont(--size); - System.out.println(currentFont.getSize()); - } + + private static final String fontFamilyName = "Dialog"; + //private static final String fontFamilyName = "Helvetica"; + + private static final boolean DEBUG = false; + + private static final FontMeasurer instance = new FontMeasurer(); + FontRenderContext fakeRenderContext; + Graphics2D fakeGraphics; + + { + BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); + fakeGraphics = image.createGraphics(); + + if (DEBUG) + System.out.println("Locale: " + Locale.getDefault()); + + fakeRenderContext = fakeGraphics.getFontRenderContext(); + } + + + public int getWidthFor(String str, int pixelHeight) { + Font font = getFontFor(pixelHeight); + Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext); + return (int) rectangle.getWidth(); + } + + public int getHeightFor(String str, int pixelHeight) { + Font font = getFontFor(pixelHeight); + Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext); + return (int) rectangle.getHeight(); + } + + public int getWidthFor(String str, Font font) { + Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext); + return (int) rectangle.getWidth(); + } + + public int getHeightFor(String str, Font font) { + Rectangle2D rectangle = font.getStringBounds(str, fakeRenderContext); + return (int) rectangle.getHeight(); + } + + public Rectangle2D getBoundsFor(String str, Font font) { + return font.getStringBounds(str, fakeRenderContext); + } + + public Font getFontFor(int pixelHeight) { + BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); + Graphics2D g2 = image.createGraphics(); + return getFontFor(pixelHeight, fakeRenderContext); + } + + public int getAscent(Font font) { + fakeGraphics.setFont(font); + FontMetrics metrics = fakeGraphics.getFontMetrics(); + if (DEBUG) + System.out.println("Ascent: " + metrics.getAscent()); + return metrics.getAscent(); + } + + public int getZHeight(Font font) { + int height = (int) font.createGlyphVector(fakeRenderContext, "Z").getOutline().getBounds().getHeight(); + if (DEBUG) + System.out.println("Z height: " + height); + return height; + } + + public Font getFontFor(int maxWidth, String string) { + float size = 12; + Font currentFont = new Font(fontFamilyName, Font.BOLD, (int) size); + //ascent is the distance between the baseline and the tallest character + int width = getWidthFor(string, currentFont); + + int direction; //direction of size change (towards smaller or bigger) + if (width > maxWidth) { + currentFont = currentFont.deriveFont(size - 1); + size--; + direction = -1; + } else { + currentFont = currentFont.deriveFont(size + 1); + size++; + direction = 1; + } + while (size > 0) { + currentFont = currentFont.deriveFont(size); + //rectangle = currentFont.getStringBounds(testString, frc); + width = getWidthFor(string, currentFont); + if (direction == 1) { + if (width > maxWidth) { + size = size - 1; + return currentFont.deriveFont(size); + } else + size = size + 1; + } else { + if (width < maxWidth) + return currentFont; + else + size = size - 1; + } + } + return null; + } + + + public Font getFontFor(int pixelHeight, FontRenderContext frc) { + float size = 12; + Font currentFont = new Font(fontFamilyName, Font.BOLD, (int) size); + // Font currentFont = new Font("Times", Font.BOLD, (int) size); + if (DEBUG) + System.out.println(currentFont.getFontName()); + //ascent is the distance between the baseline and the tallest character + int ascent = getAscent(currentFont); + + int direction; //direction of size change (towards smaller or bigger) + if (ascent > pixelHeight) { + currentFont = currentFont.deriveFont(size - 1); + size--; + direction = -1; + } else { + currentFont = currentFont.deriveFont(size + 1); + size++; + direction = 1; + } + while (size > 0) { + currentFont = currentFont.deriveFont(size); + //rectangle = currentFont.getStringBounds(testString, frc); + ascent = getAscent(currentFont); + if (direction == 1) { + if (ascent > pixelHeight) { + size = size - 0.5f; + return currentFont.deriveFont(size); + } else + size = size + 0.5f; + } else { + if (ascent < pixelHeight) + return currentFont; + else + size = size - 0.5f; + } + } + return null; + } + + public static FontMeasurer instance() { + return instance; + } + + public FontMeasurer() { + } + + public static void main(String[] args) { + //FontMeasurer.instance().getFontFor(7); + float size = 12; + Font currentFont = new Font("Sans", Font.BOLD, (int) size); + if (DEBUG) + System.out.println(currentFont.getSize()); + currentFont = currentFont.deriveFont(--size); + System.out.println(currentFont.getSize()); + currentFont = currentFont.deriveFont(--size); + System.out.println(currentFont.getSize()); + currentFont = currentFont.deriveFont(--size); + System.out.println(currentFont.getSize()); + currentFont = currentFont.deriveFont(--size); + System.out.println(currentFont.getSize()); + currentFont = currentFont.deriveFont(--size); + System.out.println(currentFont.getSize()); + } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/ImageHandler.java b/src/java/org/stathissideris/ascii2image/graphics/ImageHandler.java index 74f8d98..b3a7cb4 100755 --- a/src/java/org/stathissideris/ascii2image/graphics/ImageHandler.java +++ b/src/java/org/stathissideris/ascii2image/graphics/ImageHandler.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,125 +15,101 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.graphics; +import org.stathissideris.ascii2image.core.FileUtils; + +import javax.imageio.ImageIO; +import javax.swing.JLabel; import java.awt.Color; import java.awt.Image; import java.awt.MediaTracker; -import java.awt.Rectangle; import java.awt.Toolkit; -import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.net.URI; import java.net.URL; -import javax.imageio.ImageIO; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; +public class ImageHandler { -import org.apache.batik.bridge.BridgeContext; -import org.apache.batik.bridge.GVTBuilder; -import org.apache.batik.bridge.UserAgentAdapter; -import org.apache.batik.anim.dom.SAXSVGDocumentFactory; -import org.apache.batik.dom.util.DocumentFactory; -import org.apache.batik.ext.awt.image.codec.png.PNGEncodeParam; -import org.apache.batik.ext.awt.image.codec.png.PNGImageEncoder; -import org.apache.batik.gvt.GraphicsNode; -import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory; -import org.apache.batik.gvt.renderer.ImageRenderer; -import org.apache.batik.gvt.renderer.ImageRendererFactory; -import org.apache.batik.gvt.renderer.StaticRenderer; -import org.stathissideris.ascii2image.core.FileUtils; -import org.w3c.dom.Document; -import org.w3c.dom.svg.SVGDocument; + private static OffScreenSVGRenderer svgRenderer = + new OffScreenSVGRenderer(); -public class ImageHandler { - - private static OffScreenSVGRenderer svgRenderer = - new OffScreenSVGRenderer(); - - private static ImageHandler instance = new ImageHandler(); - - public static ImageHandler instance(){ - return instance; - } - - private static final MediaTracker tracker = new MediaTracker(new JLabel()); - - public BufferedImage loadBufferedImage(File file) throws IOException { - return ImageIO.read(file); - } - - public Image loadImage(String filename){ - URL url = ClassLoader.getSystemResource(filename); - Image result = null; - if(url != null) - result = Toolkit.getDefaultToolkit().getImage(url); - else - result = Toolkit.getDefaultToolkit().getImage(filename); -// result = null; - - //wait for the image to load before returning - tracker.addImage(result, 0); - try { - tracker.waitForID(0); - } catch (InterruptedException e) { - System.err.println("Failed to load image "+filename); - e.printStackTrace(); - } - tracker.removeImage(result, 0); - - return result; - } - - public BufferedImage renderSVG(String filename, int width, int height, boolean stretch) throws IOException { - File file = new File(filename); - URI uri = file.toURI(); - return svgRenderer.renderToImage(uri.toString(), width, height, stretch, null, null); - } - - public BufferedImage renderSVG(String filename, int width, int height, boolean stretch, String idRegex, Color color) throws IOException { - File file = new File(filename); - URI uri = file.toURI(); - return svgRenderer.renderToImage(uri.toString(), width, height, stretch, idRegex, color); - } - - - public static void main(String[] args) throws IOException{ - - OffScreenSVGRenderer renderer = new OffScreenSVGRenderer(); - - //BufferedImage image = instance.renderSVG("sphere.svg", 200, 200, false); - - //BufferedImage image = renderer.renderToImage("file:///Users/sideris/Documents/workspace/ditaa/joystick.svg", FileUtils.readFile(new File("joystick.svg")), 400, 200, false); -// BufferedImage image = renderer.renderToImage( -// null, FileUtils.readFile(new File("sphere.svg")).replaceFirst("#187637", "#3333FF"), 200, 200, false); - - String content = FileUtils.readFile(new File("sphere.svg")).replaceAll("#187637", "#1133FF"); - - System.out.println(content); - -// BufferedImage image = renderer.renderToImage( -// "file:/K:/devel/ditaa/sphere.svg", content, 200, 200, false); - - BufferedImage image = renderer.renderXMLToImage(content, 200, 200, false, null, null); - - - try { - File file = new File("testing.png"); - ImageIO.write(image, "png", file); - } catch (IOException e) { - //e.printStackTrace(); - System.err.println("Error: Cannot write to file"); - } - - } + private static ImageHandler instance = new ImageHandler(); + + public static ImageHandler instance() { + return instance; + } + + private static final MediaTracker tracker = new MediaTracker(new JLabel()); + + public BufferedImage loadBufferedImage(File file) throws IOException { + return ImageIO.read(file); + } + + public Image loadImage(String filename) { + URL url = ClassLoader.getSystemResource(filename); + Image result = null; + if (url != null) + result = Toolkit.getDefaultToolkit().getImage(url); + else + result = Toolkit.getDefaultToolkit().getImage(filename); + // result = null; + + //wait for the image to load before returning + tracker.addImage(result, 0); + try { + tracker.waitForID(0); + } catch (InterruptedException e) { + System.err.println("Failed to load image " + filename); + e.printStackTrace(); + } + tracker.removeImage(result, 0); + + return result; + } + + public BufferedImage renderSVG(String filename, int width, int height, boolean stretch) throws IOException { + File file = new File(filename); + URI uri = file.toURI(); + return svgRenderer.renderToImage(uri.toString(), width, height, stretch, null, null); + } + + public BufferedImage renderSVG(String filename, int width, int height, boolean stretch, String idRegex, Color color) throws IOException { + File file = new File(filename); + URI uri = file.toURI(); + return svgRenderer.renderToImage(uri.toString(), width, height, stretch, idRegex, color); + } + + + public static void main(String[] args) throws IOException { + + OffScreenSVGRenderer renderer = new OffScreenSVGRenderer(); + + //BufferedImage image = instance.renderSVG("sphere.svg", 200, 200, false); + + //BufferedImage image = renderer.renderToImage("file:///Users/sideris/Documents/workspace/ditaa/joystick.svg", FileUtils.readFile(new File("joystick.svg")), 400, 200, false); + // BufferedImage image = renderer.renderToImage( + // null, FileUtils.readFile(new File("sphere.svg")).replaceFirst("#187637", "#3333FF"), 200, 200, false); + + String content = FileUtils.readFile(new File("sphere.svg")).replaceAll("#187637", "#1133FF"); + + System.out.println(content); + + // BufferedImage image = renderer.renderToImage( + // "file:/K:/devel/ditaa/sphere.svg", content, 200, 200, false); + + BufferedImage image = renderer.renderXMLToImage(content, 200, 200, false, null, null); + + try { + File file = new File("testing.png"); + ImageIO.write(image, "png", file); + } catch (IOException e) { + //e.printStackTrace(); + System.err.println("Error: Cannot write to file"); + } + + } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/OffScreenSVGRenderer.java b/src/java/org/stathissideris/ascii2image/graphics/OffScreenSVGRenderer.java index 21c88bc..733cb78 100755 --- a/src/java/org/stathissideris/ascii2image/graphics/OffScreenSVGRenderer.java +++ b/src/java/org/stathissideris/ascii2image/graphics/OffScreenSVGRenderer.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,21 +15,13 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.graphics; -import java.awt.Color; -import java.awt.Rectangle; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.StringReader; - +import org.apache.batik.anim.dom.SAXSVGDocumentFactory; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.GVTBuilder; import org.apache.batik.bridge.UserAgentAdapter; -import org.apache.batik.anim.dom.SAXSVGDocumentFactory; import org.apache.batik.gvt.GraphicsNode; import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory; import org.apache.batik.gvt.renderer.ImageRenderer; @@ -38,101 +30,111 @@ import org.w3c.dom.svg.SVGDocument; import org.w3c.dom.svg.SVGElement; +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.StringReader; + public class OffScreenSVGRenderer { - - private static final boolean DEBUG = false; - - public BufferedImage renderXMLToImage(String xmlContent, int width, int height) throws IOException { - return renderXMLToImage(xmlContent, width, height, false, null, null); - } - - public BufferedImage renderXMLToImage(String xmlContent, int width, int height, boolean stretch, String idRegex, Color replacementColor) throws IOException { - // the following is necessary so that batik knows how to resolve URI fragments - // (#myLinearGradient). Otherwise the resolution fails and you cannot render. - - String uri = "file:/fake.svg"; - - SAXSVGDocumentFactory df = new SAXSVGDocumentFactory("org.apache.xerces.parsers.SAXParser"); - SVGDocument document = df.createSVGDocument(uri, new StringReader(xmlContent)); - if(idRegex != null && replacementColor != null) - replaceFill(document, idRegex, replacementColor); - return renderToImage(document, width, height, stretch); - } - - public BufferedImage renderToImage(String uri, int width, int height) throws IOException { - return renderToImage(uri, width, height, false, null, null); - } - - public BufferedImage renderToImage(String uri, int width, int height, boolean stretch, String idRegex, Color replacementColor) throws IOException { - SAXSVGDocumentFactory df = new SAXSVGDocumentFactory("org.apache.xerces.parsers.SAXParser"); - SVGDocument document = df.createSVGDocument(uri); - if(idRegex != null && replacementColor != null) - replaceFill(document, idRegex, replacementColor); - return renderToImage(document, width, height, stretch); - } - - public BufferedImage renderToImage(SVGDocument document, int width, int height){ - return renderToImage(document, width, height, false); - } - - public void replaceFill(SVGDocument document, String idRegex, Color color){ - String colorCode = String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue()); - - if(DEBUG) System.out.println("color code: "+colorCode); - - NodeList children = document.getElementsByTagName("*"); - for(int i = 0; i < children.getLength(); i++){ - if(children.item(i) instanceof SVGElement){ - SVGElement element = (SVGElement) children.item(i); - if(element.getId().matches(idRegex)){ - if(DEBUG) System.out.println("child>>> "+element+", "+element.getId()); - String style = element.getAttributeNS(null, "style"); - style = style.replaceFirst("fill:#[a-zA-z0-9]+", "fill:"+colorCode); - if(DEBUG) System.out.println(style); - element.setAttributeNS(null, "style", style); - } - } - } - } - - public BufferedImage renderToImage(SVGDocument document, int width, int height, boolean stretch){ - - ImageRendererFactory rendererFactory; - rendererFactory = new ConcreteImageRendererFactory(); - ImageRenderer renderer = rendererFactory.createStaticImageRenderer(); - - GVTBuilder builder = new GVTBuilder(); - BridgeContext ctx = new BridgeContext(new UserAgentAdapter()); - ctx.setDynamicState(BridgeContext.STATIC); - GraphicsNode rootNode = builder.build(ctx, document); - - renderer.setTree(rootNode); - - float docWidth = (float) ctx.getDocumentSize().getWidth(); - float docHeight = (float) ctx.getDocumentSize().getHeight(); - - float xscale = width/docWidth; - float yscale = height/docHeight; - if(!stretch){ - float scale = Math.min(xscale, yscale); - xscale = scale; - yscale = scale; - } - - AffineTransform px = AffineTransform.getScaleInstance(xscale, yscale); - - double tx = -0 + (width/xscale - docWidth)/2; - double ty = -0 + (height/yscale - docHeight)/2; - px.translate(tx, ty); - //cgn.setViewingTransform(px); - - renderer.updateOffScreen(width, height); - renderer.setTree(rootNode); - renderer.setTransform(px); - //renderer.clearOffScreen(); - renderer.repaint(new Rectangle(0, 0, width, height)); - - return renderer.getOffScreen(); - - } + + private static final boolean DEBUG = false; + + public BufferedImage renderXMLToImage(String xmlContent, int width, int height) throws IOException { + return renderXMLToImage(xmlContent, width, height, false, null, null); + } + + public BufferedImage renderXMLToImage(String xmlContent, int width, int height, boolean stretch, String idRegex, Color replacementColor) throws IOException { + // the following is necessary so that batik knows how to resolve URI fragments + // (#myLinearGradient). Otherwise the resolution fails and you cannot render. + + String uri = "file:/fake.svg"; + + SAXSVGDocumentFactory df = new SAXSVGDocumentFactory("org.apache.xerces.parsers.SAXParser"); + SVGDocument document = df.createSVGDocument(uri, new StringReader(xmlContent)); + if (idRegex != null && replacementColor != null) + replaceFill(document, idRegex, replacementColor); + return renderToImage(document, width, height, stretch); + } + + public BufferedImage renderToImage(String uri, int width, int height) throws IOException { + return renderToImage(uri, width, height, false, null, null); + } + + public BufferedImage renderToImage(String uri, int width, int height, boolean stretch, String idRegex, Color replacementColor) throws IOException { + SAXSVGDocumentFactory df = new SAXSVGDocumentFactory("org.apache.xerces.parsers.SAXParser"); + SVGDocument document = df.createSVGDocument(uri); + if (idRegex != null && replacementColor != null) + replaceFill(document, idRegex, replacementColor); + return renderToImage(document, width, height, stretch); + } + + public BufferedImage renderToImage(SVGDocument document, int width, int height) { + return renderToImage(document, width, height, false); + } + + public void replaceFill(SVGDocument document, String idRegex, Color color) { + String colorCode = String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue()); + + if (DEBUG) + System.out.println("color code: " + colorCode); + + NodeList children = document.getElementsByTagName("*"); + for (int i = 0; i < children.getLength(); i++) { + if (children.item(i) instanceof SVGElement) { + SVGElement element = (SVGElement) children.item(i); + if (element.getId().matches(idRegex)) { + if (DEBUG) + System.out.println("child>>> " + element + ", " + element.getId()); + String style = element.getAttributeNS(null, "style"); + style = style.replaceFirst("fill:#[a-zA-z0-9]+", "fill:" + colorCode); + if (DEBUG) + System.out.println(style); + element.setAttributeNS(null, "style", style); + } + } + } + } + + public BufferedImage renderToImage(SVGDocument document, int width, int height, boolean stretch) { + + ImageRendererFactory rendererFactory; + rendererFactory = new ConcreteImageRendererFactory(); + ImageRenderer renderer = rendererFactory.createStaticImageRenderer(); + + GVTBuilder builder = new GVTBuilder(); + BridgeContext ctx = new BridgeContext(new UserAgentAdapter()); + ctx.setDynamicState(BridgeContext.STATIC); + GraphicsNode rootNode = builder.build(ctx, document); + + renderer.setTree(rootNode); + + float docWidth = (float) ctx.getDocumentSize().getWidth(); + float docHeight = (float) ctx.getDocumentSize().getHeight(); + + float xscale = width / docWidth; + float yscale = height / docHeight; + if (!stretch) { + float scale = Math.min(xscale, yscale); + xscale = scale; + yscale = scale; + } + + AffineTransform px = AffineTransform.getScaleInstance(xscale, yscale); + + double tx = -0 + (width / xscale - docWidth) / 2; + double ty = -0 + (height / yscale - docHeight) / 2; + px.translate(tx, ty); + //cgn.setViewingTransform(px); + + renderer.updateOffScreen(width, height); + renderer.setTree(rootNode); + renderer.setTransform(px); + //renderer.clearOffScreen(); + renderer.repaint(new Rectangle(0, 0, width, height)); + + return renderer.getOffScreen(); + + } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/SVGBuilder.java b/src/java/org/stathissideris/ascii2image/graphics/SVGBuilder.java index 7be7cd5..83c1031 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/SVGBuilder.java +++ b/src/java/org/stathissideris/ascii2image/graphics/SVGBuilder.java @@ -1,10 +1,11 @@ package org.stathissideris.ascii2image.graphics; import org.stathissideris.ascii2image.core.RenderingOptions; -import org.stathissideris.ascii2image.core.ShapeAreaComparator; import org.stathissideris.ascii2image.core.Shape3DOrderingComparator; +import org.stathissideris.ascii2image.core.ShapeAreaComparator; -import java.awt.*; +import java.awt.Color; +import java.awt.Font; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.util.ArrayList; @@ -14,306 +15,309 @@ */ public class SVGBuilder { - SVGBuilder(Diagram diagram, RenderingOptions options) { + SVGBuilder(Diagram diagram, RenderingOptions options) { - this.diagram = diagram; - this.options = options; + this.diagram = diagram; + this.options = options; - float dashInterval = Math.min(diagram.getCellWidth(), diagram.getCellHeight()) / 2; - float strokeWeight = diagram.getMinimumOfCellDimension() / 10; + float dashInterval = Math.min(diagram.getCellWidth(), diagram.getCellHeight()) / 2; + float strokeWeight = diagram.getMinimumOfCellDimension() / 10; - normalStroke = String.format("stroke-width='%f' stroke-linecap='round' stroke-linejoin='round' ", - strokeWeight); + normalStroke = String.format("stroke-width='%f' stroke-linecap='round' stroke-linejoin='round' ", + strokeWeight); - dashStroke = String.format( - "stroke-width='%f' stroke-dasharray='%f,%f' stroke-miterlimit='0' " + - "stroke-linecap='butt' stroke-linejoin='round' ", - strokeWeight, dashInterval, dashInterval); + dashStroke = String.format( + "stroke-width='%f' stroke-dasharray='%f,%f' stroke-miterlimit='0' " + + "stroke-linecap='butt' stroke-linejoin='round' ", + strokeWeight, dashInterval, dashInterval); - } + } - public String build() { + public String build() { - return openSVGTag() + definitions() + render() + ""; + return openSVGTag() + definitions() + render() + ""; - } + } - private String definitions() { + private String definitions() { - String DEFS = - " \n%s" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n"; + String DEFS = + " \n%s" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n"; - if (options.getFontURL() == null) { - return String.format(DEFS, ""); - } + if (options.getFontURL() == null) { + return String.format(DEFS, ""); + } - String fontStyle = - " \n"; - return String.format(DEFS, String.format(fontStyle, options.getFontFamily(), options.getFontURL())); + return String.format(DEFS, String.format(fontStyle, options.getFontFamily(), options.getFontURL())); - } + } - private String openSVGTag() { + private String openSVGTag() { - String HEADER = - "\n" + - "\n"; + String HEADER = + "\n" + + "\n"; - return String.format( - HEADER, - diagram.getWidth(), - diagram.getHeight(), - antialiasing() - ); + return String.format( + HEADER, + diagram.getWidth(), + diagram.getHeight(), + antialiasing() + ); - } - - private String render() { + } - backgroundLayer(); + private String render() { - renderStorageShapes(); - renderRestOfShapes(); - renderTexts(); + backgroundLayer(); - return " \n" + - layer0.toString() + - layer1.toString() + - layer2.toString() + - layer3.toString() + - " \n"; + renderStorageShapes(); + renderRestOfShapes(); + renderTexts(); - } + return " \n" + + layer0.toString() + + layer1.toString() + + layer2.toString() + + layer3.toString() + + " \n"; - private void renderStorageShapes() { + } - ArrayList shapes = diagram.getAllDiagramShapes(); + private void renderStorageShapes() { - ArrayList storageShapes = findSorageShapes(shapes); + ArrayList shapes = diagram.getAllDiagramShapes(); - storageShapes.sort(new Shape3DOrderingComparator()); + ArrayList storageShapes = findSorageShapes(shapes); - for (DiagramShape shape : storageShapes) { + storageShapes.sort(new Shape3DOrderingComparator()); - GeneralPath path = shape.makeIntoRenderPath(diagram, options); + for (DiagramShape shape : storageShapes) { - SVGCommands commands = new SVGCommands(path); + GeneralPath path = shape.makeIntoRenderPath(diagram, options); - String fill = "none"; - String color = "white"; + SVGCommands commands = new SVGCommands(path); - if(!shape.isStrokeDashed()) { + String fill = "none"; + String color = "white"; - renderShadow(commands); + if (!shape.isStrokeDashed()) { - if(shape.getFillColor() != null) - fill = colorToHex(shape.getFillColor()); - else - fill = colorToHex(Color.white); + renderShadow(commands); - } + if (shape.getFillColor() != null) + fill = colorToHex(shape.getFillColor()); + else + fill = colorToHex(Color.white); - renderPath(shape, commands, color, fill); + } - renderPath(shape, commands, colorToHex(shape.getStrokeColor()), "none"); + renderPath(shape, commands, color, fill); - } + renderPath(shape, commands, colorToHex(shape.getStrokeColor()), "none"); } - private ArrayList findSorageShapes(ArrayList shapes) { - - ArrayList storageShapes = new ArrayList<>(); + } - for (DiagramShape shape : shapes) { + private ArrayList findSorageShapes(ArrayList shapes) { - if(shape.getType() == DiagramShape.TYPE_STORAGE) { - storageShapes.add(shape); - } + ArrayList storageShapes = new ArrayList<>(); - } + for (DiagramShape shape : shapes) { - return storageShapes; + if (shape.getType() == DiagramShape.TYPE_STORAGE) { + storageShapes.add(shape); + } } - private void renderRestOfShapes() { + return storageShapes; - ArrayList shapes = diagram.getAllDiagramShapes(); - ArrayList pointMarkers = new ArrayList<>(); + } - shapes.sort(new ShapeAreaComparator()); + private void renderRestOfShapes() { - for (DiagramShape shape : shapes) { + ArrayList shapes = diagram.getAllDiagramShapes(); + ArrayList pointMarkers = new ArrayList<>(); - if (shape.getType() == DiagramShape.TYPE_POINT_MARKER) { - pointMarkers.add(shape); - continue; - } - if (shape.getType() == DiagramShape.TYPE_STORAGE) { - continue; - } - if (shape.getType() == DiagramShape.TYPE_CUSTOM) { - //renderCustomShape(shape, g2); - //continue; - throw new RuntimeException("Not yet implemented"); - } + shapes.sort(new ShapeAreaComparator()); - if (shape.getPoints().isEmpty()) continue; + for (DiagramShape shape : shapes) { - GeneralPath path = shape.makeIntoRenderPath(diagram, options); + if (shape.getType() == DiagramShape.TYPE_POINT_MARKER) { + pointMarkers.add(shape); + continue; + } + if (shape.getType() == DiagramShape.TYPE_STORAGE) { + continue; + } + if (shape.getType() == DiagramShape.TYPE_CUSTOM) { + //renderCustomShape(shape, g2); + //continue; + throw new RuntimeException("Not yet implemented"); + } - SVGCommands commands = new SVGCommands(path); + if (shape.getPoints().isEmpty()) + continue; - renderPath(shape, commands); + GeneralPath path = shape.makeIntoRenderPath(diagram, options); - } + SVGCommands commands = new SVGCommands(path); - renderPointMarkers(pointMarkers); + renderPath(shape, commands); } - private void renderPath(DiagramShape shape, SVGCommands commands) { + renderPointMarkers(pointMarkers); - String fill = "none"; + } - if (shape.isClosed() && !shape.isStrokeDashed()) { + private void renderPath(DiagramShape shape, SVGCommands commands) { - if(shape.getFillColor() != null) - fill = colorToHex(shape.getFillColor()); - else - fill = "white"; + String fill = "none"; - if (shape.getType() == DiagramShape.TYPE_ARROWHEAD) { - renderPath(shape, commands, "none", fill); - } + if (shape.isClosed() && !shape.isStrokeDashed()) { - } else if (shape.isStrokeDashed()) { - fill = "white"; - } + if (shape.getFillColor() != null) + fill = colorToHex(shape.getFillColor()); + else + fill = "white"; - if (shape.getType() != DiagramShape.TYPE_ARROWHEAD) { + if (shape.getType() == DiagramShape.TYPE_ARROWHEAD) { + renderPath(shape, commands, "none", fill); + } - if (commands.isClosed && !shape.isStrokeDashed()) { - renderShadow(commands); - } + } else if (shape.isStrokeDashed()) { + fill = "white"; + } - renderPath(shape, commands, colorToHex(shape.getStrokeColor()), fill); + if (shape.getType() != DiagramShape.TYPE_ARROWHEAD) { - } + if (commands.isClosed && !shape.isStrokeDashed()) { + renderShadow(commands); + } + + renderPath(shape, commands, colorToHex(shape.getStrokeColor()), fill); } - private void renderPath(DiagramShape shape, SVGCommands commands, String stroke, String fill) { + } - String path = " \n"; + if (shape.isStrokeDashed()) + path += dashStroke; + else + path += normalStroke; - layer2.append(path); + path += "fill='" + fill + "' d='" + commands.svgPath + "' />\n"; - } + layer2.append(path); - private void renderShadow(SVGCommands commands) { + } - if (!options.dropShadows()) return; + private void renderShadow(SVGCommands commands) { - String path = " \n"; + if (!options.dropShadows()) + return; - layer1.append(path); + String path = " \n"; - } + layer1.append(path); - private void renderPointMarkers(ArrayList pointMarkers) { + } - for (DiagramShape shape : pointMarkers) { + private void renderPointMarkers(ArrayList pointMarkers) { - GeneralPath path = shape.makeIntoRenderPath(diagram, options); + for (DiagramShape shape : pointMarkers) { - String fill = "white"; + GeneralPath path = shape.makeIntoRenderPath(diagram, options); - if(shape.getFillColor() != null) - fill = colorToHex(shape.getFillColor()); + String fill = "white"; - renderPath(shape, new SVGCommands(path), colorToHex(shape.getStrokeColor()), fill); + if (shape.getFillColor() != null) + fill = colorToHex(shape.getFillColor()); - } + renderPath(shape, new SVGCommands(path), colorToHex(shape.getStrokeColor()), fill); } - private String antialiasing() { - String rendering = options.performAntialias() ? "geometricPrecision" : "optimizeSpeed"; - return String.format("shape-rendering='%s'", rendering); - } + } - private void backgroundLayer() { + private String antialiasing() { + String rendering = options.performAntialias() ? "geometricPrecision" : "optimizeSpeed"; + return String.format("shape-rendering='%s'", rendering); + } - Color color = options.getBackgroundColor(); + private void backgroundLayer() { - if (color.getAlpha() == 0) return; + Color color = options.getBackgroundColor(); - layer0.append ( - String.format(" \n", - diagram.getWidth(), - diagram.getHeight(), - colorToHex(color) - ) - ); + if (color.getAlpha() == 0) + return; - } + layer0.append( + String.format(" \n", + diagram.getWidth(), + diagram.getHeight(), + colorToHex(color) + ) + ); - private void renderTexts() { + } - for (DiagramText diagramText : diagram.getTextObjects()) { + private void renderTexts() { - Font font = diagramText.getFont(); - String text = diagramText.getText(); + for (DiagramText diagramText : diagram.getTextObjects()) { - int xPos = diagramText.getXPos(); - int yPos = diagramText.getYPos(); + Font font = diagramText.getFont(); + String text = diagramText.getText(); - renderText(text, xPos, yPos, font, diagramText.getColor()); + int xPos = diagramText.getXPos(); + int yPos = diagramText.getYPos(); - if (diagramText.hasOutline()) { + renderText(text, xPos, yPos, font, diagramText.getColor()); - Color outlineColor = diagramText.getOutlineColor(); + if (diagramText.hasOutline()) { - renderText(text, xPos + 1, yPos, font, outlineColor); - renderText(text, xPos - 1, yPos, font, outlineColor); - renderText(text, xPos, yPos + 1, font, outlineColor); - renderText(text, xPos, yPos - 1, font, outlineColor); + Color outlineColor = diagramText.getOutlineColor(); - } - } + renderText(text, xPos + 1, yPos, font, outlineColor); + renderText(text, xPos - 1, yPos, font, outlineColor); + renderText(text, xPos, yPos + 1, font, outlineColor); + renderText(text, xPos, yPos - 1, font, outlineColor); + } } - private void renderText(String text, int xPos, int yPos, Font font, Color color) { + } - String TEXT_ELEMENT = " " + - "%s\n"; + private void renderText(String text, int xPos, int yPos, Font font, Color color) { + + String TEXT_ELEMENT = " " + + "%s\n"; /* Prefer normal font weight if (font.isBold()) { @@ -321,96 +325,96 @@ private void renderText(String text, int xPos, int yPos, Font font, Color color) } */ - layer3.append( - String.format(TEXT_ELEMENT, - xPos, - yPos, - options.getFontFamily(), - font.getSize(), - colorToHex(color), - text - ) - ); - - } - - private static String colorToHex(Color color) { - return String.format("#%s%s%s", - toHex(color.getRed()), - toHex(color.getGreen()), - toHex(color.getBlue()) - ); - } - - private static String toHex(int n) { - String hex = Integer.toHexString(n); - - return n > 15 ? hex : "0" + hex; - } - - private final Diagram diagram; - private final RenderingOptions options; - - private final StringBuilder layer0 = new StringBuilder(); - private final StringBuilder layer1 = new StringBuilder(); - private final StringBuilder layer2 = new StringBuilder(); - private final StringBuilder layer3 = new StringBuilder(); - - private final String normalStroke; - private final String dashStroke; - - class SVGCommands { - - final String svgPath; - final boolean isClosed; - - SVGCommands(GeneralPath path) { - - boolean closed = false; - - float[] coords = new float[6]; - - StringBuilder builder = new StringBuilder(); - - PathIterator pathIterator = path.getPathIterator(null); - - while (!pathIterator.isDone()) { - - String commands; - - switch(pathIterator.currentSegment(coords)) { - case PathIterator.SEG_MOVETO: - commands = "M" + coords[0] + " " + coords[1] + " "; - break; - case PathIterator.SEG_LINETO: - commands = "L" + coords[0] + " " + coords[1] + " "; - break; - case PathIterator.SEG_QUADTO: - commands = "Q" + coords[0] + " " + coords[1] + " " + coords[2] + " " + coords[3] + " "; - break; - case PathIterator.SEG_CUBICTO: - commands = "C" + coords[0] + " " + coords[1] + " " + coords[2] + " " + coords[3] + " " + coords[4] + " " + coords[5] + " "; - break; - case PathIterator.SEG_CLOSE: - commands = "z"; - closed = true; - break; - default: - commands = ""; - break; - } - - builder.append(commands); + layer3.append( + String.format(TEXT_ELEMENT, + xPos, + yPos, + options.getFontFamily(), + font.getSize(), + colorToHex(color), + text + ) + ); + + } + + private static String colorToHex(Color color) { + return String.format("#%s%s%s", + toHex(color.getRed()), + toHex(color.getGreen()), + toHex(color.getBlue()) + ); + } + + private static String toHex(int n) { + String hex = Integer.toHexString(n); + + return n > 15 ? hex : "0" + hex; + } + + private final Diagram diagram; + private final RenderingOptions options; + + private final StringBuilder layer0 = new StringBuilder(); + private final StringBuilder layer1 = new StringBuilder(); + private final StringBuilder layer2 = new StringBuilder(); + private final StringBuilder layer3 = new StringBuilder(); + + private final String normalStroke; + private final String dashStroke; + + class SVGCommands { + + final String svgPath; + final boolean isClosed; + + SVGCommands(GeneralPath path) { + + boolean closed = false; + + float[] coords = new float[6]; + + StringBuilder builder = new StringBuilder(); + + PathIterator pathIterator = path.getPathIterator(null); + + while (!pathIterator.isDone()) { + + String commands; + + switch (pathIterator.currentSegment(coords)) { + case PathIterator.SEG_MOVETO: + commands = "M" + coords[0] + " " + coords[1] + " "; + break; + case PathIterator.SEG_LINETO: + commands = "L" + coords[0] + " " + coords[1] + " "; + break; + case PathIterator.SEG_QUADTO: + commands = "Q" + coords[0] + " " + coords[1] + " " + coords[2] + " " + coords[3] + " "; + break; + case PathIterator.SEG_CUBICTO: + commands = "C" + coords[0] + " " + coords[1] + " " + coords[2] + " " + coords[3] + " " + coords[4] + " " + coords[5] + " "; + break; + case PathIterator.SEG_CLOSE: + commands = "z"; + closed = true; + break; + default: + commands = ""; + break; + } - pathIterator.next(); + builder.append(commands); - } + pathIterator.next(); - isClosed = closed; - svgPath = builder.toString(); + } - } + isClosed = closed; + svgPath = builder.toString(); } + } + } diff --git a/src/java/org/stathissideris/ascii2image/graphics/SVGRenderer.java b/src/java/org/stathissideris/ascii2image/graphics/SVGRenderer.java index edf8947..2682613 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/SVGRenderer.java +++ b/src/java/org/stathissideris/ascii2image/graphics/SVGRenderer.java @@ -7,12 +7,12 @@ */ public class SVGRenderer { - public String renderToImage(Diagram diagram, RenderingOptions options) { + public String renderToImage(Diagram diagram, RenderingOptions options) { - SVGBuilder builder = new SVGBuilder(diagram, options); + SVGBuilder builder = new SVGBuilder(diagram, options); - return builder.build(); + return builder.build(); - } + } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/ShapeEdge.java b/src/java/org/stathissideris/ascii2image/graphics/ShapeEdge.java index 7ae5231..8a48414 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/ShapeEdge.java +++ b/src/java/org/stathissideris/ascii2image/graphics/ShapeEdge.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,243 +15,262 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.graphics; import java.awt.geom.GeneralPath; /** - * + * * @author Efstathios Sideris */ public class ShapeEdge { - - private static final boolean DEBUG = false; - - private static final int TYPE_HORIZONTAL = 0; - private static final int TYPE_VERTICAL = 1; - private static final int TYPE_SLOPED = 2; - - - private DiagramShape owner; - private ShapePoint startPoint; - private ShapePoint endPoint; - - public ShapeEdge(ShapePoint start, ShapePoint end, DiagramShape owner){ - this.startPoint = start; - this.endPoint = end; - this.owner = owner; - } - - public ShapeEdge(ShapeEdge other){ - this( - new ShapePoint(other.startPoint), - new ShapePoint(other.endPoint), - other.owner - ); - } - - private float getDistanceFromOrigin() { - int type = this.getType(); - if(type == TYPE_SLOPED) - throw new RuntimeException("Cannot calculate distance of sloped edge from origin"); - if(type == TYPE_HORIZONTAL) - return startPoint.y; - return startPoint.x; //vertical - } - - //TODO: moveInwardsBy() not implemented - public void moveInwardsBy(float offset){ - int type = this.getType(); - if(type == TYPE_SLOPED) - throw new RuntimeException("Cannot move a sloped edge inwards: "+this); - - float xOffset = 0; - float yOffset = 0; - - ShapePoint middle = getMiddle(); - GeneralPath path = owner.makeIntoPath(); - if(type == TYPE_HORIZONTAL){ - xOffset = 0; - ShapePoint up = new ShapePoint(middle.x, middle.y - 0.05f); - ShapePoint down = new ShapePoint(middle.x, middle.y + 0.05f); - if(path.contains(up)) yOffset = -offset; - else if(path.contains(down)) yOffset = offset; - } else if(type == TYPE_VERTICAL){ - yOffset = 0; - ShapePoint left = new ShapePoint(middle.x - 0.05f, middle.y); - ShapePoint right = new ShapePoint(middle.x + 0.05f, middle.y); - if(path.contains(left)) xOffset = -offset; - else if(path.contains(right)) xOffset = offset; - } - if(DEBUG) System.out.println("Moved edge "+this+" by "+xOffset+", "+yOffset); - translate(xOffset, yOffset); - } - - public void translate(float dx, float dy){ - startPoint.x += dx; - startPoint.y += dy; - endPoint.x += dx; - endPoint.y += dy; - } - - public ShapePoint getMiddle(){ - return new ShapePoint( - (startPoint.x + endPoint.x) / 2, - (startPoint.y + endPoint.y) / 2 - ); - } - - /** - * Returns the type of the edge - * (TYPE_HORIZONTAL, TYPE_VERTICAL, TYPE_SLOPED). - * - * @return - */ - private int getType(){ - if(isVertical()) return TYPE_VERTICAL; - if(isHorizontal()) return TYPE_HORIZONTAL; - return TYPE_SLOPED; - } - - /** - * @return - */ - public ShapePoint getEndPoint() { - return endPoint; - } - - /** - * @return - */ - public ShapePoint getStartPoint() { - return startPoint; - } - - /** - * @param point - */ - public void setEndPoint(ShapePoint point) { - endPoint = point; - } - - /** - * @param point - */ - public void setStartPoint(ShapePoint point) { - startPoint = point; - } - - /** - * @return - */ - public DiagramShape getOwner() { - return owner; - } - - /** - * @param shape - */ - public void setOwner(DiagramShape shape) { - owner = shape; - } - - public boolean equals(Object object){ - if(!(object instanceof ShapeEdge)) return false; - ShapeEdge edge = (ShapeEdge) object; - if(startPoint.equals(edge.getStartPoint()) - && endPoint.equals(edge.getEndPoint())) return true; - if(startPoint.equals(edge.getEndPoint()) - && endPoint.equals(edge.getStartPoint())) return true; - return false; - } - - public boolean touchesWith(ShapeEdge other){ - if(this.equals(other)) return true; - - if(this.isHorizontal() && other.isVertical()) return false; - if(other.isHorizontal() && this.isVertical()) return false; - - if(this.getDistanceFromOrigin() != other.getDistanceFromOrigin()) return false; - - //covering this corner case (should produce false): - // --------- - // --------- - - ShapeEdge first = new ShapeEdge(this); - ShapeEdge second = new ShapeEdge(other); - - if(first.isVertical()) { - first.changeAxis(); - second.changeAxis(); - } - - first.fixDirection(); - second.fixDirection(); - - if(first.startPoint.x > second.startPoint.x) { - ShapeEdge temp = first; - first = second; - second = temp; - } - - if(first.endPoint.equals(second.startPoint)) return false; - - // case 1: - // ---------- - // ----------- - - // case 2: - // ------ - // ----------------- - - if(this.startPoint.isWithinEdge(other) || this.endPoint.isWithinEdge(other)) return true; - if(other.startPoint.isWithinEdge(this) || other.endPoint.isWithinEdge(this)) return true; - - - return false; - } - - private void changeAxis(){ - ShapePoint temp = new ShapePoint(startPoint); - startPoint = new ShapePoint(endPoint.y, endPoint.x); - endPoint = new ShapePoint(temp.y, temp.x); - } - - /** - * if horizontal flips start and end points so that start is left of end - * if vertical flips start and end points so that start is over of end - * - */ - private void fixDirection(){ - if(isHorizontal()) { - if(startPoint.x > endPoint.x) flipDirection(); - } else if(isVertical()) { - if(startPoint.y > endPoint.y) flipDirection(); - } else { - throw new RuntimeException("Cannot fix direction of sloped edge"); - } - } - - private void flipDirection(){ - ShapePoint temp = startPoint; - startPoint = endPoint; - endPoint = temp; - } - - public boolean isHorizontal(){ - if(startPoint.y == endPoint.y) return true; - return false; - } - - public boolean isVertical(){ - if(startPoint.x == endPoint.x) return true; - return false; - } - - public String toString(){ - return startPoint+" -> "+endPoint; - } + + private static final boolean DEBUG = false; + + private static final int TYPE_HORIZONTAL = 0; + private static final int TYPE_VERTICAL = 1; + private static final int TYPE_SLOPED = 2; + + + private DiagramShape owner; + private ShapePoint startPoint; + private ShapePoint endPoint; + + public ShapeEdge(ShapePoint start, ShapePoint end, DiagramShape owner) { + this.startPoint = start; + this.endPoint = end; + this.owner = owner; + } + + public ShapeEdge(ShapeEdge other) { + this( + new ShapePoint(other.startPoint), + new ShapePoint(other.endPoint), + other.owner + ); + } + + private float getDistanceFromOrigin() { + int type = this.getType(); + if (type == TYPE_SLOPED) + throw new RuntimeException("Cannot calculate distance of sloped edge from origin"); + if (type == TYPE_HORIZONTAL) + return startPoint.y; + return startPoint.x; //vertical + } + + //TODO: moveInwardsBy() not implemented + public void moveInwardsBy(float offset) { + int type = this.getType(); + if (type == TYPE_SLOPED) + throw new RuntimeException("Cannot move a sloped edge inwards: " + this); + + float xOffset = 0; + float yOffset = 0; + + ShapePoint middle = getMiddle(); + GeneralPath path = owner.makeIntoPath(); + if (type == TYPE_HORIZONTAL) { + xOffset = 0; + ShapePoint up = new ShapePoint(middle.x, middle.y - 0.05f); + ShapePoint down = new ShapePoint(middle.x, middle.y + 0.05f); + if (path.contains(up)) + yOffset = -offset; + else if (path.contains(down)) + yOffset = offset; + } else if (type == TYPE_VERTICAL) { + yOffset = 0; + ShapePoint left = new ShapePoint(middle.x - 0.05f, middle.y); + ShapePoint right = new ShapePoint(middle.x + 0.05f, middle.y); + if (path.contains(left)) + xOffset = -offset; + else if (path.contains(right)) + xOffset = offset; + } + if (DEBUG) + System.out.println("Moved edge " + this + " by " + xOffset + ", " + yOffset); + translate(xOffset, yOffset); + } + + public void translate(float dx, float dy) { + startPoint.x += dx; + startPoint.y += dy; + endPoint.x += dx; + endPoint.y += dy; + } + + public ShapePoint getMiddle() { + return new ShapePoint( + (startPoint.x + endPoint.x) / 2, + (startPoint.y + endPoint.y) / 2 + ); + } + + /** + * Returns the type of the edge + * (TYPE_HORIZONTAL, TYPE_VERTICAL, TYPE_SLOPED). + * + * @return + */ + private int getType() { + if (isVertical()) + return TYPE_VERTICAL; + if (isHorizontal()) + return TYPE_HORIZONTAL; + return TYPE_SLOPED; + } + + /** + * @return + */ + public ShapePoint getEndPoint() { + return endPoint; + } + + /** + * @return + */ + public ShapePoint getStartPoint() { + return startPoint; + } + + /** + * @param point + */ + public void setEndPoint(ShapePoint point) { + endPoint = point; + } + + /** + * @param point + */ + public void setStartPoint(ShapePoint point) { + startPoint = point; + } + + /** + * @return + */ + public DiagramShape getOwner() { + return owner; + } + + /** + * @param shape + */ + public void setOwner(DiagramShape shape) { + owner = shape; + } + + public boolean equals(Object object) { + if (!(object instanceof ShapeEdge)) + return false; + ShapeEdge edge = (ShapeEdge) object; + if (startPoint.equals(edge.getStartPoint()) + && endPoint.equals(edge.getEndPoint())) + return true; + if (startPoint.equals(edge.getEndPoint()) + && endPoint.equals(edge.getStartPoint())) + return true; + return false; + } + + public boolean touchesWith(ShapeEdge other) { + if (this.equals(other)) + return true; + + if (this.isHorizontal() && other.isVertical()) + return false; + if (other.isHorizontal() && this.isVertical()) + return false; + + if (this.getDistanceFromOrigin() != other.getDistanceFromOrigin()) + return false; + + //covering this corner case (should produce false): + // --------- + // --------- + + ShapeEdge first = new ShapeEdge(this); + ShapeEdge second = new ShapeEdge(other); + + if (first.isVertical()) { + first.changeAxis(); + second.changeAxis(); + } + + first.fixDirection(); + second.fixDirection(); + + if (first.startPoint.x > second.startPoint.x) { + ShapeEdge temp = first; + first = second; + second = temp; + } + + if (first.endPoint.equals(second.startPoint)) + return false; + + // case 1: + // ---------- + // ----------- + + // case 2: + // ------ + // ----------------- + + if (this.startPoint.isWithinEdge(other) || this.endPoint.isWithinEdge(other)) + return true; + if (other.startPoint.isWithinEdge(this) || other.endPoint.isWithinEdge(this)) + return true; + + return false; + } + + private void changeAxis() { + ShapePoint temp = new ShapePoint(startPoint); + startPoint = new ShapePoint(endPoint.y, endPoint.x); + endPoint = new ShapePoint(temp.y, temp.x); + } + + /** + * if horizontal flips start and end points so that start is left of end + * if vertical flips start and end points so that start is over of end + * + */ + private void fixDirection() { + if (isHorizontal()) { + if (startPoint.x > endPoint.x) + flipDirection(); + } else if (isVertical()) { + if (startPoint.y > endPoint.y) + flipDirection(); + } else { + throw new RuntimeException("Cannot fix direction of sloped edge"); + } + } + + private void flipDirection() { + ShapePoint temp = startPoint; + startPoint = endPoint; + endPoint = temp; + } + + public boolean isHorizontal() { + if (startPoint.y == endPoint.y) + return true; + return false; + } + + public boolean isVertical() { + if (startPoint.x == endPoint.x) + return true; + return false; + } + + public String toString() { + return startPoint + " -> " + endPoint; + } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/ShapePoint.java b/src/java/org/stathissideris/ascii2image/graphics/ShapePoint.java index e752ce2..c751a02 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/ShapePoint.java +++ b/src/java/org/stathissideris/ascii2image/graphics/ShapePoint.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,126 +15,130 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.graphics; -import java.awt.geom.Point2D.Float; - /** - * + * * @author Efstathios Sideris */ public class ShapePoint extends java.awt.geom.Point2D.Float { - public static final int TYPE_NORMAL = 0; - public static final int TYPE_ROUND = 1; - - private boolean locked = false; - - private int type = 0; - - public ShapePoint() { - super(); - } - - public ShapePoint(float x, float y) { - super(x, y); - this.type = TYPE_NORMAL; - } - - public ShapePoint(float x, float y, int type) { - super(x, y); - this.type = type; - } - - public ShapePoint(ShapePoint other){ - this(other.x, other.y, other.type); - } - - /** - * @return - */ - public int getType() { - return type; - } - - /** - * @param i - */ - public void setType(int i) { - type = i; - } - - public boolean isInLineWith(ShapePoint point){ - if(this.x == point.x) return true; - if(this.y == point.y) return true; - return false; - } - - public boolean isWithinEdge(ShapeEdge edge) { - if(edge.isHorizontal()) { - if(x >= edge.getStartPoint().x && x <= edge.getEndPoint().x) return true; - if(x >= edge.getEndPoint().x && x <= edge.getStartPoint().x) return true; - return false; - } else if(edge.isVertical()) { - if(y >= edge.getStartPoint().y && y <= edge.getEndPoint().y) return true; - if(y >= edge.getEndPoint().y && y <= edge.getStartPoint().y) return true; - return false; - } - throw new RuntimeException("Cannot calculate is ShapePoint is within sloped edge"); - } - - public boolean isNorthOf(ShapePoint point){ - return (this.y < point.y); - } - - public boolean isSouthOf(ShapePoint point){ - return (this.y > point.y); - } - - public boolean isWestOf(ShapePoint point){ - return (this.x < point.x); - } - - public boolean isEastOf(ShapePoint point){ - return (this.x > point.x); - } - - public String toString(){ - return "("+x+", "+y+")"; - } - - public void assign(ShapePoint point){ - this.x = point.x; - this.y = point.y; - } - - /** - * Does the same as assign, but respects the - * locked attribute - * - * @param point - */ - public void moveTo(ShapePoint point){ - if(locked) return; - this.x = point.x; - this.y = point.y; - } - - - /** - * @return - */ - public boolean isLocked() { - return locked; - } - - /** - * @param b - */ - public void setLocked(boolean b) { - locked = b; - } + public static final int TYPE_NORMAL = 0; + public static final int TYPE_ROUND = 1; + + private boolean locked = false; + + private int type = 0; + + public ShapePoint() { + super(); + } + + public ShapePoint(float x, float y) { + super(x, y); + this.type = TYPE_NORMAL; + } + + public ShapePoint(float x, float y, int type) { + super(x, y); + this.type = type; + } + + public ShapePoint(ShapePoint other) { + this(other.x, other.y, other.type); + } + + /** + * @return + */ + public int getType() { + return type; + } + + /** + * @param i + */ + public void setType(int i) { + type = i; + } + + public boolean isInLineWith(ShapePoint point) { + if (this.x == point.x) + return true; + if (this.y == point.y) + return true; + return false; + } + + public boolean isWithinEdge(ShapeEdge edge) { + if (edge.isHorizontal()) { + if (x >= edge.getStartPoint().x && x <= edge.getEndPoint().x) + return true; + if (x >= edge.getEndPoint().x && x <= edge.getStartPoint().x) + return true; + return false; + } else if (edge.isVertical()) { + if (y >= edge.getStartPoint().y && y <= edge.getEndPoint().y) + return true; + if (y >= edge.getEndPoint().y && y <= edge.getStartPoint().y) + return true; + return false; + } + throw new RuntimeException("Cannot calculate is ShapePoint is within sloped edge"); + } + + public boolean isNorthOf(ShapePoint point) { + return (this.y < point.y); + } + + public boolean isSouthOf(ShapePoint point) { + return (this.y > point.y); + } + + public boolean isWestOf(ShapePoint point) { + return (this.x < point.x); + } + + public boolean isEastOf(ShapePoint point) { + return (this.x > point.x); + } + + public String toString() { + return "(" + x + ", " + y + ")"; + } + + public void assign(ShapePoint point) { + this.x = point.x; + this.y = point.y; + } + + /** + * Does the same as assign, but respects the + * locked attribute + * + * @param point + */ + public void moveTo(ShapePoint point) { + if (locked) + return; + this.x = point.x; + this.y = point.y; + } + + + /** + * @return + */ + public boolean isLocked() { + return locked; + } + + /** + * @param b + */ + public void setLocked(boolean b) { + locked = b; + } } diff --git a/src/java/org/stathissideris/ascii2image/text/AbstractCell.java b/src/java/org/stathissideris/ascii2image/text/AbstractCell.java index 276bd72..8b6e8c3 100644 --- a/src/java/org/stathissideris/ascii2image/text/AbstractCell.java +++ b/src/java/org/stathissideris/ascii2image/text/AbstractCell.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,109 +15,109 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.text; /** - * + * * @author Efstathios Sideris */ public class AbstractCell { - public int rows[][] = new int[3][3]; - { - for(int y = 0; y < 3; y++) - for(int x = 0; x < 3; x++) - rows[x][y] = 0; - } - - static AbstractCell makeHorizontalLine(){ - AbstractCell result = new AbstractCell(); - result.rows[0][1] = 1; - result.rows[1][1] = 1; - result.rows[2][1] = 1; - return result; - } - - static AbstractCell makeVerticalLine(){ - AbstractCell result = new AbstractCell(); - result.rows[1][0] = 1; - result.rows[1][1] = 1; - result.rows[1][2] = 1; - return result; - } - - static AbstractCell makeCorner1(){ - AbstractCell result = new AbstractCell(); - result.rows[1][1] = 1; - result.rows[1][2] = 1; - result.rows[2][1] = 1; - return result; - } - - static AbstractCell makeCorner2(){ - AbstractCell result = new AbstractCell(); - result.rows[0][1] = 1; - result.rows[1][1] = 1; - result.rows[1][2] = 1; - return result; - } - - static AbstractCell makeCorner3(){ - AbstractCell result = new AbstractCell(); - result.rows[0][1] = 1; - result.rows[1][1] = 1; - result.rows[1][0] = 1; - return result; - } - - static AbstractCell makeCorner4(){ - AbstractCell result = new AbstractCell(); - result.rows[2][1] = 1; - result.rows[1][1] = 1; - result.rows[1][0] = 1; - return result; - } - - static AbstractCell makeT(){ - AbstractCell result = AbstractCell.makeHorizontalLine(); - result.rows[1][2] = 1; - return result; - } - - static AbstractCell makeInverseT(){ - AbstractCell result = AbstractCell.makeHorizontalLine(); - result.rows[1][0] = 1; - return result; - } - - static AbstractCell makeK(){ - AbstractCell result = AbstractCell.makeVerticalLine(); - result.rows[2][1] = 1; - return result; - } - - static AbstractCell makeInverseK(){ - AbstractCell result = AbstractCell.makeVerticalLine(); - result.rows[0][1] = 1; - return result; - } - - static AbstractCell makeCross(){ - AbstractCell result = AbstractCell.makeVerticalLine(); - result.rows[0][1] = 1; - result.rows[2][1] = 1; - return result; - } - - static AbstractCell makeStar(){ - AbstractCell result = AbstractCell.makeVerticalLine(); - for(int y = 0; y < 3; y++) - for(int x = 0; x < 3; x++) - result.rows[x][y] = 1; - return result; - } + public int rows[][] = new int[3][3]; + + { + for (int y = 0; y < 3; y++) + for (int x = 0; x < 3; x++) + rows[x][y] = 0; + } + + static AbstractCell makeHorizontalLine() { + AbstractCell result = new AbstractCell(); + result.rows[0][1] = 1; + result.rows[1][1] = 1; + result.rows[2][1] = 1; + return result; + } + + static AbstractCell makeVerticalLine() { + AbstractCell result = new AbstractCell(); + result.rows[1][0] = 1; + result.rows[1][1] = 1; + result.rows[1][2] = 1; + return result; + } + + static AbstractCell makeCorner1() { + AbstractCell result = new AbstractCell(); + result.rows[1][1] = 1; + result.rows[1][2] = 1; + result.rows[2][1] = 1; + return result; + } + + static AbstractCell makeCorner2() { + AbstractCell result = new AbstractCell(); + result.rows[0][1] = 1; + result.rows[1][1] = 1; + result.rows[1][2] = 1; + return result; + } + + static AbstractCell makeCorner3() { + AbstractCell result = new AbstractCell(); + result.rows[0][1] = 1; + result.rows[1][1] = 1; + result.rows[1][0] = 1; + return result; + } + + static AbstractCell makeCorner4() { + AbstractCell result = new AbstractCell(); + result.rows[2][1] = 1; + result.rows[1][1] = 1; + result.rows[1][0] = 1; + return result; + } + + static AbstractCell makeT() { + AbstractCell result = AbstractCell.makeHorizontalLine(); + result.rows[1][2] = 1; + return result; + } + + static AbstractCell makeInverseT() { + AbstractCell result = AbstractCell.makeHorizontalLine(); + result.rows[1][0] = 1; + return result; + } + + static AbstractCell makeK() { + AbstractCell result = AbstractCell.makeVerticalLine(); + result.rows[2][1] = 1; + return result; + } + + static AbstractCell makeInverseK() { + AbstractCell result = AbstractCell.makeVerticalLine(); + result.rows[0][1] = 1; + return result; + } + + static AbstractCell makeCross() { + AbstractCell result = AbstractCell.makeVerticalLine(); + result.rows[0][1] = 1; + result.rows[2][1] = 1; + return result; + } + + static AbstractCell makeStar() { + AbstractCell result = AbstractCell.makeVerticalLine(); + for (int y = 0; y < 3; y++) + for (int x = 0; x < 3; x++) + result.rows[x][y] = 1; + return result; + } } diff --git a/src/java/org/stathissideris/ascii2image/text/AbstractionGrid.java b/src/java/org/stathissideris/ascii2image/text/AbstractionGrid.java index f226269..cc92f7e 100644 --- a/src/java/org/stathissideris/ascii2image/text/AbstractionGrid.java +++ b/src/java/org/stathissideris/ascii2image/text/AbstractionGrid.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,7 +15,6 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.text; @@ -23,160 +22,163 @@ import java.util.Iterator; /** - * + * * @author Efstathios Sideris */ public class AbstractionGrid { - private static final boolean DEBUG = false; - - private TextGrid grid; - - /** - * Makes an AbstractionGrid using internalGrid as - * its internal buffer - * - * @param internalGrid - * @return - */ - public static AbstractionGrid makeUsingBuffer(TextGrid internalGrid){ - if(internalGrid.getWidth() % 3 != 0 - || internalGrid.getHeight() % 3 != 0) throw new IllegalArgumentException("Passed TextGrid must have dimensions that are divisible by 3."); - AbstractionGrid result = new AbstractionGrid(internalGrid.getWidth() / 3, internalGrid.getHeight() / 3); - result.setInternalBuffer(internalGrid); - return result; - } - - /** - * Makes an AbstractionGrid using the cellSet - * of textGrid. - * - * @param textGrid - * @param cellSet - */ - public AbstractionGrid(TextGrid textGrid, CellSet cellSet){ - this(textGrid.getWidth(), textGrid.getHeight()); + private static final boolean DEBUG = false; + + private TextGrid grid; + + /** + * Makes an AbstractionGrid using internalGrid as + * its internal buffer + * + * @param internalGrid + * @return + */ + public static AbstractionGrid makeUsingBuffer(TextGrid internalGrid) { + if (internalGrid.getWidth() % 3 != 0 + || internalGrid.getHeight() % 3 != 0) + throw new IllegalArgumentException("Passed TextGrid must have dimensions that are divisible by 3."); + AbstractionGrid result = new AbstractionGrid(internalGrid.getWidth() / 3, internalGrid.getHeight() / 3); + result.setInternalBuffer(internalGrid); + return result; + } + + /** + * Makes an AbstractionGrid using the cellSet + * of textGrid. + * + * @param textGrid + * @param cellSet + */ + public AbstractionGrid(TextGrid textGrid, CellSet cellSet) { + this(textGrid.getWidth(), textGrid.getHeight()); /*this(cellSet.getWidth(), cellSet.getHeight()); cellSet = new CellSet(cellSet); cellSet.translate( - cellSet.getMinX(), - cellSet.getMinY());*/ - - if(DEBUG){ - System.out.println("Making AbstractionGrid using buffer:"); - textGrid.printDebug(); - System.out.println("...and the following CellSet:"); - cellSet.printAsGrid(); - } - - Iterator it = cellSet.iterator(); - while(it.hasNext()){ - TextGrid.Cell cell = (TextGrid.Cell) it.next(); - if(textGrid.isBlank(cell)) continue; - if(textGrid.isCross(cell)){ - set(cell.x, cell.y, AbstractCell.makeCross()); - } else if(textGrid.isT(cell)){ - set(cell.x, cell.y, AbstractCell.makeT()); - } else if(textGrid.isK(cell)){ - set(cell.x, cell.y, AbstractCell.makeK()); - } else if(textGrid.isInverseT(cell)){ - set(cell.x, cell.y, AbstractCell.makeInverseT()); - } else if(textGrid.isInverseK(cell)){ - set(cell.x, cell.y, AbstractCell.makeInverseK()); - } else if(textGrid.isCorner1(cell)){ - set(cell.x, cell.y, AbstractCell.makeCorner1()); - } else if(textGrid.isCorner2(cell)){ - set(cell.x, cell.y, AbstractCell.makeCorner2()); - } else if(textGrid.isCorner3(cell)){ - set(cell.x, cell.y, AbstractCell.makeCorner3()); - } else if(textGrid.isCorner4(cell)){ - set(cell.x, cell.y, AbstractCell.makeCorner4()); - } else if(textGrid.isHorizontalLine(cell)){ - set(cell.x, cell.y, AbstractCell.makeHorizontalLine()); - } else if(textGrid.isVerticalLine(cell)){ - set(cell.x, cell.y, AbstractCell.makeVerticalLine()); - } else if(textGrid.isCrossOnLine(cell)){ - set(cell.x, cell.y, AbstractCell.makeCross()); - } else if(textGrid.isStarOnLine(cell)){ - set(cell.x, cell.y, AbstractCell.makeStar()); - } - } - - if(DEBUG){ - System.out.println("...the resulting AbstractionGrid is:"); - grid.printDebug(); - } - } - - private AbstractionGrid(int width, int height){ - grid = new TextGrid(width*3, height*3); - } - - public TextGrid getCopyOfInternalBuffer(){ - return new TextGrid(grid); - } - - private void setInternalBuffer(TextGrid grid){ - this.grid = grid; - } - - - public int getWidth(){ - return grid.getWidth() / 3; - } - - public int getHeight(){ - return grid.getHeight() / 3; - } - - public TextGrid getAsTextGrid(){ - TextGrid result = new TextGrid(getWidth(), getHeight()); - for(int y = 0; y < grid.getHeight(); y++){ - for(int x = 0; x < grid.getWidth(); x++){ - TextGrid.Cell cell = grid.new Cell(x, y); - if(!grid.isBlank(cell)) result.set(x/3, y/3, '*'); - } - } - if (DEBUG){ - System.out.println("Getting AbstractionGrid as textGrid.\nAbstractionGrid:"); - grid.printDebug(); - System.out.println("...as text grid:"); - result.printDebug(); - } - - return result; - } - - public ArrayList getDistinctShapes(){ - ArrayList result = new ArrayList(); - - CellSet nonBlank = grid.getAllNonBlank(); - ArrayList distinct = nonBlank.breakIntoDistinctBoundaries(); - - Iterator it = distinct.iterator(); - while (it.hasNext()) { - CellSet set = it.next(); - AbstractionGrid temp = new AbstractionGrid(this.getWidth(), this.getHeight()); - temp.fillCells(set); - result.add(temp.getAsTextGrid().getAllNonBlank()); - } - - return result; - } - - protected void fillCells(CellSet cells){ - grid.fillCellsWith(cells, '*'); - } - - public void set(int xPos, int yPos, AbstractCell cell){ - xPos *= 3; - yPos *= 3; - for(int y = 0; y < 3; y++){ - for(int x = 0; x < 3; x++){ - if(cell.rows[x][y] == 1){ - grid.set(xPos + x, yPos + y, '*'); - } - } - } - } + + if (DEBUG) { + System.out.println("Making AbstractionGrid using buffer:"); + textGrid.printDebug(); + System.out.println("...and the following CellSet:"); + cellSet.printAsGrid(); + } + + Iterator it = cellSet.iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = (TextGrid.Cell) it.next(); + if (textGrid.isBlank(cell)) + continue; + if (textGrid.isCross(cell)) { + set(cell.x, cell.y, AbstractCell.makeCross()); + } else if (textGrid.isT(cell)) { + set(cell.x, cell.y, AbstractCell.makeT()); + } else if (textGrid.isK(cell)) { + set(cell.x, cell.y, AbstractCell.makeK()); + } else if (textGrid.isInverseT(cell)) { + set(cell.x, cell.y, AbstractCell.makeInverseT()); + } else if (textGrid.isInverseK(cell)) { + set(cell.x, cell.y, AbstractCell.makeInverseK()); + } else if (textGrid.isCorner1(cell)) { + set(cell.x, cell.y, AbstractCell.makeCorner1()); + } else if (textGrid.isCorner2(cell)) { + set(cell.x, cell.y, AbstractCell.makeCorner2()); + } else if (textGrid.isCorner3(cell)) { + set(cell.x, cell.y, AbstractCell.makeCorner3()); + } else if (textGrid.isCorner4(cell)) { + set(cell.x, cell.y, AbstractCell.makeCorner4()); + } else if (textGrid.isHorizontalLine(cell)) { + set(cell.x, cell.y, AbstractCell.makeHorizontalLine()); + } else if (textGrid.isVerticalLine(cell)) { + set(cell.x, cell.y, AbstractCell.makeVerticalLine()); + } else if (textGrid.isCrossOnLine(cell)) { + set(cell.x, cell.y, AbstractCell.makeCross()); + } else if (textGrid.isStarOnLine(cell)) { + set(cell.x, cell.y, AbstractCell.makeStar()); + } + } + + if (DEBUG) { + System.out.println("...the resulting AbstractionGrid is:"); + grid.printDebug(); + } + } + + private AbstractionGrid(int width, int height) { + grid = new TextGrid(width * 3, height * 3); + } + + public TextGrid getCopyOfInternalBuffer() { + return new TextGrid(grid); + } + + private void setInternalBuffer(TextGrid grid) { + this.grid = grid; + } + + + public int getWidth() { + return grid.getWidth() / 3; + } + + public int getHeight() { + return grid.getHeight() / 3; + } + + public TextGrid getAsTextGrid() { + TextGrid result = new TextGrid(getWidth(), getHeight()); + for (int y = 0; y < grid.getHeight(); y++) { + for (int x = 0; x < grid.getWidth(); x++) { + TextGrid.Cell cell = grid.new Cell(x, y); + if (!grid.isBlank(cell)) + result.set(x / 3, y / 3, '*'); + } + } + if (DEBUG) { + System.out.println("Getting AbstractionGrid as textGrid.\nAbstractionGrid:"); + grid.printDebug(); + System.out.println("...as text grid:"); + result.printDebug(); + } + + return result; + } + + public ArrayList getDistinctShapes() { + ArrayList result = new ArrayList(); + + CellSet nonBlank = grid.getAllNonBlank(); + ArrayList distinct = nonBlank.breakIntoDistinctBoundaries(); + + Iterator it = distinct.iterator(); + while (it.hasNext()) { + CellSet set = it.next(); + AbstractionGrid temp = new AbstractionGrid(this.getWidth(), this.getHeight()); + temp.fillCells(set); + result.add(temp.getAsTextGrid().getAllNonBlank()); + } + + return result; + } + + protected void fillCells(CellSet cells) { + grid.fillCellsWith(cells, '*'); + } + + public void set(int xPos, int yPos, AbstractCell cell) { + xPos *= 3; + yPos *= 3; + for (int y = 0; y < 3; y++) { + for (int x = 0; x < 3; x++) { + if (cell.rows[x][y] == 1) { + grid.set(xPos + x, yPos + y, '*'); + } + } + } + } } diff --git a/src/java/org/stathissideris/ascii2image/text/CellSet.java b/src/java/org/stathissideris/ascii2image/text/CellSet.java index 0a40b8c..47f6f4a 100644 --- a/src/java/org/stathissideris/ascii2image/text/CellSet.java +++ b/src/java/org/stathissideris/ascii2image/text/CellSet.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,122 +15,121 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.text; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; -import java.util.Hashtable; import java.util.Iterator; import java.util.Set; /** - * + * * @author Efstathios Sideris */ public class CellSet implements Iterable { - private static final boolean DEBUG = false; - private static final boolean VERBOSE_DEBUG = false; - - public static final int TYPE_CLOSED = 0; - public static final int TYPE_OPEN = 1; - public static final int TYPE_MIXED = 2; - public static final int TYPE_HAS_CLOSED_AREA = 3; - public static final int TYPE_UNDETERMINED = 4; - - Set internalSet = new HashSet(); - - private int type = TYPE_UNDETERMINED; - private boolean typeIsValid = false; - - private static final Object FAKE = new Object(); - - public CellSet(){ - - } - - public CellSet(CellSet other){ - addAll(other); - } - - public Iterator iterator(){ - return internalSet.iterator(); - } - - public Object add(TextGrid.Cell cell){ - return internalSet.add(cell); - } - - public void addAll(CellSet set){ - internalSet.addAll(set.internalSet); - } - - void clear(){ - internalSet.clear(); - } - - public int size() { - return internalSet.size(); - } - - public TextGrid.Cell getFirst(){ - //return internalSet.get(0); - return (TextGrid.Cell) internalSet.iterator().next(); - } - - public void printAsGrid(){ - TextGrid grid = new TextGrid(getMaxX()+2, getMaxY()+2); - grid.fillCellsWith(this, '*'); - grid.printDebug(); - } - - public void printDebug(){ - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cell = it.next(); - System.out.print(cell); - if(it.hasNext()) System.out.print(" "); - } - System.out.println(); - } - - public String getCellsAsString(){ - StringBuffer str = new StringBuffer(); - Iterator it = iterator(); - while(it.hasNext()){ - str.append(it.next().toString()); - if(it.hasNext()) str.append("/"); - } - return str.toString(); - } - - public String toString(){ - TextGrid grid = new TextGrid(getMaxX()+2, getMaxY()+2); - grid.fillCellsWith(this, '*'); - return grid.getDebugString(); - } - - /** - * Deep copy - * - * @param set - * @return - */ - public static CellSet copyCellSet(CellSet set) { - TextGrid grid = new TextGrid(); - CellSet newSet = new CellSet(); - - Iterator it = set.iterator(); - while (it.hasNext()) { - TextGrid.Cell cell = (TextGrid.Cell) it.next(); - TextGrid.Cell newCell = grid.new Cell(cell); - newSet.add(newCell); - } - return newSet; - } + private static final boolean DEBUG = false; + private static final boolean VERBOSE_DEBUG = false; + + public static final int TYPE_CLOSED = 0; + public static final int TYPE_OPEN = 1; + public static final int TYPE_MIXED = 2; + public static final int TYPE_HAS_CLOSED_AREA = 3; + public static final int TYPE_UNDETERMINED = 4; + + Set internalSet = new HashSet(); + + private int type = TYPE_UNDETERMINED; + private boolean typeIsValid = false; + + private static final Object FAKE = new Object(); + + public CellSet() { + + } + + public CellSet(CellSet other) { + addAll(other); + } + + public Iterator iterator() { + return internalSet.iterator(); + } + + public Object add(TextGrid.Cell cell) { + return internalSet.add(cell); + } + + public void addAll(CellSet set) { + internalSet.addAll(set.internalSet); + } + + void clear() { + internalSet.clear(); + } + + public int size() { + return internalSet.size(); + } + + public TextGrid.Cell getFirst() { + //return internalSet.get(0); + return (TextGrid.Cell) internalSet.iterator().next(); + } + + public void printAsGrid() { + TextGrid grid = new TextGrid(getMaxX() + 2, getMaxY() + 2); + grid.fillCellsWith(this, '*'); + grid.printDebug(); + } + + public void printDebug() { + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = it.next(); + System.out.print(cell); + if (it.hasNext()) + System.out.print(" "); + } + System.out.println(); + } + + public String getCellsAsString() { + StringBuffer str = new StringBuffer(); + Iterator it = iterator(); + while (it.hasNext()) { + str.append(it.next().toString()); + if (it.hasNext()) + str.append("/"); + } + return str.toString(); + } + + public String toString() { + TextGrid grid = new TextGrid(getMaxX() + 2, getMaxY() + 2); + grid.fillCellsWith(this, '*'); + return grid.getDebugString(); + } + + /** + * Deep copy + * + * @param set + * @return + */ + public static CellSet copyCellSet(CellSet set) { + TextGrid grid = new TextGrid(); + CellSet newSet = new CellSet(); + + Iterator it = set.iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = (TextGrid.Cell) it.next(); + TextGrid.Cell newCell = grid.new Cell(cell); + newSet.add(newCell); + } + return newSet; + } /*public BoundarySet(BoundarySet set) { Iterator it = set.iterator(); @@ -140,548 +139,580 @@ public static CellSet copyCellSet(CellSet set) { } }*/ - public int getType(TextGrid grid) { - if(typeIsValid) return type; - typeIsValid = true; - if(size() == 1) { - type = TYPE_OPEN; - return TYPE_OPEN; - } - int typeTrace = getTypeAccordingToTraceMethod(grid); - - if(DEBUG){ - System.out.println("trace: "+typeTrace); - } - - if(typeTrace == TYPE_OPEN) { - type = TYPE_OPEN; - return TYPE_OPEN; - } - if(typeTrace == TYPE_CLOSED) { - type = TYPE_CLOSED; - return TYPE_CLOSED; - } - - if(typeTrace == TYPE_UNDETERMINED) { - int typeFill = getTypeAccordingToFillMethod(grid); - if(typeFill == TYPE_HAS_CLOSED_AREA){ - type = TYPE_MIXED; - return TYPE_MIXED; - } else if(typeFill == TYPE_OPEN){ - type = TYPE_OPEN; - return TYPE_OPEN; - } - } - - //in the case that both return undetermined: - type = TYPE_UNDETERMINED; - return TYPE_UNDETERMINED; - } - - private int getTypeAccordingToTraceMethod(TextGrid grid) { - if(size() < 2) return TYPE_OPEN; - - TextGrid workGrid = TextGrid.makeSameSizeAs(grid); - grid.copyCellsTo(this, workGrid); - - //start with a line end if it exists or with a "random" cell if not - TextGrid.Cell start = null; - for(TextGrid.Cell cell : this) - if(workGrid.isLinesEnd(cell)) - start = cell; - if(start == null) start = (TextGrid.Cell) getFirst(); - - if (DEBUG) - System.out.println("Tracing:\nStarting at "+start+" ("+grid.getCellTypeAsString(start)+")"); - TextGrid.Cell previous = start; - TextGrid.Cell cell = null; - CellSet nextCells = workGrid.followCell(previous); - if(nextCells.size() == 0) return TYPE_OPEN; - cell = (TextGrid.Cell) nextCells.getFirst(); - if (DEBUG) - System.out.println("\tat cell "+cell+" ("+grid.getCellTypeAsString(cell)+")"); - - - while(!cell.equals(start)){ - nextCells = workGrid.followCell(cell, previous); - if(nextCells.size() == 0) { - if (DEBUG) - System.out.println("-> Found dead-end, shape is open"); - return TYPE_OPEN; - } if(nextCells.size() == 1) { - previous = cell; - cell = (TextGrid.Cell) nextCells.getFirst(); - if (DEBUG) - System.out.println("\tat cell "+cell+" ("+grid.getCellTypeAsString(cell)+")"); - } else if(nextCells.size() > 1) { - if (DEBUG) - System.out.println("-> Found intersection at cell "+cell); - return TYPE_UNDETERMINED; - } - } - if (DEBUG) - System.out.println("-> Arrived back to start, shape is closed"); - return TYPE_CLOSED; - -// boolean hasMoved = false; -// -// CellSet workSet; -// workSet = new CellSet(this); -// -// TextGrid.Cell start = (TextGrid.Cell) get(0); -// -// workSet.remove(start); -// TextGrid.Cell cell = workSet.findCellNextTo(start); -// -// while(true && cell != null){ -// -// hasMoved = true; -// workSet.remove(cell); -// -// CellSet setOfNeighbours = workSet.findCellsNextTo(cell); -// -// if(setOfNeighbours.isEmpty()) break; -// -// TextGrid.Cell c = null; -// if(setOfNeighbours.size() == 1) c = (TextGrid.Cell) setOfNeighbours.get(0); -// if(setOfNeighbours.size() > 1) return TYPE_UNDETERMINED; -// if(c == null) break; -// else cell = c; -// } -// if(cell != null && start.isNextTo(cell) && hasMoved) return TYPE_CLOSED; -// else return TYPE_OPEN; - } - - private int getTypeAccordingToFillMethod(TextGrid grid){ - if(size() == 0) return TYPE_OPEN; - - CellSet tempSet = copyCellSet(this); - tempSet.translate( -this.getMinX() + 1, -this.getMinY() + 1); - TextGrid subGrid = grid.getSubGrid(getMinX() - 1, getMinY() - 1, getWidth() + 3, getHeight() + 3); - AbstractionGrid abstraction = new AbstractionGrid(subGrid, tempSet); - TextGrid temp = abstraction.getCopyOfInternalBuffer(); - - int width = temp.getWidth(); - int height = temp.getHeight(); - - TextGrid.Cell fillCell = null; - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - TextGrid.Cell cCell = temp.new Cell(x, y); - if(temp.isBlank(cCell)){ - fillCell = cCell; - break; - } - } - } - - if(fillCell == null){ - System.err.println("Unexpected error: fill method cannot fill anywhere"); - return TYPE_UNDETERMINED; - } - - temp.fillContinuousArea(fillCell, '*'); - if(VERBOSE_DEBUG) {System.out.println("Buffer after filling:"); temp.printDebug();} - - if(temp.hasBlankCells()) return TYPE_HAS_CLOSED_AREA; - else return TYPE_OPEN; - } - - public void translate(int dx, int dy){ - typeIsValid = false; - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cCell = it.next(); - cCell.x += dx; - cCell.y += dy; - } - } - - public TextGrid.Cell find(TextGrid.Cell cell){ - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cCell = it.next(); - if(cCell.equals(cell)) return cCell; - } - return null; - } - - public boolean contains(TextGrid.Cell cell){ - if(cell == null) return false; - return internalSet.contains(cell); - } - -// public boolean contains(TextGrid.Cell cell){ -// Iterator it = iterator(); -// while(it.hasNext()){ -// TextGrid.Cell cCell = it.next(); -// if(cCell.equals(cell)) return true; -// } -// return false; -// } - - public void addSet(CellSet set){ - typeIsValid = false; - this.addAll(set); - } - - public boolean hasCommonCells(CellSet otherSet){ - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cell = it.next(); - if(otherSet.contains(cell)) return true; - } - return false; - } + public int getType(TextGrid grid) { + if (typeIsValid) + return type; + typeIsValid = true; + if (size() == 1) { + type = TYPE_OPEN; + return TYPE_OPEN; + } + int typeTrace = getTypeAccordingToTraceMethod(grid); + + if (DEBUG) { + System.out.println("trace: " + typeTrace); + } + + if (typeTrace == TYPE_OPEN) { + type = TYPE_OPEN; + return TYPE_OPEN; + } + if (typeTrace == TYPE_CLOSED) { + type = TYPE_CLOSED; + return TYPE_CLOSED; + } + + if (typeTrace == TYPE_UNDETERMINED) { + int typeFill = getTypeAccordingToFillMethod(grid); + if (typeFill == TYPE_HAS_CLOSED_AREA) { + type = TYPE_MIXED; + return TYPE_MIXED; + } else if (typeFill == TYPE_OPEN) { + type = TYPE_OPEN; + return TYPE_OPEN; + } + } + + //in the case that both return undetermined: + type = TYPE_UNDETERMINED; + return TYPE_UNDETERMINED; + } + + private int getTypeAccordingToTraceMethod(TextGrid grid) { + if (size() < 2) + return TYPE_OPEN; + + TextGrid workGrid = TextGrid.makeSameSizeAs(grid); + grid.copyCellsTo(this, workGrid); + + //start with a line end if it exists or with a "random" cell if not + TextGrid.Cell start = null; + for (TextGrid.Cell cell : this) + if (workGrid.isLinesEnd(cell)) + start = cell; + if (start == null) + start = (TextGrid.Cell) getFirst(); + + if (DEBUG) + System.out.println("Tracing:\nStarting at " + start + " (" + grid.getCellTypeAsString(start) + ")"); + TextGrid.Cell previous = start; + TextGrid.Cell cell = null; + CellSet nextCells = workGrid.followCell(previous); + if (nextCells.size() == 0) + return TYPE_OPEN; + cell = (TextGrid.Cell) nextCells.getFirst(); + if (DEBUG) + System.out.println("\tat cell " + cell + " (" + grid.getCellTypeAsString(cell) + ")"); + + while (!cell.equals(start)) { + nextCells = workGrid.followCell(cell, previous); + if (nextCells.size() == 0) { + if (DEBUG) + System.out.println("-> Found dead-end, shape is open"); + return TYPE_OPEN; + } + if (nextCells.size() == 1) { + previous = cell; + cell = (TextGrid.Cell) nextCells.getFirst(); + if (DEBUG) + System.out.println("\tat cell " + cell + " (" + grid.getCellTypeAsString(cell) + ")"); + } else if (nextCells.size() > 1) { + if (DEBUG) + System.out.println("-> Found intersection at cell " + cell); + return TYPE_UNDETERMINED; + } + } + if (DEBUG) + System.out.println("-> Arrived back to start, shape is closed"); + return TYPE_CLOSED; + + // boolean hasMoved = false; + // + // CellSet workSet; + // workSet = new CellSet(this); + // + // TextGrid.Cell start = (TextGrid.Cell) get(0); + // + // workSet.remove(start); + // TextGrid.Cell cell = workSet.findCellNextTo(start); + // + // while(true && cell != null){ + // + // hasMoved = true; + // workSet.remove(cell); + // + // CellSet setOfNeighbours = workSet.findCellsNextTo(cell); + // + // if(setOfNeighbours.isEmpty()) break; + // + // TextGrid.Cell c = null; + // if(setOfNeighbours.size() == 1) c = (TextGrid.Cell) setOfNeighbours.get(0); + // if(setOfNeighbours.size() > 1) return TYPE_UNDETERMINED; + // if(c == null) break; + // else cell = c; + // } + // if(cell != null && start.isNextTo(cell) && hasMoved) return TYPE_CLOSED; + // else return TYPE_OPEN; + } + + private int getTypeAccordingToFillMethod(TextGrid grid) { + if (size() == 0) + return TYPE_OPEN; + + CellSet tempSet = copyCellSet(this); + tempSet.translate(-this.getMinX() + 1, -this.getMinY() + 1); + TextGrid subGrid = grid.getSubGrid(getMinX() - 1, getMinY() - 1, getWidth() + 3, getHeight() + 3); + AbstractionGrid abstraction = new AbstractionGrid(subGrid, tempSet); + TextGrid temp = abstraction.getCopyOfInternalBuffer(); + + int width = temp.getWidth(); + int height = temp.getHeight(); + + TextGrid.Cell fillCell = null; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + TextGrid.Cell cCell = temp.new Cell(x, y); + if (temp.isBlank(cCell)) { + fillCell = cCell; + break; + } + } + } + + if (fillCell == null) { + System.err.println("Unexpected error: fill method cannot fill anywhere"); + return TYPE_UNDETERMINED; + } + + temp.fillContinuousArea(fillCell, '*'); + if (VERBOSE_DEBUG) { + System.out.println("Buffer after filling:"); + temp.printDebug(); + } + + if (temp.hasBlankCells()) + return TYPE_HAS_CLOSED_AREA; + else + return TYPE_OPEN; + } + + public void translate(int dx, int dy) { + typeIsValid = false; + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cCell = it.next(); + cCell.x += dx; + cCell.y += dy; + } + } + + public TextGrid.Cell find(TextGrid.Cell cell) { + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cCell = it.next(); + if (cCell.equals(cell)) + return cCell; + } + return null; + } + + public boolean contains(TextGrid.Cell cell) { + if (cell == null) + return false; + return internalSet.contains(cell); + } + + // public boolean contains(TextGrid.Cell cell){ + // Iterator it = iterator(); + // while(it.hasNext()){ + // TextGrid.Cell cCell = it.next(); + // if(cCell.equals(cell)) return true; + // } + // return false; + // } + + public void addSet(CellSet set) { + typeIsValid = false; + this.addAll(set); + } + + public boolean hasCommonCells(CellSet otherSet) { + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = it.next(); + if (otherSet.contains(cell)) + return true; + } + return false; + } + + public TextGrid.Cell find(int x, int y) { + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cCell = it.next(); + if (cCell.x == x && cCell.y == y) + return cCell; + } + return null; + } + + public CellSet getFilledEquivalent(TextGrid textGrid) { + if (this.getType(textGrid) == CellSet.TYPE_OPEN) + return new CellSet(this); + TextGrid grid = new TextGrid(getMaxX() + 2, getMaxY() + 2); + grid.fillCellsWith(this, '*'); + + //find a cell that has a blank both on the east and the west + TextGrid.Cell cell = null; + boolean finished = false; + for (int y = 0; y < grid.getHeight() && !finished; y++) { + for (int x = 0; x < grid.getWidth() && !finished; x++) { + cell = grid.new Cell(x, y); + if (!grid.isBlank(cell) + && grid.isBlank(cell.getEast()) + && grid.isBlank(cell.getWest())) { + finished = true; + } + } + } + if (cell != null) { + cell = cell.getEast(); + if (grid.isOutOfBounds(cell)) + return new CellSet(this); + grid.fillContinuousArea(cell, '*'); + return grid.getAllNonBlank(); + } + System.err.println("Unexpected error, cannot find the filled equivalent of CellSet"); + return null; + } + + /** + * Returns the first cell that is found to be next to cell. + * + * @param cell + * @return + */ + public TextGrid.Cell findCellNextTo(TextGrid.Cell cell) { + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cCell = it.next(); + if (cCell.isNextTo(cell)) + return cCell; + } + return null; + } + + /** + * Returns all the cells that are found to be next to cell. + * + * @param cell + * @return + */ + public CellSet findCellsNextTo(TextGrid.Cell cell) { + if (cell == null) + throw new IllegalArgumentException("cell cannot be null"); + CellSet set = new CellSet(); + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cCell = it.next(); + if (cCell.isNextTo(cell)) + set.add(cCell); + } + return set; + } + + public void appendSet(CellSet set) { + typeIsValid = false; + Iterator it = set.iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = it.next(); + if (find(cell) == null) + add(cell); + } + } + + public void subtractSet(CellSet set) { + typeIsValid = false; + Iterator it = set.iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = it.next(); + TextGrid.Cell thisCell = find(cell); + if (thisCell != null) + remove(thisCell); + } + } + + public int getWidth() { + return getMaxX() - getMinX(); + } + + + public int getHeight() { + return getMaxY() - getMinY(); + } + + public int getMaxX() { + int result = 0; + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = it.next(); + if (cell.x > result) + result = cell.x; + } + return result; + } + + public int getMinX() { + int result = Integer.MAX_VALUE; + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = it.next(); + if (cell.x < result) + result = cell.x; + } + return result; + } + + + public int getMaxY() { + int result = 0; + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = it.next(); + if (cell.y > result) + result = cell.y; + } + return result; + } + + public int getMinY() { + int result = Integer.MAX_VALUE; + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = it.next(); + if (cell.y < result) + result = cell.y; + } + return result; + } + + + public Object remove(TextGrid.Cell cell) { + typeIsValid = false; + cell = find(cell); + if (cell != null) + return internalSet.remove(cell); + else + return null; + } + + public boolean equals(Object o) { + CellSet otherSet = (CellSet) o; + return internalSet.equals(otherSet.internalSet); + } + + + public static ArrayList removeDuplicateSets(ArrayList list) { + ArrayList uniqueSets = new ArrayList(); + + Iterator it = list.iterator(); + while (it.hasNext()) { + CellSet set = it.next(); + boolean isOriginal = true; + Iterator uniquesIt = uniqueSets.iterator(); + while (uniquesIt.hasNext()) { + CellSet uniqueSet = uniquesIt.next(); + if (set.equals(uniqueSet)) { + isOriginal = false; + } + } + if (isOriginal) + uniqueSets.add(set); + } + return uniqueSets; + } + + + /** + * Takes into account character info from the grid + * + * @return ArrayList of distinct BoundarySetS + */ + public ArrayList breakIntoDistinctBoundaries(TextGrid grid) { + ArrayList result; + + AbstractionGrid temp = new AbstractionGrid(grid, this); + result = temp.getDistinctShapes(); + + return result; + } + + + /** + * + * @return ArrayList of distinct BoundarySetS + */ + public ArrayList breakIntoDistinctBoundaries() { + ArrayList result = new ArrayList(); + + //CellSet tempSet = copyCellSet(this); + //tempSet.translate( - this.getMinX() + 1, - this.getMinY() + 1); + + // TextGrid boundaryGrid = new TextGrid(tempSet.getMaxX()+2, tempSet.getMaxY()+2); + // boundaryGrid.fillCellsWith(tempSet, '*'); + + TextGrid boundaryGrid = new TextGrid(getMaxX() + 2, getMaxY() + 2); + boundaryGrid.fillCellsWith(this, '*'); + + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell cell = (TextGrid.Cell) it.next(); + if (boundaryGrid.isBlank(cell.x, cell.y)) + continue; + CellSet boundarySet = boundaryGrid.fillContinuousArea(cell.x, cell.y, ' '); + //boundarySet.translate( this.getMinX() - 1, this.getMinY() - 1); + result.add(boundarySet); + } + return result; + } + + + /** + * + * Breaks that: + *
+   *  +-----+
+   *  |     |
+   *  +  ---+-------------------
+   *  |     |
+   *  +-----+
+   * 
+ * + * into the following 3: + * + *
+   *  +-----+
+   *  |     |
+   *  +     +
+   *  |     |
+   *  +-----+
+   *
+   *     ---
+   *         -------------------
+   * 
+ * + * @param grid + * @return a list of boundaries that are either open or closed but not mixed + * and they are equivalent to the this + */ + public ArrayList breakTrulyMixedBoundaries(TextGrid grid) { + ArrayList result = new ArrayList(); + CellSet visitedEnds = new CellSet(); + + TextGrid workGrid = TextGrid.makeSameSizeAs(grid); + grid.copyCellsTo(this, workGrid); + + if (DEBUG) { + System.out.println("Breaking truly mixed boundaries below:"); + workGrid.printDebug(); + } + + Iterator it = iterator(); + while (it.hasNext()) { + TextGrid.Cell start = (TextGrid.Cell) it.next(); + if (workGrid.isLinesEnd(start) && !visitedEnds.contains(start)) { + + if (DEBUG) + System.out.println("Starting new subshape:"); + + CellSet set = new CellSet(); + set.add(start); + if (DEBUG) + System.out.println("Added boundary " + start); + + TextGrid.Cell previous = start; + TextGrid.Cell cell = null; + CellSet nextCells = workGrid.followCell(previous); + if (nextCells.size() == 0) + throw new IllegalArgumentException("This shape is either open but multipart or has only one cell, and cannot be processed by this method"); + cell = (TextGrid.Cell) nextCells.getFirst(); + set.add(cell); + if (DEBUG) + System.out.println("Added boundary " + cell); + + boolean finished = false; + if (workGrid.isLinesEnd(cell)) { + visitedEnds.add(cell); + finished = true; + } + + while (!finished) { + nextCells = workGrid.followCell(cell, previous); + if (nextCells.size() == 1) { + set.add(cell); + if (DEBUG) + System.out.println("Added boundary " + cell); + previous = cell; + cell = (TextGrid.Cell) nextCells.getFirst(); + //if(!cell.equals(start) && grid.isPointCell(cell)) + // s.addToPoints(makePointForCell(cell, workGrid, cellWidth, cellHeight, allRound)); + if (workGrid.isLinesEnd(cell)) { + visitedEnds.add(cell); + finished = true; + } + } else if (nextCells.size() > 1) { + finished = true; + } + } + result.add(set); + } + } + + //substract all boundary sets from this CellSet + CellSet whatsLeft = new CellSet(this); + for (CellSet set : result) { + whatsLeft.subtractSet(set); + if (DEBUG) + set.printAsGrid(); + } + result.add(whatsLeft); + if (DEBUG) + whatsLeft.printAsGrid(); + + return result; + } + + + public TextGrid makeIntoGrid() { + TextGrid grid = new TextGrid(getMaxX() + 2, getMaxY() + 2); + grid.fillCellsWith(this, '*'); + return grid; + } + + public CellSet makeScaledOneThirdEquivalent() { + TextGrid gridBig = this.makeIntoGrid(); + gridBig.fillCellsWith(this, '*'); + if (VERBOSE_DEBUG) { + System.out.println("---> making ScaledOneThirdEquivalent of:"); + gridBig.printDebug(); + } + + TextGrid gridSmall = new TextGrid((getMaxX() + 2) / 3, (getMaxY() + 2) / 3); + + for (int y = 0; y < gridBig.getHeight(); y++) { + for (int x = 0; x < gridBig.getWidth(); x++) { + TextGrid.Cell cell = gridBig.new Cell(x, y); + if (!gridBig.isBlank(cell)) + gridSmall.set(x / 3, y / 3, '*'); + } + } + + if (VERBOSE_DEBUG) { + System.out.println("---> made into grid:"); + gridSmall.printDebug(); + } + + return gridSmall.getAllNonBlank(); + } - public TextGrid.Cell find(int x, int y){ - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cCell = it.next(); - if(cCell.x == x && cCell.y == y) return cCell; - } - return null; - } - - public CellSet getFilledEquivalent(TextGrid textGrid){ - if(this.getType(textGrid) == CellSet.TYPE_OPEN) return new CellSet(this); - TextGrid grid = new TextGrid(getMaxX()+2, getMaxY()+2); - grid.fillCellsWith(this, '*'); - - //find a cell that has a blank both on the east and the west - TextGrid.Cell cell = null; - boolean finished = false; - for(int y = 0; y < grid.getHeight() && !finished; y++){ - for(int x = 0; x < grid.getWidth() && !finished; x++){ - cell = grid.new Cell(x, y); - if(!grid.isBlank(cell) - && grid.isBlank(cell.getEast()) - && grid.isBlank(cell.getWest())){ - finished = true; - } - } - } - if(cell != null){ - cell = cell.getEast(); - if(grid.isOutOfBounds(cell)) return new CellSet(this); - grid.fillContinuousArea(cell, '*'); - return grid.getAllNonBlank(); - } - System.err.println("Unexpected error, cannot find the filled equivalent of CellSet"); - return null; - } - - /** - * Returns the first cell that is found to be next to cell. - * - * @param cell - * @return - */ - public TextGrid.Cell findCellNextTo(TextGrid.Cell cell){ - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cCell = it.next(); - if(cCell.isNextTo(cell)) return cCell; - } - return null; - } - - /** - * Returns all the cells that are found to be next to cell. - * - * @param cell - * @return - */ - public CellSet findCellsNextTo(TextGrid.Cell cell){ - if(cell == null) throw new IllegalArgumentException("cell cannot be null"); - CellSet set = new CellSet(); - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cCell = it.next(); - if(cCell.isNextTo(cell)) set.add(cCell); - } - return set; - } - - public void appendSet(CellSet set){ - typeIsValid = false; - Iterator it = set.iterator(); - while(it.hasNext()){ - TextGrid.Cell cell = it.next(); - if(find(cell) == null) add(cell); - } - } - - public void subtractSet(CellSet set){ - typeIsValid = false; - Iterator it = set.iterator(); - while(it.hasNext()){ - TextGrid.Cell cell = it.next(); - TextGrid.Cell thisCell = find(cell); - if(thisCell != null) remove(thisCell); - } - } - - public int getWidth(){ - return getMaxX() - getMinX(); - } - - - public int getHeight(){ - return getMaxY() - getMinY(); - } - - public int getMaxX(){ - int result = 0; - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cell = it.next(); - if(cell.x > result) result = cell.x; - } - return result; - } - - public int getMinX(){ - int result = Integer.MAX_VALUE; - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cell = it.next(); - if(cell.x < result) result = cell.x; - } - return result; - } - - - public int getMaxY(){ - int result = 0; - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cell = it.next(); - if(cell.y > result) result = cell.y; - } - return result; - } - - public int getMinY(){ - int result = Integer.MAX_VALUE; - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cell = it.next(); - if(cell.y < result) result = cell.y; - } - return result; - } - - - public Object remove(TextGrid.Cell cell){ - typeIsValid = false; - cell = find(cell); - if(cell != null) return internalSet.remove(cell); - else return null; - } - - public boolean equals(Object o){ - CellSet otherSet = (CellSet) o; - return internalSet.equals(otherSet.internalSet); - } - - - public static ArrayList removeDuplicateSets(ArrayList list) { - ArrayList uniqueSets = new ArrayList(); - - Iterator it = list.iterator(); - while(it.hasNext()){ - CellSet set = it.next(); - boolean isOriginal = true; - Iterator uniquesIt = uniqueSets.iterator(); - while(uniquesIt.hasNext()){ - CellSet uniqueSet = uniquesIt.next(); - if(set.equals(uniqueSet)){ - isOriginal = false; - } - } - if(isOriginal) uniqueSets.add(set); - } - return uniqueSets; - } - - - /** - * Takes into account character info from the grid - * - * @return ArrayList of distinct BoundarySetS - */ - public ArrayList breakIntoDistinctBoundaries(TextGrid grid){ - ArrayList result; - - AbstractionGrid temp = new AbstractionGrid(grid, this); - result = temp.getDistinctShapes(); - - return result; - } - - - /** - * - * @return ArrayList of distinct BoundarySetS - */ - public ArrayList breakIntoDistinctBoundaries(){ - ArrayList result = new ArrayList(); - - //CellSet tempSet = copyCellSet(this); - //tempSet.translate( - this.getMinX() + 1, - this.getMinY() + 1); - -// TextGrid boundaryGrid = new TextGrid(tempSet.getMaxX()+2, tempSet.getMaxY()+2); -// boundaryGrid.fillCellsWith(tempSet, '*'); - - TextGrid boundaryGrid = new TextGrid(getMaxX()+2, getMaxY()+2); - boundaryGrid.fillCellsWith(this, '*'); - - - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell cell = (TextGrid.Cell) it.next(); - if(boundaryGrid.isBlank(cell.x, cell.y)) continue; - CellSet boundarySet = boundaryGrid.fillContinuousArea(cell.x, cell.y, ' '); - //boundarySet.translate( this.getMinX() - 1, this.getMinY() - 1); - result.add(boundarySet); - } - return result; - } - - - /** - * - * Breaks that: - *
-	 *  +-----+
-	 *  |     |
-	 *  +  ---+-------------------
-	 *  |     |
-	 *  +-----+
-	 * 
- * - * into the following 3: - * - *
-	 *  +-----+
-	 *  |     |
-	 *  +     +
-	 *  |     |
-	 *  +-----+
-	 * 
-	 *     ---
-	 *         -------------------
-	 * 
- * - * @param grid - * @return a list of boundaries that are either open or closed but not mixed - * and they are equivalent to the this - */ - public ArrayList breakTrulyMixedBoundaries(TextGrid grid){ - ArrayList result = new ArrayList(); - CellSet visitedEnds = new CellSet(); - - TextGrid workGrid = TextGrid.makeSameSizeAs(grid); - grid.copyCellsTo(this, workGrid); - - if (DEBUG){ - System.out.println("Breaking truly mixed boundaries below:"); - workGrid.printDebug(); - } - - Iterator it = iterator(); - while(it.hasNext()){ - TextGrid.Cell start = (TextGrid.Cell) it.next(); - if(workGrid.isLinesEnd(start) && !visitedEnds.contains(start)){ - - if (DEBUG) - System.out.println("Starting new subshape:"); - - CellSet set = new CellSet(); - set.add(start); - if(DEBUG) System.out.println("Added boundary "+start); - - TextGrid.Cell previous = start; - TextGrid.Cell cell = null; - CellSet nextCells = workGrid.followCell(previous); - if(nextCells.size() == 0) - throw new IllegalArgumentException("This shape is either open but multipart or has only one cell, and cannot be processed by this method"); - cell = (TextGrid.Cell) nextCells.getFirst(); - set.add(cell); - if(DEBUG) System.out.println("Added boundary "+cell); - - boolean finished = false; - if(workGrid.isLinesEnd(cell)){ - visitedEnds.add(cell); - finished = true; - } - - while(!finished){ - nextCells = workGrid.followCell(cell, previous); - if(nextCells.size() == 1) { - set.add(cell); - if(DEBUG) System.out.println("Added boundary " + cell); - previous = cell; - cell = (TextGrid.Cell) nextCells.getFirst(); - //if(!cell.equals(start) && grid.isPointCell(cell)) - // s.addToPoints(makePointForCell(cell, workGrid, cellWidth, cellHeight, allRound)); - if(workGrid.isLinesEnd(cell)){ - visitedEnds.add(cell); - finished = true; - } - } else if(nextCells.size() > 1) { - finished = true; - } - } - result.add(set); - } - } - - //substract all boundary sets from this CellSet - CellSet whatsLeft = new CellSet(this); - for(CellSet set : result) { - whatsLeft.subtractSet(set); - if(DEBUG) set.printAsGrid(); - } - result.add(whatsLeft); - if(DEBUG) whatsLeft.printAsGrid(); - - return result; - } - - - public TextGrid makeIntoGrid(){ - TextGrid grid = new TextGrid(getMaxX()+2, getMaxY()+2); - grid.fillCellsWith(this, '*'); - return grid; - } - - public CellSet makeScaledOneThirdEquivalent(){ - TextGrid gridBig = this.makeIntoGrid(); - gridBig.fillCellsWith(this, '*'); - if (VERBOSE_DEBUG){ - System.out.println("---> making ScaledOneThirdEquivalent of:"); - gridBig.printDebug(); - } - - - TextGrid gridSmall = new TextGrid((getMaxX() + 2) / 3, (getMaxY() + 2) / 3); - - - for(int y = 0; y < gridBig.getHeight(); y++){ - for(int x = 0; x < gridBig.getWidth(); x++){ - TextGrid.Cell cell = gridBig.new Cell(x, y); - if(!gridBig.isBlank(cell)) gridSmall.set(x/3, y/3, '*'); - } - } - - if (VERBOSE_DEBUG){ - System.out.println("---> made into grid:"); - gridSmall.printDebug(); - } - - return gridSmall.getAllNonBlank(); - } - } diff --git a/src/java/org/stathissideris/ascii2image/text/GridPattern.java b/src/java/org/stathissideris/ascii2image/text/GridPattern.java index bb96452..5898e35 100644 --- a/src/java/org/stathissideris/ascii2image/text/GridPattern.java +++ b/src/java/org/stathissideris/ascii2image/text/GridPattern.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,7 +15,6 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.text; @@ -27,7 +26,7 @@ * This is a TextGrid (usually 3x3) that contains the equivalent of a * 2D reqular expression (which uses custom syntax to make things more * visual, but standard syntax is also possible). - * + * * The custom syntax is: * . means anything * b means any boundary (any of - = / \ + | :) @@ -40,20 +39,20 @@ * ( means a boundary but not | nor : * s means a straight boundary (one of - = + | :) * S means not a straight boundary (none of - = + | :) - * + * * 1 means a cell that has entry point 1 * 2 means a cell that has entry point 2 * 3 means a cell that has entry point 3 etc. up to number 8 - * + * * %1 means a cell that does not have entry point 1 etc. - * + * * See below for an explanation of entry points - * + * * +, \, / and the space are literal (as is any other character) - * - * + * + * * Entry points - * + * *
  * 1   2   3
  *  *--*--*
@@ -63,271 +62,274 @@
  *  *--*--*
  * 7   6   5
  * 
- * + * * We number the entry points for each cell as in the diagram * above. If a cell is occupied by a character, we define as * entry points the points of the above diagram that the character * can touch with the end of its lines. For example - has * entry points 8 and 4, | and : have entry points 2 and 6, * / has 3 and 7, \ has 1 and 5, + has 2, 6, 8 and 4 etc. - * - * + * + * * @author Efstathios Sideris */ public class GridPattern extends TextGrid { - - private ArrayList regExps = new ArrayList(); //TODO optimise: store as PatternS - private boolean regExpsAreValid = false; - - private static final boolean DEBUG = false; - - private boolean usesStandardSyntax = false; - - public GridPattern(){ - super(3, 3); - } - - public GridPattern(String row1, String row2, String row3){ - super(Math.max(Math.max(row1.length(), row2.length()), row3.length()), 3); - setTo(row1, row2, row3); - regExpsAreValid = false; - } - - public boolean usesStandardSyntax() { - return usesStandardSyntax; - } - - public void setUsesStandardSyntax(boolean b) { - usesStandardSyntax = b; - regExpsAreValid = false; - } - - public boolean isMatchedBy(TextGrid grid){ + + private ArrayList regExps = new ArrayList(); //TODO optimise: store as PatternS + private boolean regExpsAreValid = false; + + private static final boolean DEBUG = false; + + private boolean usesStandardSyntax = false; + + public GridPattern() { + super(3, 3); + } + + public GridPattern(String row1, String row2, String row3) { + super(Math.max(Math.max(row1.length(), row2.length()), row3.length()), 3); + setTo(row1, row2, row3); + regExpsAreValid = false; + } + + public boolean usesStandardSyntax() { + return usesStandardSyntax; + } + + public void setUsesStandardSyntax(boolean b) { + usesStandardSyntax = b; + regExpsAreValid = false; + } + + public boolean isMatchedBy(TextGrid grid) { /*if(grid.getHeight() != this.getHeight() || grid.getWidth() != this.getWidth()) return false;*/ - if(!regExpsAreValid) prepareRegExps(); - - for(int i = 0; i < grid.getHeight(); i++) { - String row = grid.getRow(i).toString(); - Pattern regexp = regExps.get(i); - if(!regexp.matcher(row).matches()) { - if(DEBUG) - System.out.println(row+" does not match "+regexp); - return false; - } - } - return true; - } - - private void prepareRegExps(){ - regExpsAreValid = true; - regExps.clear(); - if (DEBUG) - System.out.println("Trying to match:"); - if(!usesStandardSyntax){ - Iterator it = getRows().iterator(); - while (it.hasNext()) { - String row = it.next().toString(); - regExps.add(Pattern.compile(makeRegExp(row))); - if(DEBUG) - System.out.println(row+" becomes "+makeRegExp(row)); - } - } else { - Iterator it = getRows().iterator(); - while (it.hasNext()) { - String row = it.next().toString(); - regExps.add(Pattern.compile(row)); - } - } - } - - private String makeRegExp(String pattern){ - StringBuilder result = new StringBuilder(); - int tokensHandled = 0; - for(int i = 0; i < pattern.length() && tokensHandled < 3; i++){ - char c = pattern.charAt(i); - if(c == '[') { - result.append("[^|:]"); - } else if(c == '|') { - result.append("[|:]"); - } else if(c == '-') { - result.append("[-=]"); - } else if(c == '!') { - result.append("[^-=\\/\\\\+|:]"); - } else if(c == 'b') { - result.append("[-=\\/\\\\+|:]"); - } else if(c == '^') { - result.append("[\\/\\\\+|:]"); - } else if(c == '(') { - result.append("[-=\\/\\\\+]"); - } else if(c == '~') { - result.append("."); - } else if(c == '+') { - result.append("\\+"); - } else if(c == '\\') { - result.append("\\\\"); - } else if(c == 's') { - result.append("[-=+|:]"); - } else if(c == 'S') { - result.append("[\\/\\\\]"); - } else if(c == '*') { - result.append("\\*"); - - //entry points - } else if(c == '1') { - result.append("[\\\\]"); - - } else if(c == '2') { - result.append("[|:+\\/\\\\]"); - - } else if(c == '3') { - result.append("[\\/]"); - - } else if(c == '4') { - result.append("[-=+\\/\\\\]"); - - } else if(c == '5') { - result.append("[\\\\]"); - - } else if(c == '6') { - result.append("[|:+\\/\\\\]"); - - } else if(c == '7') { - result.append("[\\/]"); - - } else if(c == '8') { - result.append("[-=+\\/\\\\]"); - - //entry point negations - } else if(c == '%') { - if(i+1 > pattern.length()){ - throw new RuntimeException("Invalid pattern, found % at the end"); - } - c = pattern.charAt(++i); - - if(c == '1') { - result.append("[^\\\\]"); - - } else if(c == '2') { - result.append("[^|:+\\/\\\\]"); - - } else if(c == '3') { - result.append("[^\\/]"); - - } else if(c == '4') { - result.append("[^-=+\\/\\\\]"); - - } else if(c == '5') { - result.append("[^\\\\]"); - - } else if(c == '6') { - result.append("[^|:+\\/\\\\]"); - - } else if(c == '7') { - result.append("[^\\/]"); - - } else if(c == '8') { - result.append("[^-=+\\/\\\\]"); - } - } else result.append(String.valueOf(c)); - tokensHandled++; - } - return result.toString(); - } - - - public void setTo(String row1, String row2, String row3){ - if(getHeight() != 3) throw new RuntimeException("This method can only be called for GridPatternS with height 3"); - regExpsAreValid = false; - writeStringTo(0, 0, row1); - writeStringTo(0, 1, row2); - writeStringTo(0, 2, row3); - //don't use setRow() here! - } - - public static void main(String[] args) { - TextGrid grid = new TextGrid(3, 3); -// grid.setRow(0, " "); -// grid.setRow(1, "-\\ "); -// grid.setRow(2, " | "); -// -// if(GridPatternGroup.corner2Criteria.isAnyMatchedBy(grid)){ -// System.out.println("Grid is corner 2"); -// } else { -// System.out.println("Grid is not corner 2"); -// } -// -// if(grid.isCorner2(grid.new Cell(1,1))){ -// System.out.println("Grid is corner 2"); -// } else { -// System.out.println("Grid is not corner 2"); -// } -// -// -// grid.setRow(0, "-+ "); -// grid.setRow(1, " | "); -// grid.setRow(2, "-+ "); -// -// if(GridPatternGroup.cornerCriteria.isAnyMatchedBy(grid)){ -// System.out.println("Grid is corner"); -// } else { -// System.out.println("Grid is not corner"); -// } -// -// if(grid.isCorner(grid.new Cell(1,1))){ -// System.out.println("Grid is corner"); -// } else { -// System.out.println("Grid is not corner"); -// } - - grid.setRow(0, "---"); - grid.setRow(1, " / "); - grid.setRow(2, "---"); - grid.printDebug(); - if(GridPatternGroup.loneDiagonalCriteria.isAnyMatchedBy(grid)){ - System.out.println("Grid is lone diagonal"); - } else { - System.out.println("Grid is not lone diagonal"); - } - - grid.setRow(0, "--/"); - grid.setRow(1, " / "); - grid.setRow(2, "---"); - grid.printDebug(); - if(GridPatternGroup.loneDiagonalCriteria.isAnyMatchedBy(grid)){ - System.out.println("Grid is lone diagonal"); - } else { - System.out.println("Grid is not lone diagonal"); - } - - grid.setRow(0, "-- "); - grid.setRow(1, " \\ "); - grid.setRow(2, "---"); - grid.printDebug(); - if(GridPatternGroup.loneDiagonalCriteria.isAnyMatchedBy(grid)){ - System.out.println("Grid is lone diagonal"); - } else { - System.out.println("Grid is not lone diagonal"); - } - - grid.setRow(0, "-- "); - grid.setRow(1, " \\ "); - grid.setRow(2, "--\\"); - grid.printDebug(); - if(GridPatternGroup.loneDiagonalCriteria.isAnyMatchedBy(grid)){ - System.out.println("Grid is lone diagonal"); - } else { - System.out.println("Grid is not lone diagonal"); - } - - grid.setRow(0, " "); - grid.setRow(1, "-\\/"); - grid.setRow(2, " ||"); - grid.printDebug(); - if(GridPatternGroup.loneDiagonalCriteria.isAnyMatchedBy(grid)){ - System.out.println("Grid is lone diagonal"); - } else { - System.out.println("Grid is not lone diagonal"); - } - } + if (!regExpsAreValid) + prepareRegExps(); + + for (int i = 0; i < grid.getHeight(); i++) { + String row = grid.getRow(i).toString(); + Pattern regexp = regExps.get(i); + if (!regexp.matcher(row).matches()) { + if (DEBUG) + System.out.println(row + " does not match " + regexp); + return false; + } + } + return true; + } + + private void prepareRegExps() { + regExpsAreValid = true; + regExps.clear(); + if (DEBUG) + System.out.println("Trying to match:"); + if (!usesStandardSyntax) { + Iterator it = getRows().iterator(); + while (it.hasNext()) { + String row = it.next().toString(); + regExps.add(Pattern.compile(makeRegExp(row))); + if (DEBUG) + System.out.println(row + " becomes " + makeRegExp(row)); + } + } else { + Iterator it = getRows().iterator(); + while (it.hasNext()) { + String row = it.next().toString(); + regExps.add(Pattern.compile(row)); + } + } + } + + private String makeRegExp(String pattern) { + StringBuilder result = new StringBuilder(); + int tokensHandled = 0; + for (int i = 0; i < pattern.length() && tokensHandled < 3; i++) { + char c = pattern.charAt(i); + if (c == '[') { + result.append("[^|:]"); + } else if (c == '|') { + result.append("[|:]"); + } else if (c == '-') { + result.append("[-=]"); + } else if (c == '!') { + result.append("[^-=\\/\\\\+|:]"); + } else if (c == 'b') { + result.append("[-=\\/\\\\+|:]"); + } else if (c == '^') { + result.append("[\\/\\\\+|:]"); + } else if (c == '(') { + result.append("[-=\\/\\\\+]"); + } else if (c == '~') { + result.append("."); + } else if (c == '+') { + result.append("\\+"); + } else if (c == '\\') { + result.append("\\\\"); + } else if (c == 's') { + result.append("[-=+|:]"); + } else if (c == 'S') { + result.append("[\\/\\\\]"); + } else if (c == '*') { + result.append("\\*"); + + //entry points + } else if (c == '1') { + result.append("[\\\\]"); + + } else if (c == '2') { + result.append("[|:+\\/\\\\]"); + + } else if (c == '3') { + result.append("[\\/]"); + + } else if (c == '4') { + result.append("[-=+\\/\\\\]"); + + } else if (c == '5') { + result.append("[\\\\]"); + + } else if (c == '6') { + result.append("[|:+\\/\\\\]"); + + } else if (c == '7') { + result.append("[\\/]"); + + } else if (c == '8') { + result.append("[-=+\\/\\\\]"); + + //entry point negations + } else if (c == '%') { + if (i + 1 > pattern.length()) { + throw new RuntimeException("Invalid pattern, found % at the end"); + } + c = pattern.charAt(++i); + + if (c == '1') { + result.append("[^\\\\]"); + + } else if (c == '2') { + result.append("[^|:+\\/\\\\]"); + + } else if (c == '3') { + result.append("[^\\/]"); + + } else if (c == '4') { + result.append("[^-=+\\/\\\\]"); + + } else if (c == '5') { + result.append("[^\\\\]"); + + } else if (c == '6') { + result.append("[^|:+\\/\\\\]"); + + } else if (c == '7') { + result.append("[^\\/]"); + + } else if (c == '8') { + result.append("[^-=+\\/\\\\]"); + } + } else + result.append(String.valueOf(c)); + tokensHandled++; + } + return result.toString(); + } + + + public void setTo(String row1, String row2, String row3) { + if (getHeight() != 3) + throw new RuntimeException("This method can only be called for GridPatternS with height 3"); + regExpsAreValid = false; + writeStringTo(0, 0, row1); + writeStringTo(0, 1, row2); + writeStringTo(0, 2, row3); + //don't use setRow() here! + } + + public static void main(String[] args) { + TextGrid grid = new TextGrid(3, 3); + // grid.setRow(0, " "); + // grid.setRow(1, "-\\ "); + // grid.setRow(2, " | "); + // + // if(GridPatternGroup.corner2Criteria.isAnyMatchedBy(grid)){ + // System.out.println("Grid is corner 2"); + // } else { + // System.out.println("Grid is not corner 2"); + // } + // + // if(grid.isCorner2(grid.new Cell(1,1))){ + // System.out.println("Grid is corner 2"); + // } else { + // System.out.println("Grid is not corner 2"); + // } + // + // + // grid.setRow(0, "-+ "); + // grid.setRow(1, " | "); + // grid.setRow(2, "-+ "); + // + // if(GridPatternGroup.cornerCriteria.isAnyMatchedBy(grid)){ + // System.out.println("Grid is corner"); + // } else { + // System.out.println("Grid is not corner"); + // } + // + // if(grid.isCorner(grid.new Cell(1,1))){ + // System.out.println("Grid is corner"); + // } else { + // System.out.println("Grid is not corner"); + // } + + grid.setRow(0, "---"); + grid.setRow(1, " / "); + grid.setRow(2, "---"); + grid.printDebug(); + if (GridPatternGroup.loneDiagonalCriteria.isAnyMatchedBy(grid)) { + System.out.println("Grid is lone diagonal"); + } else { + System.out.println("Grid is not lone diagonal"); + } + + grid.setRow(0, "--/"); + grid.setRow(1, " / "); + grid.setRow(2, "---"); + grid.printDebug(); + if (GridPatternGroup.loneDiagonalCriteria.isAnyMatchedBy(grid)) { + System.out.println("Grid is lone diagonal"); + } else { + System.out.println("Grid is not lone diagonal"); + } + + grid.setRow(0, "-- "); + grid.setRow(1, " \\ "); + grid.setRow(2, "---"); + grid.printDebug(); + if (GridPatternGroup.loneDiagonalCriteria.isAnyMatchedBy(grid)) { + System.out.println("Grid is lone diagonal"); + } else { + System.out.println("Grid is not lone diagonal"); + } + + grid.setRow(0, "-- "); + grid.setRow(1, " \\ "); + grid.setRow(2, "--\\"); + grid.printDebug(); + if (GridPatternGroup.loneDiagonalCriteria.isAnyMatchedBy(grid)) { + System.out.println("Grid is lone diagonal"); + } else { + System.out.println("Grid is not lone diagonal"); + } + + grid.setRow(0, " "); + grid.setRow(1, "-\\/"); + grid.setRow(2, " ||"); + grid.printDebug(); + if (GridPatternGroup.loneDiagonalCriteria.isAnyMatchedBy(grid)) { + System.out.println("Grid is lone diagonal"); + } else { + System.out.println("Grid is not lone diagonal"); + } + } } diff --git a/src/java/org/stathissideris/ascii2image/text/GridPatternGroup.java b/src/java/org/stathissideris/ascii2image/text/GridPatternGroup.java index 0d8e184..111fff9 100644 --- a/src/java/org/stathissideris/ascii2image/text/GridPatternGroup.java +++ b/src/java/org/stathissideris/ascii2image/text/GridPatternGroup.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,7 +15,6 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.text; @@ -23,412 +22,406 @@ import java.util.Iterator; /** - * + * * @author Efstathios Sideris */ public class GridPatternGroup extends ArrayList { - public boolean areAllMatchedBy(TextGrid grid){ - Iterator it = iterator(); - while (it.hasNext()) { - GridPattern pattern = it.next(); - if(!pattern.isMatchedBy(grid)) return false; - } - return true; - } - - public boolean isAnyMatchedBy(TextGrid grid){ - Iterator it = iterator(); - while (it.hasNext()) { - GridPattern pattern = it.next(); - if(pattern.isMatchedBy(grid)) return true; - } - return false; - } - - - public void add(GridPattern... patterns) { - for(GridPattern p : patterns) add(p); - } - - //TODO: define criteria for on-line type? - - public static final GridPatternGroup cornerCriteria = new GridPatternGroup(); - public static final GridPatternGroup normalCornerCriteria = new GridPatternGroup(); - public static final GridPatternGroup roundCornerCriteria = new GridPatternGroup(); - - public static final GridPatternGroup corner1Criteria = new GridPatternGroup(); - public static final GridPatternGroup corner2Criteria = new GridPatternGroup(); - public static final GridPatternGroup corner3Criteria = new GridPatternGroup(); - public static final GridPatternGroup corner4Criteria = new GridPatternGroup(); - - - public static final GridPatternGroup normalCorner1Criteria = new GridPatternGroup(); - public static final GridPatternGroup normalCorner2Criteria = new GridPatternGroup(); - public static final GridPatternGroup normalCorner3Criteria = new GridPatternGroup(); - public static final GridPatternGroup normalCorner4Criteria = new GridPatternGroup(); - - public static final GridPatternGroup roundCorner1Criteria = new GridPatternGroup(); - public static final GridPatternGroup roundCorner2Criteria = new GridPatternGroup(); - public static final GridPatternGroup roundCorner3Criteria = new GridPatternGroup(); - public static final GridPatternGroup roundCorner4Criteria = new GridPatternGroup(); - - public static final GridPatternGroup intersectionCriteria = new GridPatternGroup(); - public static final GridPatternGroup TCriteria = new GridPatternGroup(); - public static final GridPatternGroup inverseTCriteria = new GridPatternGroup(); - public static final GridPatternGroup KCriteria = new GridPatternGroup(); - public static final GridPatternGroup inverseKCriteria = new GridPatternGroup(); - - public static final GridPatternGroup crossCriteria = new GridPatternGroup(); - - public static final GridPatternGroup stubCriteria = new GridPatternGroup(); - public static final GridPatternGroup verticalLinesEndCriteria = new GridPatternGroup(); - public static final GridPatternGroup horizontalLinesEndCriteria = new GridPatternGroup(); - public static final GridPatternGroup linesEndCriteria = new GridPatternGroup(); - - public static final GridPatternGroup crossOnLineCriteria = new GridPatternGroup(); - public static final GridPatternGroup horizontalCrossOnLineCriteria = new GridPatternGroup(); - public static final GridPatternGroup verticalCrossOnLineCriteria = new GridPatternGroup(); - - public static final GridPatternGroup starOnLineCriteria = new GridPatternGroup(); - public static final GridPatternGroup horizontalStarOnLineCriteria = new GridPatternGroup(); - public static final GridPatternGroup verticalStarOnLineCriteria = new GridPatternGroup(); - - public static final GridPatternGroup loneDiagonalCriteria = new GridPatternGroup(); - - static { - GridPattern crossPattern1 = new GridPattern( - ".6.", - "4+8", - ".2." - ); - crossCriteria.add(crossPattern1); - - GridPattern KPattern1 = new GridPattern( - ".6.", - "%4+8", - ".2." - ); - KCriteria.add(KPattern1); - - GridPattern inverseKPattern1 = new GridPattern( - ".6.", - "4+%8", - ".2." - ); - inverseKCriteria.add(inverseKPattern1); - - GridPattern TPattern1 = new GridPattern( - ".%6.", - "4+8", - ".2." - ); - TCriteria.add(TPattern1); - - GridPattern inverseTPattern1 = new GridPattern( - ".6.", - "4+8", - ".%2." - ); - inverseTCriteria.add(inverseTPattern1); - - - // ****** normal corners ******* - - GridPattern normalCorner1Pattern1 = new GridPattern( - ".[.", - "~+(", - ".^." - ); - normalCorner1Criteria.add(normalCorner1Pattern1); - - GridPattern normalCorner2Pattern1 = new GridPattern( - ".[.", - "(+~", - ".^." - ); - normalCorner2Criteria.add(normalCorner2Pattern1); - - GridPattern normalCorner3Pattern1 = new GridPattern( - ".^.", - "(+~", - ".[." - ); - normalCorner3Criteria.add(normalCorner3Pattern1); - - GridPattern normalCorner4Pattern1 = new GridPattern( - ".^.", - "~+(", - ".[." - ); - normalCorner4Criteria.add(normalCorner4Pattern1); - - // ******* round corners ******* - - GridPattern roundCorner1Pattern1 = new GridPattern( - ".[.", - "~/4", - ".2." - ); - roundCorner1Criteria.add(roundCorner1Pattern1); - - GridPattern roundCorner2Pattern1 = new GridPattern( - ".[.", - "4\\~", - ".2." - ); - roundCorner2Criteria.add(roundCorner2Pattern1); - - GridPattern roundCorner3Pattern1 = new GridPattern( - ".6.", - "4/~", - ".[." - ); - roundCorner3Criteria.add(roundCorner3Pattern1); - - GridPattern roundCorner4Pattern1 = new GridPattern( - ".6.", - "~\\8", - ".[." - ); - roundCorner4Criteria.add(roundCorner4Pattern1); - - //stubs - - GridPattern stubPattern1 = new GridPattern( - "!^!", - "!+!", - ".!." - ); - stubCriteria.add(stubPattern1); - - GridPattern stubPattern2 = new GridPattern( - "!^!", - "!+!", - ".-." - ); - stubCriteria.add(stubPattern2); - - GridPattern stubPattern3 = new GridPattern( - "!!.", - "(+!", - "!!." - ); - stubCriteria.add(stubPattern3); - - GridPattern stubPattern4 = new GridPattern( - "!!.", - "(+|", - "!!." - ); - stubCriteria.add(stubPattern4); - - GridPattern stubPattern5 = new GridPattern( - ".!.", - "!+!", - "!^!" - ); - stubCriteria.add(stubPattern5); - - GridPattern stubPattern6 = new GridPattern( - ".-.", - "!+!", - "!^!" - ); - stubCriteria.add(stubPattern6); - - GridPattern stubPattern7 = new GridPattern( - ".!!", - "!+(", - ".!!" - ); - stubCriteria.add(stubPattern7); - - GridPattern stubPattern8 = new GridPattern( - ".!!", - "|+(", - ".!!" - ); - stubCriteria.add(stubPattern8); - - - // ****** ends of lines ****** - GridPattern verticalLinesEndPattern1 = new GridPattern( - ".^.", - ".|.", - ".!." - ); - verticalLinesEndCriteria.add(verticalLinesEndPattern1); - - GridPattern verticalLinesEndPattern2 = new GridPattern( - ".^.", - ".|.", - ".-." - ); - verticalLinesEndCriteria.add(verticalLinesEndPattern2); - - GridPattern horizontalLinesEndPattern3 = new GridPattern( - "...", - "(-!", - "..." - ); - horizontalLinesEndCriteria.add(horizontalLinesEndPattern3); - - GridPattern horizontalLinesEndPattern4 = new GridPattern( - "...", - "(-|", - "..." - ); - horizontalLinesEndCriteria.add(horizontalLinesEndPattern4); - - GridPattern verticalLinesEndPattern5 = new GridPattern( - ".!.", - ".|.", - ".^." - ); - verticalLinesEndCriteria.add(verticalLinesEndPattern5); - - GridPattern verticalLinesEndPattern6 = new GridPattern( - ".-.", - ".|.", - ".^." - ); - verticalLinesEndCriteria.add(verticalLinesEndPattern6); - - GridPattern horizontalLinesEndPattern7 = new GridPattern( - "...", - "!-(", - "..." - ); - horizontalLinesEndCriteria.add(horizontalLinesEndPattern7); - - GridPattern horizontalLinesEndPattern8 = new GridPattern( - "...", - "|-(", - "..." - ); - horizontalLinesEndCriteria.add(horizontalLinesEndPattern8); - - - - // ****** others ******* - - GridPattern horizontalCrossOnLinePattern1 = new GridPattern( - "...", - "(+(", - "..." - ); - horizontalCrossOnLineCriteria.add(horizontalCrossOnLinePattern1); - - GridPattern verticalCrossOnLinePattern1 = new GridPattern( - ".^.", - ".+.", - ".^." - ); - verticalCrossOnLineCriteria.add(verticalCrossOnLinePattern1); - - - GridPattern horizontalStarOnLinePattern1 = new GridPattern( - "...", - "(*(", - "..." - ); - horizontalStarOnLineCriteria.add(horizontalStarOnLinePattern1); - - GridPattern horizontalStarOnLinePattern2 = new GridPattern( - "...", - "!*(", - "..." - ); - horizontalStarOnLineCriteria.add(horizontalStarOnLinePattern2); - - GridPattern horizontalStarOnLinePattern3 = new GridPattern( - "...", - "(*!", - "..." - ); - horizontalStarOnLineCriteria.add(horizontalStarOnLinePattern3); - - - GridPattern verticalStarOnLinePattern1 = new GridPattern( - ".^.", - ".*.", - ".^." - ); - verticalStarOnLineCriteria.add(verticalStarOnLinePattern1); - - GridPattern verticalStarOnLinePattern2 = new GridPattern( - ".!.", - ".*.", - ".^." - ); - verticalStarOnLineCriteria.add(verticalStarOnLinePattern2); - - GridPattern verticalStarOnLinePattern3 = new GridPattern( - ".^.", - ".*.", - ".!." - ); - verticalStarOnLineCriteria.add(verticalStarOnLinePattern3); - - - GridPattern loneDiagonalPattern1 = new GridPattern( - ".%6%7", - "%4/%8", - "%3%2." - ); - loneDiagonalCriteria.add(loneDiagonalPattern1); - - GridPattern loneDiagonalPattern2 = new GridPattern( - "%1%6.", - "%4\\%8", - ".%2%5" - ); - loneDiagonalCriteria.add(loneDiagonalPattern2); - - - //groups - - intersectionCriteria.addAll(crossCriteria); - intersectionCriteria.addAll(KCriteria); - intersectionCriteria.addAll(TCriteria); - intersectionCriteria.addAll(inverseKCriteria); - intersectionCriteria.addAll(inverseTCriteria); - - normalCornerCriteria.addAll(normalCorner1Criteria); - normalCornerCriteria.addAll(normalCorner2Criteria); - normalCornerCriteria.addAll(normalCorner3Criteria); - normalCornerCriteria.addAll(normalCorner4Criteria); - - roundCornerCriteria.addAll(roundCorner1Criteria); - roundCornerCriteria.addAll(roundCorner2Criteria); - roundCornerCriteria.addAll(roundCorner3Criteria); - roundCornerCriteria.addAll(roundCorner4Criteria); - - corner1Criteria.addAll(normalCorner1Criteria); - corner1Criteria.addAll(roundCorner1Criteria); - - corner2Criteria.addAll(normalCorner2Criteria); - corner2Criteria.addAll(roundCorner2Criteria); - - corner3Criteria.addAll(normalCorner3Criteria); - corner3Criteria.addAll(roundCorner3Criteria); - - corner4Criteria.addAll(normalCorner4Criteria); - corner4Criteria.addAll(roundCorner4Criteria); - - cornerCriteria.addAll(normalCornerCriteria); - cornerCriteria.addAll(roundCornerCriteria); - - crossOnLineCriteria.addAll(horizontalCrossOnLineCriteria); - crossOnLineCriteria.addAll(verticalCrossOnLineCriteria); - - starOnLineCriteria.addAll(horizontalStarOnLineCriteria); - starOnLineCriteria.addAll(verticalStarOnLineCriteria); - - - linesEndCriteria.addAll(horizontalLinesEndCriteria); - linesEndCriteria.addAll(verticalLinesEndCriteria); - linesEndCriteria.addAll(stubCriteria); - - } + public boolean areAllMatchedBy(TextGrid grid) { + Iterator it = iterator(); + while (it.hasNext()) { + GridPattern pattern = it.next(); + if (!pattern.isMatchedBy(grid)) + return false; + } + return true; + } + + public boolean isAnyMatchedBy(TextGrid grid) { + Iterator it = iterator(); + while (it.hasNext()) { + GridPattern pattern = it.next(); + if (pattern.isMatchedBy(grid)) + return true; + } + return false; + } + + + public void add(GridPattern... patterns) { + for (GridPattern p : patterns) + add(p); + } + + //TODO: define criteria for on-line type? + + public static final GridPatternGroup cornerCriteria = new GridPatternGroup(); + public static final GridPatternGroup normalCornerCriteria = new GridPatternGroup(); + public static final GridPatternGroup roundCornerCriteria = new GridPatternGroup(); + + public static final GridPatternGroup corner1Criteria = new GridPatternGroup(); + public static final GridPatternGroup corner2Criteria = new GridPatternGroup(); + public static final GridPatternGroup corner3Criteria = new GridPatternGroup(); + public static final GridPatternGroup corner4Criteria = new GridPatternGroup(); + + + public static final GridPatternGroup normalCorner1Criteria = new GridPatternGroup(); + public static final GridPatternGroup normalCorner2Criteria = new GridPatternGroup(); + public static final GridPatternGroup normalCorner3Criteria = new GridPatternGroup(); + public static final GridPatternGroup normalCorner4Criteria = new GridPatternGroup(); + + public static final GridPatternGroup roundCorner1Criteria = new GridPatternGroup(); + public static final GridPatternGroup roundCorner2Criteria = new GridPatternGroup(); + public static final GridPatternGroup roundCorner3Criteria = new GridPatternGroup(); + public static final GridPatternGroup roundCorner4Criteria = new GridPatternGroup(); + + public static final GridPatternGroup intersectionCriteria = new GridPatternGroup(); + public static final GridPatternGroup TCriteria = new GridPatternGroup(); + public static final GridPatternGroup inverseTCriteria = new GridPatternGroup(); + public static final GridPatternGroup KCriteria = new GridPatternGroup(); + public static final GridPatternGroup inverseKCriteria = new GridPatternGroup(); + + public static final GridPatternGroup crossCriteria = new GridPatternGroup(); + + public static final GridPatternGroup stubCriteria = new GridPatternGroup(); + public static final GridPatternGroup verticalLinesEndCriteria = new GridPatternGroup(); + public static final GridPatternGroup horizontalLinesEndCriteria = new GridPatternGroup(); + public static final GridPatternGroup linesEndCriteria = new GridPatternGroup(); + + public static final GridPatternGroup crossOnLineCriteria = new GridPatternGroup(); + public static final GridPatternGroup horizontalCrossOnLineCriteria = new GridPatternGroup(); + public static final GridPatternGroup verticalCrossOnLineCriteria = new GridPatternGroup(); + + public static final GridPatternGroup starOnLineCriteria = new GridPatternGroup(); + public static final GridPatternGroup horizontalStarOnLineCriteria = new GridPatternGroup(); + public static final GridPatternGroup verticalStarOnLineCriteria = new GridPatternGroup(); + + public static final GridPatternGroup loneDiagonalCriteria = new GridPatternGroup(); + + static { + GridPattern crossPattern1 = new GridPattern( + ".6.", + "4+8", + ".2." + ); + crossCriteria.add(crossPattern1); + + GridPattern KPattern1 = new GridPattern( + ".6.", + "%4+8", + ".2." + ); + KCriteria.add(KPattern1); + + GridPattern inverseKPattern1 = new GridPattern( + ".6.", + "4+%8", + ".2." + ); + inverseKCriteria.add(inverseKPattern1); + + GridPattern TPattern1 = new GridPattern( + ".%6.", + "4+8", + ".2." + ); + TCriteria.add(TPattern1); + + GridPattern inverseTPattern1 = new GridPattern( + ".6.", + "4+8", + ".%2." + ); + inverseTCriteria.add(inverseTPattern1); + + // ****** normal corners ******* + + GridPattern normalCorner1Pattern1 = new GridPattern( + ".[.", + "~+(", + ".^." + ); + normalCorner1Criteria.add(normalCorner1Pattern1); + + GridPattern normalCorner2Pattern1 = new GridPattern( + ".[.", + "(+~", + ".^." + ); + normalCorner2Criteria.add(normalCorner2Pattern1); + + GridPattern normalCorner3Pattern1 = new GridPattern( + ".^.", + "(+~", + ".[." + ); + normalCorner3Criteria.add(normalCorner3Pattern1); + + GridPattern normalCorner4Pattern1 = new GridPattern( + ".^.", + "~+(", + ".[." + ); + normalCorner4Criteria.add(normalCorner4Pattern1); + + // ******* round corners ******* + + GridPattern roundCorner1Pattern1 = new GridPattern( + ".[.", + "~/4", + ".2." + ); + roundCorner1Criteria.add(roundCorner1Pattern1); + + GridPattern roundCorner2Pattern1 = new GridPattern( + ".[.", + "4\\~", + ".2." + ); + roundCorner2Criteria.add(roundCorner2Pattern1); + + GridPattern roundCorner3Pattern1 = new GridPattern( + ".6.", + "4/~", + ".[." + ); + roundCorner3Criteria.add(roundCorner3Pattern1); + + GridPattern roundCorner4Pattern1 = new GridPattern( + ".6.", + "~\\8", + ".[." + ); + roundCorner4Criteria.add(roundCorner4Pattern1); + + //stubs + + GridPattern stubPattern1 = new GridPattern( + "!^!", + "!+!", + ".!." + ); + stubCriteria.add(stubPattern1); + + GridPattern stubPattern2 = new GridPattern( + "!^!", + "!+!", + ".-." + ); + stubCriteria.add(stubPattern2); + + GridPattern stubPattern3 = new GridPattern( + "!!.", + "(+!", + "!!." + ); + stubCriteria.add(stubPattern3); + + GridPattern stubPattern4 = new GridPattern( + "!!.", + "(+|", + "!!." + ); + stubCriteria.add(stubPattern4); + + GridPattern stubPattern5 = new GridPattern( + ".!.", + "!+!", + "!^!" + ); + stubCriteria.add(stubPattern5); + + GridPattern stubPattern6 = new GridPattern( + ".-.", + "!+!", + "!^!" + ); + stubCriteria.add(stubPattern6); + + GridPattern stubPattern7 = new GridPattern( + ".!!", + "!+(", + ".!!" + ); + stubCriteria.add(stubPattern7); + + GridPattern stubPattern8 = new GridPattern( + ".!!", + "|+(", + ".!!" + ); + stubCriteria.add(stubPattern8); + + // ****** ends of lines ****** + GridPattern verticalLinesEndPattern1 = new GridPattern( + ".^.", + ".|.", + ".!." + ); + verticalLinesEndCriteria.add(verticalLinesEndPattern1); + + GridPattern verticalLinesEndPattern2 = new GridPattern( + ".^.", + ".|.", + ".-." + ); + verticalLinesEndCriteria.add(verticalLinesEndPattern2); + + GridPattern horizontalLinesEndPattern3 = new GridPattern( + "...", + "(-!", + "..." + ); + horizontalLinesEndCriteria.add(horizontalLinesEndPattern3); + + GridPattern horizontalLinesEndPattern4 = new GridPattern( + "...", + "(-|", + "..." + ); + horizontalLinesEndCriteria.add(horizontalLinesEndPattern4); + + GridPattern verticalLinesEndPattern5 = new GridPattern( + ".!.", + ".|.", + ".^." + ); + verticalLinesEndCriteria.add(verticalLinesEndPattern5); + + GridPattern verticalLinesEndPattern6 = new GridPattern( + ".-.", + ".|.", + ".^." + ); + verticalLinesEndCriteria.add(verticalLinesEndPattern6); + + GridPattern horizontalLinesEndPattern7 = new GridPattern( + "...", + "!-(", + "..." + ); + horizontalLinesEndCriteria.add(horizontalLinesEndPattern7); + + GridPattern horizontalLinesEndPattern8 = new GridPattern( + "...", + "|-(", + "..." + ); + horizontalLinesEndCriteria.add(horizontalLinesEndPattern8); + + // ****** others ******* + + GridPattern horizontalCrossOnLinePattern1 = new GridPattern( + "...", + "(+(", + "..." + ); + horizontalCrossOnLineCriteria.add(horizontalCrossOnLinePattern1); + + GridPattern verticalCrossOnLinePattern1 = new GridPattern( + ".^.", + ".+.", + ".^." + ); + verticalCrossOnLineCriteria.add(verticalCrossOnLinePattern1); + + GridPattern horizontalStarOnLinePattern1 = new GridPattern( + "...", + "(*(", + "..." + ); + horizontalStarOnLineCriteria.add(horizontalStarOnLinePattern1); + + GridPattern horizontalStarOnLinePattern2 = new GridPattern( + "...", + "!*(", + "..." + ); + horizontalStarOnLineCriteria.add(horizontalStarOnLinePattern2); + + GridPattern horizontalStarOnLinePattern3 = new GridPattern( + "...", + "(*!", + "..." + ); + horizontalStarOnLineCriteria.add(horizontalStarOnLinePattern3); + + GridPattern verticalStarOnLinePattern1 = new GridPattern( + ".^.", + ".*.", + ".^." + ); + verticalStarOnLineCriteria.add(verticalStarOnLinePattern1); + + GridPattern verticalStarOnLinePattern2 = new GridPattern( + ".!.", + ".*.", + ".^." + ); + verticalStarOnLineCriteria.add(verticalStarOnLinePattern2); + + GridPattern verticalStarOnLinePattern3 = new GridPattern( + ".^.", + ".*.", + ".!." + ); + verticalStarOnLineCriteria.add(verticalStarOnLinePattern3); + + GridPattern loneDiagonalPattern1 = new GridPattern( + ".%6%7", + "%4/%8", + "%3%2." + ); + loneDiagonalCriteria.add(loneDiagonalPattern1); + + GridPattern loneDiagonalPattern2 = new GridPattern( + "%1%6.", + "%4\\%8", + ".%2%5" + ); + loneDiagonalCriteria.add(loneDiagonalPattern2); + + //groups + + intersectionCriteria.addAll(crossCriteria); + intersectionCriteria.addAll(KCriteria); + intersectionCriteria.addAll(TCriteria); + intersectionCriteria.addAll(inverseKCriteria); + intersectionCriteria.addAll(inverseTCriteria); + + normalCornerCriteria.addAll(normalCorner1Criteria); + normalCornerCriteria.addAll(normalCorner2Criteria); + normalCornerCriteria.addAll(normalCorner3Criteria); + normalCornerCriteria.addAll(normalCorner4Criteria); + + roundCornerCriteria.addAll(roundCorner1Criteria); + roundCornerCriteria.addAll(roundCorner2Criteria); + roundCornerCriteria.addAll(roundCorner3Criteria); + roundCornerCriteria.addAll(roundCorner4Criteria); + + corner1Criteria.addAll(normalCorner1Criteria); + corner1Criteria.addAll(roundCorner1Criteria); + + corner2Criteria.addAll(normalCorner2Criteria); + corner2Criteria.addAll(roundCorner2Criteria); + + corner3Criteria.addAll(normalCorner3Criteria); + corner3Criteria.addAll(roundCorner3Criteria); + + corner4Criteria.addAll(normalCorner4Criteria); + corner4Criteria.addAll(roundCorner4Criteria); + + cornerCriteria.addAll(normalCornerCriteria); + cornerCriteria.addAll(roundCornerCriteria); + + crossOnLineCriteria.addAll(horizontalCrossOnLineCriteria); + crossOnLineCriteria.addAll(verticalCrossOnLineCriteria); + + starOnLineCriteria.addAll(horizontalStarOnLineCriteria); + starOnLineCriteria.addAll(verticalStarOnLineCriteria); + + linesEndCriteria.addAll(horizontalLinesEndCriteria); + linesEndCriteria.addAll(verticalLinesEndCriteria); + linesEndCriteria.addAll(stubCriteria); + + } } diff --git a/src/java/org/stathissideris/ascii2image/text/StringUtils.java b/src/java/org/stathissideris/ascii2image/text/StringUtils.java index cd5ff29..7f37c70 100644 --- a/src/java/org/stathissideris/ascii2image/text/StringUtils.java +++ b/src/java/org/stathissideris/ascii2image/text/StringUtils.java @@ -31,25 +31,26 @@ */ public class StringUtils { - /** - * The indexOf idiom - * - * @param big - * @param fragment - * @return - */ - public static boolean contains(String big, String fragment) { - return (big.indexOf(fragment) != -1); - } - - public static String repeatString(String string, int repeats) { - if (repeats == 0) return ""; - String buffer = ""; - for (int i = 0; i < repeats; i++) { - buffer += string; - } - return buffer; + /** + * The indexOf idiom + * + * @param big + * @param fragment + * @return + */ + public static boolean contains(String big, String fragment) { + return (big.indexOf(fragment) != -1); + } + + public static String repeatString(String string, int repeats) { + if (repeats == 0) + return ""; + String buffer = ""; + for (int i = 0; i < repeats; i++) { + buffer += string; } + return buffer; + } /*public static String repeatString(String string, int repeats){ if(repeats == 0) return ""; @@ -60,137 +61,143 @@ public static String repeatString(String string, int repeats) { return buffer.toString(); }*/ - public static boolean isBlank(String s) { - return (s.length() == 0 || s.matches("^\\s*$")); - } - - /** - * - * Converts the first character of string into a capital letter - * - * @param string - * @return - */ - public static String firstToUpper(String string) { - return string.substring(0, 1).toUpperCase() + string.substring(1); - } - - public static String insertSpaceAtCaps(String string) { + public static boolean isBlank(String s) { + return (s.length() == 0 || s.matches("^\\s*$")); + } - int uppers = 0; + /** + * Converts the first character of string into a capital letter + * + * @param string + * @return + */ + public static String firstToUpper(String string) { + return string.substring(0, 1).toUpperCase() + string.substring(1); + } - //first we count - for (int i = 0; i < string.length(); i++) { - if (Character.isUpperCase(string.charAt(i))) uppers++; - } - - int[] indexes = null; + public static String insertSpaceAtCaps(String string) { - if (Character.isUpperCase(string.charAt(0))) { - indexes = new int[uppers]; - } else { - indexes = new int[++uppers]; - } - indexes[0] = 0; - int k = 1; + int uppers = 0; - //then we find the indexes (we have checked the first char already) - for (int j = 1; j < string.length(); j++) { - if (Character.isUpperCase(string.charAt(j))) indexes[k++] = j; - } - - StringBuffer buffer = new StringBuffer(""); - //and finally we breakup the String - for (int i = 0; i < indexes.length; i++) { - if (i + 1 < indexes.length) { - buffer.append(string.substring(indexes[i], indexes[i + 1])); - buffer.append(" "); - } else { - buffer.append(string.substring(indexes[i])); - } - } - return buffer.toString(); + //first we count + for (int i = 0; i < string.length(); i++) { + if (Character.isUpperCase(string.charAt(i))) + uppers++; } + int[] indexes = null; - public static boolean isOneOf(char c, char[] group) { - for (int i = 0; i < group.length; i++) - if (c == group[i]) return true; - return false; + if (Character.isUpperCase(string.charAt(0))) { + indexes = new int[uppers]; + } else { + indexes = new int[++uppers]; } + indexes[0] = 0; + int k = 1; - public static boolean isOneOf(String str, String[] group) { - for (int i = 0; i < group.length; i++) - if (str.equals(group[i])) return true; - return false; + //then we find the indexes (we have checked the first char already) + for (int j = 1; j < string.length(); j++) { + if (Character.isUpperCase(string.charAt(j))) + indexes[k++] = j; } - public static String getPath(String fullPath) { - if (fullPath.lastIndexOf("\\") != -1) - return fullPath.substring(0, fullPath.lastIndexOf("\\")); - else return ""; - } - - public static String getBaseFilename(String fullPath) { - if (fullPath.lastIndexOf(".") != -1 && fullPath.lastIndexOf("\\") != -1) - return fullPath.substring(fullPath.lastIndexOf("\\") + 1, fullPath.lastIndexOf(".")); - else return fullPath; - } - - public static String getExtension(String fullPath) { - if (fullPath.lastIndexOf(".") != -1) - return fullPath.substring(fullPath.lastIndexOf(".") + 1); - else return ""; - } - - - public static void main(String[] args) { - System.out.println("1 " + StringUtils.firstToUpper("testing")); - System.out.println("2 " + StringUtils.firstToUpper(" testing")); - System.out.println("3 " + StringUtils.firstToUpper("_testing")); - System.out.println("4 " + StringUtils.firstToUpper("Testing")); - System.out.println("5 " + StringUtils.firstToUpper("ttesting")); - String path = "C:\\Files\\test.txt"; - System.out.println(path); - System.out.println(StringUtils.getPath(path)); - System.out.println(StringUtils.getBaseFilename(path)); - System.out.println(StringUtils.getExtension(path)); - - path = "test.txt"; - System.out.println(path); - System.out.println(StringUtils.getPath(path)); - System.out.println(StringUtils.getBaseFilename(path)); - System.out.println(StringUtils.getExtension(path)); - - path = "test"; - System.out.println(path); - System.out.println("path: " + StringUtils.getPath(path)); - System.out.println("base: " + StringUtils.getBaseFilename(path)); - System.out.println(" ext: " + StringUtils.getExtension(path)); - - - } - - public static Iterator createTextSplitter(Pattern pattern, CharSequence s) { - return new Iterator() { - Pattern regex = pattern; - CharSequence rest = s; - - @Override - public boolean hasNext() { - return rest.length() > 0; - } - - @Override - public String next() { - Matcher m = regex.matcher(rest); - if (m.find()) { - String ret = m.group(1); - rest = rest.subSequence(ret.length(), rest.length()); - return ret; - } - throw new NoSuchElementException(); - } - }; + StringBuffer buffer = new StringBuffer(""); + //and finally we breakup the String + for (int i = 0; i < indexes.length; i++) { + if (i + 1 < indexes.length) { + buffer.append(string.substring(indexes[i], indexes[i + 1])); + buffer.append(" "); + } else { + buffer.append(string.substring(indexes[i])); + } } + return buffer.toString(); + } + + + public static boolean isOneOf(char c, char[] group) { + for (int i = 0; i < group.length; i++) + if (c == group[i]) + return true; + return false; + } + + public static boolean isOneOf(String str, String[] group) { + for (int i = 0; i < group.length; i++) + if (str.equals(group[i])) + return true; + return false; + } + + public static String getPath(String fullPath) { + if (fullPath.lastIndexOf("\\") != -1) + return fullPath.substring(0, fullPath.lastIndexOf("\\")); + else + return ""; + } + + public static String getBaseFilename(String fullPath) { + if (fullPath.lastIndexOf(".") != -1 && fullPath.lastIndexOf("\\") != -1) + return fullPath.substring(fullPath.lastIndexOf("\\") + 1, fullPath.lastIndexOf(".")); + else + return fullPath; + } + + public static String getExtension(String fullPath) { + if (fullPath.lastIndexOf(".") != -1) + return fullPath.substring(fullPath.lastIndexOf(".") + 1); + else + return ""; + } + + + public static void main(String[] args) { + System.out.println("1 " + StringUtils.firstToUpper("testing")); + System.out.println("2 " + StringUtils.firstToUpper(" testing")); + System.out.println("3 " + StringUtils.firstToUpper("_testing")); + System.out.println("4 " + StringUtils.firstToUpper("Testing")); + System.out.println("5 " + StringUtils.firstToUpper("ttesting")); + String path = "C:\\Files\\test.txt"; + System.out.println(path); + System.out.println(StringUtils.getPath(path)); + System.out.println(StringUtils.getBaseFilename(path)); + System.out.println(StringUtils.getExtension(path)); + + path = "test.txt"; + System.out.println(path); + System.out.println(StringUtils.getPath(path)); + System.out.println(StringUtils.getBaseFilename(path)); + System.out.println(StringUtils.getExtension(path)); + + path = "test"; + System.out.println(path); + System.out.println("path: " + StringUtils.getPath(path)); + System.out.println("base: " + StringUtils.getBaseFilename(path)); + System.out.println(" ext: " + StringUtils.getExtension(path)); + + + } + + public static Iterator createTextSplitter(Pattern pattern, CharSequence s) { + return new Iterator() { + Pattern regex = pattern; + CharSequence rest = s; + + @Override + public boolean hasNext() { + return rest.length() > 0; + } + + @Override + public String next() { + Matcher m = regex.matcher(rest); + if (m.find()) { + String ret = m.group(1); + rest = rest.subSequence(ret.length(), rest.length()); + return ret; + } + throw new NoSuchElementException(); + } + }; + } } diff --git a/src/java/org/stathissideris/ascii2image/text/TextGrid.java b/src/java/org/stathissideris/ascii2image/text/TextGrid.java index 082057c..477d5d8 100644 --- a/src/java/org/stathissideris/ascii2image/text/TextGrid.java +++ b/src/java/org/stathissideris/ascii2image/text/TextGrid.java @@ -15,17 +15,25 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.text; import org.stathissideris.ascii2image.core.FileUtils; import org.stathissideris.ascii2image.core.ProcessingOptions; -import java.awt.*; -import java.io.*; -import java.util.*; +import java.awt.Color; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Stack; import java.util.function.IntUnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,1842 +48,2043 @@ */ public class TextGrid { - private static final boolean DEBUG = false; - private static final char PLAIN_MODE = 'P'; - public static final char LATEX_MODE = 'L'; - - private ArrayList rows; - private List modeRows; - - private static char[] boundaries = {'/', '\\', '|', '-', '*', '=', ':'}; - private static char[] undisputableBoundaries = {'|', '-', '*', '=', ':'}; - private static char[] horizontalLines = {'-', '='}; - private static char[] verticalLines = {'|', ':'}; - private static char[] arrowHeads = {'<', '>', '^', 'v', 'V'}; - private static char[] cornerChars = {'\\', '/', '+'}; - private static char[] pointMarkers = {'*'}; - private static char[] dashedLines = {':', '~', '='}; - - private static char[] entryPoints1 = {'\\'}; - private static char[] entryPoints2 = {'|', ':', '+', '\\', '/'}; - private static char[] entryPoints3 = {'/'}; - private static char[] entryPoints4 = {'-', '=', '+', '\\', '/'}; - private static char[] entryPoints5 = {'\\'}; - private static char[] entryPoints6 = {'|', ':', '+', '\\', '/'}; - private static char[] entryPoints7 = {'/'}; - private static char[] entryPoints8 = {'-', '=', '+', '\\', '/'}; - - - - private static HashMap humanColorCodes = new HashMap(); - static { - humanColorCodes.put("GRE", "9D9"); - humanColorCodes.put("BLU", "55B"); - humanColorCodes.put("PNK", "FAA"); - humanColorCodes.put("RED", "E32"); - humanColorCodes.put("YEL", "FF3"); - humanColorCodes.put("BLK", "000"); - - } - - private static HashSet markupTags = - new HashSet(); - - static { - markupTags.add("d"); - markupTags.add("s"); - markupTags.add("io"); - markupTags.add("c"); - markupTags.add("mo"); - markupTags.add("tr"); - markupTags.add("o"); - } - - private void updateModeRows(){ - // Collectors.toList creates ArrayList and you see no performance penalty - // caused by using LinkedList here. - this.modeRows = this.rows.stream().map(TextGrid::transformRowToModeRow).collect(toList()); - } - - public static String transformRowToModeRow(CharSequence row){ - StringBuilder b = new StringBuilder(); - row.chars().map(new IntUnaryOperator(){ - /** - * This field hold current mode of a cell in the grid. - * Only 2 values can be assigned, which are 'P' and 'L'. - * They respectively represent 'Plain', which is normal ditaa mode, and 'LaTeX', which is LaTeX - * formula mode. - */ - int cur = PLAIN_MODE; - - @Override - public int applyAsInt(int operand){ - if(cur == PLAIN_MODE){ - if(operand == '$'){ - cur = LATEX_MODE; - return LATEX_MODE; - } - return PLAIN_MODE; - } else if(cur == LATEX_MODE){ - if(operand == '$'){ - cur = PLAIN_MODE; - return LATEX_MODE; - } - return this.cur; - } - throw new IllegalStateException(); - } - }).forEach(c->b.append((char) c)); - String ret = b.toString(); - if(ret.endsWith("L")) - if(row.charAt(row.length() - 1) != '$') - throw new IllegalArgumentException("LaTex mode was started but not finished."); - return ret; - } - - public void addToMarkupTags(Collection tags){ - markupTags.addAll(tags); - } - - public static void main(String[] args) throws Exception { - TextGrid grid = new TextGrid(); - grid.loadFrom("tests/text/art10.txt"); - - grid.writeStringTo(grid.new Cell(28, 1), "testing"); - - grid.findMarkupTags(); - - grid.printDebug(); - //System.out.println(grid.fillContinuousArea(0, 0, '-').size()+" cells filled"); - //grid.fillContinuousArea(4, 4, '-'); - //grid.getSubGrid(1,1,3,3).printDebug(); - //grid.printDebug(); - } - - - public TextGrid(){ - rows = new ArrayList(); - this.updateModeRows(); - } - - public TextGrid(int width, int height){ - String space = StringUtils.repeatString(" ", width); - rows = new ArrayList(); - for(int i = 0; i < height; i++) - rows.add(new StringBuilder(space)); - this.updateModeRows(); - } - - public static TextGrid makeSameSizeAs(TextGrid grid){ - return new TextGrid(grid.getWidth(), grid.getHeight()); - } - - - public TextGrid(TextGrid otherGrid){ - rows = new ArrayList(); - for(StringBuilder row : otherGrid.getRows()) { - rows.add(new StringBuilder(row)); - } - this.updateModeRows(); - } - - public void clear(){ - String blank = StringUtils.repeatString(" ", getWidth()); - int height = getHeight(); - rows.clear(); - for(int i = 0; i < height; i++) - rows.add(new StringBuilder(blank)); - } - -// duplicated code due to lots of hits to this function - public char get(int x, int y){ - if(x > getWidth() - 1 - || y > getHeight() - 1 - || x < 0 - || y < 0) return 0; - return rows.get(y).charAt(x); - } - - //duplicated code due to lots of hits to this function - public char get(Cell cell){ - if(cell.x > getWidth() - 1 - || cell.y > getHeight() - 1 - || cell.x < 0 - || cell.y < 0) return 0; - return rows.get(cell.y).charAt(cell.x); - } - - public StringBuilder getRow(int y){ - return rows.get(y); - } - - public TextGrid getSubGrid(int x, int y, int width, int height){ - TextGrid grid = new TextGrid(width, height); - for(int i = 0; i < height; i++){ - grid.setRow(i, new StringBuilder(getRow(y + i).subSequence(x, x + width))); - } - return grid; - } - - public TextGrid getTestingSubGrid(Cell cell){ - return getSubGrid(cell.x - 1, cell.y - 1, 3, 3); - } - - - public String getStringAt(int x, int y, int length){ - return getStringAt(new Cell(x, y), length); - } - - public String getStringAt(Cell cell, int length){ - int x = cell.x; - int y = cell.y; - if(x > getWidth() - 1 - || y > getHeight() - 1 - || x < 0 - || y < 0) return null; - return rows.get(y).substring(x, x + length); - } - - public char getNorthOf(int x, int y){ return get(x, y - 1); } - public char getSouthOf(int x, int y){ return get(x, y + 1); } - public char getEastOf(int x, int y){ return get(x + 1, y); } - public char getWestOf(int x, int y){ return get(x - 1, y); } - - public char getNorthOf(Cell cell){ return getNorthOf(cell.x, cell.y); } - public char getSouthOf(Cell cell){ return getSouthOf(cell.x, cell.y); } - public char getEastOf(Cell cell){ return getEastOf(cell.x, cell.y); } - public char getWestOf(Cell cell){ return getWestOf(cell.x, cell.y); } - - public void writeStringTo(int x, int y, String str){ - writeStringTo(new Cell(x, y), str); - } - - public void writeStringTo(Cell cell, String str){ - if(isOutOfBounds(cell)) return; - rows.get(cell.y).replace(cell.x, cell.x + str.length(), str); - } - - public void set(Cell cell, char c){ - set(cell.x, cell.y, c); - } - - public void set(int x, int y, char c){ - if(x > getWidth() - 1 || y > getHeight() - 1) return; - StringBuilder row = rows.get(y); - row.setCharAt(x, c); - } - - public void setRow(int y, String row){ - if(y > getHeight() || row.length() != getWidth()) - throw new IllegalArgumentException("setRow out of bounds or string wrong size"); - rows.set(y, new StringBuilder(row)); - } - - public void setRow(int y, StringBuilder row){ - if(y > getHeight() || row.length() != getWidth()) - throw new IllegalArgumentException("setRow out of bounds or string wrong size"); - rows.set(y, row); - } - - public int getWidth(){ - if(rows.size() == 0) return 0; //empty buffer - return rows.get(0).length(); - } - - public int getHeight(){ - return rows.size(); - } - - public void printDebug(){ - Iterator it = rows.iterator(); - int i = 0; - System.out.println( - " " - +StringUtils.repeatString("0123456789", (int) Math.floor(getWidth()/10)+1)); - while(it.hasNext()){ - String row = it.next().toString(); - String index = new Integer(i).toString(); - if(i < 10) index = " "+index; - System.out.println(index+" ("+row+")"); - i++; - } - } - - public String getDebugString(){ - StringBuilder buffer = new StringBuilder(); - Iterator it = rows.iterator(); - int i = 0; - buffer.append( - " " - +StringUtils.repeatString("0123456789", (int) Math.floor(getWidth()/10)+1)+"\n"); - while(it.hasNext()){ - String row = it.next().toString(); - String index = new Integer(i).toString(); - if(i < 10) index = " "+index; - row = row.replaceAll("\n", "\\\\n"); - row = row.replaceAll("\r", "\\\\r"); - buffer.append(index+" ("+row+")\n"); - i++; - } - return buffer.toString(); - } - - public String toString(){ - return getDebugString(); - } - - /** - * Adds grid to this. Space characters in this grid - * are replaced with the corresponding contents of - * grid, otherwise the contents are unchanged. - * - * @param grid - * @return false if the grids are of different size - */ - public boolean add(TextGrid grid){ - if(getWidth() != grid.getWidth() - || getHeight() != grid.getHeight()) return false; - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - if(get(xi, yi) == ' ') set(xi, yi, grid.get(xi, yi)); - } - } - return true; - } - - /** - * Replaces letters or numbers that are on horizontal or vertical - * lines, with the appropriate character that will make the line - * continuous (| for vertical and - for horizontal lines) - * - */ - public void replaceTypeOnLine(){ - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - char c = get(xi, yi); - if(Character.isLetterOrDigit(c)) { - boolean isOnHorizontalLine = isOnHorizontalLine(xi, yi); - boolean isOnVerticalLine = isOnVerticalLine(xi, yi); - if(isOnHorizontalLine && isOnVerticalLine){ - set(xi, yi, '+'); - if(DEBUG) System.out.println("replaced type on line '"+c+"' with +"); - } else if(isOnHorizontalLine){ - set(xi, yi, '-'); - if(DEBUG) System.out.println("replaced type on line '"+c+"' with -"); - } else if(isOnVerticalLine){ - set(xi, yi, '|'); - if(DEBUG) System.out.println("replaced type on line '"+c+"' with |"); - } - } - } - } - } - - public void replacePointMarkersOnLine(){ - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - char c = get(xi, yi); - Cell cell = new Cell(xi, yi); - if(StringUtils.isOneOf(c, pointMarkers) - && isStarOnLine(cell) && isInPlainMode(cell)){ - - boolean isOnHorizontalLine = false; - if(StringUtils.isOneOf(get(cell.getEast()), horizontalLines)) - isOnHorizontalLine = true; - if(StringUtils.isOneOf(get(cell.getWest()), horizontalLines)) - isOnHorizontalLine = true; - - boolean isOnVerticalLine = false; - if(StringUtils.isOneOf(get(cell.getNorth()), verticalLines)) - isOnVerticalLine = true; - if(StringUtils.isOneOf(get(cell.getSouth()), verticalLines)) - isOnVerticalLine = true; - - if(isOnHorizontalLine && isOnVerticalLine){ - set(xi, yi, '+'); - if(DEBUG) System.out.println("replaced marker on line '"+c+"' with +"); - } else if(isOnHorizontalLine){ - set(xi, yi, '-'); - if(DEBUG) System.out.println("replaced marker on line '"+c+"' with -"); - } else if(isOnVerticalLine){ - set(xi, yi, '|'); - if(DEBUG) System.out.println("replaced marker on line '"+c+"' with |"); - } - } - } - } - } - - public CellSet getPointMarkersOnLine(){ - CellSet result = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - if(! isInPlainMode(xi, yi)) - continue; - char c = get(xi, yi); - if(StringUtils.isOneOf(c, pointMarkers) - && isStarOnLine(new Cell(xi, yi))){ - result.add(new Cell(xi, yi)); - } - } - } - return result; - } - - - public void replaceHumanColorCodes(){ - int height = getHeight(); - for(int y = 0; y < height; y++) - rows.set(y, replaceHumanColorCodes(y, rows.get(y))); - } - - private StringBuilder replaceHumanColorCodes(int rowIndex, StringBuilder in){ - Pattern p = Pattern.compile("(c.{0,3}|[^c]+)"); - StringBuilder ret = new StringBuilder(in.length()); - Iterator i = createTextSplitter(p, in); - while(i.hasNext()){ - String next = i.next(); - if(isInPlainMode(ret.length(), rowIndex) && looksColorCode(next)){ - String replacement = humanColorCodes.get(next.substring(1, 4)); - if(replacement != null) - ret.append(String.format("c%s", replacement)); - else - ret.append(next); - } else - ret.append(next); - } - return ret; - } - - private static boolean looksColorCode(String word){ - return word.length() >= 4 && word.startsWith("c"); - } - - - /** - * Replace all occurrences of c1 with c2 - * - * @param c1 - * @param c2 - */ - public void replaceAll(char c1, char c2){ - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - char c = get(xi, yi); - if(c == c1) set(xi, yi, c2); - } - } - } - - public boolean hasBlankCells(){ - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - Cell cell = new Cell(x, y); - if(isBlank(cell)) return true; - } - } - return false; - } - - - public CellSet getAllNonBlank(){ - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - Cell cell = new Cell(x, y); - if(!isBlank(cell)) set.add(cell); - } - } - return set; - } - - public CellSet getAllBoundaries(){ - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - Cell cell = new Cell(x, y); - if(isBoundary(cell)) set.add(cell); - } - } - return set; - } - - - public CellSet getAllBlanksBetweenCharacters(){ - CellSet set = new CellSet(); - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - Cell cell = new Cell(x, y); - if(isBlankBetweenCharacters(cell)) set.add(cell); - } - } - return set; - } - - - /** - * Returns an ArrayList of CellStringPairs that - * represents all the continuous (non-blank) Strings - * in the grid. Used on buffers that contain only - * type, in order to find the positions and the - * contents of the strings. - * - * @return - */ - public ArrayList findStrings(){ - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width; x++){ - if(!isBlank(x, y)){ - Cell start = new Cell(x, y); - String str = String.valueOf(get(x,y)); - char c = get(++x, y); - boolean finished = false; - //while(c != ' '){ - while(!finished){ - str += String.valueOf(c); - c = get(++x, y); - char next = get(x + 1, y); - if((c == ' ' || c == 0) && (next == ' ' || next == 0)) - finished = true; - } - result.add(new CellStringPair(start, str)); - } - } - } - return result; - } - - /** - * This is done in a bit of a messy way, should be impossible - * to go out of sync with corresponding GridPatternGroup. - * - * @param cell - * @param entryPointId - * @return - */ - public boolean hasEntryPoint(Cell cell, int entryPointId){ - String result = ""; - char c = get(cell); - if(entryPointId == 1) { - return StringUtils.isOneOf(c, entryPoints1); - - } else if(entryPointId == 2) { - return StringUtils.isOneOf(c, entryPoints2); - - } else if(entryPointId == 3) { - return StringUtils.isOneOf(c, entryPoints3); - - } else if(entryPointId == 4) { - return StringUtils.isOneOf(c, entryPoints4); - - } else if(entryPointId == 5) { - return StringUtils.isOneOf(c, entryPoints5); - - } else if(entryPointId == 6) { - return StringUtils.isOneOf(c, entryPoints6); - - } else if(entryPointId == 7) { - return StringUtils.isOneOf(c, entryPoints7); - - } else if(entryPointId == 8) { - return StringUtils.isOneOf(c, entryPoints8); - } - return false; - } - - /** - * true if cell is blank and the east and west cells are not - * (used to find gaps between words) - * - * @param cell - * @return - */ - public boolean isBlankBetweenCharacters(Cell cell){ - return (isBlank(cell) - && !isBlank(cell.getEast()) - && !isBlank(cell.getWest())); - } - - /** - * Makes blank all the cells that contain non-text - * elements. - */ - public void removeNonText(){ - //the following order is significant - //since the south-pointing arrowheads - //are determined based on the surrounding boundaries - removeArrowheads(); - removeColorCodes(); - removeBoundaries(); - removeMarkupTags(); - } - - public void removeArrowheads(){ - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - Cell cell = new Cell(xi, yi); - if(isArrowhead(cell)) set(cell, ' '); - } - } - } - - public void removeColorCodes(){ - Iterator cells = findColorCodes().iterator(); - while(cells.hasNext()){ - Cell cell = ((CellColorPair) cells.next()).cell; - set(cell, ' '); - cell = cell.getEast(); set(cell, ' '); - cell = cell.getEast(); set(cell, ' '); - cell = cell.getEast(); set(cell, ' '); - } - } - - public void removeBoundaries(){ - ArrayList toBeRemoved = new ArrayList(); - - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - Cell cell = new Cell(xi, yi); - if(isBoundary(cell)) toBeRemoved.add(cell); - } - } - - //remove in two stages, because decision of - //isBoundary depends on content of surrounding - //cells - Iterator it = toBeRemoved.iterator(); - while(it.hasNext()){ - Cell cell = (Cell) it.next(); - if(isInPlainMode(cell)) - set(cell, ' '); - } - } - - public ArrayList findArrowheads(){ - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - Cell cell = new Cell(xi, yi); - if(isArrowhead(cell)) result.add(cell); - } - } - if(DEBUG) System.out.println(result.size()+" arrowheads found"); - return result; - } - - - public ArrayList findColorCodes(){ - Pattern colorCodePattern = Pattern.compile("c[A-F0-9]{3}"); - ArrayList result = new ArrayList(); - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width - 3; xi++){ - Cell cell = new Cell(xi, yi); - if(! isInPlainMode(cell)) - continue; - String s = getStringAt(cell, 4); - Matcher matcher = colorCodePattern.matcher(s); - if(matcher.matches()){ - char cR = s.charAt(1); - char cG = s.charAt(2); - char cB = s.charAt(3); - int r = Integer.valueOf(String.valueOf(cR), 16).intValue() * 17; - int g = Integer.valueOf(String.valueOf(cG), 16).intValue() * 17; - int b = Integer.valueOf(String.valueOf(cB), 16).intValue() * 17; - result.add(new CellColorPair(cell, new Color(r, g, b))); - } - } - } - if(DEBUG) System.out.println(result.size()+" color codes found"); - return result; - } - - public ArrayList findMarkupTags(){ - Pattern tagPattern = Pattern.compile("\\{(.+?)\\}"); - ArrayList result = new ArrayList(); - - int width = getWidth(); - int height = getHeight(); - for(int y = 0; y < height; y++){ - for(int x = 0; x < width - 3; x++){ - if(! isInPlainMode(x, y)) - continue; - Cell cell = new Cell(x, y); - char c = get(cell); - if(c == '{'){ - String rowPart = rows.get(y).substring(x); - Matcher matcher = tagPattern.matcher(rowPart); - if(matcher.find()){ - String tagName = matcher.group(1); - if(markupTags.contains(tagName)){ - if(DEBUG) System.out.println("found tag "+tagName+" at "+x+", "+y); - result.add(new CellTagPair(new Cell(x, y), tagName)); - } - } - } - } - } - return result; - } - - public void removeMarkupTags(){ - Iterator it = findMarkupTags().iterator(); - while (it.hasNext()) { - CellTagPair pair = (CellTagPair) it.next(); - String tagName = pair.tag; - if(tagName == null) continue; - int length = 2 + tagName.length(); - writeStringTo(pair.cell, StringUtils.repeatString(" ", length)); - } - } - - - - public boolean matchesAny(GridPatternGroup criteria){ - return criteria.isAnyMatchedBy(this); - } - - public boolean matchesAll(GridPatternGroup criteria){ - return criteria.areAllMatchedBy(this); - } - - public boolean matches(GridPattern criteria){ - return criteria.isMatchedBy(this); - } - - - public boolean isOnHorizontalLine(Cell cell){ return isOnHorizontalLine(cell.x, cell.y); } - private boolean isOnHorizontalLine(int x, int y){ - char c1 = get(x - 1, y); - char c2 = get(x + 1, y); - if(isHorizontalLine(c1) && isHorizontalLine(c2)) return true; - return false; - } - - public boolean isOnVerticalLine(Cell cell){ return isOnVerticalLine(cell.x, cell.y); } - private boolean isOnVerticalLine(int x, int y){ - char c1 = get(x, y - 1); - char c2 = get(x, y + 1); - if(isVerticalLine(c1) && isVerticalLine(c2)) return true; - return false; - } - - - public static boolean isBoundary(char c){ - return StringUtils.isOneOf(c, boundaries); - } - public boolean isBoundary(int x, int y){ return isBoundary(new Cell(x, y)); } - public boolean isBoundary(Cell cell){ - if(! isInPlainMode(cell)) - return false; - char c = get(cell.x, cell.y); - if(0 == c) return false; - if('+' == c || '\\' == c || '/' == c){ - System.out.print(""); - if( - isIntersection(cell) - || isCorner(cell) - || isStub(cell) - || isCrossOnLine(cell)){ - return true; - } else return false; - } - //return StringUtils.isOneOf(c, undisputableBoundaries); - if(StringUtils.isOneOf(c, boundaries) && !isLoneDiagonal(cell)){ - return true; - } - return false; - } - - public boolean isLine(Cell cell){ - return isHorizontalLine(cell) || isVerticalLine(cell); - } - - public static boolean isHorizontalLine(char c){ - return StringUtils.isOneOf(c, horizontalLines); - } - - public boolean isHorizontalLine(Cell cell){ - return isHorizontalLine(cell.x, cell.y) && isInPlainMode(cell); - } - - public boolean isHorizontalLine(int x, int y){ - char c = get(x, y); - if(0 == c) - return false; - return StringUtils.isOneOf(c, horizontalLines) && isInPlainMode(x, y); - } - - public static boolean isVerticalLine(char c){ - return StringUtils.isOneOf(c, verticalLines); - } - - public boolean isVerticalLine(Cell cell){ - return isVerticalLine(cell.x, cell.y) && isInPlainMode(cell); - } - - public boolean isVerticalLine(int x, int y){ - char c = get(x, y); - if(0 == c) - return false; - return StringUtils.isOneOf(c, verticalLines); - } - - public boolean isLinesEnd(int x, int y){ - return isLinesEnd(new Cell(x, y)); - } - - /** - * Stubs are also considered end of lines - * - * @param cell - * @return - */ - public boolean isLinesEnd(Cell cell){ - return matchesAny(cell, GridPatternGroup.linesEndCriteria) && isInPlainMode(cell); - } - - public boolean isVerticalLinesEnd(Cell cell){ - return matchesAny(cell, GridPatternGroup.verticalLinesEndCriteria) && isInPlainMode(cell); - } - - public boolean isHorizontalLinesEnd(Cell cell){ - return matchesAny(cell, GridPatternGroup.horizontalLinesEndCriteria) && isInPlainMode(cell); - } - - - public boolean isPointCell(Cell cell){ - return ( - isCorner(cell) - || isIntersection(cell) - || isStub(cell) - || isLinesEnd(cell)) && isInPlainMode(cell); - } - - - public boolean containsAtLeastOneDashedLine(CellSet set){ - Iterator it = set.iterator(); - while(it.hasNext()) { - Cell cell = (Cell) it.next(); - if(StringUtils.isOneOf(get(cell), dashedLines)) return true; - } - return false; - } - - public boolean exactlyOneNeighbourIsBoundary(Cell cell) { - int howMany = 0; - if(isBoundary(cell.getNorth())) howMany++; - if(isBoundary(cell.getSouth())) howMany++; - if(isBoundary(cell.getEast())) howMany++; - if(isBoundary(cell.getWest())) howMany++; - return (howMany == 1); - } - - /** - * - * A stub looks like that: - * - *
-	 *
-	 * +- or -+ or + or + or /- or -/ or / (you get the point)
-	 *             |    |                |
-	 *
-	 * 
- * - * @param cell - * @return - */ - - public boolean isStub(Cell cell){ - return matchesAny(cell, GridPatternGroup.stubCriteria); - } - - public boolean isCrossOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.crossOnLineCriteria); - } - - public boolean isHorizontalCrossOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.horizontalCrossOnLineCriteria); - } - - public boolean isVerticalCrossOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.verticalCrossOnLineCriteria); - } - - public boolean isStarOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.starOnLineCriteria); - } - - public boolean isLoneDiagonal(Cell cell){ - return matchesAny(cell, GridPatternGroup.loneDiagonalCriteria); - } - - - public boolean isHorizontalStarOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.horizontalStarOnLineCriteria); - } - - public boolean isVerticalStarOnLine(Cell cell){ - return matchesAny(cell, GridPatternGroup.verticalStarOnLineCriteria); - } - - public boolean isArrowhead(Cell cell){ - return (isNorthArrowhead(cell) - || isSouthArrowhead(cell) - || isWestArrowhead(cell) - || isEastArrowhead(cell)); - } - - public boolean isNorthArrowhead(Cell cell){ - return get(cell) == '^' && isInPlainMode(cell); - } - - public boolean isEastArrowhead(Cell cell){ - return get(cell) == '>' && isInPlainMode(cell); - } - - public boolean isWestArrowhead(Cell cell){ - return get(cell) == '<' && isInPlainMode(cell); - } - - public boolean isSouthArrowhead(Cell cell){ - return (get(cell) == 'v' || get(cell) == 'V') - && isVerticalLine(cell.getNorth()) - && isInPlainMode(cell); - } - - -// unicode for bullets -// -// 2022 bullet -// 25CF black circle -// 25AA black circle (small) -// 25A0 black square -// 25A1 white square -// 25CB white circle -// 25BA black right-pointing pointer - - - public boolean isBullet(int x, int y){ - return isBullet(new Cell(x, y)); - } - - public boolean isBullet(Cell cell){ - char c = get(cell); - if((c == 'o' || c == '*') - && isBlank(cell.getEast()) - && isBlank(cell.getWest()) - && Character.isLetterOrDigit(get(cell.getEast().getEast())) - && isInPlainMode(cell)) - return true; - return false; - } - - public void replaceBullets(){ - int width = getWidth(); - int height = getHeight(); - for(int yi = 0; yi < height; yi++){ - for(int xi = 0; xi < width; xi++){ - Cell cell = new Cell(xi, yi); - if(isBullet(cell)){ - set(cell, ' '); - set(cell.getEast(), '\u2022'); - } - } - } - } - - /** - * true if the cell is not blank - * but the previous (west) is - * - * @param cell - * @return - */ - public boolean isStringsStart(Cell cell){ - return (!isBlank(cell) && isBlank(cell.getWest())); - } - - /** - * true if the cell is not blank - * but the next (east) is - * - * @param cell - * @return - */ - public boolean isStringsEnd(Cell cell){ - return (!isBlank(cell) - //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); - && isBlank(cell.getEast())); - } - - public int otherStringsStartInTheSameColumn(Cell cell){ - if(!isStringsStart(cell)) return 0; - int result = 0; - int height = getHeight(); - for(int y = 0; y < height; y++){ - Cell cCell = new Cell(cell.x, y); - if(!cCell.equals(cell) && isStringsStart(cCell)){ - result++; - } - } - return result; - } - - public int otherStringsEndInTheSameColumn(Cell cell){ - if(!isStringsEnd(cell)) return 0; - int result = 0; - int height = getHeight(); - for(int y = 0; y < height; y++){ - Cell cCell = new Cell(cell.x, y); - if(!cCell.equals(cell) && isStringsEnd(cCell)){ - result++; - } - } - return result; - } - - public boolean isColumnBlank(int x){ - int height = getHeight(); - for(int y = 0; y < height; y++){ - if(!isBlank(x, y)) return false; - } - return true; - } - - - public CellSet followLine(int x, int y){ - return followLine(new Cell(x, y)); - } - - public CellSet followIntersection(Cell cell){ - return followIntersection(cell, null); - } - - public CellSet followIntersection(Cell cell, Cell blocked){ - if(!isIntersection(cell)) return null; - CellSet result = new CellSet(); - Cell cN = cell.getNorth(); - Cell cS = cell.getSouth(); - Cell cE = cell.getEast(); - Cell cW = cell.getWest(); - if(hasEntryPoint(cN, 6)) result.add(cN); - if(hasEntryPoint(cS, 2)) result.add(cS); - if(hasEntryPoint(cE, 8)) result.add(cE); - if(hasEntryPoint(cW, 4)) result.add(cW); - if(result.contains(blocked)) result.remove(blocked); - return result; - } - - /** - * Returns the neighbours of a line-cell that are boundaries - * (0 to 2 cells are returned) - * - * @param cell - * @return null if the cell is not a line - */ - public CellSet followLine(Cell cell){ - if(isHorizontalLine(cell)){ - CellSet result = new CellSet(); - if(isBoundary(cell.getEast())) result.add(cell.getEast()); - if(isBoundary(cell.getWest())) result.add(cell.getWest()); - return result; - } else if (isVerticalLine(cell)){ - CellSet result = new CellSet(); - if(isBoundary(cell.getNorth())) result.add(cell.getNorth()); - if(isBoundary(cell.getSouth())) result.add(cell.getSouth()); - return result; - } - return null; - } - - public CellSet followLine(Cell cell, Cell blocked){ - CellSet nextCells = followLine(cell); - if(nextCells.contains(blocked)) nextCells.remove(blocked); - return nextCells; - } - - public CellSet followCorner(Cell cell){ - return followCorner(cell, null); - } - - public CellSet followCorner(Cell cell, Cell blocked){ - if(!isCorner(cell)) return null; - if(isCorner1(cell)) return followCorner1(cell, blocked); - if(isCorner2(cell)) return followCorner2(cell, blocked); - if(isCorner3(cell)) return followCorner3(cell, blocked); - if(isCorner4(cell)) return followCorner4(cell, blocked); - return null; - } - - public CellSet followCorner1(Cell cell){ - return followCorner1(cell, null); - } - public CellSet followCorner1(Cell cell, Cell blocked){ - if(!isCorner1(cell)) return null; - CellSet result = new CellSet(); - if(!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); - if(!cell.getEast().equals(blocked)) result.add(cell.getEast()); - return result; - } - - public CellSet followCorner2(Cell cell){ - return followCorner2(cell, null); - } - public CellSet followCorner2(Cell cell, Cell blocked){ - if(!isCorner2(cell)) return null; - CellSet result = new CellSet(); - if(!cell.getSouth().equals(blocked)) result.add(cell.getSouth()); - if(!cell.getWest().equals(blocked)) result.add(cell.getWest()); - return result; - } - - public CellSet followCorner3(Cell cell){ - return followCorner3(cell, null); - } - public CellSet followCorner3(Cell cell, Cell blocked){ - if(!isCorner3(cell)) return null; - CellSet result = new CellSet(); - if(!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); - if(!cell.getWest().equals(blocked)) result.add(cell.getWest()); - return result; - } - - public CellSet followCorner4(Cell cell){ - return followCorner4(cell, null); - } - public CellSet followCorner4(Cell cell, Cell blocked){ - if(!isCorner4(cell)) return null; - CellSet result = new CellSet(); - if(!cell.getNorth().equals(blocked)) result.add(cell.getNorth()); - if(!cell.getEast().equals(blocked)) result.add(cell.getEast()); - return result; - } - - - public CellSet followStub(Cell cell){ - return followStub(cell, null); - } - public CellSet followStub(Cell cell, Cell blocked){ - if(!isStub(cell)) return null; - CellSet result = new CellSet(); - if(isBoundary(cell.getEast())) result.add(cell.getEast()); - else if(isBoundary(cell.getWest())) result.add(cell.getWest()); - else if(isBoundary(cell.getNorth())) result.add(cell.getNorth()); - else if(isBoundary(cell.getSouth())) result.add(cell.getSouth()); - if(result.contains(blocked)) result.remove(blocked); - return result; - } - - public CellSet followCell(Cell cell){ - return followCell(cell, null); - } - - public CellSet followCell(Cell cell, Cell blocked){ - if(isIntersection(cell)) return followIntersection(cell, blocked); - if(isCorner(cell)) return followCorner(cell, blocked); - if(isLine(cell)) return followLine(cell, blocked); - if(isStub(cell)) return followStub(cell, blocked); - if(isCrossOnLine(cell)) return followCrossOnLine(cell, blocked); - System.err.println("Ambiguous input at position "+cell+":"); - TextGrid subGrid = getTestingSubGrid(cell); - subGrid.printDebug(); - throw new RuntimeException("Cannot follow cell "+cell+": cannot determine cell type"); - } - - public String getCellTypeAsString(Cell cell){ - if(isK(cell)) return "K"; - if(isT(cell)) return "T"; - if(isInverseK(cell)) return "inverse K"; - if(isInverseT(cell)) return "inverse T"; - if(isCorner1(cell)) return "corner 1"; - if(isCorner2(cell)) return "corner 2"; - if(isCorner3(cell)) return "corner 3"; - if(isCorner4(cell)) return "corner 4"; - if(isLine(cell)) return "line"; - if(isStub(cell)) return "stub"; - if(isCrossOnLine(cell)) return "crossOnLine"; - return "unrecognisable type"; - } - - - public CellSet followCrossOnLine(Cell cell, Cell blocked){ - CellSet result = new CellSet(); - if(isHorizontalCrossOnLine(cell)){ - result.add(cell.getEast()); - result.add(cell.getWest()); - } else if(isVerticalCrossOnLine(cell)){ - result.add(cell.getNorth()); - result.add(cell.getSouth()); - } - if(result.contains(blocked)) result.remove(blocked); - return result; - } - - public boolean isOutOfBounds(Cell cell){ - if(cell.x > getWidth() - 1 - || cell.y > getHeight() - 1 - || cell.x < 0 - || cell.y < 0) return true; - return false; - } - - public boolean isOutOfBounds(int x, int y){ - char c = get(x, y); - if(0 == c) return true; - return false; - } - - public boolean isBlank(Cell cell){ - char c = get(cell); - if(0 == c) return false; - return c == ' '; - } - - public boolean isBlank(int x, int y){ - char c = get(x, y); - if(0 == c) return true; - return c == ' '; - } - - public boolean isCorner(Cell cell){ - return isCorner(cell.x, cell.y); - } - public boolean isCorner(int x, int y){ - return (isNormalCorner(x,y) || isRoundCorner(x,y)); - } - - - public boolean matchesAny(Cell cell, GridPatternGroup criteria){ - TextGrid subGrid = getTestingSubGrid(cell); - return subGrid.matchesAny(criteria); - } - - public boolean isCorner1(Cell cell){ - return matchesAny(cell, GridPatternGroup.corner1Criteria); - } - - public boolean isCorner2(Cell cell){ - return matchesAny(cell, GridPatternGroup.corner2Criteria); - } - - public boolean isCorner3(Cell cell){ - return matchesAny(cell, GridPatternGroup.corner3Criteria); - } - - public boolean isCorner4(Cell cell){ - return matchesAny(cell, GridPatternGroup.corner4Criteria); - } - - public boolean isCross(Cell cell){ - return matchesAny(cell, GridPatternGroup.crossCriteria); - } - - public boolean isK(Cell cell){ - return matchesAny(cell, GridPatternGroup.KCriteria); - } - - public boolean isInverseK(Cell cell){ - return matchesAny(cell, GridPatternGroup.inverseKCriteria); - } - - public boolean isT(Cell cell){ - return matchesAny(cell, GridPatternGroup.TCriteria); - } - - public boolean isInverseT(Cell cell){ - return matchesAny(cell, GridPatternGroup.inverseTCriteria); - } - - public boolean isNormalCorner(Cell cell){ - return matchesAny(cell, GridPatternGroup.normalCornerCriteria); - } - public boolean isNormalCorner(int x, int y){ - return isNormalCorner(new Cell(x, y)); - } - - public boolean isRoundCorner(Cell cell){ - return matchesAny(cell, GridPatternGroup.roundCornerCriteria); - } - - public boolean isRoundCorner(int x, int y){ - return isRoundCorner(new Cell(x, y)); - } - - public boolean isIntersection(Cell cell){ - return matchesAny(cell, GridPatternGroup.intersectionCriteria); - } - public boolean isIntersection(int x, int y){ - return isIntersection(new Cell(x, y)); - } - - public void copyCellsTo(CellSet cells, TextGrid grid){ - Iterator it = cells.iterator(); - while(it.hasNext()){ - Cell cell = (Cell) it.next(); - grid.set(cell, this.get(cell)); - } - } - - public boolean equals(TextGrid grid){ - if(grid.getHeight() != this.getHeight() - || grid.getWidth() != this.getWidth() - ){ - return false; - } - int height = grid.getHeight(); - for(int i = 0; i < height; i++){ - String row1 = this.getRow(i).toString(); - String row2 = grid.getRow(i).toString(); - if(!row1.equals(row2)) return false; - } - return true; - } - - /** - * Fills all the cells in cells with c - * - * @param cells - * @param c - */ - public void fillCellsWith(Iterable cells, char c){ - Iterator it = cells.iterator(); - while(it.hasNext()){ - Cell cell = it.next(); - set(cell.x, cell.y, c); - } - } - - /** - * - * Fills the continuous area with if c1 characters with c2, - * flooding from cell x, y - * - * @param x - * @param y - * @param c1 the character to replace - * @param c2 the character to replace c1 with - * @return the list of cells filled - */ -// public CellSet fillContinuousArea(int x, int y, char c1, char c2){ -// CellSet cells = new CellSet(); -// //fillContinuousArea_internal(x, y, c1, c2, cells); -// seedFill(new Cell(x, y), c1, c2); -// return cells; -// } - - public CellSet fillContinuousArea(int x, int y, char c){ - return fillContinuousArea(new Cell(x, y), c); - } - - public CellSet fillContinuousArea(Cell cell, char c){ - if(isOutOfBounds(cell)) throw new IllegalArgumentException("Attempted to fill area out of bounds: "+cell); - return seedFillOld(cell, c); - } - - private CellSet seedFill(Cell seed, char newChar){ - CellSet cellsFilled = new CellSet(); - char oldChar = get(seed); - - if(oldChar == newChar) return cellsFilled; - if(isOutOfBounds(seed)) return cellsFilled; - - Stack stack = new Stack(); - - stack.push(seed); - - while(!stack.isEmpty()){ - Cell cell = (Cell) stack.pop(); - - //set(cell, newChar); - cellsFilled.add(cell); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if(get(nCell) == oldChar && !cellsFilled.contains(nCell)) stack.push(nCell); - if(get(sCell) == oldChar && !cellsFilled.contains(sCell)) stack.push(sCell); - if(get(eCell) == oldChar && !cellsFilled.contains(eCell)) stack.push(eCell); - if(get(wCell) == oldChar && !cellsFilled.contains(wCell)) stack.push(wCell); - } - - return cellsFilled; - } - - private CellSet seedFillOld(Cell seed, char newChar){ - CellSet cellsFilled = new CellSet(); - char oldChar = get(seed); - - if(oldChar == newChar) return cellsFilled; - if(isOutOfBounds(seed)) return cellsFilled; - - Stack stack = new Stack(); - - stack.push(seed); - - while(!stack.isEmpty()){ - Cell cell = (Cell) stack.pop(); - - set(cell, newChar); - cellsFilled.add(cell); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if(get(nCell) == oldChar) stack.push(nCell); - if(get(sCell) == oldChar) stack.push(sCell); - if(get(eCell) == oldChar) stack.push(eCell); - if(get(wCell) == oldChar) stack.push(wCell); - } - - return cellsFilled; - } - - - /** - * - * Locates and returns the '*' boundaries that we would - * encounter if we did a flood-fill at seed. - * - * @param seed - * @return - */ - public CellSet findBoundariesExpandingFrom(Cell seed){ - CellSet boundaries = new CellSet(); - char oldChar = get(seed); - - if(isOutOfBounds(seed)) return boundaries; - - char newChar = 1; //TODO: kludge - - Stack stack = new Stack(); - - stack.push(seed); - - while(!stack.isEmpty()){ - Cell cell = (Cell) stack.pop(); - - set(cell, newChar); - - Cell nCell = cell.getNorth(); - Cell sCell = cell.getSouth(); - Cell eCell = cell.getEast(); - Cell wCell = cell.getWest(); - - if(get(nCell) == oldChar) stack.push(nCell); - else if(get(nCell) == '*') boundaries.add(nCell); - - if(get(sCell) == oldChar) stack.push(sCell); - else if(get(sCell) == '*') boundaries.add(sCell); - - if(get(eCell) == oldChar) stack.push(eCell); - else if(get(eCell) == '*') boundaries.add(eCell); - - if(get(wCell) == oldChar) stack.push(wCell); - else if(get(wCell) == '*') boundaries.add(wCell); - } - - return boundaries; - } - - - //TODO: incomplete method seedFillLine() - private CellSet seedFillLine(Cell cell, char newChar){ - CellSet cellsFilled = new CellSet(); - - Stack stack = new Stack(); - - char oldChar = get(cell); - - if(oldChar == newChar) return cellsFilled; - if(isOutOfBounds(cell)) return cellsFilled; - - stack.push(new LineSegment(cell.x, cell.x, cell.y, 1)); - stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, -1)); - - int left; - while(!stack.isEmpty()){ - LineSegment segment = (LineSegment) stack.pop(); - int x; - //expand to the left - for( - x = segment.x1; - x >= 0 && get(x, segment.y) == oldChar; - --x){ - set(x, segment.y, newChar); - cellsFilled.add(new Cell(x, segment.y)); - } - - left = cell.getEast().x; - boolean skip = (x > segment.x1)? true : false; - - if(left < segment.x1){ //leak on left? - //TODO: i think the first param should be x - stack.push( - //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); - new LineSegment(x, left, segment.y - 1, -segment.dy)); - } - - x = segment.x1 + 1; - do { - if(!skip) { - for( ; x < getWidth() && get(x, segment.y) == oldChar; ++x){ - set(x, segment.y, newChar); - cellsFilled.add(new Cell(x, segment.y)); - } - - stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); - if(x > segment.x2 + 1) //leak on right? - stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, -segment.dy)); - } - skip = false; //skip only once - - for(++x; x <= segment.x2 && get(x, segment.y) != oldChar; ++x){;} - left = x; - } while( x < segment.x2); - } - - return cellsFilled; - } - - public boolean cellContainsDashedLineChar(Cell cell){ - char c = get(cell); - return StringUtils.isOneOf(c, dashedLines); - } - - public boolean loadFrom(String filename) - throws FileNotFoundException, IOException - { - return loadFrom(filename, null); - } - - public boolean loadFrom(String filename, ProcessingOptions options) - throws IOException - { - - String encoding = (options == null) ? null : options.getCharacterEncoding(); - ArrayList lines = new ArrayList(); - InputStream is; - if ("-".equals(filename)) - is = System.in; - else - is = new FileInputStream(filename); - String[] linesArray = FileUtils.readFile(is, filename, encoding).split("(\r)?\n"); - for(int i = 0; i < linesArray.length; i++) - lines.add(new StringBuilder(linesArray[i])); - - return initialiseWithLines(lines, options); - } - - public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException { - - ArrayList lines = new ArrayList(); - String[] linesArray = text.split("(\r)?\n"); - for(int i = 0; i < linesArray.length; i++) - lines.add(new StringBuilder(linesArray[i])); - - return initialiseWithLines(lines, options); - } - - public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException { - - //remove blank rows at the bottom - boolean done = false; - int i; - for(i = lines.size() - 1; i >= 0 && !done; i--){ - StringBuilder row = lines.get(i); - if(!StringUtils.isBlank(row.toString())) done = true; - } - rows = new ArrayList(lines.subList(0, i + 2)); - this.updateModeRows(); - try{ - - if(options != null) fixTabs(options.getTabSize()); - else fixTabs(options.DEFAULT_TAB_SIZE); - - - // make all lines of equal length - // add blank outline around the buffer to prevent fill glitch - // convert tabs to spaces (or remove them if setting is 0) - - int blankBorderSize = 2; - - int maxLength = 0; - int index = 0; - - String encoding = null; - if(options != null) encoding = options.getCharacterEncoding(); - - Iterator it = rows.iterator(); - while(it.hasNext()){ - String row = it.next().toString(); - if(encoding != null){ - byte[] bytes = row.getBytes(); - row = new String(bytes, encoding); - } - if(row.length() > maxLength) maxLength = row.length(); - rows.set(index, new StringBuilder(row)); - index++; - } - - it = rows.iterator(); - ArrayList newRows = new ArrayList(); - //TODO: make the following depend on blankBorderSize - - StringBuilder topBottomRow = - new StringBuilder(StringUtils.repeatString(" ", maxLength + blankBorderSize * 2)); - - newRows.add(topBottomRow); - newRows.add(topBottomRow); - while(it.hasNext()){ - StringBuilder row = it.next(); - - if(row.length() < maxLength){ - String borderString = StringUtils.repeatString(" ", blankBorderSize); - StringBuilder newRow = new StringBuilder(); - - newRow.append(borderString); - newRow.append(row); - newRow.append(StringUtils.repeatString(" ", maxLength - row.length())); - newRow.append(borderString); - - newRows.add(newRow); - } else{ //TODO: why is the following line like that? - newRows.add(new StringBuilder(" ").append(row).append(" ")); - } - } - //TODO: make the following depend on blankBorderSize - newRows.add(topBottomRow); - newRows.add(topBottomRow); - rows = newRows; - } finally{ - this.updateModeRows(); - } - - replaceBullets(); - replaceHumanColorCodes(); - - return true; - } - - private void fixTabs(int tabSize){ - - int rowIndex = 0; - Iterator it = rows.iterator(); - - while(it.hasNext()){ - String row = it.next().toString(); - StringBuilder newRow = new StringBuilder(); - - char[] chars = row.toCharArray(); - for(int i = 0; i < chars.length; i++){ - if(chars[i] == '\t'){ - int spacesLeft = tabSize - newRow.length() % tabSize; - if(DEBUG){ - System.out.println("Found tab. Spaces left: "+spacesLeft); - } - String spaces = StringUtils.repeatString(" ", spacesLeft); - newRow.append(spaces); - } else { - String character = Character.toString(chars[i]); - newRow.append(character); - } - } - rows.set(rowIndex, newRow); - rowIndex++; - } - } - - /** - * @return - */ - protected ArrayList getRows() { - return rows; - } - - private boolean isInPlainMode(Cell cell){ - return this.modeRows.get(cell.y).charAt(cell.x) == PLAIN_MODE; - } - - private boolean isInPlainMode(int x, int y){ - return this.modeRows.get(y).charAt(x) == PLAIN_MODE; - } - - public class CellColorPair{ - public CellColorPair(Cell cell, Color color){ - this.cell = cell; - this.color = color; - } - public Color color; - public Cell cell; - } - - public class CellStringPair{ - public CellStringPair(Cell cell, String string){ - this.cell = cell; - this.string = string; - } - public Cell cell; - public String string; - } - - public class CellTagPair{ - public CellTagPair(Cell cell, String tag){ - this.cell = cell; - this.tag = tag; - } - public Cell cell; - public String tag; - } - - - public class Cell{ - - public int x, y; - - public Cell(Cell cell){ - this(cell.x, cell.y); - } - - public Cell(int x, int y){ - this.x = x; - this.y = y; - } - - public Cell getNorth(){ return new Cell(x, y - 1); } - public Cell getSouth(){ return new Cell(x, y + 1); } - public Cell getEast(){ return new Cell(x + 1, y); } - public Cell getWest(){ return new Cell(x - 1, y); } - - public Cell getNW(){ return new Cell(x - 1, y - 1); } - public Cell getNE(){ return new Cell(x + 1, y - 1); } - public Cell getSW(){ return new Cell(x - 1, y + 1); } - public Cell getSE(){ return new Cell(x + 1, y + 1); } - - public CellSet getNeighbours4(){ - CellSet result = new CellSet(); - - result.add(getNorth()); - result.add(getSouth()); - result.add(getWest()); - result.add(getEast()); - - return result; - } - - public CellSet getNeighbours8(){ - CellSet result = new CellSet(); - - result.add(getNorth()); - result.add(getSouth()); - result.add(getWest()); - result.add(getEast()); - - result.add(getNW()); - result.add(getNE()); - result.add(getSW()); - result.add(getSE()); - - return result; - } - - - public boolean isNorthOf(Cell cell){ - if(this.y < cell.y) return true; - return false; - } - - public boolean isSouthOf(Cell cell){ - if(this.y > cell.y) return true; - return false; - } - - public boolean isWestOf(Cell cell){ - if(this.x < cell.x) return true; - return false; - } - - public boolean isEastOf(Cell cell){ - if(this.x > cell.x) return true; - return false; - } - - - public boolean equals(Object o){ - Cell cell = (Cell) o; - if(cell == null) return false; - if(x == cell.x && y == cell.y) return true; - else return false; - } - - public int hashCode() { - return (x << 16) | y; - } - - public boolean isNextTo(int x2, int y2){ - if(Math.abs(x2 - x) == 1 && Math.abs(y2 - y) == 1) return false; - if(Math.abs(x2 - x) == 1 && y2 == y) return true; - if(Math.abs(y2 - y) == 1 && x2 == x) return true; - return false; - } - - public boolean isNextTo(Cell cell){ - if(cell == null) throw new IllegalArgumentException("cell cannot be null"); - return this.isNextTo(cell.x, cell.y); - } - - public String toString(){ - return "("+x+", "+y+")"; - } - - public void scale(int s){ - x = x * s; - y = y * s; - } - - } - - private class LineSegment{ - int x1, x2, y, dy; - public LineSegment(int x1, int x2, int y, int dy){ - this.x1 = x1; - this.x2 = x2; - this.y = y; - this.dy = dy; - } - } + private static final boolean DEBUG = false; + private static final char PLAIN_MODE = 'P'; + public static final char LATEX_MODE = 'L'; + + private ArrayList rows; + private List modeRows; + + private static char[] boundaries = { '/', '\\', '|', '-', '*', '=', ':' }; + private static char[] undisputableBoundaries = { '|', '-', '*', '=', ':' }; + private static char[] horizontalLines = { '-', '=' }; + private static char[] verticalLines = { '|', ':' }; + private static char[] arrowHeads = { '<', '>', '^', 'v', 'V' }; + private static char[] cornerChars = { '\\', '/', '+' }; + private static char[] pointMarkers = { '*' }; + private static char[] dashedLines = { ':', '~', '=' }; + + private static char[] entryPoints1 = { '\\' }; + private static char[] entryPoints2 = { '|', ':', '+', '\\', '/' }; + private static char[] entryPoints3 = { '/' }; + private static char[] entryPoints4 = { '-', '=', '+', '\\', '/' }; + private static char[] entryPoints5 = { '\\' }; + private static char[] entryPoints6 = { '|', ':', '+', '\\', '/' }; + private static char[] entryPoints7 = { '/' }; + private static char[] entryPoints8 = { '-', '=', '+', '\\', '/' }; + + + private static HashMap humanColorCodes = new HashMap(); + + static { + humanColorCodes.put("GRE", "9D9"); + humanColorCodes.put("BLU", "55B"); + humanColorCodes.put("PNK", "FAA"); + humanColorCodes.put("RED", "E32"); + humanColorCodes.put("YEL", "FF3"); + humanColorCodes.put("BLK", "000"); + + } + + private static HashSet markupTags = + new HashSet(); + + static { + markupTags.add("d"); + markupTags.add("s"); + markupTags.add("io"); + markupTags.add("c"); + markupTags.add("mo"); + markupTags.add("tr"); + markupTags.add("o"); + } + + private void updateModeRows() { + // Collectors.toList creates ArrayList and you see no performance penalty + // caused by using LinkedList here. + this.modeRows = this.rows.stream().map(TextGrid::transformRowToModeRow).collect(toList()); + } + + public static String transformRowToModeRow(CharSequence row) { + StringBuilder b = new StringBuilder(); + row.chars().map(new IntUnaryOperator() { + /** + * This field hold current mode of a cell in the grid. + * Only 2 values can be assigned, which are 'P' and 'L'. + * They respectively represent 'Plain', which is normal ditaa mode, and 'LaTeX', which is LaTeX + * formula mode. + */ + int cur = PLAIN_MODE; + + @Override + public int applyAsInt(int operand) { + if (cur == PLAIN_MODE) { + if (operand == '$') { + cur = LATEX_MODE; + return LATEX_MODE; + } + return PLAIN_MODE; + } else if (cur == LATEX_MODE) { + if (operand == '$') { + cur = PLAIN_MODE; + return LATEX_MODE; + } + return this.cur; + } + throw new IllegalStateException(); + } + }).forEach(c -> b.append((char) c)); + String ret = b.toString(); + if (ret.endsWith("L")) + if (row.charAt(row.length() - 1) != '$') + throw new IllegalArgumentException("LaTex mode was started but not finished."); + return ret; + } + + public void addToMarkupTags(Collection tags) { + markupTags.addAll(tags); + } + + public static void main(String[] args) throws Exception { + TextGrid grid = new TextGrid(); + grid.loadFrom("tests/text/art10.txt"); + + grid.writeStringTo(grid.new Cell(28, 1), "testing"); + + grid.findMarkupTags(); + + grid.printDebug(); + //System.out.println(grid.fillContinuousArea(0, 0, '-').size()+" cells filled"); + //grid.fillContinuousArea(4, 4, '-'); + //grid.getSubGrid(1,1,3,3).printDebug(); + //grid.printDebug(); + } + + + public TextGrid() { + rows = new ArrayList(); + this.updateModeRows(); + } + + public TextGrid(int width, int height) { + String space = StringUtils.repeatString(" ", width); + rows = new ArrayList(); + for (int i = 0; i < height; i++) + rows.add(new StringBuilder(space)); + this.updateModeRows(); + } + + public static TextGrid makeSameSizeAs(TextGrid grid) { + return new TextGrid(grid.getWidth(), grid.getHeight()); + } + + + public TextGrid(TextGrid otherGrid) { + rows = new ArrayList(); + for (StringBuilder row : otherGrid.getRows()) { + rows.add(new StringBuilder(row)); + } + this.updateModeRows(); + } + + public void clear() { + String blank = StringUtils.repeatString(" ", getWidth()); + int height = getHeight(); + rows.clear(); + for (int i = 0; i < height; i++) + rows.add(new StringBuilder(blank)); + } + + // duplicated code due to lots of hits to this function + public char get(int x, int y) { + if (x > getWidth() - 1 + || y > getHeight() - 1 + || x < 0 + || y < 0) + return 0; + return rows.get(y).charAt(x); + } + + //duplicated code due to lots of hits to this function + public char get(Cell cell) { + if (cell.x > getWidth() - 1 + || cell.y > getHeight() - 1 + || cell.x < 0 + || cell.y < 0) + return 0; + return rows.get(cell.y).charAt(cell.x); + } + + public StringBuilder getRow(int y) { + return rows.get(y); + } + + public TextGrid getSubGrid(int x, int y, int width, int height) { + TextGrid grid = new TextGrid(width, height); + for (int i = 0; i < height; i++) { + grid.setRow(i, new StringBuilder(getRow(y + i).subSequence(x, x + width))); + } + return grid; + } + + public TextGrid getTestingSubGrid(Cell cell) { + return getSubGrid(cell.x - 1, cell.y - 1, 3, 3); + } + + + public String getStringAt(int x, int y, int length) { + return getStringAt(new Cell(x, y), length); + } + + public String getStringAt(Cell cell, int length) { + int x = cell.x; + int y = cell.y; + if (x > getWidth() - 1 + || y > getHeight() - 1 + || x < 0 + || y < 0) + return null; + return rows.get(y).substring(x, x + length); + } + + public char getNorthOf(int x, int y) { + return get(x, y - 1); + } + + public char getSouthOf(int x, int y) { + return get(x, y + 1); + } + + public char getEastOf(int x, int y) { + return get(x + 1, y); + } + + public char getWestOf(int x, int y) { + return get(x - 1, y); + } + + public char getNorthOf(Cell cell) { + return getNorthOf(cell.x, cell.y); + } + + public char getSouthOf(Cell cell) { + return getSouthOf(cell.x, cell.y); + } + + public char getEastOf(Cell cell) { + return getEastOf(cell.x, cell.y); + } + + public char getWestOf(Cell cell) { + return getWestOf(cell.x, cell.y); + } + + public void writeStringTo(int x, int y, String str) { + writeStringTo(new Cell(x, y), str); + } + + public void writeStringTo(Cell cell, String str) { + if (isOutOfBounds(cell)) + return; + rows.get(cell.y).replace(cell.x, cell.x + str.length(), str); + } + + public void set(Cell cell, char c) { + set(cell.x, cell.y, c); + } + + public void set(int x, int y, char c) { + if (x > getWidth() - 1 || y > getHeight() - 1) + return; + StringBuilder row = rows.get(y); + row.setCharAt(x, c); + } + + public void setRow(int y, String row) { + if (y > getHeight() || row.length() != getWidth()) + throw new IllegalArgumentException("setRow out of bounds or string wrong size"); + rows.set(y, new StringBuilder(row)); + } + + public void setRow(int y, StringBuilder row) { + if (y > getHeight() || row.length() != getWidth()) + throw new IllegalArgumentException("setRow out of bounds or string wrong size"); + rows.set(y, row); + } + + public int getWidth() { + if (rows.size() == 0) + return 0; //empty buffer + return rows.get(0).length(); + } + + public int getHeight() { + return rows.size(); + } + + public void printDebug() { + Iterator it = rows.iterator(); + int i = 0; + System.out.println( + " " + + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1)); + while (it.hasNext()) { + String row = it.next().toString(); + String index = new Integer(i).toString(); + if (i < 10) + index = " " + index; + System.out.println(index + " (" + row + ")"); + i++; + } + } + + public String getDebugString() { + StringBuilder buffer = new StringBuilder(); + Iterator it = rows.iterator(); + int i = 0; + buffer.append( + " " + + StringUtils.repeatString("0123456789", (int) Math.floor(getWidth() / 10) + 1) + "\n"); + while (it.hasNext()) { + String row = it.next().toString(); + String index = new Integer(i).toString(); + if (i < 10) + index = " " + index; + row = row.replaceAll("\n", "\\\\n"); + row = row.replaceAll("\r", "\\\\r"); + buffer.append(index + " (" + row + ")\n"); + i++; + } + return buffer.toString(); + } + + public String toString() { + return getDebugString(); + } + + /** + * Adds grid to this. Space characters in this grid + * are replaced with the corresponding contents of + * grid, otherwise the contents are unchanged. + * + * @param grid + * @return false if the grids are of different size + */ + public boolean add(TextGrid grid) { + if (getWidth() != grid.getWidth() + || getHeight() != grid.getHeight()) + return false; + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + if (get(xi, yi) == ' ') + set(xi, yi, grid.get(xi, yi)); + } + } + return true; + } + + /** + * Replaces letters or numbers that are on horizontal or vertical + * lines, with the appropriate character that will make the line + * continuous (| for vertical and - for horizontal lines) + * + */ + public void replaceTypeOnLine() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + if (Character.isLetterOrDigit(c)) { + boolean isOnHorizontalLine = isOnHorizontalLine(xi, yi); + boolean isOnVerticalLine = isOnVerticalLine(xi, yi); + if (isOnHorizontalLine && isOnVerticalLine) { + set(xi, yi, '+'); + if (DEBUG) + System.out.println("replaced type on line '" + c + "' with +"); + } else if (isOnHorizontalLine) { + set(xi, yi, '-'); + if (DEBUG) + System.out.println("replaced type on line '" + c + "' with -"); + } else if (isOnVerticalLine) { + set(xi, yi, '|'); + if (DEBUG) + System.out.println("replaced type on line '" + c + "' with |"); + } + } + } + } + } + + public void replacePointMarkersOnLine() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + Cell cell = new Cell(xi, yi); + if (StringUtils.isOneOf(c, pointMarkers) + && isStarOnLine(cell) && isInPlainMode(cell)) { + + boolean isOnHorizontalLine = false; + if (StringUtils.isOneOf(get(cell.getEast()), horizontalLines)) + isOnHorizontalLine = true; + if (StringUtils.isOneOf(get(cell.getWest()), horizontalLines)) + isOnHorizontalLine = true; + + boolean isOnVerticalLine = false; + if (StringUtils.isOneOf(get(cell.getNorth()), verticalLines)) + isOnVerticalLine = true; + if (StringUtils.isOneOf(get(cell.getSouth()), verticalLines)) + isOnVerticalLine = true; + + if (isOnHorizontalLine && isOnVerticalLine) { + set(xi, yi, '+'); + if (DEBUG) + System.out.println("replaced marker on line '" + c + "' with +"); + } else if (isOnHorizontalLine) { + set(xi, yi, '-'); + if (DEBUG) + System.out.println("replaced marker on line '" + c + "' with -"); + } else if (isOnVerticalLine) { + set(xi, yi, '|'); + if (DEBUG) + System.out.println("replaced marker on line '" + c + "' with |"); + } + } + } + } + } + + public CellSet getPointMarkersOnLine() { + CellSet result = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + if (!isInPlainMode(xi, yi)) + continue; + char c = get(xi, yi); + if (StringUtils.isOneOf(c, pointMarkers) + && isStarOnLine(new Cell(xi, yi))) { + result.add(new Cell(xi, yi)); + } + } + } + return result; + } + + + public void replaceHumanColorCodes() { + int height = getHeight(); + for (int y = 0; y < height; y++) + rows.set(y, replaceHumanColorCodes(y, rows.get(y))); + } + + private StringBuilder replaceHumanColorCodes(int rowIndex, StringBuilder in) { + Pattern p = Pattern.compile("(c.{0,3}|[^c]+)"); + StringBuilder ret = new StringBuilder(in.length()); + Iterator i = createTextSplitter(p, in); + while (i.hasNext()) { + String next = i.next(); + if (isInPlainMode(ret.length(), rowIndex) && looksColorCode(next)) { + String replacement = humanColorCodes.get(next.substring(1, 4)); + if (replacement != null) + ret.append(String.format("c%s", replacement)); + else + ret.append(next); + } else + ret.append(next); + } + return ret; + } + + private static boolean looksColorCode(String word) { + return word.length() >= 4 && word.startsWith("c"); + } + + + /** + * Replace all occurrences of c1 with c2 + * + * @param c1 + * @param c2 + */ + public void replaceAll(char c1, char c2) { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + char c = get(xi, yi); + if (c == c1) + set(xi, yi, c2); + } + } + } + + public boolean hasBlankCells() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (isBlank(cell)) + return true; + } + } + return false; + } + + + public CellSet getAllNonBlank() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (!isBlank(cell)) + set.add(cell); + } + } + return set; + } + + public CellSet getAllBoundaries() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (isBoundary(cell)) + set.add(cell); + } + } + return set; + } + + + public CellSet getAllBlanksBetweenCharacters() { + CellSet set = new CellSet(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + Cell cell = new Cell(x, y); + if (isBlankBetweenCharacters(cell)) + set.add(cell); + } + } + return set; + } + + + /** + * Returns an ArrayList of CellStringPairs that + * represents all the continuous (non-blank) Strings + * in the grid. Used on buffers that contain only + * type, in order to find the positions and the + * contents of the strings. + * + * @return + */ + public ArrayList findStrings() { + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (!isBlank(x, y)) { + Cell start = new Cell(x, y); + String str = String.valueOf(get(x, y)); + char c = get(++x, y); + boolean finished = false; + //while(c != ' '){ + while (!finished) { + str += String.valueOf(c); + c = get(++x, y); + char next = get(x + 1, y); + if ((c == ' ' || c == 0) && (next == ' ' || next == 0)) + finished = true; + } + result.add(new CellStringPair(start, str)); + } + } + } + return result; + } + + /** + * This is done in a bit of a messy way, should be impossible + * to go out of sync with corresponding GridPatternGroup. + * + * @param cell + * @param entryPointId + * @return + */ + public boolean hasEntryPoint(Cell cell, int entryPointId) { + String result = ""; + char c = get(cell); + if (entryPointId == 1) { + return StringUtils.isOneOf(c, entryPoints1); + + } else if (entryPointId == 2) { + return StringUtils.isOneOf(c, entryPoints2); + + } else if (entryPointId == 3) { + return StringUtils.isOneOf(c, entryPoints3); + + } else if (entryPointId == 4) { + return StringUtils.isOneOf(c, entryPoints4); + + } else if (entryPointId == 5) { + return StringUtils.isOneOf(c, entryPoints5); + + } else if (entryPointId == 6) { + return StringUtils.isOneOf(c, entryPoints6); + + } else if (entryPointId == 7) { + return StringUtils.isOneOf(c, entryPoints7); + + } else if (entryPointId == 8) { + return StringUtils.isOneOf(c, entryPoints8); + } + return false; + } + + /** + * true if cell is blank and the east and west cells are not + * (used to find gaps between words) + * + * @param cell + * @return + */ + public boolean isBlankBetweenCharacters(Cell cell) { + return (isBlank(cell) + && !isBlank(cell.getEast()) + && !isBlank(cell.getWest())); + } + + /** + * Makes blank all the cells that contain non-text + * elements. + */ + public void removeNonText() { + //the following order is significant + //since the south-pointing arrowheads + //are determined based on the surrounding boundaries + removeArrowheads(); + removeColorCodes(); + removeBoundaries(); + removeMarkupTags(); + } + + public void removeArrowheads() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isArrowhead(cell)) + set(cell, ' '); + } + } + } + + public void removeColorCodes() { + Iterator cells = findColorCodes().iterator(); + while (cells.hasNext()) { + Cell cell = ((CellColorPair) cells.next()).cell; + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + cell = cell.getEast(); + set(cell, ' '); + } + } + + public void removeBoundaries() { + ArrayList toBeRemoved = new ArrayList(); + + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isBoundary(cell)) + toBeRemoved.add(cell); + } + } + + //remove in two stages, because decision of + //isBoundary depends on content of surrounding + //cells + Iterator it = toBeRemoved.iterator(); + while (it.hasNext()) { + Cell cell = (Cell) it.next(); + if (isInPlainMode(cell)) + set(cell, ' '); + } + } + + public ArrayList findArrowheads() { + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isArrowhead(cell)) + result.add(cell); + } + } + if (DEBUG) + System.out.println(result.size() + " arrowheads found"); + return result; + } + + + public ArrayList findColorCodes() { + Pattern colorCodePattern = Pattern.compile("c[A-F0-9]{3}"); + ArrayList result = new ArrayList(); + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width - 3; xi++) { + Cell cell = new Cell(xi, yi); + if (!isInPlainMode(cell)) + continue; + String s = getStringAt(cell, 4); + Matcher matcher = colorCodePattern.matcher(s); + if (matcher.matches()) { + char cR = s.charAt(1); + char cG = s.charAt(2); + char cB = s.charAt(3); + int r = Integer.valueOf(String.valueOf(cR), 16).intValue() * 17; + int g = Integer.valueOf(String.valueOf(cG), 16).intValue() * 17; + int b = Integer.valueOf(String.valueOf(cB), 16).intValue() * 17; + result.add(new CellColorPair(cell, new Color(r, g, b))); + } + } + } + if (DEBUG) + System.out.println(result.size() + " color codes found"); + return result; + } + + public ArrayList findMarkupTags() { + Pattern tagPattern = Pattern.compile("\\{(.+?)\\}"); + ArrayList result = new ArrayList(); + + int width = getWidth(); + int height = getHeight(); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width - 3; x++) { + if (!isInPlainMode(x, y)) + continue; + Cell cell = new Cell(x, y); + char c = get(cell); + if (c == '{') { + String rowPart = rows.get(y).substring(x); + Matcher matcher = tagPattern.matcher(rowPart); + if (matcher.find()) { + String tagName = matcher.group(1); + if (markupTags.contains(tagName)) { + if (DEBUG) + System.out.println("found tag " + tagName + " at " + x + ", " + y); + result.add(new CellTagPair(new Cell(x, y), tagName)); + } + } + } + } + } + return result; + } + + public void removeMarkupTags() { + Iterator it = findMarkupTags().iterator(); + while (it.hasNext()) { + CellTagPair pair = (CellTagPair) it.next(); + String tagName = pair.tag; + if (tagName == null) + continue; + int length = 2 + tagName.length(); + writeStringTo(pair.cell, StringUtils.repeatString(" ", length)); + } + } + + + public boolean matchesAny(GridPatternGroup criteria) { + return criteria.isAnyMatchedBy(this); + } + + public boolean matchesAll(GridPatternGroup criteria) { + return criteria.areAllMatchedBy(this); + } + + public boolean matches(GridPattern criteria) { + return criteria.isMatchedBy(this); + } + + + public boolean isOnHorizontalLine(Cell cell) { + return isOnHorizontalLine(cell.x, cell.y); + } + + private boolean isOnHorizontalLine(int x, int y) { + char c1 = get(x - 1, y); + char c2 = get(x + 1, y); + if (isHorizontalLine(c1) && isHorizontalLine(c2)) + return true; + return false; + } + + public boolean isOnVerticalLine(Cell cell) { + return isOnVerticalLine(cell.x, cell.y); + } + + private boolean isOnVerticalLine(int x, int y) { + char c1 = get(x, y - 1); + char c2 = get(x, y + 1); + if (isVerticalLine(c1) && isVerticalLine(c2)) + return true; + return false; + } + + + public static boolean isBoundary(char c) { + return StringUtils.isOneOf(c, boundaries); + } + + public boolean isBoundary(int x, int y) { + return isBoundary(new Cell(x, y)); + } + + public boolean isBoundary(Cell cell) { + if (!isInPlainMode(cell)) + return false; + char c = get(cell.x, cell.y); + if (0 == c) + return false; + if ('+' == c || '\\' == c || '/' == c) { + System.out.print(""); + if ( + isIntersection(cell) + || isCorner(cell) + || isStub(cell) + || isCrossOnLine(cell)) { + return true; + } else + return false; + } + //return StringUtils.isOneOf(c, undisputableBoundaries); + if (StringUtils.isOneOf(c, boundaries) && !isLoneDiagonal(cell)) { + return true; + } + return false; + } + + public boolean isLine(Cell cell) { + return isHorizontalLine(cell) || isVerticalLine(cell); + } + + public static boolean isHorizontalLine(char c) { + return StringUtils.isOneOf(c, horizontalLines); + } + + public boolean isHorizontalLine(Cell cell) { + return isHorizontalLine(cell.x, cell.y) && isInPlainMode(cell); + } + + public boolean isHorizontalLine(int x, int y) { + char c = get(x, y); + if (0 == c) + return false; + return StringUtils.isOneOf(c, horizontalLines) && isInPlainMode(x, y); + } + + public static boolean isVerticalLine(char c) { + return StringUtils.isOneOf(c, verticalLines); + } + + public boolean isVerticalLine(Cell cell) { + return isVerticalLine(cell.x, cell.y) && isInPlainMode(cell); + } + + public boolean isVerticalLine(int x, int y) { + char c = get(x, y); + if (0 == c) + return false; + return StringUtils.isOneOf(c, verticalLines); + } + + public boolean isLinesEnd(int x, int y) { + return isLinesEnd(new Cell(x, y)); + } + + /** + * Stubs are also considered end of lines + * + * @param cell + * @return + */ + public boolean isLinesEnd(Cell cell) { + return matchesAny(cell, GridPatternGroup.linesEndCriteria) && isInPlainMode(cell); + } + + public boolean isVerticalLinesEnd(Cell cell) { + return matchesAny(cell, GridPatternGroup.verticalLinesEndCriteria) && isInPlainMode(cell); + } + + public boolean isHorizontalLinesEnd(Cell cell) { + return matchesAny(cell, GridPatternGroup.horizontalLinesEndCriteria) && isInPlainMode(cell); + } + + + public boolean isPointCell(Cell cell) { + return ( + isCorner(cell) + || isIntersection(cell) + || isStub(cell) + || isLinesEnd(cell)) && isInPlainMode(cell); + } + + + public boolean containsAtLeastOneDashedLine(CellSet set) { + Iterator it = set.iterator(); + while (it.hasNext()) { + Cell cell = (Cell) it.next(); + if (StringUtils.isOneOf(get(cell), dashedLines)) + return true; + } + return false; + } + + public boolean exactlyOneNeighbourIsBoundary(Cell cell) { + int howMany = 0; + if (isBoundary(cell.getNorth())) + howMany++; + if (isBoundary(cell.getSouth())) + howMany++; + if (isBoundary(cell.getEast())) + howMany++; + if (isBoundary(cell.getWest())) + howMany++; + return (howMany == 1); + } + + /** + * + * A stub looks like that: + * + *
+   *
+   * +- or -+ or + or + or /- or -/ or / (you get the point)
+   *             |    |                |
+   *
+   * 
+ * + * @param cell + * @return + */ + + public boolean isStub(Cell cell) { + return matchesAny(cell, GridPatternGroup.stubCriteria); + } + + public boolean isCrossOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.crossOnLineCriteria); + } + + public boolean isHorizontalCrossOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.horizontalCrossOnLineCriteria); + } + + public boolean isVerticalCrossOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.verticalCrossOnLineCriteria); + } + + public boolean isStarOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.starOnLineCriteria); + } + + public boolean isLoneDiagonal(Cell cell) { + return matchesAny(cell, GridPatternGroup.loneDiagonalCriteria); + } + + + public boolean isHorizontalStarOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.horizontalStarOnLineCriteria); + } + + public boolean isVerticalStarOnLine(Cell cell) { + return matchesAny(cell, GridPatternGroup.verticalStarOnLineCriteria); + } + + public boolean isArrowhead(Cell cell) { + return (isNorthArrowhead(cell) + || isSouthArrowhead(cell) + || isWestArrowhead(cell) + || isEastArrowhead(cell)); + } + + public boolean isNorthArrowhead(Cell cell) { + return get(cell) == '^' && isInPlainMode(cell); + } + + public boolean isEastArrowhead(Cell cell) { + return get(cell) == '>' && isInPlainMode(cell); + } + + public boolean isWestArrowhead(Cell cell) { + return get(cell) == '<' && isInPlainMode(cell); + } + + public boolean isSouthArrowhead(Cell cell) { + return (get(cell) == 'v' || get(cell) == 'V') + && isVerticalLine(cell.getNorth()) + && isInPlainMode(cell); + } + + // unicode for bullets + // + // 2022 bullet + // 25CF black circle + // 25AA black circle (small) + // 25A0 black square + // 25A1 white square + // 25CB white circle + // 25BA black right-pointing pointer + + + public boolean isBullet(int x, int y) { + return isBullet(new Cell(x, y)); + } + + public boolean isBullet(Cell cell) { + char c = get(cell); + if ((c == 'o' || c == '*') + && isBlank(cell.getEast()) + && isBlank(cell.getWest()) + && Character.isLetterOrDigit(get(cell.getEast().getEast())) + && isInPlainMode(cell)) + return true; + return false; + } + + public void replaceBullets() { + int width = getWidth(); + int height = getHeight(); + for (int yi = 0; yi < height; yi++) { + for (int xi = 0; xi < width; xi++) { + Cell cell = new Cell(xi, yi); + if (isBullet(cell)) { + set(cell, ' '); + set(cell.getEast(), '\u2022'); + } + } + } + } + + /** + * true if the cell is not blank + * but the previous (west) is + * + * @param cell + * @return + */ + public boolean isStringsStart(Cell cell) { + return (!isBlank(cell) && isBlank(cell.getWest())); + } + + /** + * true if the cell is not blank + * but the next (east) is + * + * @param cell + * @return + */ + public boolean isStringsEnd(Cell cell) { + return (!isBlank(cell) + //&& (isBlank(cell.getEast()) || get(cell.getEast()) == 0)); + && isBlank(cell.getEast())); + } + + public int otherStringsStartInTheSameColumn(Cell cell) { + if (!isStringsStart(cell)) + return 0; + int result = 0; + int height = getHeight(); + for (int y = 0; y < height; y++) { + Cell cCell = new Cell(cell.x, y); + if (!cCell.equals(cell) && isStringsStart(cCell)) { + result++; + } + } + return result; + } + + public int otherStringsEndInTheSameColumn(Cell cell) { + if (!isStringsEnd(cell)) + return 0; + int result = 0; + int height = getHeight(); + for (int y = 0; y < height; y++) { + Cell cCell = new Cell(cell.x, y); + if (!cCell.equals(cell) && isStringsEnd(cCell)) { + result++; + } + } + return result; + } + + public boolean isColumnBlank(int x) { + int height = getHeight(); + for (int y = 0; y < height; y++) { + if (!isBlank(x, y)) + return false; + } + return true; + } + + + public CellSet followLine(int x, int y) { + return followLine(new Cell(x, y)); + } + + public CellSet followIntersection(Cell cell) { + return followIntersection(cell, null); + } + + public CellSet followIntersection(Cell cell, Cell blocked) { + if (!isIntersection(cell)) + return null; + CellSet result = new CellSet(); + Cell cN = cell.getNorth(); + Cell cS = cell.getSouth(); + Cell cE = cell.getEast(); + Cell cW = cell.getWest(); + if (hasEntryPoint(cN, 6)) + result.add(cN); + if (hasEntryPoint(cS, 2)) + result.add(cS); + if (hasEntryPoint(cE, 8)) + result.add(cE); + if (hasEntryPoint(cW, 4)) + result.add(cW); + if (result.contains(blocked)) + result.remove(blocked); + return result; + } + + /** + * Returns the neighbours of a line-cell that are boundaries + * (0 to 2 cells are returned) + * + * @param cell + * @return null if the cell is not a line + */ + public CellSet followLine(Cell cell) { + if (isHorizontalLine(cell)) { + CellSet result = new CellSet(); + if (isBoundary(cell.getEast())) + result.add(cell.getEast()); + if (isBoundary(cell.getWest())) + result.add(cell.getWest()); + return result; + } else if (isVerticalLine(cell)) { + CellSet result = new CellSet(); + if (isBoundary(cell.getNorth())) + result.add(cell.getNorth()); + if (isBoundary(cell.getSouth())) + result.add(cell.getSouth()); + return result; + } + return null; + } + + public CellSet followLine(Cell cell, Cell blocked) { + CellSet nextCells = followLine(cell); + if (nextCells.contains(blocked)) + nextCells.remove(blocked); + return nextCells; + } + + public CellSet followCorner(Cell cell) { + return followCorner(cell, null); + } + + public CellSet followCorner(Cell cell, Cell blocked) { + if (!isCorner(cell)) + return null; + if (isCorner1(cell)) + return followCorner1(cell, blocked); + if (isCorner2(cell)) + return followCorner2(cell, blocked); + if (isCorner3(cell)) + return followCorner3(cell, blocked); + if (isCorner4(cell)) + return followCorner4(cell, blocked); + return null; + } + + public CellSet followCorner1(Cell cell) { + return followCorner1(cell, null); + } + + public CellSet followCorner1(Cell cell, Cell blocked) { + if (!isCorner1(cell)) + return null; + CellSet result = new CellSet(); + if (!cell.getSouth().equals(blocked)) + result.add(cell.getSouth()); + if (!cell.getEast().equals(blocked)) + result.add(cell.getEast()); + return result; + } + + public CellSet followCorner2(Cell cell) { + return followCorner2(cell, null); + } + + public CellSet followCorner2(Cell cell, Cell blocked) { + if (!isCorner2(cell)) + return null; + CellSet result = new CellSet(); + if (!cell.getSouth().equals(blocked)) + result.add(cell.getSouth()); + if (!cell.getWest().equals(blocked)) + result.add(cell.getWest()); + return result; + } + + public CellSet followCorner3(Cell cell) { + return followCorner3(cell, null); + } + + public CellSet followCorner3(Cell cell, Cell blocked) { + if (!isCorner3(cell)) + return null; + CellSet result = new CellSet(); + if (!cell.getNorth().equals(blocked)) + result.add(cell.getNorth()); + if (!cell.getWest().equals(blocked)) + result.add(cell.getWest()); + return result; + } + + public CellSet followCorner4(Cell cell) { + return followCorner4(cell, null); + } + + public CellSet followCorner4(Cell cell, Cell blocked) { + if (!isCorner4(cell)) + return null; + CellSet result = new CellSet(); + if (!cell.getNorth().equals(blocked)) + result.add(cell.getNorth()); + if (!cell.getEast().equals(blocked)) + result.add(cell.getEast()); + return result; + } + + + public CellSet followStub(Cell cell) { + return followStub(cell, null); + } + + public CellSet followStub(Cell cell, Cell blocked) { + if (!isStub(cell)) + return null; + CellSet result = new CellSet(); + if (isBoundary(cell.getEast())) + result.add(cell.getEast()); + else if (isBoundary(cell.getWest())) + result.add(cell.getWest()); + else if (isBoundary(cell.getNorth())) + result.add(cell.getNorth()); + else if (isBoundary(cell.getSouth())) + result.add(cell.getSouth()); + if (result.contains(blocked)) + result.remove(blocked); + return result; + } + + public CellSet followCell(Cell cell) { + return followCell(cell, null); + } + + public CellSet followCell(Cell cell, Cell blocked) { + if (isIntersection(cell)) + return followIntersection(cell, blocked); + if (isCorner(cell)) + return followCorner(cell, blocked); + if (isLine(cell)) + return followLine(cell, blocked); + if (isStub(cell)) + return followStub(cell, blocked); + if (isCrossOnLine(cell)) + return followCrossOnLine(cell, blocked); + System.err.println("Ambiguous input at position " + cell + ":"); + TextGrid subGrid = getTestingSubGrid(cell); + subGrid.printDebug(); + throw new RuntimeException("Cannot follow cell " + cell + ": cannot determine cell type"); + } + + public String getCellTypeAsString(Cell cell) { + if (isK(cell)) + return "K"; + if (isT(cell)) + return "T"; + if (isInverseK(cell)) + return "inverse K"; + if (isInverseT(cell)) + return "inverse T"; + if (isCorner1(cell)) + return "corner 1"; + if (isCorner2(cell)) + return "corner 2"; + if (isCorner3(cell)) + return "corner 3"; + if (isCorner4(cell)) + return "corner 4"; + if (isLine(cell)) + return "line"; + if (isStub(cell)) + return "stub"; + if (isCrossOnLine(cell)) + return "crossOnLine"; + return "unrecognisable type"; + } + + + public CellSet followCrossOnLine(Cell cell, Cell blocked) { + CellSet result = new CellSet(); + if (isHorizontalCrossOnLine(cell)) { + result.add(cell.getEast()); + result.add(cell.getWest()); + } else if (isVerticalCrossOnLine(cell)) { + result.add(cell.getNorth()); + result.add(cell.getSouth()); + } + if (result.contains(blocked)) + result.remove(blocked); + return result; + } + + public boolean isOutOfBounds(Cell cell) { + if (cell.x > getWidth() - 1 + || cell.y > getHeight() - 1 + || cell.x < 0 + || cell.y < 0) + return true; + return false; + } + + public boolean isOutOfBounds(int x, int y) { + char c = get(x, y); + if (0 == c) + return true; + return false; + } + + public boolean isBlank(Cell cell) { + char c = get(cell); + if (0 == c) + return false; + return c == ' '; + } + + public boolean isBlank(int x, int y) { + char c = get(x, y); + if (0 == c) + return true; + return c == ' '; + } + + public boolean isCorner(Cell cell) { + return isCorner(cell.x, cell.y); + } + + public boolean isCorner(int x, int y) { + return (isNormalCorner(x, y) || isRoundCorner(x, y)); + } + + + public boolean matchesAny(Cell cell, GridPatternGroup criteria) { + TextGrid subGrid = getTestingSubGrid(cell); + return subGrid.matchesAny(criteria); + } + + public boolean isCorner1(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner1Criteria); + } + + public boolean isCorner2(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner2Criteria); + } + + public boolean isCorner3(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner3Criteria); + } + + public boolean isCorner4(Cell cell) { + return matchesAny(cell, GridPatternGroup.corner4Criteria); + } + + public boolean isCross(Cell cell) { + return matchesAny(cell, GridPatternGroup.crossCriteria); + } + + public boolean isK(Cell cell) { + return matchesAny(cell, GridPatternGroup.KCriteria); + } + + public boolean isInverseK(Cell cell) { + return matchesAny(cell, GridPatternGroup.inverseKCriteria); + } + + public boolean isT(Cell cell) { + return matchesAny(cell, GridPatternGroup.TCriteria); + } + + public boolean isInverseT(Cell cell) { + return matchesAny(cell, GridPatternGroup.inverseTCriteria); + } + + public boolean isNormalCorner(Cell cell) { + return matchesAny(cell, GridPatternGroup.normalCornerCriteria); + } + + public boolean isNormalCorner(int x, int y) { + return isNormalCorner(new Cell(x, y)); + } + + public boolean isRoundCorner(Cell cell) { + return matchesAny(cell, GridPatternGroup.roundCornerCriteria); + } + + public boolean isRoundCorner(int x, int y) { + return isRoundCorner(new Cell(x, y)); + } + + public boolean isIntersection(Cell cell) { + return matchesAny(cell, GridPatternGroup.intersectionCriteria); + } + + public boolean isIntersection(int x, int y) { + return isIntersection(new Cell(x, y)); + } + + public void copyCellsTo(CellSet cells, TextGrid grid) { + Iterator it = cells.iterator(); + while (it.hasNext()) { + Cell cell = (Cell) it.next(); + grid.set(cell, this.get(cell)); + } + } + + public boolean equals(TextGrid grid) { + if (grid.getHeight() != this.getHeight() + || grid.getWidth() != this.getWidth() + ) { + return false; + } + int height = grid.getHeight(); + for (int i = 0; i < height; i++) { + String row1 = this.getRow(i).toString(); + String row2 = grid.getRow(i).toString(); + if (!row1.equals(row2)) + return false; + } + return true; + } + + /** + * Fills all the cells in cells with c + * + * @param cells + * @param c + */ + public void fillCellsWith(Iterable cells, char c) { + Iterator it = cells.iterator(); + while (it.hasNext()) { + Cell cell = it.next(); + set(cell.x, cell.y, c); + } + } + + /** + * + * Fills the continuous area with if c1 characters with c2, + * flooding from cell x, y + * + * @param x + * @param y + * @param c1 the character to replace + * @param c2 the character to replace c1 with + * @return the list of cells filled + */ + // public CellSet fillContinuousArea(int x, int y, char c1, char c2){ + // CellSet cells = new CellSet(); + // //fillContinuousArea_internal(x, y, c1, c2, cells); + // seedFill(new Cell(x, y), c1, c2); + // return cells; + // } + public CellSet fillContinuousArea(int x, int y, char c) { + return fillContinuousArea(new Cell(x, y), c); + } + + public CellSet fillContinuousArea(Cell cell, char c) { + if (isOutOfBounds(cell)) + throw new IllegalArgumentException("Attempted to fill area out of bounds: " + cell); + return seedFillOld(cell, c); + } + + private CellSet seedFill(Cell seed, char newChar) { + CellSet cellsFilled = new CellSet(); + char oldChar = get(seed); + + if (oldChar == newChar) + return cellsFilled; + if (isOutOfBounds(seed)) + return cellsFilled; + + Stack stack = new Stack(); + + stack.push(seed); + + while (!stack.isEmpty()) { + Cell cell = (Cell) stack.pop(); + + //set(cell, newChar); + cellsFilled.add(cell); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if (get(nCell) == oldChar && !cellsFilled.contains(nCell)) + stack.push(nCell); + if (get(sCell) == oldChar && !cellsFilled.contains(sCell)) + stack.push(sCell); + if (get(eCell) == oldChar && !cellsFilled.contains(eCell)) + stack.push(eCell); + if (get(wCell) == oldChar && !cellsFilled.contains(wCell)) + stack.push(wCell); + } + + return cellsFilled; + } + + private CellSet seedFillOld(Cell seed, char newChar) { + CellSet cellsFilled = new CellSet(); + char oldChar = get(seed); + + if (oldChar == newChar) + return cellsFilled; + if (isOutOfBounds(seed)) + return cellsFilled; + + Stack stack = new Stack(); + + stack.push(seed); + + while (!stack.isEmpty()) { + Cell cell = (Cell) stack.pop(); + + set(cell, newChar); + cellsFilled.add(cell); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if (get(nCell) == oldChar) + stack.push(nCell); + if (get(sCell) == oldChar) + stack.push(sCell); + if (get(eCell) == oldChar) + stack.push(eCell); + if (get(wCell) == oldChar) + stack.push(wCell); + } + + return cellsFilled; + } + + + /** + * + * Locates and returns the '*' boundaries that we would + * encounter if we did a flood-fill at seed. + * + * @param seed + * @return + */ + public CellSet findBoundariesExpandingFrom(Cell seed) { + CellSet boundaries = new CellSet(); + char oldChar = get(seed); + + if (isOutOfBounds(seed)) + return boundaries; + + char newChar = 1; //TODO: kludge + + Stack stack = new Stack(); + + stack.push(seed); + + while (!stack.isEmpty()) { + Cell cell = (Cell) stack.pop(); + + set(cell, newChar); + + Cell nCell = cell.getNorth(); + Cell sCell = cell.getSouth(); + Cell eCell = cell.getEast(); + Cell wCell = cell.getWest(); + + if (get(nCell) == oldChar) + stack.push(nCell); + else if (get(nCell) == '*') + boundaries.add(nCell); + + if (get(sCell) == oldChar) + stack.push(sCell); + else if (get(sCell) == '*') + boundaries.add(sCell); + + if (get(eCell) == oldChar) + stack.push(eCell); + else if (get(eCell) == '*') + boundaries.add(eCell); + + if (get(wCell) == oldChar) + stack.push(wCell); + else if (get(wCell) == '*') + boundaries.add(wCell); + } + + return boundaries; + } + + + //TODO: incomplete method seedFillLine() + private CellSet seedFillLine(Cell cell, char newChar) { + CellSet cellsFilled = new CellSet(); + + Stack stack = new Stack(); + + char oldChar = get(cell); + + if (oldChar == newChar) + return cellsFilled; + if (isOutOfBounds(cell)) + return cellsFilled; + + stack.push(new LineSegment(cell.x, cell.x, cell.y, 1)); + stack.push(new LineSegment(cell.x, cell.x, cell.y + 1, -1)); + + int left; + while (!stack.isEmpty()) { + LineSegment segment = (LineSegment) stack.pop(); + int x; + //expand to the left + for ( + x = segment.x1; + x >= 0 && get(x, segment.y) == oldChar; + --x) { + set(x, segment.y, newChar); + cellsFilled.add(new Cell(x, segment.y)); + } + + left = cell.getEast().x; + boolean skip = (x > segment.x1) ? true : false; + + if (left < segment.x1) { //leak on left? + //TODO: i think the first param should be x + stack.push( + //new LineSegment(segment.y, left, segment.x1 - 1, -segment.dy)); + new LineSegment(x, left, segment.y - 1, -segment.dy)); + } + + x = segment.x1 + 1; + do { + if (!skip) { + for (; x < getWidth() && get(x, segment.y) == oldChar; ++x) { + set(x, segment.y, newChar); + cellsFilled.add(new Cell(x, segment.y)); + } + + stack.push(new LineSegment(left, x - 1, segment.y, segment.dy)); + if (x > segment.x2 + 1) //leak on right? + stack.push(new LineSegment(segment.x2 + 1, x - 1, segment.y, -segment.dy)); + } + skip = false; //skip only once + + for (++x; x <= segment.x2 && get(x, segment.y) != oldChar; ++x) { + ; + } + left = x; + } while (x < segment.x2); + } + + return cellsFilled; + } + + public boolean cellContainsDashedLineChar(Cell cell) { + char c = get(cell); + return StringUtils.isOneOf(c, dashedLines); + } + + public boolean loadFrom(String filename) + throws FileNotFoundException, IOException { + return loadFrom(filename, null); + } + + public boolean loadFrom(String filename, ProcessingOptions options) + throws IOException { + + String encoding = (options == null) ? null : options.getCharacterEncoding(); + ArrayList lines = new ArrayList(); + InputStream is; + if ("-".equals(filename)) + is = System.in; + else + is = new FileInputStream(filename); + String[] linesArray = FileUtils.readFile(is, filename, encoding).split("(\r)?\n"); + for (int i = 0; i < linesArray.length; i++) + lines.add(new StringBuilder(linesArray[i])); + + return initialiseWithLines(lines, options); + } + + public boolean initialiseWithText(String text, ProcessingOptions options) throws UnsupportedEncodingException { + + ArrayList lines = new ArrayList(); + String[] linesArray = text.split("(\r)?\n"); + for (int i = 0; i < linesArray.length; i++) + lines.add(new StringBuilder(linesArray[i])); + + return initialiseWithLines(lines, options); + } + + public boolean initialiseWithLines(ArrayList lines, ProcessingOptions options) throws UnsupportedEncodingException { + + //remove blank rows at the bottom + boolean done = false; + int i; + for (i = lines.size() - 1; i >= 0 && !done; i--) { + StringBuilder row = lines.get(i); + if (!StringUtils.isBlank(row.toString())) + done = true; + } + rows = new ArrayList(lines.subList(0, i + 2)); + this.updateModeRows(); + try { + + if (options != null) + fixTabs(options.getTabSize()); + else + fixTabs(options.DEFAULT_TAB_SIZE); + + // make all lines of equal length + // add blank outline around the buffer to prevent fill glitch + // convert tabs to spaces (or remove them if setting is 0) + + int blankBorderSize = 2; + + int maxLength = 0; + int index = 0; + + String encoding = null; + if (options != null) + encoding = options.getCharacterEncoding(); + + Iterator it = rows.iterator(); + while (it.hasNext()) { + String row = it.next().toString(); + if (encoding != null) { + byte[] bytes = row.getBytes(); + row = new String(bytes, encoding); + } + if (row.length() > maxLength) + maxLength = row.length(); + rows.set(index, new StringBuilder(row)); + index++; + } + + it = rows.iterator(); + ArrayList newRows = new ArrayList(); + //TODO: make the following depend on blankBorderSize + + StringBuilder topBottomRow = + new StringBuilder(StringUtils.repeatString(" ", maxLength + blankBorderSize * 2)); + + newRows.add(topBottomRow); + newRows.add(topBottomRow); + while (it.hasNext()) { + StringBuilder row = it.next(); + + if (row.length() < maxLength) { + String borderString = StringUtils.repeatString(" ", blankBorderSize); + StringBuilder newRow = new StringBuilder(); + + newRow.append(borderString); + newRow.append(row); + newRow.append(StringUtils.repeatString(" ", maxLength - row.length())); + newRow.append(borderString); + + newRows.add(newRow); + } else { //TODO: why is the following line like that? + newRows.add(new StringBuilder(" ").append(row).append(" ")); + } + } + //TODO: make the following depend on blankBorderSize + newRows.add(topBottomRow); + newRows.add(topBottomRow); + rows = newRows; + } finally { + this.updateModeRows(); + } + + replaceBullets(); + replaceHumanColorCodes(); + + return true; + } + + private void fixTabs(int tabSize) { + + int rowIndex = 0; + Iterator it = rows.iterator(); + + while (it.hasNext()) { + String row = it.next().toString(); + StringBuilder newRow = new StringBuilder(); + + char[] chars = row.toCharArray(); + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '\t') { + int spacesLeft = tabSize - newRow.length() % tabSize; + if (DEBUG) { + System.out.println("Found tab. Spaces left: " + spacesLeft); + } + String spaces = StringUtils.repeatString(" ", spacesLeft); + newRow.append(spaces); + } else { + String character = Character.toString(chars[i]); + newRow.append(character); + } + } + rows.set(rowIndex, newRow); + rowIndex++; + } + } + + /** + * @return + */ + protected ArrayList getRows() { + return rows; + } + + private boolean isInPlainMode(Cell cell) { + return this.modeRows.get(cell.y).charAt(cell.x) == PLAIN_MODE; + } + + private boolean isInPlainMode(int x, int y) { + return this.modeRows.get(y).charAt(x) == PLAIN_MODE; + } + + public class CellColorPair { + public CellColorPair(Cell cell, Color color) { + this.cell = cell; + this.color = color; + } + + public Color color; + public Cell cell; + } + + public class CellStringPair { + public CellStringPair(Cell cell, String string) { + this.cell = cell; + this.string = string; + } + + public Cell cell; + public String string; + } + + public class CellTagPair { + public CellTagPair(Cell cell, String tag) { + this.cell = cell; + this.tag = tag; + } + + public Cell cell; + public String tag; + } + + + public class Cell { + + public int x, y; + + public Cell(Cell cell) { + this(cell.x, cell.y); + } + + public Cell(int x, int y) { + this.x = x; + this.y = y; + } + + public Cell getNorth() { + return new Cell(x, y - 1); + } + + public Cell getSouth() { + return new Cell(x, y + 1); + } + + public Cell getEast() { + return new Cell(x + 1, y); + } + + public Cell getWest() { + return new Cell(x - 1, y); + } + + public Cell getNW() { + return new Cell(x - 1, y - 1); + } + + public Cell getNE() { + return new Cell(x + 1, y - 1); + } + + public Cell getSW() { + return new Cell(x - 1, y + 1); + } + + public Cell getSE() { + return new Cell(x + 1, y + 1); + } + + public CellSet getNeighbours4() { + CellSet result = new CellSet(); + + result.add(getNorth()); + result.add(getSouth()); + result.add(getWest()); + result.add(getEast()); + + return result; + } + + public CellSet getNeighbours8() { + CellSet result = new CellSet(); + + result.add(getNorth()); + result.add(getSouth()); + result.add(getWest()); + result.add(getEast()); + + result.add(getNW()); + result.add(getNE()); + result.add(getSW()); + result.add(getSE()); + + return result; + } + + + public boolean isNorthOf(Cell cell) { + if (this.y < cell.y) + return true; + return false; + } + + public boolean isSouthOf(Cell cell) { + if (this.y > cell.y) + return true; + return false; + } + + public boolean isWestOf(Cell cell) { + if (this.x < cell.x) + return true; + return false; + } + + public boolean isEastOf(Cell cell) { + if (this.x > cell.x) + return true; + return false; + } + + + public boolean equals(Object o) { + Cell cell = (Cell) o; + if (cell == null) + return false; + if (x == cell.x && y == cell.y) + return true; + else + return false; + } + + public int hashCode() { + return (x << 16) | y; + } + + public boolean isNextTo(int x2, int y2) { + if (Math.abs(x2 - x) == 1 && Math.abs(y2 - y) == 1) + return false; + if (Math.abs(x2 - x) == 1 && y2 == y) + return true; + if (Math.abs(y2 - y) == 1 && x2 == x) + return true; + return false; + } + + public boolean isNextTo(Cell cell) { + if (cell == null) + throw new IllegalArgumentException("cell cannot be null"); + return this.isNextTo(cell.x, cell.y); + } + + public String toString() { + return "(" + x + ", " + y + ")"; + } + + public void scale(int s) { + x = x * s; + y = y * s; + } + + } + + private class LineSegment { + int x1, x2, y, dy; + + public LineSegment(int x1, int x2, int y, int dy) { + this.x1 = x1; + this.x2 = x2; + this.y = y; + this.dy = dy; + } + } } diff --git a/test/java/org/stathissideris/ascii2image/test/CellSetTest.java b/test/java/org/stathissideris/ascii2image/test/CellSetTest.java index 4d602d4..f996b64 100644 --- a/test/java/org/stathissideris/ascii2image/test/CellSetTest.java +++ b/test/java/org/stathissideris/ascii2image/test/CellSetTest.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,34 +15,35 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.test; -import static org.junit.Assert.*; - import org.junit.Before; import org.junit.Test; import org.stathissideris.ascii2image.text.CellSet; import org.stathissideris.ascii2image.text.TextGrid; +import static org.junit.Assert.assertTrue; + public class CellSetTest { - - TextGrid g = new TextGrid(); - CellSet set = new CellSet(); - - @Before public void setUp() { - set.add(g.new Cell(10, 20)); - set.add(g.new Cell(10, 60)); - set.add(g.new Cell(10, 30)); - set.add(g.new Cell(60, 20)); - } - - @Test public void testContains() { - TextGrid.Cell cell1 = g.new Cell(10, 20); - TextGrid.Cell cell2 = g.new Cell(10, 20); - assertTrue(cell1.equals(cell2)); - assertTrue(set.contains(cell1)); - } + TextGrid g = new TextGrid(); + CellSet set = new CellSet(); + + @Before + public void setUp() { + set.add(g.new Cell(10, 20)); + set.add(g.new Cell(10, 60)); + set.add(g.new Cell(10, 30)); + set.add(g.new Cell(60, 20)); + } + + @Test + public void testContains() { + TextGrid.Cell cell1 = g.new Cell(10, 20); + TextGrid.Cell cell2 = g.new Cell(10, 20); + + assertTrue(cell1.equals(cell2)); + assertTrue(set.contains(cell1)); + } } diff --git a/test/java/org/stathissideris/ascii2image/test/GenerateExpectedImages.java b/test/java/org/stathissideris/ascii2image/test/GenerateExpectedImages.java index c9c2bec..abae5f2 100644 --- a/test/java/org/stathissideris/ascii2image/test/GenerateExpectedImages.java +++ b/test/java/org/stathissideris/ascii2image/test/GenerateExpectedImages.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,20 +15,19 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.test; /** * Run this to generate the expected (correct) images for unit testing * when the code is at a state that produces correct output. - * + * * @author sideris * */ public class GenerateExpectedImages { - public static void main(String[] args) { - VisualTester.generateImages(VisualTester.getFilesToRender(), "tests/images-expected"); - System.out.println("Done"); - } + public static void main(String[] args) { + VisualTester.generateImages(VisualTester.getFilesToRender(), "tests/images-expected"); + System.out.println("Done"); + } } diff --git a/test/java/org/stathissideris/ascii2image/test/GridPatternTest.java b/test/java/org/stathissideris/ascii2image/test/GridPatternTest.java index 44b5207..79eed16 100644 --- a/test/java/org/stathissideris/ascii2image/test/GridPatternTest.java +++ b/test/java/org/stathissideris/ascii2image/test/GridPatternTest.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,35 +15,33 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.test; -import static org.junit.Assert.*; - import org.junit.Before; import org.junit.Test; -import org.stathissideris.ascii2image.text.CellSet; import org.stathissideris.ascii2image.text.GridPattern; import org.stathissideris.ascii2image.text.TextGrid; public class GridPatternTest { - TextGrid g = new TextGrid(6,4); - GridPattern pattern = new GridPattern(); - - @Before public void setUp() { - g.setRow(0, "+----+"); - g.setRow(1, "| |"); - g.setRow(2, "| |"); - g.setRow(3, "+----+"); - } - - @Test public void testContains() { - pattern.isMatchedBy(g); - pattern.isMatchedBy(g); - pattern.isMatchedBy(g); - pattern.isMatchedBy(g); - pattern.isMatchedBy(g); - } + TextGrid g = new TextGrid(6, 4); + GridPattern pattern = new GridPattern(); + + @Before + public void setUp() { + g.setRow(0, "+----+"); + g.setRow(1, "| |"); + g.setRow(2, "| |"); + g.setRow(3, "+----+"); + } + + @Test + public void testContains() { + pattern.isMatchedBy(g); + pattern.isMatchedBy(g); + pattern.isMatchedBy(g); + pattern.isMatchedBy(g); + pattern.isMatchedBy(g); + } } diff --git a/test/java/org/stathissideris/ascii2image/test/TextGridTest.java b/test/java/org/stathissideris/ascii2image/test/TextGridTest.java index d1425c2..0320bcd 100644 --- a/test/java/org/stathissideris/ascii2image/test/TextGridTest.java +++ b/test/java/org/stathissideris/ascii2image/test/TextGridTest.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,287 +15,297 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.test; -import static org.junit.Assert.*; - -import java.io.FileNotFoundException; -import java.io.IOException; - import org.junit.Before; import org.junit.Test; import org.stathissideris.ascii2image.text.AbstractionGrid; import org.stathissideris.ascii2image.text.CellSet; import org.stathissideris.ascii2image.text.TextGrid; +import java.io.FileNotFoundException; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + public class TextGridTest { - - @Before public void setUp() { - } - - @Test public void testFillContinuousAreaSquareOutside() throws FileNotFoundException, IOException { - TextGrid squareGrid; - squareGrid = new TextGrid(); - squareGrid.loadFrom("tests/text/simple_square01.txt"); - - CellSet filledArea = squareGrid.fillContinuousArea(0, 0, '*'); - int size = filledArea.size(); - assertEquals(64, size); - - CellSet expectedFilledArea = new CellSet(); - addSquareToCellSet(squareGrid, expectedFilledArea, 0,0, 11,2); - addSquareToCellSet(squareGrid, expectedFilledArea, 0,7, 11,2); - addSquareToCellSet(squareGrid, expectedFilledArea, 0,2, 2,5); - addSquareToCellSet(squareGrid, expectedFilledArea, 9,2, 2,5); - assertEquals(expectedFilledArea, filledArea); - } - - @Test public void testFillContinuousAreaSquareInside() throws FileNotFoundException, IOException { - TextGrid squareGrid; - squareGrid = new TextGrid(); - squareGrid.loadFrom("tests/text/simple_square01.txt"); - - CellSet filledArea = squareGrid.fillContinuousArea(3, 3, '*'); - int size = filledArea.size(); - assertEquals(15, size); - - CellSet expectedFilledArea = new CellSet(); - addSquareToCellSet(squareGrid, expectedFilledArea, 3,3, 5,3); - assertEquals(expectedFilledArea, filledArea); - } - - @Test public void testFillContinuousAreaUInside() throws FileNotFoundException, IOException { - TextGrid uGrid; - uGrid = new TextGrid(); - uGrid.loadFrom("tests/text/simple_U01.txt"); - - CellSet filledArea = uGrid.fillContinuousArea(3, 3, '*'); - int size = filledArea.size(); - - assertEquals(62, size); - - CellSet expectedFilledArea = new CellSet(); - addSquareToCellSet(uGrid, expectedFilledArea, 3,3, 5,5); - addSquareToCellSet(uGrid, expectedFilledArea, 14,3, 5,5); - addSquareToCellSet(uGrid, expectedFilledArea, 8,6, 6,2); - assertEquals(expectedFilledArea, filledArea); - } - - @Test public void testFillContinuousAreaUOutside() throws FileNotFoundException, IOException { - TextGrid uGrid; - uGrid = new TextGrid(); - uGrid.loadFrom("tests/text/simple_U01.txt"); - - CellSet filledArea = uGrid.fillContinuousArea(0, 0, '*'); - int size = filledArea.size(); - - assertEquals(128, size); - - CellSet expectedFilledArea = new CellSet(); - addSquareToCellSet(uGrid, expectedFilledArea, 0,0, 2,11); - addSquareToCellSet(uGrid, expectedFilledArea, 20,0, 2,11); - - addSquareToCellSet(uGrid, expectedFilledArea, 0,0, 22, 2); - addSquareToCellSet(uGrid, expectedFilledArea, 0,9, 22, 2); - - addSquareToCellSet(uGrid, expectedFilledArea, 9,2, 4, 3); - - assertEquals(expectedFilledArea, filledArea); - } - - @Test public void testFillContinuousAreaSOutside() throws FileNotFoundException, IOException { - TextGrid uGrid; - uGrid = new TextGrid(); - uGrid.loadFrom("tests/text/simple_S01.txt"); - - CellSet filledArea = uGrid.fillContinuousArea(0, 0, '*'); - int size = filledArea.size(); - - assertEquals(246, size); - - CellSet expectedFilledArea = new CellSet(); - addSquareToCellSet(uGrid, expectedFilledArea, 0, 0, 25, 2); - addSquareToCellSet(uGrid, expectedFilledArea, 0,12, 25, 2); - - addSquareToCellSet(uGrid, expectedFilledArea, 0, 0, 2,14); - addSquareToCellSet(uGrid, expectedFilledArea, 23, 0, 2,14); - - addSquareToCellSet(uGrid, expectedFilledArea, 9, 0, 7, 7); - addSquareToCellSet(uGrid, expectedFilledArea, 0, 7, 9, 7); - addSquareToCellSet(uGrid, expectedFilledArea, 16, 7, 9, 7); - - expectedFilledArea.add(uGrid.new Cell(22, 6)); - - assertEquals(expectedFilledArea, filledArea); - } - - @Test public void testFillContinuousAreaSInside1() throws FileNotFoundException, IOException { - TextGrid uGrid; - uGrid = new TextGrid(); - uGrid.loadFrom("tests/text/simple_S01.txt"); - - CellSet filledArea = uGrid.fillContinuousArea(3, 3, '*'); - int size = filledArea.size(); - - assertEquals(15, size); - - CellSet expectedFilledArea = new CellSet(); - addSquareToCellSet(uGrid, expectedFilledArea, 3, 3, 5, 3); - assertEquals(expectedFilledArea, filledArea); - } - - @Test public void testFillContinuousAreaSInside2() throws FileNotFoundException, IOException { - TextGrid uGrid; - uGrid = new TextGrid(); - uGrid.loadFrom("tests/text/simple_S01.txt"); - - CellSet filledArea = uGrid.fillContinuousArea(17, 3, '*'); - int size = filledArea.size(); - - assertEquals(15, size); - - CellSet expectedFilledArea = new CellSet(); - addSquareToCellSet(uGrid, expectedFilledArea, 17, 3, 5, 3); - assertEquals(expectedFilledArea, filledArea); - } - - - @Test public void testFindBoundariesExpandingFromSquare() throws FileNotFoundException, IOException { - TextGrid grid; - grid = new TextGrid(); - grid.loadFrom("tests/text/simple_square01.txt"); - - CellSet wholeGridSet = new CellSet(); - addSquareToCellSet(grid, wholeGridSet, 0,0, grid.getWidth(),grid.getHeight()); - - TextGrid copyGrid = new AbstractionGrid(grid, wholeGridSet).getCopyOfInternalBuffer(); - CellSet boundaries = copyGrid.findBoundariesExpandingFrom(copyGrid.new Cell(8, 8)); - int size = boundaries.size(); - - assertEquals(56, size); - - CellSet expectedBoundaries = new CellSet(); - - addSquareToCellSet(copyGrid, expectedBoundaries, 8, 7, 17,1); - addSquareToCellSet(copyGrid, expectedBoundaries, 8,19, 17,1); - - addSquareToCellSet(copyGrid, expectedBoundaries, 7, 8, 1,11); - addSquareToCellSet(copyGrid, expectedBoundaries,25, 8, 1,11); - - assertEquals(expectedBoundaries, boundaries); - - } - - @Test public void testFindBoundariesExpandingFromUInside() throws FileNotFoundException, IOException { - TextGrid grid; - grid = new TextGrid(); - grid.loadFrom("tests/text/simple_U01.txt"); - - CellSet wholeGridSet = new CellSet(); - addSquareToCellSet(grid, wholeGridSet, 0,0, grid.getWidth(),grid.getHeight()); - - TextGrid copyGrid = new AbstractionGrid(grid, wholeGridSet).getCopyOfInternalBuffer(); - CellSet boundaries = copyGrid.findBoundariesExpandingFrom(copyGrid.new Cell(8, 8)); - int size1 = boundaries.size(); - - assertEquals(150, size1); - - String expectedBoundariesString = - "(47,25)/(43,7)/(25,25)/(58,18)/(18,7)/(52,7)/(58,12)/(13,25)/(7,8)/" - +"(7,11)/(57,7)/(7,10)/(24,25)/(18,25)/(12,7)/(37,25)/(58,19)/(35,16)/(58,11)/(25,12)/" - +"(54,25)/(29,16)/(16,25)/(49,7)/(7,12)/(26,25)/(19,7)/(58,17)/(55,25)/(46,25)/(17,25)/" - +"(58,13)/(32,16)/(25,11)/(51,25)/(21,25)/(58,14)/(36,16)/(7,16)/(32,25)/(55,7)/(25,8)/" - +"(10,25)/(58,21)/(20,7)/(27,25)/(31,16)/(58,9)/(45,25)/(58,20)/(56,25)/(10,7)/(39,25)/" - +"(44,7)/(58,10)/(33,16)/(46,7)/(7,9)/(58,22)/(17,7)/(48,25)/(7,15)/(38,16)/(54,7)/(11,25)/" - +"(9,7)/(7,14)/(58,24)/(40,25)/(30,16)/(58,23)/(47,7)/(7,13)/(19,25)/(8,7)/(25,16)/(53,25)/" - +"(39,16)/(23,7)/(42,25)/(53,7)/(40,15)/(7,23)/(12,25)/(48,7)/(30,25)/(42,7)/(7,24)/(40,14)/" - +"(14,7)/(35,25)/(52,25)/(58,16)/(25,15)/(9,25)/(40,16)/(7,22)/(43,25)/(25,9)/(29,25)/" - +"(56,7)/(28,16)/(22,7)/(8,25)/(25,10)/(15,7)/(41,7)/(34,25)/(11,7)/(45,7)/(7,21)/(7,18)/" - +"(38,25)/(50,7)/(58,15)/(15,25)/(40,12)/(27,16)/(21,7)/(57,25)/(44,25)/(25,13)/(37,16)/" - +"(16,7)/(7,17)/(25,14)/(50,25)/(20,25)/(33,25)/(40,13)/(22,25)/(26,16)/(24,7)/(31,25)/" - +"(40,8)/(7,19)/(58,8)/(41,25)/(28,25)/(40,11)/(14,25)/(34,16)/(51,7)/(7,20)/(40,10)/" - +"(23,25)/(13,7)/(49,25)/(40,9)/(36,25)"; - - CellSet expectedBoundaries = cellSetFromCellsString(expectedBoundariesString, grid); - - assertEquals(expectedBoundaries, boundaries); - } - - - @Test public void testFindBoundariesExpandingFromUOutside() throws FileNotFoundException, IOException { - TextGrid grid; - grid = new TextGrid(); - grid.loadFrom("tests/text/simple_U01.txt"); - - CellSet wholeGridSet = new CellSet(); - addSquareToCellSet(grid, wholeGridSet, 0,0, grid.getWidth(),grid.getHeight()); - - TextGrid copyGrid = new AbstractionGrid(grid, wholeGridSet).getCopyOfInternalBuffer(); - CellSet boundaries = copyGrid.findBoundariesExpandingFrom(copyGrid.new Cell(0, 0)); - int size = boundaries.size(); - - assertEquals(154, size); - - System.out.println(boundaries.getCellsAsString()); - - String expectedBoundariesString = - "(47,25)/(43,7)/(25,25)/(58,18)/(18,7)/(52,7)/(13,25)/(58,12)/(7,8)/(7,11)/" - +"(7,10)/(57,7)/(24,25)/(18,25)/(12,7)/(37,25)/(58,19)/(35,16)/(58,11)/" - +"(25,12)/(54,25)/(29,16)/(16,25)/(7,7)/(7,12)/(49,7)/(26,25)/(19,7)/(58,17)/" - +"(55,25)/(46,25)/(17,25)/(58,13)/(32,16)/(25,11)/(51,25)/(21,25)/(36,16)/" - +"(58,14)/(7,16)/(32,25)/(25,8)/(10,25)/(55,7)/(58,21)/(20,7)/(27,25)/(31,16)/" - +"(58,9)/(45,25)/(58,20)/(25,7)/(56,25)/(10,7)/(39,25)/(44,7)/(33,16)/(58,10)/" - +"(7,9)/(46,7)/(58,22)/(17,7)/(48,25)/(7,15)/(38,16)/(54,7)/(11,25)/(9,7)/(7,14)/" - +"(58,24)/(40,25)/(30,16)/(58,23)/(7,13)/(47,7)/(19,25)/(8,7)/(53,25)/(39,16)/(23,7)/" - +"(42,25)/(40,15)/(7,23)/(12,25)/(53,7)/(48,7)/(30,25)/(7,24)/(7,25)/(42,7)/(40,14)/" - +"(14,7)/(52,25)/(35,25)/(58,16)/(25,15)/(9,25)/(7,22)/(43,25)/(25,9)/(29,25)/(28,16)/" - +"(56,7)/(22,7)/(25,10)/(8,25)/(15,7)/(41,7)/(34,25)/(11,7)/(7,21)/(45,7)/(7,18)/" - +"(40,7)/(38,25)/(50,7)/(15,25)/(58,15)/(40,12)/(27,16)/(21,7)/(57,25)/(44,25)/" - +"(25,13)/(37,16)/(16,7)/(25,14)/(7,17)/(50,25)/(33,25)/(20,25)/(40,13)/(22,25)/" - +"(26,16)/(24,7)/(31,25)/(40,8)/(7,19)/(58,25)/(58,8)/(41,25)/(28,25)/(40,11)/" - +"(14,25)/(34,16)/(58,7)/(7,20)/(51,7)/(40,10)/(23,25)/(13,7)/(49,25)/(40,9)/(36,25)"; - - - CellSet expectedBoundaries = cellSetFromCellsString(expectedBoundariesString, grid); - - assertEquals(expectedBoundaries, boundaries); - } - - - @Test public void testCellSetFromCellsString(){ - TextGrid grid; - grid = new TextGrid(); - - String str = "(9,7)/(0, 2)/(3 ,2)/(5,3)"; - CellSet cellSet = cellSetFromCellsString(str, grid); - - CellSet expectedCellSet = new CellSet(); - expectedCellSet.add(grid.new Cell(0, 2)); - expectedCellSet.add(grid.new Cell(3, 2)); - expectedCellSet.add(grid.new Cell(5, 3)); - expectedCellSet.add(grid.new Cell(9, 7)); - - assertEquals(expectedCellSet, cellSet); - } - - private void addSquareToCellSet(TextGrid grid, CellSet cellSet, int x, int y, int width, int height) { - for(int xx = 0; xx < width; xx++){ - for(int yy = 0; yy < height; yy++){ - cellSet.add(grid.new Cell(x + xx, y + yy)); - } - } - } - - private CellSet cellSetFromCellsString(String str, TextGrid grid){ - String[] cellStrings = str.split("/"); - CellSet set = new CellSet(); - for(String cellString : cellStrings) { - int x = Integer.parseInt(cellString.substring(1, cellString.indexOf(",")).trim()); - int y = Integer.parseInt(cellString.substring(cellString.indexOf(",") + 1, cellString.length() - 1).trim()); - set.add(grid.new Cell(x, y)); - } - return set; - } + + @Before + public void setUp() { + } + + @Test + public void testFillContinuousAreaSquareOutside() throws FileNotFoundException, IOException { + TextGrid squareGrid; + squareGrid = new TextGrid(); + squareGrid.loadFrom("tests/text/simple_square01.txt"); + + CellSet filledArea = squareGrid.fillContinuousArea(0, 0, '*'); + int size = filledArea.size(); + assertEquals(64, size); + + CellSet expectedFilledArea = new CellSet(); + addSquareToCellSet(squareGrid, expectedFilledArea, 0, 0, 11, 2); + addSquareToCellSet(squareGrid, expectedFilledArea, 0, 7, 11, 2); + addSquareToCellSet(squareGrid, expectedFilledArea, 0, 2, 2, 5); + addSquareToCellSet(squareGrid, expectedFilledArea, 9, 2, 2, 5); + assertEquals(expectedFilledArea, filledArea); + } + + @Test + public void testFillContinuousAreaSquareInside() throws FileNotFoundException, IOException { + TextGrid squareGrid; + squareGrid = new TextGrid(); + squareGrid.loadFrom("tests/text/simple_square01.txt"); + + CellSet filledArea = squareGrid.fillContinuousArea(3, 3, '*'); + int size = filledArea.size(); + assertEquals(15, size); + + CellSet expectedFilledArea = new CellSet(); + addSquareToCellSet(squareGrid, expectedFilledArea, 3, 3, 5, 3); + assertEquals(expectedFilledArea, filledArea); + } + + @Test + public void testFillContinuousAreaUInside() throws FileNotFoundException, IOException { + TextGrid uGrid; + uGrid = new TextGrid(); + uGrid.loadFrom("tests/text/simple_U01.txt"); + + CellSet filledArea = uGrid.fillContinuousArea(3, 3, '*'); + int size = filledArea.size(); + + assertEquals(62, size); + + CellSet expectedFilledArea = new CellSet(); + addSquareToCellSet(uGrid, expectedFilledArea, 3, 3, 5, 5); + addSquareToCellSet(uGrid, expectedFilledArea, 14, 3, 5, 5); + addSquareToCellSet(uGrid, expectedFilledArea, 8, 6, 6, 2); + assertEquals(expectedFilledArea, filledArea); + } + + @Test + public void testFillContinuousAreaUOutside() throws FileNotFoundException, IOException { + TextGrid uGrid; + uGrid = new TextGrid(); + uGrid.loadFrom("tests/text/simple_U01.txt"); + + CellSet filledArea = uGrid.fillContinuousArea(0, 0, '*'); + int size = filledArea.size(); + + assertEquals(128, size); + + CellSet expectedFilledArea = new CellSet(); + addSquareToCellSet(uGrid, expectedFilledArea, 0, 0, 2, 11); + addSquareToCellSet(uGrid, expectedFilledArea, 20, 0, 2, 11); + + addSquareToCellSet(uGrid, expectedFilledArea, 0, 0, 22, 2); + addSquareToCellSet(uGrid, expectedFilledArea, 0, 9, 22, 2); + + addSquareToCellSet(uGrid, expectedFilledArea, 9, 2, 4, 3); + + assertEquals(expectedFilledArea, filledArea); + } + + @Test + public void testFillContinuousAreaSOutside() throws FileNotFoundException, IOException { + TextGrid uGrid; + uGrid = new TextGrid(); + uGrid.loadFrom("tests/text/simple_S01.txt"); + + CellSet filledArea = uGrid.fillContinuousArea(0, 0, '*'); + int size = filledArea.size(); + + assertEquals(246, size); + + CellSet expectedFilledArea = new CellSet(); + addSquareToCellSet(uGrid, expectedFilledArea, 0, 0, 25, 2); + addSquareToCellSet(uGrid, expectedFilledArea, 0, 12, 25, 2); + + addSquareToCellSet(uGrid, expectedFilledArea, 0, 0, 2, 14); + addSquareToCellSet(uGrid, expectedFilledArea, 23, 0, 2, 14); + + addSquareToCellSet(uGrid, expectedFilledArea, 9, 0, 7, 7); + addSquareToCellSet(uGrid, expectedFilledArea, 0, 7, 9, 7); + addSquareToCellSet(uGrid, expectedFilledArea, 16, 7, 9, 7); + + expectedFilledArea.add(uGrid.new Cell(22, 6)); + + assertEquals(expectedFilledArea, filledArea); + } + + @Test + public void testFillContinuousAreaSInside1() throws FileNotFoundException, IOException { + TextGrid uGrid; + uGrid = new TextGrid(); + uGrid.loadFrom("tests/text/simple_S01.txt"); + + CellSet filledArea = uGrid.fillContinuousArea(3, 3, '*'); + int size = filledArea.size(); + + assertEquals(15, size); + + CellSet expectedFilledArea = new CellSet(); + addSquareToCellSet(uGrid, expectedFilledArea, 3, 3, 5, 3); + assertEquals(expectedFilledArea, filledArea); + } + + @Test + public void testFillContinuousAreaSInside2() throws FileNotFoundException, IOException { + TextGrid uGrid; + uGrid = new TextGrid(); + uGrid.loadFrom("tests/text/simple_S01.txt"); + + CellSet filledArea = uGrid.fillContinuousArea(17, 3, '*'); + int size = filledArea.size(); + + assertEquals(15, size); + + CellSet expectedFilledArea = new CellSet(); + addSquareToCellSet(uGrid, expectedFilledArea, 17, 3, 5, 3); + assertEquals(expectedFilledArea, filledArea); + } + + + @Test + public void testFindBoundariesExpandingFromSquare() throws FileNotFoundException, IOException { + TextGrid grid; + grid = new TextGrid(); + grid.loadFrom("tests/text/simple_square01.txt"); + + CellSet wholeGridSet = new CellSet(); + addSquareToCellSet(grid, wholeGridSet, 0, 0, grid.getWidth(), grid.getHeight()); + + TextGrid copyGrid = new AbstractionGrid(grid, wholeGridSet).getCopyOfInternalBuffer(); + CellSet boundaries = copyGrid.findBoundariesExpandingFrom(copyGrid.new Cell(8, 8)); + int size = boundaries.size(); + + assertEquals(56, size); + + CellSet expectedBoundaries = new CellSet(); + + addSquareToCellSet(copyGrid, expectedBoundaries, 8, 7, 17, 1); + addSquareToCellSet(copyGrid, expectedBoundaries, 8, 19, 17, 1); + + addSquareToCellSet(copyGrid, expectedBoundaries, 7, 8, 1, 11); + addSquareToCellSet(copyGrid, expectedBoundaries, 25, 8, 1, 11); + + assertEquals(expectedBoundaries, boundaries); + + } + + @Test + public void testFindBoundariesExpandingFromUInside() throws FileNotFoundException, IOException { + TextGrid grid; + grid = new TextGrid(); + grid.loadFrom("tests/text/simple_U01.txt"); + + CellSet wholeGridSet = new CellSet(); + addSquareToCellSet(grid, wholeGridSet, 0, 0, grid.getWidth(), grid.getHeight()); + + TextGrid copyGrid = new AbstractionGrid(grid, wholeGridSet).getCopyOfInternalBuffer(); + CellSet boundaries = copyGrid.findBoundariesExpandingFrom(copyGrid.new Cell(8, 8)); + int size1 = boundaries.size(); + + assertEquals(150, size1); + + String expectedBoundariesString = + "(47,25)/(43,7)/(25,25)/(58,18)/(18,7)/(52,7)/(58,12)/(13,25)/(7,8)/" + + "(7,11)/(57,7)/(7,10)/(24,25)/(18,25)/(12,7)/(37,25)/(58,19)/(35,16)/(58,11)/(25,12)/" + + "(54,25)/(29,16)/(16,25)/(49,7)/(7,12)/(26,25)/(19,7)/(58,17)/(55,25)/(46,25)/(17,25)/" + + "(58,13)/(32,16)/(25,11)/(51,25)/(21,25)/(58,14)/(36,16)/(7,16)/(32,25)/(55,7)/(25,8)/" + + "(10,25)/(58,21)/(20,7)/(27,25)/(31,16)/(58,9)/(45,25)/(58,20)/(56,25)/(10,7)/(39,25)/" + + "(44,7)/(58,10)/(33,16)/(46,7)/(7,9)/(58,22)/(17,7)/(48,25)/(7,15)/(38,16)/(54,7)/(11,25)/" + + "(9,7)/(7,14)/(58,24)/(40,25)/(30,16)/(58,23)/(47,7)/(7,13)/(19,25)/(8,7)/(25,16)/(53,25)/" + + "(39,16)/(23,7)/(42,25)/(53,7)/(40,15)/(7,23)/(12,25)/(48,7)/(30,25)/(42,7)/(7,24)/(40,14)/" + + "(14,7)/(35,25)/(52,25)/(58,16)/(25,15)/(9,25)/(40,16)/(7,22)/(43,25)/(25,9)/(29,25)/" + + "(56,7)/(28,16)/(22,7)/(8,25)/(25,10)/(15,7)/(41,7)/(34,25)/(11,7)/(45,7)/(7,21)/(7,18)/" + + "(38,25)/(50,7)/(58,15)/(15,25)/(40,12)/(27,16)/(21,7)/(57,25)/(44,25)/(25,13)/(37,16)/" + + "(16,7)/(7,17)/(25,14)/(50,25)/(20,25)/(33,25)/(40,13)/(22,25)/(26,16)/(24,7)/(31,25)/" + + "(40,8)/(7,19)/(58,8)/(41,25)/(28,25)/(40,11)/(14,25)/(34,16)/(51,7)/(7,20)/(40,10)/" + + "(23,25)/(13,7)/(49,25)/(40,9)/(36,25)"; + + CellSet expectedBoundaries = cellSetFromCellsString(expectedBoundariesString, grid); + + assertEquals(expectedBoundaries, boundaries); + } + + + @Test + public void testFindBoundariesExpandingFromUOutside() throws FileNotFoundException, IOException { + TextGrid grid; + grid = new TextGrid(); + grid.loadFrom("tests/text/simple_U01.txt"); + + CellSet wholeGridSet = new CellSet(); + addSquareToCellSet(grid, wholeGridSet, 0, 0, grid.getWidth(), grid.getHeight()); + + TextGrid copyGrid = new AbstractionGrid(grid, wholeGridSet).getCopyOfInternalBuffer(); + CellSet boundaries = copyGrid.findBoundariesExpandingFrom(copyGrid.new Cell(0, 0)); + int size = boundaries.size(); + + assertEquals(154, size); + + System.out.println(boundaries.getCellsAsString()); + + String expectedBoundariesString = + "(47,25)/(43,7)/(25,25)/(58,18)/(18,7)/(52,7)/(13,25)/(58,12)/(7,8)/(7,11)/" + + "(7,10)/(57,7)/(24,25)/(18,25)/(12,7)/(37,25)/(58,19)/(35,16)/(58,11)/" + + "(25,12)/(54,25)/(29,16)/(16,25)/(7,7)/(7,12)/(49,7)/(26,25)/(19,7)/(58,17)/" + + "(55,25)/(46,25)/(17,25)/(58,13)/(32,16)/(25,11)/(51,25)/(21,25)/(36,16)/" + + "(58,14)/(7,16)/(32,25)/(25,8)/(10,25)/(55,7)/(58,21)/(20,7)/(27,25)/(31,16)/" + + "(58,9)/(45,25)/(58,20)/(25,7)/(56,25)/(10,7)/(39,25)/(44,7)/(33,16)/(58,10)/" + + "(7,9)/(46,7)/(58,22)/(17,7)/(48,25)/(7,15)/(38,16)/(54,7)/(11,25)/(9,7)/(7,14)/" + + "(58,24)/(40,25)/(30,16)/(58,23)/(7,13)/(47,7)/(19,25)/(8,7)/(53,25)/(39,16)/(23,7)/" + + "(42,25)/(40,15)/(7,23)/(12,25)/(53,7)/(48,7)/(30,25)/(7,24)/(7,25)/(42,7)/(40,14)/" + + "(14,7)/(52,25)/(35,25)/(58,16)/(25,15)/(9,25)/(7,22)/(43,25)/(25,9)/(29,25)/(28,16)/" + + "(56,7)/(22,7)/(25,10)/(8,25)/(15,7)/(41,7)/(34,25)/(11,7)/(7,21)/(45,7)/(7,18)/" + + "(40,7)/(38,25)/(50,7)/(15,25)/(58,15)/(40,12)/(27,16)/(21,7)/(57,25)/(44,25)/" + + "(25,13)/(37,16)/(16,7)/(25,14)/(7,17)/(50,25)/(33,25)/(20,25)/(40,13)/(22,25)/" + + "(26,16)/(24,7)/(31,25)/(40,8)/(7,19)/(58,25)/(58,8)/(41,25)/(28,25)/(40,11)/" + + "(14,25)/(34,16)/(58,7)/(7,20)/(51,7)/(40,10)/(23,25)/(13,7)/(49,25)/(40,9)/(36,25)"; + + CellSet expectedBoundaries = cellSetFromCellsString(expectedBoundariesString, grid); + + assertEquals(expectedBoundaries, boundaries); + } + + + @Test + public void testCellSetFromCellsString() { + TextGrid grid; + grid = new TextGrid(); + + String str = "(9,7)/(0, 2)/(3 ,2)/(5,3)"; + CellSet cellSet = cellSetFromCellsString(str, grid); + + CellSet expectedCellSet = new CellSet(); + expectedCellSet.add(grid.new Cell(0, 2)); + expectedCellSet.add(grid.new Cell(3, 2)); + expectedCellSet.add(grid.new Cell(5, 3)); + expectedCellSet.add(grid.new Cell(9, 7)); + + assertEquals(expectedCellSet, cellSet); + } + + private void addSquareToCellSet(TextGrid grid, CellSet cellSet, int x, int y, int width, int height) { + for (int xx = 0; xx < width; xx++) { + for (int yy = 0; yy < height; yy++) { + cellSet.add(grid.new Cell(x + xx, y + yy)); + } + } + } + + private CellSet cellSetFromCellsString(String str, TextGrid grid) { + String[] cellStrings = str.split("/"); + CellSet set = new CellSet(); + for (String cellString : cellStrings) { + int x = Integer.parseInt(cellString.substring(1, cellString.indexOf(",")).trim()); + int y = Integer.parseInt(cellString.substring(cellString.indexOf(",") + 1, cellString.length() - 1).trim()); + set.add(grid.new Cell(x, y)); + } + return set; + } } diff --git a/test/java/org/stathissideris/ascii2image/test/TrivialTest.java b/test/java/org/stathissideris/ascii2image/test/TrivialTest.java index 755cec0..f87dfd8 100644 --- a/test/java/org/stathissideris/ascii2image/test/TrivialTest.java +++ b/test/java/org/stathissideris/ascii2image/test/TrivialTest.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,16 +15,16 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.test; -import static org.junit.Assert.*; - import org.junit.Test; +import static org.junit.Assert.assertTrue; + public class TrivialTest { - @Test public void testContains() { - assertTrue(true); - } + @Test + public void testContains() { + assertTrue(true); + } } diff --git a/test/java/org/stathissideris/ascii2image/test/VisualTester.java b/test/java/org/stathissideris/ascii2image/test/VisualTester.java index 75bc898..9943d1a 100644 --- a/test/java/org/stathissideris/ascii2image/test/VisualTester.java +++ b/test/java/org/stathissideris/ascii2image/test/VisualTester.java @@ -1,10 +1,10 @@ /** * ditaa - Diagrams Through Ascii Art - * + * * Copyright (C) 2004-2011 Efstathios Sideris * * ditaa is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as + * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * @@ -15,13 +15,30 @@ * * You should have received a copy of the GNU Lesser General Public * License along with ditaa. If not, see . - * */ package org.stathissideris.ascii2image.test; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.stathissideris.ascii2image.core.ConversionOptions; +import org.stathissideris.ascii2image.graphics.BitmapRenderer; +import org.stathissideris.ascii2image.graphics.Diagram; +import org.stathissideris.ascii2image.graphics.ImageHandler; +import org.stathissideris.ascii2image.graphics.SVGRenderer; +import org.stathissideris.ascii2image.text.TextGrid; + +import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; -import java.io.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -31,298 +48,284 @@ import java.util.List; import java.util.Set; -import javax.imageio.ImageIO; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import static org.junit.Assert.*; -import org.stathissideris.ascii2image.core.ConversionOptions; -import org.stathissideris.ascii2image.graphics.BitmapRenderer; -import org.stathissideris.ascii2image.graphics.Diagram; -import org.stathissideris.ascii2image.graphics.ImageHandler; -import org.stathissideris.ascii2image.graphics.SVGRenderer; -import org.stathissideris.ascii2image.text.TextGrid; +import static org.junit.Assert.assertTrue; /** * If ran as a Java application, it produces an HTML report for manual * inspection. If ran as a junit test it runs a pixel-by-pixel * comparison between the images in the "images-expected" and the * images generated by the test. - * + * * @author Efstathios Sideris */ @RunWith(Parameterized.class) public class VisualTester { - private static final String HTMLReportName = "test_suite"; - private static final String expectedDir = "test-resources/images-expected"; - private static final String actualDir = "test-resources/images"; - - private File textFile; - private int index; - - public static void main(String[] args){ - generate(); - } - - public static void generate() { - String reportDir = "test-resources/images"; - VisualTester.createHTMLTestReport(getFilesToRender(), reportDir, HTMLReportName); - System.out.println("Tests completed"); - } - - @Test - public void compareImages() throws FileNotFoundException, IOException { - ConversionOptions options = new ConversionOptions(); - File actualFile = new File(actualDir + File.separator + textFile.getName() + ".png"); - File expectedFile = new File(expectedDir + File.separator + textFile.getName() + ".png"); - - System.out.println(index + ") Rendering "+textFile+" to "+actualFile); - - if(!expectedFile.exists()){ - System.out.println("Skipping " + textFile + " -- reference image does not exist"); - throw new FileNotFoundException("Reference image "+expectedFile+" does not exist"); - } - - TextGrid grid = new TextGrid(); - grid.loadFrom(textFile.toString()); - Diagram diagram = new Diagram(grid, options); - - RenderedImage image = new BitmapRenderer().renderToImage(diagram, options.renderingOptions); - - File file = new File(actualFile.getAbsolutePath()); - ImageIO.write(image, "png", file); - - //compare images pixel-by-pixel - BufferedImage actualImage = ImageHandler.instance().loadBufferedImage(actualFile); - BufferedImage expectedImage = ImageHandler.instance().loadBufferedImage(expectedFile); - - assertTrue("Images are not the same size", actualImage.getWidth() == expectedImage.getWidth() - && actualImage.getHeight() == expectedImage.getHeight()); - - boolean pixelsEqual = true; - int x = 0; - int y = 0; - - OUTER: - for(y = 0; y < expectedImage.getHeight(); y++) { - for(x = 0; x < expectedImage.getWidth(); x++) { - int expectedPixel = expectedImage.getRGB(x, y); - int actualPixel = actualImage.getRGB(x, y); - if(actualPixel != expectedPixel) { - pixelsEqual = false; - break OUTER; - } - } - } - - assertTrue("Images for "+textFile.getName()+" are not pixel-identical, first different pixel at: "+x+","+y, pixelsEqual); - } - - public VisualTester(File textFile, int index) { - this.textFile = textFile; - this.index = index; - } - - @Parameters - public static Collection getTestParameters() { - List filesToRender = getFilesToRender(); - Object[] params = new Object[filesToRender.size()]; - - int i = 0; - for(File file : filesToRender) { - params[i] = new Object[]{ file, i }; - i++; - } - - return Arrays.asList(params); - } - - public static List getFilesToRender() { - String textDir = "test-resources/text"; - - File textDirObj = new File(textDir); - ArrayList textFiles - = new ArrayList(Arrays.asList(textDirObj.listFiles())); - - Set excludedFiles = new HashSet(); - excludedFiles.addAll( Arrays.asList(new String[]{ - "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.txt", - "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.2.txt", - "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.3.txt", - "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.4.txt", - "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.edit.txt", - "dak_orgstruktur_vs_be.ditaa.txt" - })); - - Iterator it = textFiles.iterator(); - while(it.hasNext()){ - String filename = it.next().toString(); - if(!filename.matches(".+\\.txt$") || isInExcluded(filename, excludedFiles)){ - it.remove(); - } - } - - return textFiles; - } - - private static boolean isInExcluded(String filename, Set excludedSet) { - for(String excluded : excludedSet) { - if(filename.endsWith(excluded)) return true; - } - return false; - } - - public static void generateImages(List textFiles, String destinationDir) { - - ConversionOptions options = new ConversionOptions(); - - for(File textFile : textFiles) { - TextGrid grid = new TextGrid(); - - File toFile = new File(destinationDir + File.separator + textFile.getName() + ".png"); - - - long a = java.lang.System.nanoTime(); - long b; - try { - System.out.println("Rendering "+textFile+" to "+toFile); - - grid.loadFrom(textFile.toString()); - Diagram diagram = new Diagram(grid, options); - - RenderedImage image = new BitmapRenderer().renderToImage(diagram, options.renderingOptions); - - b = java.lang.System.nanoTime(); - java.lang.System.out.println( "Done in " + Math.round((b - a)/10e6) + "msec"); - - try { - File file = new File(toFile.getAbsolutePath()); - ImageIO.write(image, "png", file); - } catch (IOException e) { - //e.printStackTrace(); - System.err.println("Error: Cannot write to file "+toFile); - System.exit(1); - } - - } catch (Exception e) { - System.err.println("!!! Failed to render: "+textFile+" !!!\n"); - System.err.println(grid.getDebugString()+"\n"); - e.printStackTrace(System.err); - - continue; - } - } - - } - - - public static boolean createHTMLTestReport(List textFiles, String reportDir, String reportName){ - - ConversionOptions options = new ConversionOptions(); - - String reportFilename = reportDir+"/"+reportName+".html"; - - if(!(new File(reportDir).exists())){ - File dir = new File(reportDir); - dir.mkdir(); - } - - PrintWriter s = null; - try { - s = new PrintWriter(new FileWriter(reportFilename)); - } catch (IOException e) { - System.err.println("Cannot open file "+reportFilename+" for writing:"); - e.printStackTrace(); - return false; - } - - s.println(""); - s.println("

ditaa test suite

"); - s.println("

generated on: "+Calendar.getInstance().getTime()+"

"); - - - for(File textFile : textFiles) { - TextGrid grid = new TextGrid(); - - File toFilePng = new File(reportDir + File.separator + textFile.getName() + ".png"); - File toFileSvg = new File(reportDir + File.separator + textFile.getName() + ".svg"); - - long a = java.lang.System.nanoTime(); - long b; - try { - System.out.println("Rendering " + textFile + " to " + toFilePng); - - grid.loadFrom(textFile.toString()); - Diagram diagram = new Diagram(grid, options); - - RenderedImage image = new BitmapRenderer().renderToImage(diagram, options.renderingOptions); - - b = java.lang.System.nanoTime(); - java.lang.System.out.println( "Done in " + Math.round((b - a)/10e6) + "msec"); - - //png - try { - File file = new File(toFilePng.getAbsolutePath()); - ImageIO.write(image, "png", file); - } catch (IOException e) { - //e.printStackTrace(); - System.err.println("Error: Cannot write to file " + toFilePng); - System.exit(1); - } - - //SVG - System.out.println("Rendering " + textFile + " to " + toFileSvg); - try { - File file = new File(toFileSvg.getAbsolutePath()); - String content = new SVGRenderer().renderToImage(diagram, options.renderingOptions); - new PrintStream(new FileOutputStream(file)).print(content); - } catch (Exception e) { - System.err.println("Error: Cannot write to file " + toFileSvg); - System.exit(1); - } - - - } catch (Exception e) { - s.println("!!! Failed to render: "+textFile+" !!!"); - s.println("
\n"+grid.getDebugString()+"\n
"); - s.println(e.getMessage()); - s.println("
"); - s.flush(); - - System.err.println("!!! Failed to render: "+textFile+" !!!"); - e.printStackTrace(System.err); - - continue; - } - - s.println(makeReportTable(textFile.getName(), grid, toFilePng.getName(), toFileSvg.getName(), b - a)); - s.println("
"); - s.flush(); - } - - s.println(""); - - s.flush(); - s.close(); - - - System.out.println("Wrote HTML report to " + new File(reportFilename).getAbsolutePath()); - - return true; - - } - - private static String makeReportTable(String gridURI, TextGrid grid, String imagePngURI, String imageSvgURI, long time){ - StringBuffer buffer = new StringBuffer("
"); - buffer.append(""); - buffer.append(""); - buffer.append(""); - buffer.append("

"+gridURI+" ("+Math.round(time/10e6)+"msec)

\n"+grid.getDebugString()+"\n
"); - buffer.append("
"); - return buffer.toString(); - } + private static final String HTMLReportName = "test_suite"; + private static final String expectedDir = "test-resources/images-expected"; + private static final String actualDir = "test-resources/images"; + + private File textFile; + private int index; + + public static void main(String[] args) { + generate(); + } + + public static void generate() { + String reportDir = "test-resources/images"; + VisualTester.createHTMLTestReport(getFilesToRender(), reportDir, HTMLReportName); + System.out.println("Tests completed"); + } + + @Test + public void compareImages() throws FileNotFoundException, IOException { + ConversionOptions options = new ConversionOptions(); + File actualFile = new File(actualDir + File.separator + textFile.getName() + ".png"); + File expectedFile = new File(expectedDir + File.separator + textFile.getName() + ".png"); + + System.out.println(index + ") Rendering " + textFile + " to " + actualFile); + + if (!expectedFile.exists()) { + System.out.println("Skipping " + textFile + " -- reference image does not exist"); + throw new FileNotFoundException("Reference image " + expectedFile + " does not exist"); + } + + TextGrid grid = new TextGrid(); + grid.loadFrom(textFile.toString()); + Diagram diagram = new Diagram(grid, options); + + RenderedImage image = new BitmapRenderer().renderToImage(diagram, options.renderingOptions); + + File file = new File(actualFile.getAbsolutePath()); + ImageIO.write(image, "png", file); + + //compare images pixel-by-pixel + BufferedImage actualImage = ImageHandler.instance().loadBufferedImage(actualFile); + BufferedImage expectedImage = ImageHandler.instance().loadBufferedImage(expectedFile); + + assertTrue("Images are not the same size", actualImage.getWidth() == expectedImage.getWidth() + && actualImage.getHeight() == expectedImage.getHeight()); + + boolean pixelsEqual = true; + int x = 0; + int y = 0; + + OUTER: + for (y = 0; y < expectedImage.getHeight(); y++) { + for (x = 0; x < expectedImage.getWidth(); x++) { + int expectedPixel = expectedImage.getRGB(x, y); + int actualPixel = actualImage.getRGB(x, y); + if (actualPixel != expectedPixel) { + pixelsEqual = false; + break OUTER; + } + } + } + + assertTrue("Images for " + textFile.getName() + " are not pixel-identical, first different pixel at: " + x + "," + y, pixelsEqual); + } + + public VisualTester(File textFile, int index) { + this.textFile = textFile; + this.index = index; + } + + @Parameters + public static Collection getTestParameters() { + List filesToRender = getFilesToRender(); + Object[] params = new Object[filesToRender.size()]; + + int i = 0; + for (File file : filesToRender) { + params[i] = new Object[] { file, i }; + i++; + } + + return Arrays.asList(params); + } + + public static List getFilesToRender() { + String textDir = "test-resources/text"; + + File textDirObj = new File(textDir); + ArrayList textFiles + = new ArrayList(Arrays.asList(textDirObj.listFiles())); + + Set excludedFiles = new HashSet(); + excludedFiles.addAll(Arrays.asList(new String[] { + "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.txt", + "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.2.txt", + "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.3.txt", + "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.4.txt", + "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.edit.txt", + "dak_orgstruktur_vs_be.ditaa.txt" + })); + + Iterator it = textFiles.iterator(); + while (it.hasNext()) { + String filename = it.next().toString(); + if (!filename.matches(".+\\.txt$") || isInExcluded(filename, excludedFiles)) { + it.remove(); + } + } + + return textFiles; + } + + private static boolean isInExcluded(String filename, Set excludedSet) { + for (String excluded : excludedSet) { + if (filename.endsWith(excluded)) + return true; + } + return false; + } + + public static void generateImages(List textFiles, String destinationDir) { + + ConversionOptions options = new ConversionOptions(); + + for (File textFile : textFiles) { + TextGrid grid = new TextGrid(); + + File toFile = new File(destinationDir + File.separator + textFile.getName() + ".png"); + + long a = java.lang.System.nanoTime(); + long b; + try { + System.out.println("Rendering " + textFile + " to " + toFile); + + grid.loadFrom(textFile.toString()); + Diagram diagram = new Diagram(grid, options); + + RenderedImage image = new BitmapRenderer().renderToImage(diagram, options.renderingOptions); + + b = java.lang.System.nanoTime(); + java.lang.System.out.println("Done in " + Math.round((b - a) / 10e6) + "msec"); + + try { + File file = new File(toFile.getAbsolutePath()); + ImageIO.write(image, "png", file); + } catch (IOException e) { + //e.printStackTrace(); + System.err.println("Error: Cannot write to file " + toFile); + System.exit(1); + } + + } catch (Exception e) { + System.err.println("!!! Failed to render: " + textFile + " !!!\n"); + System.err.println(grid.getDebugString() + "\n"); + e.printStackTrace(System.err); + + continue; + } + } + + } + + + public static boolean createHTMLTestReport(List textFiles, String reportDir, String reportName) { + + ConversionOptions options = new ConversionOptions(); + + String reportFilename = reportDir + "/" + reportName + ".html"; + + if (!(new File(reportDir).exists())) { + File dir = new File(reportDir); + dir.mkdir(); + } + + PrintWriter s = null; + try { + s = new PrintWriter(new FileWriter(reportFilename)); + } catch (IOException e) { + System.err.println("Cannot open file " + reportFilename + " for writing:"); + e.printStackTrace(); + return false; + } + + s.println(""); + s.println("

ditaa test suite

"); + s.println("

generated on: " + Calendar.getInstance().getTime() + "

"); + + for (File textFile : textFiles) { + TextGrid grid = new TextGrid(); + + File toFilePng = new File(reportDir + File.separator + textFile.getName() + ".png"); + File toFileSvg = new File(reportDir + File.separator + textFile.getName() + ".svg"); + + long a = java.lang.System.nanoTime(); + long b; + try { + System.out.println("Rendering " + textFile + " to " + toFilePng); + + grid.loadFrom(textFile.toString()); + Diagram diagram = new Diagram(grid, options); + + RenderedImage image = new BitmapRenderer().renderToImage(diagram, options.renderingOptions); + + b = java.lang.System.nanoTime(); + java.lang.System.out.println("Done in " + Math.round((b - a) / 10e6) + "msec"); + + //png + try { + File file = new File(toFilePng.getAbsolutePath()); + ImageIO.write(image, "png", file); + } catch (IOException e) { + //e.printStackTrace(); + System.err.println("Error: Cannot write to file " + toFilePng); + System.exit(1); + } + + //SVG + System.out.println("Rendering " + textFile + " to " + toFileSvg); + try { + File file = new File(toFileSvg.getAbsolutePath()); + String content = new SVGRenderer().renderToImage(diagram, options.renderingOptions); + new PrintStream(new FileOutputStream(file)).print(content); + } catch (Exception e) { + System.err.println("Error: Cannot write to file " + toFileSvg); + System.exit(1); + } + + + } catch (Exception e) { + s.println("!!! Failed to render: " + textFile + " !!!"); + s.println("
\n" + grid.getDebugString() + "\n
"); + s.println(e.getMessage()); + s.println("
"); + s.flush(); + + System.err.println("!!! Failed to render: " + textFile + " !!!"); + e.printStackTrace(System.err); + + continue; + } + + s.println(makeReportTable(textFile.getName(), grid, toFilePng.getName(), toFileSvg.getName(), b - a)); + s.println("
"); + s.flush(); + } + + s.println(""); + + s.flush(); + s.close(); + + System.out.println("Wrote HTML report to " + new File(reportFilename).getAbsolutePath()); + + return true; + + } + + private static String makeReportTable(String gridURI, TextGrid grid, String imagePngURI, String imageSvgURI, long time) { + StringBuffer buffer = new StringBuffer("
"); + buffer.append(""); + buffer.append(""); + buffer.append(""); + buffer.append("

" + gridURI + " (" + Math.round(time / 10e6) + "msec)

\n" + grid.getDebugString() + "\n
"); + buffer.append("
"); + return buffer.toString(); + } } diff --git a/test/java/sandbox/Sandbox.java b/test/java/sandbox/Sandbox.java index 7ec90df..1bb98e2 100644 --- a/test/java/sandbox/Sandbox.java +++ b/test/java/sandbox/Sandbox.java @@ -9,8 +9,10 @@ import org.stathissideris.ascii2image.text.TextGrid; import javax.imageio.ImageIO; -import javax.swing.*; -import java.awt.*; +import javax.swing.JLabel; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Insets; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.io.File; @@ -21,123 +23,123 @@ import static com.github.dakusui.crest.Crest.assertThat; public class Sandbox { - @Test - public void latexMath() throws IOException { - String latex = "$\\sum_{i=0}^{n}x^i$"; - TeXFormula formula = new TeXFormula(latex); - - // Note: Old interface for creating icons: - // TeXIcon icon = formula.createTeXIcon(TeXConstants.STYLE_DISPLAY, 20); - // Note: New interface using builder pattern (inner class): - TeXIcon icon = formula.new TeXIconBuilder().setStyle(TeXConstants.STYLE_DISPLAY).setSize(20) - .build(); - - icon.setInsets(new Insets(5, 5, 5, 5)); - - BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), - BufferedImage.TYPE_INT_ARGB); - Graphics2D g2 = image.createGraphics(); - g2.setColor(Color.white); - g2.fillRect(0, 0, icon.getIconWidth(), icon.getIconHeight()); - JLabel jl = new JLabel(); - jl.setForeground(new Color(0, 0, 0)); - icon.paintIcon(jl, g2, 0, 0); - File file = new File("target/Example1.png"); - ImageIO.write(image, "png", file.getAbsoluteFile()); - } - - @Test - public void latexMath2() throws IOException { - String latex = "$\\sum_{i=0}^{n}x^i$"; - - BufferedImage image = new BufferedImage( - 640, 480, - BufferedImage.TYPE_INT_ARGB - ); - DiagramText.drawTeXFormula(image.createGraphics(), latex, 0, 0, Color.black, 20); - File file = new File("target/Example1.png"); - ImageIO.write(image, "png", file.getAbsoluteFile()); - } + @Test + public void latexMath() throws IOException { + String latex = "$\\sum_{i=0}^{n}x^i$"; + TeXFormula formula = new TeXFormula(latex); + + // Note: Old interface for creating icons: + // TeXIcon icon = formula.createTeXIcon(TeXConstants.STYLE_DISPLAY, 20); + // Note: New interface using builder pattern (inner class): + TeXIcon icon = formula.new TeXIconBuilder().setStyle(TeXConstants.STYLE_DISPLAY).setSize(20) + .build(); + + icon.setInsets(new Insets(5, 5, 5, 5)); + + BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), + BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = image.createGraphics(); + g2.setColor(Color.white); + g2.fillRect(0, 0, icon.getIconWidth(), icon.getIconHeight()); + JLabel jl = new JLabel(); + jl.setForeground(new Color(0, 0, 0)); + icon.paintIcon(jl, g2, 0, 0); + File file = new File("target/Example1.png"); + ImageIO.write(image, "png", file.getAbsoluteFile()); + } + + @Test + public void latexMath2() throws IOException { + String latex = "$\\sum_{i=0}^{n}x^i$"; + + BufferedImage image = new BufferedImage( + 640, 480, + BufferedImage.TYPE_INT_ARGB + ); + DiagramText.drawTeXFormula(image.createGraphics(), latex, 0, 0, Color.black, 20); + File file = new File("target/Example1.png"); + ImageIO.write(image, "png", file.getAbsoluteFile()); + } + + /** + * This method is based on a discussion How to compare images for similarity made in the Stackoverflow.com + * If 2 images are identical, this method will return 1.0. + * + * @param fileA input image A + * @param fileB input image B + * @return The similarity. + */ + public static double compareImages(File fileA, File fileB) { + double percentage = 0; + try { + // take buffer data from both image files // + BufferedImage biA = ImageIO.read(fileA); + DataBuffer dbA = biA.getData().getDataBuffer(); + int sizeA = dbA.getSize(); + BufferedImage biB = ImageIO.read(fileB); + DataBuffer dbB = biB.getData().getDataBuffer(); + int sizeB = dbB.getSize(); + int count = 0; + // compare data-buffer objects // + if (sizeA == sizeB) { + + for (int i = 0; i < sizeA; i++) { + + if (dbA.getElem(i) == dbB.getElem(i)) { + count = count + 1; + } - /** - * This method is based on a discussion How to compare images for similarity made in the Stackoverflow.com - * If 2 images are identical, this method will return 1.0. - * - * @param fileA input image A - * @param fileB input image B - * @return The similarity. - */ - public static double compareImages(File fileA, File fileB) { - double percentage = 0; - try { - // take buffer data from both image files // - BufferedImage biA = ImageIO.read(fileA); - DataBuffer dbA = biA.getData().getDataBuffer(); - int sizeA = dbA.getSize(); - BufferedImage biB = ImageIO.read(fileB); - DataBuffer dbB = biB.getData().getDataBuffer(); - int sizeB = dbB.getSize(); - int count = 0; - // compare data-buffer objects // - if (sizeA == sizeB) { - - for (int i = 0; i < sizeA; i++) { - - if (dbA.getElem(i) == dbB.getElem(i)) { - count = count + 1; - } - - } - percentage = ((double) count * 100) / sizeA; - } else { - System.out.println("Both the images are not of same size"); - } - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); } - return percentage; - } - - - @Test - public void givenStringContainingLaTeXFormula4whenTransform$thenResultIsCorrect() { - assertThat( - TextGrid.transformRowToModeRow("xyz$xyz^^^$xyz"), - asString().equalTo("PPPLLLLLLLLPPP").$() - ); - } - - @Test(expected = IllegalArgumentException.class) - public void givenStringContainingUnfinishedLaTeXFormula$whenTransform$thenIllegalArgumentException() { - TextGrid.transformRowToModeRow("xyz$xyz^^^_xyz"); - } - - @Test - public void givenStringNotContainingLaTeXFormula$whenTransform$thenResultIsCorrect() { - assertThat( - TextGrid.transformRowToModeRow("xyz_xyz^^^_xyz"), - asString().equalTo("PPPPPPPPPPPPPP").$() - ); - } - - @Test - public void givenEmptyString$whenTransform$thenResultIsEmpty() { - assertThat( - TextGrid.transformRowToModeRow(""), - asString().equalTo("").$() - ); - } - - - @Test - public void testSplit() { - String s = "hello$HELLO$ world"; - Iterator i = StringUtils.createTextSplitter(DiagramText.TEXT_SPLITTING_REGEX, s); - - while (i.hasNext()) - System.out.println(i.next()); + percentage = ((double) count * 100) / sizeA; + } else { + System.out.println("Both the images are not of same size"); + } + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); } + return percentage; + } + + + @Test + public void givenStringContainingLaTeXFormula4whenTransform$thenResultIsCorrect() { + assertThat( + TextGrid.transformRowToModeRow("xyz$xyz^^^$xyz"), + asString().equalTo("PPPLLLLLLLLPPP").$() + ); + } + + @Test(expected = IllegalArgumentException.class) + public void givenStringContainingUnfinishedLaTeXFormula$whenTransform$thenIllegalArgumentException() { + TextGrid.transformRowToModeRow("xyz$xyz^^^_xyz"); + } + + @Test + public void givenStringNotContainingLaTeXFormula$whenTransform$thenResultIsCorrect() { + assertThat( + TextGrid.transformRowToModeRow("xyz_xyz^^^_xyz"), + asString().equalTo("PPPPPPPPPPPPPP").$() + ); + } + + @Test + public void givenEmptyString$whenTransform$thenResultIsEmpty() { + assertThat( + TextGrid.transformRowToModeRow(""), + asString().equalTo("").$() + ); + } + + + @Test + public void testSplit() { + String s = "hello$HELLO$ world"; + Iterator i = StringUtils.createTextSplitter(DiagramText.TEXT_SPLITTING_REGEX, s); + + while (i.hasNext()) + System.out.println(i.next()); + } } From 2706fd9f47c5e55fe27b52553cdeaeda9f862406 Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Mon, 29 Oct 2018 06:20:08 +0900 Subject: [PATCH 10/12] Uniform logics for rendering/drawing text on SVGBuilder/Graphics2D. --- .../ascii2image/graphics/DiagramText.java | 93 ++++++++++++++++--- .../ascii2image/graphics/SVGBuilder.java | 51 +--------- 2 files changed, 81 insertions(+), 63 deletions(-) diff --git a/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java b/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java index 2cef56f..37c8af8 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java +++ b/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java @@ -21,6 +21,7 @@ import org.scilab.forge.jlatexmath.TeXConstants; import org.scilab.forge.jlatexmath.TeXFormula; import org.scilab.forge.jlatexmath.TeXIcon; +import org.stathissideris.ascii2image.core.RenderingOptions; import org.stathissideris.ascii2image.text.StringUtils; import javax.swing.JLabel; @@ -32,6 +33,8 @@ import java.util.regex.Pattern; import java.util.stream.Stream; +import static org.stathissideris.ascii2image.graphics.SVGBuilder.colorToHex; + /** * @author Efstathios Sideris */ @@ -110,35 +113,71 @@ public void drawOn(Graphics2D g2) { if (this.hasOutline()) { g2.setColor(this.getOutlineColor()); Stream.of(1, -1) - .peek(d -> draw(g2, this.getXPos() + d, this.getYPos())) - .peek(d -> draw(g2, this.getXPos(), this.getYPos() + d)) + .peek(d -> draw(g2, this.getXPos() + d, this.getYPos(), this.getColor())) + .peek(d -> draw(g2, this.getXPos(), this.getYPos() + d, this.getColor())) .forEach(d -> { }); } g2.setColor(this.getColor()); - draw(g2, this.getXPos(), this.getYPos()); + draw(g2, this.getXPos(), this.getYPos(), getColor()); } - private void draw(Graphics2D g2, int xPos, int yPos) { - Iterator i = StringUtils.createTextSplitter(TEXT_SPLITTING_REGEX, getText()); + private void draw(Graphics2D g2, int xPos, int yPos, Color color) { + Iterator i = StringUtils.createTextSplitter(TEXT_SPLITTING_REGEX, this.getText()); int x = xPos; while (i.hasNext()) { String text = i.next(); - if (text.startsWith("$")) - x += drawTeXFormula( - g2, + if (isTeXFormula(text)) + x += drawTeXFormula(g2, text, - x, yPos, this.getColor(), - this.getFont().getSize()); + x, yPos, color, + font.getSize()); else - x += drawString( - g2, + x += drawString(g2, text, - x, yPos, this.getColor(), - this.getFont()); + x, yPos, color, + font); + } + } + + public void renderOn(StringBuilder svgBuildingBuffer, RenderingOptions options) { + if (this.hasOutline()) { + Stream.of(1, -1) + .peek(d -> render(svgBuildingBuffer, options, + this.getXPos() + d, this.getYPos(), + this.getOutlineColor())) + .peek(d -> render(svgBuildingBuffer, options, + this.getXPos(), this.getYPos() + d, + this.getOutlineColor())) + .forEach(d -> { + }); + } + render(svgBuildingBuffer, options, this.getXPos(), this.getYPos(), getColor()); + } + + private void render(StringBuilder svgBuildingBuffer, RenderingOptions options, + int xPos, int yPos, Color color) { + Iterator i = StringUtils.createTextSplitter(TEXT_SPLITTING_REGEX, this.getText()); + int x = xPos; + while (i.hasNext()) { + String token = i.next(); + if (isTeXFormula(token)) + x += renderTeXFormula(svgBuildingBuffer, options, + token, + x, yPos, color, + font.getSize()); + else + x += renderString(svgBuildingBuffer, options, + token, + x, yPos, color, + font); } } + private static boolean isTeXFormula(String text) { + return text.startsWith("$"); + } + /** * @return */ @@ -252,4 +291,30 @@ private static int drawString(Graphics2D g2, String text, int xPos, int yPos, Co return FontMeasurer.instance().getWidthFor(text, font); } + @SuppressWarnings("unused") + private static int renderTeXFormula(StringBuilder svgBuildingBuffer, RenderingOptions options, String text, int x, int yPos, Color color, int size) { + throw new UnsupportedOperationException("Rendering LaTeX formula in .svg format is not currently supported."); + } + + private static int renderString(StringBuilder svgBuildingBuffer, RenderingOptions options, String text, int xPos, int yPos, Color color, Font font) { + String TEXT_ELEMENT = " " + + "\n"; + /* Prefer normal font weight + if (font.isBold()) { + style = " font-weight='bold'"; + } + */ + + svgBuildingBuffer.append( + String.format(TEXT_ELEMENT, + xPos, + yPos, + options.getFontFamily(), + font.getSize(), + colorToHex(color), + text + ) + ); + return FontMeasurer.instance().getWidthFor(text, font); + } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/SVGBuilder.java b/src/java/org/stathissideris/ascii2image/graphics/SVGBuilder.java index 83c1031..2247620 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/SVGBuilder.java +++ b/src/java/org/stathissideris/ascii2image/graphics/SVGBuilder.java @@ -5,7 +5,6 @@ import org.stathissideris.ascii2image.core.ShapeAreaComparator; import java.awt.Color; -import java.awt.Font; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.util.ArrayList; @@ -289,56 +288,10 @@ private void backgroundLayer() { } private void renderTexts() { - - for (DiagramText diagramText : diagram.getTextObjects()) { - - Font font = diagramText.getFont(); - String text = diagramText.getText(); - - int xPos = diagramText.getXPos(); - int yPos = diagramText.getYPos(); - - renderText(text, xPos, yPos, font, diagramText.getColor()); - - if (diagramText.hasOutline()) { - - Color outlineColor = diagramText.getOutlineColor(); - - renderText(text, xPos + 1, yPos, font, outlineColor); - renderText(text, xPos - 1, yPos, font, outlineColor); - renderText(text, xPos, yPos + 1, font, outlineColor); - renderText(text, xPos, yPos - 1, font, outlineColor); - - } - } - - } - - private void renderText(String text, int xPos, int yPos, Font font, Color color) { - - String TEXT_ELEMENT = " " + - "\n"; - - /* Prefer normal font weight - if (font.isBold()) { - style = " font-weight='bold'"; - } - */ - - layer3.append( - String.format(TEXT_ELEMENT, - xPos, - yPos, - options.getFontFamily(), - font.getSize(), - colorToHex(color), - text - ) - ); - + diagram.getTextObjects().forEach(each -> each.renderOn(this.layer3, this.options)); } - private static String colorToHex(Color color) { + public static String colorToHex(Color color) { return String.format("#%s%s%s", toHex(color.getRed()), toHex(color.getGreen()), From 9c914586ce0c28f84713c9dfb19b06df17ecd86b Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Mon, 29 Oct 2018 06:55:09 +0900 Subject: [PATCH 11/12] Implement commandline option to enable LaTeX mode whose default is disabled. (WIP) --- .../ascii2image/core/ConversionOptions.java | 12 ++- .../ascii2image/core/ProcessingOptions.java | 7 ++ .../ascii2image/graphics/Diagram.java | 80 +++++++++---------- .../ascii2image/graphics/DiagramText.java | 8 +- 4 files changed, 60 insertions(+), 47 deletions(-) diff --git a/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java b/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java index 52a7eb1..fce1a70 100644 --- a/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java +++ b/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java @@ -29,9 +29,9 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.HashMap; +import java.util.Objects; /** - * * @author Efstathios Sideris */ public class ConversionOptions { @@ -49,8 +49,10 @@ public void setDebug(boolean value) { public ConversionOptions() { } - /** Parse a color from a 6- or 8-digit hex string. For example, FF0000 is red. - * If eight digits, last two digits are alpha. */ + /** + * Parse a color from a 6- or 8-digit hex string. For example, FF0000 is red. + * If eight digits, last two digits are alpha. + */ public static Color parseColor(String hexString) { if (hexString.length() == 6) { return new Color(Integer.parseInt(hexString, 16)); @@ -107,6 +109,10 @@ public ConversionOptions(CommandLine cmdLine) throws UnsupportedEncodingExceptio processingOptions.setCharacterEncoding(encoding); } + if (cmdLine.hasOption("latex")) + processingOptions.enableLaTeXmath( + Objects.equals(cmdLine.getOptionValue("latex", "no"), "yes")); + if (cmdLine.hasOption("svg")) { renderingOptions.setImageType(RenderingOptions.ImageType.SVG); } diff --git a/src/java/org/stathissideris/ascii2image/core/ProcessingOptions.java b/src/java/org/stathissideris/ascii2image/core/ProcessingOptions.java index 6d56f9f..c7b52dc 100644 --- a/src/java/org/stathissideris/ascii2image/core/ProcessingOptions.java +++ b/src/java/org/stathissideris/ascii2image/core/ProcessingOptions.java @@ -35,6 +35,7 @@ public class ProcessingOptions { private boolean overwriteFiles = false; private boolean performSeparationOfCommonEdges = true; private boolean allCornersAreRound = false; + private boolean latexMathEnabled = false; public static final int USE_TAGS = 0; public static final int RENDER_TAGS = 1; @@ -237,5 +238,11 @@ public CustomShapeDefinition getFromCustomShapes(String tagName) { return customShapes.get(tagName); } + public void enableLaTeXmath(boolean b) { + this.latexMathEnabled = b; + } + public boolean isLaTeXmathEnabled() { + return this.latexMathEnabled; + } } diff --git a/src/java/org/stathissideris/ascii2image/graphics/Diagram.java b/src/java/org/stathissideris/ascii2image/graphics/Diagram.java index 80cd53c..342f4a8 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/Diagram.java +++ b/src/java/org/stathissideris/ascii2image/graphics/Diagram.java @@ -35,7 +35,6 @@ import java.util.Iterator; /** - * * @author Efstathios Sideris */ public class Diagram { @@ -53,54 +52,53 @@ public class Diagram { /** - * *

An outline of the inner workings of this very important (and monstrous) * constructor is presented here. Boundary processing is the first step * of the process:

* *
    - *
  1. Copy the grid into a work grid and remove all type-on-line - * and point markers from the work grid
  2. - *
  3. Split grid into distinct shapes by plotting the grid - * onto an AbstractionGrid and its getDistinctShapes() method.
  4. - *
  5. Find all the possible boundary sets of each of the - * distinct shapes. This can produce duplicate shapes (if the boundaries - * are the same when filling from the inside and the outside).
  6. - *
  7. Remove duplicate boundaries.
  8. - *
  9. Remove obsolete boundaries. Obsolete boundaries are the ones that are - * the sum of their parts when plotted as filled shapes. (see method - * removeObsoleteShapes())
  10. - *
  11. Separate the found boundary sets to open, closed or mixed - * (See CellSet class on how its done).
  12. - *
  13. Are there any closed boundaries? - *
      - *
    • YES. Subtract all the closed boundaries from each of the - * open ones. That should convert the mixed shapes into open.
    • - *
    • NO. In this (harder) case, we use the method - * breakTrulyMixedBoundaries() of CellSet to break boundaries - * into open and closed shapes (would work in any case, but it's - * probably slower than the other method). This method is based - * on tracing from the lines' ends and splitting when we get to - * an intersection.
    • - *
    - *
  14. - *
  15. If we had to eliminate any mixed shapes, we separate the found - * boundary sets again to open, closed or mixed.
  16. + *
  17. Copy the grid into a work grid and remove all type-on-line + * and point markers from the work grid
  18. + *
  19. Split grid into distinct shapes by plotting the grid + * onto an AbstractionGrid and its getDistinctShapes() method.
  20. + *
  21. Find all the possible boundary sets of each of the + * distinct shapes. This can produce duplicate shapes (if the boundaries + * are the same when filling from the inside and the outside).
  22. + *
  23. Remove duplicate boundaries.
  24. + *
  25. Remove obsolete boundaries. Obsolete boundaries are the ones that are + * the sum of their parts when plotted as filled shapes. (see method + * removeObsoleteShapes())
  26. + *
  27. Separate the found boundary sets to open, closed or mixed + * (See CellSet class on how its done).
  28. + *
  29. Are there any closed boundaries? + *
      + *
    • YES. Subtract all the closed boundaries from each of the + * open ones. That should convert the mixed shapes into open.
    • + *
    • NO. In this (harder) case, we use the method + * breakTrulyMixedBoundaries() of CellSet to break boundaries + * into open and closed shapes (would work in any case, but it's + * probably slower than the other method). This method is based + * on tracing from the lines' ends and splitting when we get to + * an intersection.
    • + *
    + *
  30. + *
  31. If we had to eliminate any mixed shapes, we separate the found + * boundary sets again to open, closed or mixed.
  32. *
* *

At this stage, the boundary processing is all complete and we * proceed with using those boundaries to create the shapes:

* *
    - *
  1. Create closed shapes.
  2. - *
  3. Create open shapes. That's when the line end corrections are - * also applied, concerning the positioning of the ends of lines - * see methods connectEndsToAnchors() and moveEndsToCellEdges() of - * DiagramShape.
  4. - *
  5. Assign color codes to closed shapes.
  6. - *
  7. Assign extended markup tags to closed shapes.

    - *
  8. Create arrowheads.

    - *
  9. Create point markers.

    + *
  10. Create closed shapes.
  11. + *
  12. Create open shapes. That's when the line end corrections are + * also applied, concerning the positioning of the ends of lines + * see methods connectEndsToAnchors() and moveEndsToCellEdges() of + * DiagramShape.
  13. + *
  14. Assign color codes to closed shapes.
  15. + *
  16. Assign extended markup tags to closed shapes.

    + *
  17. Create arrowheads.

    + *
  18. Create point markers.

    *
* *

Finally, the text processing occurs: [pending]

@@ -565,11 +563,12 @@ else if (type == CellSet.TYPE_MIXED) int maxX = getCellMaxX(lastCell); DiagramText textObject; + boolean laTeXmathEnabled = options.processingOptions.isLaTeXmathEnabled(); if (FontMeasurer.instance().getWidthFor(string, font) > maxX - minX) { //does not fit horizontally Font lessWideFont = FontMeasurer.instance().getFontFor(maxX - minX, string); - textObject = new DiagramText(minX, y, string, lessWideFont); + textObject = new DiagramText(minX, y, string, lessWideFont, laTeXmathEnabled); } else - textObject = new DiagramText(minX, y, string, font); + textObject = new DiagramText(minX, y, string, font, laTeXmathEnabled); textObject.centerVerticallyBetween(getCellMinY(cell), getCellMaxY(cell)); @@ -648,7 +647,6 @@ public ArrayList getAllDiagramShapes() { * when plotted as filled shapes. * * @return true if it removed any obsolete. - * */ private boolean removeObsoleteShapes(TextGrid grid, ArrayList sets) { if (DEBUG) diff --git a/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java b/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java index 37c8af8..58675aa 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java +++ b/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java @@ -42,6 +42,7 @@ public class DiagramText extends DiagramComponent { public static final Color DEFAULT_COLOR = Color.black; public static final Pattern TEXT_SPLITTING_REGEX = Pattern.compile("([^$]+|\\$[^$]*\\$)"); + private final boolean latexMathEnabled; private String text; private Font font; @@ -51,7 +52,7 @@ public class DiagramText extends DiagramComponent { private boolean hasOutline = false; private Color outlineColor = Color.white; - public DiagramText(int x, int y, String text, Font font) { + public DiagramText(int x, int y, String text, Font font, boolean latexMathEnabled) { if (text == null) throw new IllegalArgumentException("DiagramText cannot be initialised with a null string"); if (font == null) @@ -61,6 +62,7 @@ public DiagramText(int x, int y, String text, Font font) { this.yPos = y; this.text = text; this.font = font; + this.latexMathEnabled = latexMathEnabled; } public void centerInBounds(Rectangle2D bounds) { @@ -174,8 +176,8 @@ private void render(StringBuilder svgBuildingBuffer, RenderingOptions options, } } - private static boolean isTeXFormula(String text) { - return text.startsWith("$"); + private boolean isTeXFormula(String text) { + return this.latexMathEnabled && text.startsWith("$"); } /** From f3112c8fd35c8215db120e239c3c62dc8ff152c5 Mon Sep 17 00:00:00 2001 From: Hiroshi Ukai Date: Mon, 29 Oct 2018 20:55:22 +0900 Subject: [PATCH 12/12] Make LaTeX math mode option work. --- .../stathissideris/ascii2image/core/CommandLineConverter.java | 2 +- .../org/stathissideris/ascii2image/core/ConversionOptions.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/java/org/stathissideris/ascii2image/core/CommandLineConverter.java b/src/java/org/stathissideris/ascii2image/core/CommandLineConverter.java index 2020be4..34e3fb9 100644 --- a/src/java/org/stathissideris/ascii2image/core/CommandLineConverter.java +++ b/src/java/org/stathissideris/ascii2image/core/CommandLineConverter.java @@ -41,7 +41,6 @@ import java.io.UnsupportedEncodingException; /** - * * @author Efstathios Sideris */ public class CommandLineConverter { @@ -67,6 +66,7 @@ public static void main(String[] args) { cmdLnOptions.addOption("d", "debug", false, "Renders the debug grid over the resulting image."); cmdLnOptions.addOption("r", "round-corners", false, "Causes all corners to be rendered as round corners."); cmdLnOptions.addOption("E", "no-separation", false, "Prevents the separation of common edges of shapes."); + cmdLnOptions.addOption("L", "latex-math", false, "Enable LaTeX math mode."); cmdLnOptions.addOption("h", "html", false, "In this case the input is an HTML file. The contents of the
 tags are rendered as diagrams and saved in the images directory and a new HTML file is produced with the appropriate  tags.");
     cmdLnOptions.addOption("T", "transparent", false, "Causes the diagram to be rendered on a transparent background. Overrides --background.");
 
diff --git a/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java b/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java
index fce1a70..2157267 100644
--- a/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java
+++ b/src/java/org/stathissideris/ascii2image/core/ConversionOptions.java
@@ -82,6 +82,7 @@ public ConversionOptions(CommandLine cmdLine) throws UnsupportedEncodingExceptio
 
     processingOptions.setAllCornersAreRound(cmdLine.hasOption("round-corners"));
     processingOptions.setPerformSeparationOfCommonEdges(!cmdLine.hasOption("no-separation"));
+    processingOptions.enableLaTeXmath(cmdLine.hasOption("latex-math"));
     renderingOptions.setAntialias(!cmdLine.hasOption("no-antialias"));
     renderingOptions.setFixedSlope(cmdLine.hasOption("fixed-slope"));