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..215e9b8 --- /dev/null +++ b/pom.xml @@ -0,0 +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 + + + 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/core/CommandLineConverter.java b/src/java/org/stathissideris/ascii2image/core/CommandLineConverter.java index 937953a..34e3fb9 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("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.");
+
+    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..2157267 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,133 @@
 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;
+import java.util.Objects;
 
 /**
- * 
  * @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"));
+    processingOptions.enableLaTeXmath(cmdLine.hasOption("latex-math"));
+    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("latex"))
+      processingOptions.enableLaTeXmath(
+          Objects.equals(cmdLine.getOptionValue("latex", "no"), "yes"));
+
+    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..c7b52dc 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,234 @@
  *
  * 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;
+  private boolean latexMathEnabled = 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);
+  }
+
+  public void enableLaTeXmath(boolean b) {
+    this.latexMathEnabled = b;
+  }
+
+  public boolean isLaTeXmathEnabled() {
+    return this.latexMathEnabled;
+  }
 }
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 40d1bd3..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,10 +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.graphics;
 
+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.BasicStroke;
 import java.awt.Canvas;
 import java.awt.Color;
@@ -40,308 +46,300 @@
 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
  */
 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();
+    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()){
 				g2.setColor(text.getOutlineColor());
@@ -352,145 +350,139 @@ 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){
-			Graphics 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());
-			}
-		}
-	}
-	
-	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..342f4a8 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,995 @@
 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; + 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, laTeXmathEnabled); + } else + textObject = new DiagramText(minX, y, string, font, laTeXmathEnabled); + + 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 ef411ca..58675aa 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java +++ b/src/java/org/stathissideris/ascii2image/graphics/DiagramText.java @@ -1,192 +1,322 @@ /** * 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 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; import java.awt.Color; import java.awt.Font; -import java.awt.Rectangle; +import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; +import java.util.Iterator; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static org.stathissideris.ascii2image.graphics.SVGBuilder.colorToHex; /** - * * @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 final boolean latexMathEnabled; + + 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, boolean latexMathEnabled) { + 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; + this.latexMathEnabled = latexMathEnabled; + } + + 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(), 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(), getColor()); + } + + 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 (isTeXFormula(text)) + x += drawTeXFormula(g2, + text, + x, yPos, color, + font.getSize()); + else + x += drawString(g2, + text, + 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 boolean isTeXFormula(String text) { + return this.latexMathEnabled && text.startsWith("$"); + } + + /** + * @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); + } + + @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/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..2247620 100644 --- a/src/java/org/stathissideris/ascii2image/graphics/SVGBuilder.java +++ b/src/java/org/stathissideris/ascii2image/graphics/SVGBuilder.java @@ -1,10 +1,10 @@ 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.geom.GeneralPath; import java.awt.geom.PathIterator; import java.util.ArrayList; @@ -14,403 +14,360 @@ */ 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() { + private String render() { - backgroundLayer(); + backgroundLayer(); - renderStorageShapes(); - renderRestOfShapes(); - renderTexts(); + renderStorageShapes(); + renderRestOfShapes(); + renderTexts(); - return " \n" + - layer0.toString() + - layer1.toString() + - layer2.toString() + - layer3.toString() + - " \n"; + return " \n" + + layer0.toString() + + layer1.toString() + + layer2.toString() + + layer3.toString() + + " \n"; - } + } - private void renderStorageShapes() { + private void renderStorageShapes() { - ArrayList shapes = diagram.getAllDiagramShapes(); + ArrayList shapes = diagram.getAllDiagramShapes(); - ArrayList storageShapes = findSorageShapes(shapes); + ArrayList storageShapes = findSorageShapes(shapes); - storageShapes.sort(new Shape3DOrderingComparator()); + storageShapes.sort(new Shape3DOrderingComparator()); - for (DiagramShape shape : storageShapes) { + for (DiagramShape shape : storageShapes) { - GeneralPath path = shape.makeIntoRenderPath(diagram, options); + GeneralPath path = shape.makeIntoRenderPath(diagram, options); - SVGCommands commands = new SVGCommands(path); + SVGCommands commands = new SVGCommands(path); - String fill = "none"; - String color = "white"; + String fill = "none"; + String color = "white"; - if(!shape.isStrokeDashed()) { + if (!shape.isStrokeDashed()) { - renderShadow(commands); + renderShadow(commands); - if(shape.getFillColor() != null) - fill = colorToHex(shape.getFillColor()); - else - fill = colorToHex(Color.white); - - } + 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) { - - String fill = "none"; + renderPointMarkers(pointMarkers); - if (shape.isClosed() && !shape.isStrokeDashed()) { + } - if(shape.getFillColor() != null) - fill = colorToHex(shape.getFillColor()); - else - fill = "white"; + private void renderPath(DiagramShape shape, SVGCommands commands) { - if (shape.getType() == DiagramShape.TYPE_ARROWHEAD) { - renderPath(shape, commands, "none", fill); - } + String fill = "none"; - } else if (shape.isStrokeDashed()) { - fill = "white"; - } + if (shape.isClosed() && !shape.isStrokeDashed()) { - if (shape.getType() != DiagramShape.TYPE_ARROWHEAD) { + if (shape.getFillColor() != null) + fill = colorToHex(shape.getFillColor()); + else + fill = "white"; - if (commands.isClosed && !shape.isStrokeDashed()) { - renderShadow(commands); - } - - renderPath(shape, commands, colorToHex(shape.getStrokeColor()), fill); - - } + if (shape.getType() == DiagramShape.TYPE_ARROWHEAD) { + renderPath(shape, commands, "none", fill); + } + } else if (shape.isStrokeDashed()) { + fill = "white"; } - private void renderPath(DiagramShape shape, SVGCommands commands, String stroke, String fill) { + if (shape.getType() != DiagramShape.TYPE_ARROWHEAD) { - String path = " \n"; - - layer2.append(path); + renderPath(shape, commands, colorToHex(shape.getStrokeColor()), fill); } - private void renderShadow(SVGCommands commands) { + } - if (!options.dropShadows()) return; + private void renderPath(DiagramShape shape, SVGCommands commands, String stroke, String fill) { - String path = " \n"; + String path = " \n"; - private void renderPointMarkers(ArrayList pointMarkers) { + layer2.append(path); - for (DiagramShape shape : pointMarkers) { + } - GeneralPath path = shape.makeIntoRenderPath(diagram, options); + private void renderShadow(SVGCommands commands) { - String fill = "white"; + if (!options.dropShadows()) + return; - if(shape.getFillColor() != null) - fill = colorToHex(shape.getFillColor()); + String path = " \n"; - renderPath(shape, new SVGCommands(path), colorToHex(shape.getStrokeColor()), fill); + layer1.append(path); - } + } - } + private void renderPointMarkers(ArrayList pointMarkers) { - private String antialiasing() { - String rendering = options.performAntialias() ? "geometricPrecision" : "optimizeSpeed"; - return String.format("shape-rendering='%s'", rendering); - } + for (DiagramShape shape : pointMarkers) { - private void backgroundLayer() { + GeneralPath path = shape.makeIntoRenderPath(diagram, options); - Color color = options.getBackgroundColor(); + String fill = "white"; - if (color.getAlpha() == 0) return; + if (shape.getFillColor() != null) + fill = colorToHex(shape.getFillColor()); - layer0.append ( - String.format(" \n", - diagram.getWidth(), - diagram.getHeight(), - colorToHex(color) - ) - ); + renderPath(shape, new SVGCommands(path), colorToHex(shape.getStrokeColor()), fill); } - private void renderTexts() { + } - for (DiagramText diagramText : diagram.getTextObjects()) { + private String antialiasing() { + String rendering = options.performAntialias() ? "geometricPrecision" : "optimizeSpeed"; + return String.format("shape-rendering='%s'", rendering); + } - Font font = diagramText.getFont(); - String text = diagramText.getText(); + private void backgroundLayer() { - int xPos = diagramText.getXPos(); - int yPos = diagramText.getYPos(); + Color color = options.getBackgroundColor(); - renderText(text, xPos, yPos, font, diagramText.getColor()); + if (color.getAlpha() == 0) + return; - if (diagramText.hasOutline()) { + layer0.append( + String.format(" \n", + diagram.getWidth(), + diagram.getHeight(), + colorToHex(color) + ) + ); - 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 renderTexts() { + diagram.getTextObjects().forEach(each -> each.renderOn(this.layer3, this.options)); + } - } - } + public 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); - private void renderText(String text, int xPos, int yPos, Font font, Color color) { + return n > 15 ? hex : "0" + hex; + } - String TEXT_ELEMENT = " " + - "%s\n"; + private final Diagram diagram; + private final RenderingOptions options; - /* 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 - ) - ); + 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; - private static String colorToHex(Color color) { - return String.format("#%s%s%s", - toHex(color.getRed()), - toHex(color.getGreen()), - toHex(color.getBlue()) - ); - } + class SVGCommands { - private static String toHex(int n) { - String hex = Integer.toHexString(n); + final String svgPath; + final boolean isClosed; - return n > 15 ? hex : "0" + hex; - } + SVGCommands(GeneralPath path) { - private final Diagram diagram; - private final RenderingOptions options; + boolean closed = false; - private final StringBuilder layer0 = new StringBuilder(); - private final StringBuilder layer1 = new StringBuilder(); - private final StringBuilder layer2 = new StringBuilder(); - private final StringBuilder layer3 = new StringBuilder(); + float[] coords = new float[6]; - private final String normalStroke; - private final String dashStroke; + StringBuilder builder = new StringBuilder(); - class SVGCommands { + PathIterator pathIterator = path.getPathIterator(null); - final String svgPath; - final boolean isClosed; + while (!pathIterator.isDone()) { - SVGCommands(GeneralPath path) { + String commands; - 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; - } + 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); + builder.append(commands); - pathIterator.next(); + 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 e753245..7f37c70 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,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 ""; @@ -55,115 +60,144 @@ 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..04ba75f 100644 --- a/src/java/org/stathissideris/ascii2image/text/TextGrid.java +++ b/src/java/org/stathissideris/ascii2image/text/TextGrid.java @@ -1,1786 +1,2089 @@ /** * 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 org.stathissideris.ascii2image.core.FileUtils; +import org.stathissideris.ascii2image.core.ProcessingOptions; + import java.awt.Color; -import java.io.*; -import java.util.*; +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; -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()); - } - - -// 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())) ) - 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)); - - 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 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; + } + } } diff --git a/test-resources/text/art-latexmath-1.txt b/test-resources/text/art-latexmath-1.txt new file mode 100644 index 0000000..f1f2a58 --- /dev/null +++ b/test-resources/text/art-latexmath-1.txt @@ -0,0 +1,38 @@ + +$Box_1$ $Box^2$ ++---------------------+ +------+ /---------\ +|$\sum_{i=0}^{n}x^i$ | |$cBLU$| | | +| +--->|cRED +-=-+cGRE$C_k$| +|{io} | |cXYZ | |{o} | ++----------+----------+ +---+--+ \---------/ + | | + | : + | V + | +-------------------+ + +---------->*$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 + +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).$ + + + $v \sim \mathcal{N} (m,\sigma^2)$ \ No newline at end of file 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 new file mode 100644 index 0000000..1bb98e2 --- /dev/null +++ b/test/java/sandbox/Sandbox.java @@ -0,0 +1,145 @@ +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.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; +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()); + } + +}