diff --git a/src/com/google/zxing/BarcodeFormat.java b/src/com/google/zxing/BarcodeFormat.java new file mode 100644 index 000000000..1cca09a50 --- /dev/null +++ b/src/com/google/zxing/BarcodeFormat.java @@ -0,0 +1,103 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +import java.util.Hashtable; + +/** + * Enumerates barcode formats known to this package. + * + * @author Sean Owen + */ +public final class BarcodeFormat { + + // No, we can't use an enum here. J2ME doesn't support it. + + private static final Hashtable VALUES = new Hashtable(); + + /** QR Code 2D barcode format. */ + public static final BarcodeFormat QR_CODE = new BarcodeFormat("QR_CODE"); + + /** Data Matrix 2D barcode format. */ + public static final BarcodeFormat DATA_MATRIX = new BarcodeFormat("DATA_MATRIX"); + + /** UPC-E 1D format. */ + public static final BarcodeFormat UPC_E = new BarcodeFormat("UPC_E"); + + /** UPC-A 1D format. */ + public static final BarcodeFormat UPC_A = new BarcodeFormat("UPC_A"); + + /** EAN-8 1D format. */ + public static final BarcodeFormat EAN_8 = new BarcodeFormat("EAN_8"); + + /** EAN-13 1D format. */ + public static final BarcodeFormat EAN_13 = new BarcodeFormat("EAN_13"); + + /** UPC/EAN extension format. Not a stand-alone format. */ + public static final BarcodeFormat UPC_EAN_EXTENSION = new BarcodeFormat("UPC_EAN_EXTENSION"); + + /** Code 128 1D format. */ + public static final BarcodeFormat CODE_128 = new BarcodeFormat("CODE_128"); + + /** Code 39 1D format. */ + public static final BarcodeFormat CODE_39 = new BarcodeFormat("CODE_39"); + + /** Code 93 1D format. */ + public static final BarcodeFormat CODE_93 = new BarcodeFormat("CODE_93"); + + /** CODABAR 1D format. */ + public static final BarcodeFormat CODABAR = new BarcodeFormat("CODABAR"); + + /** ITF (Interleaved Two of Five) 1D format. */ + public static final BarcodeFormat ITF = new BarcodeFormat("ITF"); + + /** RSS 14 */ + public static final BarcodeFormat RSS14 = new BarcodeFormat("RSS14"); + + /** PDF417 format. */ + public static final BarcodeFormat PDF417 = new BarcodeFormat("PDF417"); + + /** RSS EXPANDED */ + public static final BarcodeFormat RSS_EXPANDED = new BarcodeFormat("RSS_EXPANDED"); + + private final String name; + + private BarcodeFormat(String name) { + this.name = name; + VALUES.put(name, this); + } + + public String getName() { + return name; + } + + public String toString() { + return name; + } + + public static BarcodeFormat valueOf(String name) { + if (name == null || name.length() == 0) { + throw new IllegalArgumentException(); + } + BarcodeFormat format = (BarcodeFormat) VALUES.get(name); + if (format == null) { + throw new IllegalArgumentException(); + } + return format; + } + +} diff --git a/src/com/google/zxing/ChecksumException.java b/src/com/google/zxing/ChecksumException.java new file mode 100644 index 000000000..dedb4be99 --- /dev/null +++ b/src/com/google/zxing/ChecksumException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +/** + * Thrown when a barcode was successfully detected and decoded, but + * was not returned because its checksum feature failed. + * + * @author Sean Owen + */ +public final class ChecksumException extends ReaderException { + + private static final ChecksumException instance = new ChecksumException(); + + private ChecksumException() { + // do nothing + } + + public static ChecksumException getChecksumInstance() { + return instance; + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/DecodeHintType.java b/src/com/google/zxing/DecodeHintType.java new file mode 100644 index 000000000..fb08bb39a --- /dev/null +++ b/src/com/google/zxing/DecodeHintType.java @@ -0,0 +1,81 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +import java.io.Reader; + +/** + * Encapsulates a type of hint that a caller may pass to a barcode reader to help it + * more quickly or accurately decode it. It is up to implementations to decide what, + * if anything, to do with the information that is supplied. + * + * @author Sean Owen + * @author dswitkin@google.com (Daniel Switkin) + * @see Reader#decode(BinaryBitmap,java.util.Hashtable) + */ +public final class DecodeHintType { + + // No, we can't use an enum here. J2ME doesn't support it. + + /** + * Unspecified, application-specific hint. Maps to an unspecified {@link Object}. + */ + public static final DecodeHintType OTHER = new DecodeHintType(); + + /** + * Image is a pure monochrome image of a barcode. Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + public static final DecodeHintType PURE_BARCODE = new DecodeHintType(); + + /** + * Image is known to be of one of a few possible formats. + * Maps to a {@link java.util.Vector} of {@link BarcodeFormat}s. + */ + public static final DecodeHintType POSSIBLE_FORMATS = new DecodeHintType(); + + /** + * Spend more time to try to find a barcode; optimize for accuracy, not speed. + * Doesn't matter what it maps to; use {@link Boolean#TRUE}. + */ + public static final DecodeHintType TRY_HARDER = new DecodeHintType(); + + /** + * Specifies what character encoding to use when decoding, where applicable (type String) + */ + public static final DecodeHintType CHARACTER_SET = new DecodeHintType(); + + /** + * Allowed lengths of encoded data -- reject anything else. Maps to an int[]. + */ + public static final DecodeHintType ALLOWED_LENGTHS = new DecodeHintType(); + + /** + * Assume Code 39 codes employ a check digit. Maps to {@link Boolean}. + */ + public static final DecodeHintType ASSUME_CODE_39_CHECK_DIGIT = new DecodeHintType(); + + /** + * The caller needs to be notified via callback when a possible {@link ResultPoint} + * is found. Maps to a {@link ResultPointCallback}. + */ + public static final DecodeHintType NEED_RESULT_POINT_CALLBACK = new DecodeHintType(); + + private DecodeHintType() { + } + +} diff --git a/src/com/google/zxing/EncodeHintType.java b/src/com/google/zxing/EncodeHintType.java new file mode 100644 index 000000000..35afc1530 --- /dev/null +++ b/src/com/google/zxing/EncodeHintType.java @@ -0,0 +1,39 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +/** + * These are a set of hints that you may pass to Writers to specify their behavior. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class EncodeHintType { + + /** + * Specifies what degree of error correction to use, for example in QR Codes (type Integer). + */ + public static final EncodeHintType ERROR_CORRECTION = new EncodeHintType(); + + /** + * Specifies what character encoding to use where applicable (type String) + */ + public static final EncodeHintType CHARACTER_SET = new EncodeHintType(); + + private EncodeHintType() { + } + +} diff --git a/src/com/google/zxing/FormatException.java b/src/com/google/zxing/FormatException.java new file mode 100644 index 000000000..6967e93de --- /dev/null +++ b/src/com/google/zxing/FormatException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +/** + * Thrown when a barcode was successfully detected, but some aspect of + * the content did not conform to the barcode's format rules. This could have + * been due to a mis-detection. + * + * @author Sean Owen + */ +public final class FormatException extends ReaderException { + + private static final FormatException instance = new FormatException(); + + private FormatException() { + // do nothing + } + + public static FormatException getFormatInstance() { + return instance; + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/NotFoundException.java b/src/com/google/zxing/NotFoundException.java new file mode 100644 index 000000000..dedab8dfc --- /dev/null +++ b/src/com/google/zxing/NotFoundException.java @@ -0,0 +1,37 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +/** + * Thrown when a barcode was not found in the image. It might have been + * partially detected but could not be confirmed. + * + * @author Sean Owen + */ +public final class NotFoundException extends ReaderException { + + private static final NotFoundException instance = new NotFoundException(); + + private NotFoundException() { + // do nothing + } + + public static NotFoundException getNotFoundInstance() { + return instance; + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/ReaderException.java b/src/com/google/zxing/ReaderException.java new file mode 100644 index 000000000..224a497e5 --- /dev/null +++ b/src/com/google/zxing/ReaderException.java @@ -0,0 +1,98 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +/** + * The general exception class throw when something goes wrong during decoding of a barcode. + * This includes, but is not limited to, failing checksums / error correction algorithms, being + * unable to locate finder timing patterns, and so on. + * + * @author Sean Owen + */ +public abstract class ReaderException extends Exception { + + // TODO: Currently we throw up to 400 ReaderExceptions while scanning a single 240x240 image before + // rejecting it. This involves a lot of overhead and memory allocation, and affects both performance + // and latency on continuous scan clients. In the future, we should change all the decoders not to + // throw exceptions for routine events, like not finding a barcode on a given row. Instead, we + // should return error codes back to the callers, and simply delete this class. In the mean time, I + // have altered this class to be as lightweight as possible, by ignoring the exception string, and + // by disabling the generation of stack traces, which is especially time consuming. These are just + // temporary measures, pending the big cleanup. + + //private static final ReaderException instance = new ReaderException(); + + // EXCEPTION TRACKING SUPPORT + // Identifies who is throwing exceptions and how often. To use: + // + // 1. Uncomment these lines and the code below which uses them. + // 2. Uncomment the two corresponding lines in j2se/CommandLineRunner.decode() + // 3. Change core to build as Java 1.5 temporarily +// private static int exceptionCount = 0; +// private static Map throwers = new HashMap(32); + + ReaderException() { + // do nothing + } + + //public static ReaderException getInstance() { +// Exception e = new Exception(); +// // Take the stack frame before this one. +// StackTraceElement stack = e.getStackTrace()[1]; +// String key = stack.getClassName() + "." + stack.getMethodName() + "(), line " + +// stack.getLineNumber(); +// if (throwers.containsKey(key)) { +// Integer value = throwers.get(key); +// value++; +// throwers.put(key, value); +// } else { +// throwers.put(key, 1); +// } +// exceptionCount++; + + //return instance; + //} + +// public static int getExceptionCountAndReset() { +// int temp = exceptionCount; +// exceptionCount = 0; +// return temp; +// } +// +// public static String getThrowersAndReset() { +// StringBuilder builder = new StringBuilder(1024); +// Object[] keys = throwers.keySet().toArray(); +// for (int x = 0; x < keys.length; x++) { +// String key = (String) keys[x]; +// Integer value = throwers.get(key); +// builder.append(key); +// builder.append(": "); +// builder.append(value); +// builder.append("\n"); +// } +// throwers.clear(); +// return builder.toString(); +// } + + // Prevent stack traces from being taken + // srowen says: huh, my IDE is saying this is not an override. native methods can't be overridden? + // This, at least, does not hurt. Because we use a singleton pattern here, it doesn't matter anyhow. + public final Throwable fillInStackTrace() { + return null; + } + +} diff --git a/src/com/google/zxing/ResultPoint.java b/src/com/google/zxing/ResultPoint.java new file mode 100644 index 000000000..0c8e883d7 --- /dev/null +++ b/src/com/google/zxing/ResultPoint.java @@ -0,0 +1,127 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +/** + *

Encapsulates a point of interest in an image containing a barcode. Typically, this + * would be the location of a finder pattern or the corner of the barcode, for example.

+ * + * @author Sean Owen + */ +public class ResultPoint { + + private final float x; + private final float y; + + public ResultPoint(float x, float y) { + this.x = x; + this.y = y; + } + + public final float getX() { + return x; + } + + public final float getY() { + return y; + } + + public boolean equals(Object other) { + if (other instanceof ResultPoint) { + ResultPoint otherPoint = (ResultPoint) other; + return x == otherPoint.x && y == otherPoint.y; + } + return false; + } + + public int hashCode() { + return 31 * Float.floatToIntBits(x) + Float.floatToIntBits(y); + } + + public String toString() { + StringBuffer result = new StringBuffer(25); + result.append('('); + result.append(x); + result.append(','); + result.append(y); + result.append(')'); + return result.toString(); + } + + /** + *

Orders an array of three ResultPoints in an order [A,B,C] such that AB < AC and + * BC < AC and the angle between BC and BA is less than 180 degrees. + */ + public static void orderBestPatterns(ResultPoint[] patterns) { + + // Find distances between pattern centers + float zeroOneDistance = distance(patterns[0], patterns[1]); + float oneTwoDistance = distance(patterns[1], patterns[2]); + float zeroTwoDistance = distance(patterns[0], patterns[2]); + + ResultPoint pointA, pointB, pointC; + // Assume one closest to other two is B; A and C will just be guesses at first + if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) { + pointB = patterns[0]; + pointA = patterns[1]; + pointC = patterns[2]; + } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) { + pointB = patterns[1]; + pointA = patterns[0]; + pointC = patterns[2]; + } else { + pointB = patterns[2]; + pointA = patterns[0]; + pointC = patterns[1]; + } + + // Use cross product to figure out whether A and C are correct or flipped. + // This asks whether BC x BA has a positive z component, which is the arrangement + // we want for A, B, C. If it's negative, then we've got it flipped around and + // should swap A and C. + if (crossProductZ(pointA, pointB, pointC) < 0.0f) { + ResultPoint temp = pointA; + pointA = pointC; + pointC = temp; + } + + patterns[0] = pointA; + patterns[1] = pointB; + patterns[2] = pointC; + } + + + /** + * @return distance between two points + */ + public static float distance(ResultPoint pattern1, ResultPoint pattern2) { + float xDiff = pattern1.getX() - pattern2.getX(); + float yDiff = pattern1.getY() - pattern2.getY(); + return (float) Math.sqrt((double) (xDiff * xDiff + yDiff * yDiff)); + } + + /** + * Returns the z component of the cross product between vectors BC and BA. + */ + private static float crossProductZ(ResultPoint pointA, ResultPoint pointB, ResultPoint pointC) { + float bX = pointB.x; + float bY = pointB.y; + return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX)); + } + + +} diff --git a/src/com/google/zxing/Writer.java b/src/com/google/zxing/Writer.java new file mode 100644 index 000000000..c0b74d7d7 --- /dev/null +++ b/src/com/google/zxing/Writer.java @@ -0,0 +1,54 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +import java.util.Hashtable; + +import com.google.zxing.common.BitMatrix; + +/** + * The base class for all objects which encode/generate a barcode image. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public interface Writer { + + /** + * Encode a barcode using the default settings. + * + * @param contents The contents to encode in the barcode + * @param format The barcode format to generate + * @param width The preferred width in pixels + * @param height The preferred height in pixels + * @return The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white) + */ + BitMatrix encode(String contents, BarcodeFormat format, int multiple) + throws WriterException; + + /** + * + * @param contents The contents to encode in the barcode + * @param format The barcode format to generate + * @param width The preferred width in pixels + * @param height The preferred height in pixels + * @param hints Additional parameters to supply to the encoder + * @return The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white) + */ + BitMatrix encode(String contents, BarcodeFormat format, int multiple, Hashtable hints) + throws WriterException; + +} diff --git a/src/com/google/zxing/WriterException.java b/src/com/google/zxing/WriterException.java new file mode 100644 index 000000000..0c19af01d --- /dev/null +++ b/src/com/google/zxing/WriterException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing; + +/** + * A base class which covers the range of exceptions which may occur when encoding a barcode using + * the Writer framework. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class WriterException extends Exception { + + public WriterException() { + super(); + } + + public WriterException(String message) { + super(message); + } + +} diff --git a/src/com/google/zxing/client/j2se/MatrixToImageWriter.java b/src/com/google/zxing/client/j2se/MatrixToImageWriter.java new file mode 100644 index 000000000..c4bcb1f25 --- /dev/null +++ b/src/com/google/zxing/client/j2se/MatrixToImageWriter.java @@ -0,0 +1,80 @@ +/* + * Copyright 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.client.j2se; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +import javax.imageio.ImageIO; + +import com.google.zxing.common.BitMatrix; + +/** + * Writes a {@link BitMatrix} to {@link BufferedImage}, + * file or stream. Provided here instead of core since it depends on + * Java SE libraries. + * + * @author Sean Owen + */ +public final class MatrixToImageWriter { + + private static final int BLACK = 0xFF000000; + private static final int WHITE = 0xFFFFFFFF; + + private MatrixToImageWriter() {} + + /** + * Renders a {@link BitMatrix} as an image, where "false" bits are rendered + * as white, and "true" bits are rendered as black. + */ + public static BufferedImage toBufferedImage(BitMatrix matrix) { + int width = matrix.getWidth(); + int height = matrix.getHeight(); + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE); + } + } + return image; + } + + /** + * Writes a {@link BitMatrix} to a file. + * + * @see #toBufferedImage(BitMatrix) + */ + public static void writeToFile(BitMatrix matrix, String format, File file) + throws IOException { + BufferedImage image = toBufferedImage(matrix); + ImageIO.write(image, format, file); + } + + /** + * Writes a {@link BitMatrix} to a stream. + * + * @see #toBufferedImage(BitMatrix) + */ + public static void writeToStream(BitMatrix matrix, String format, OutputStream stream) + throws IOException { + BufferedImage image = toBufferedImage(matrix); + ImageIO.write(image, format, stream); + } + +} diff --git a/src/com/google/zxing/common/BitArray.java b/src/com/google/zxing/common/BitArray.java new file mode 100644 index 000000000..bce6ceeb3 --- /dev/null +++ b/src/com/google/zxing/common/BitArray.java @@ -0,0 +1,247 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common; + +/** + *

A simple, fast array of bits, represented compactly by an array of ints internally.

+ * + * @author Sean Owen + */ +public final class BitArray { + + // TODO: I have changed these members to be public so ProGuard can inline get() and set(). Ideally + // they'd be private and we'd use the -allowaccessmodification flag, but Dalvik rejects the + // resulting binary at runtime on Android. If we find a solution to this, these should be changed + // back to private. + public int[] bits; + public int size; + + public BitArray() { + this.size = 0; + this.bits = new int[1]; + } + + public BitArray(int size) { + this.size = size; + this.bits = makeArray(size); + } + + public int getSize() { + return size; + } + + public int getSizeInBytes() { + return (size + 7) >> 3; + } + + private void ensureCapacity(int size) { + if (size > bits.length << 5) { + int[] newBits = makeArray(size); + System.arraycopy(bits, 0, newBits, 0, bits.length); + this.bits = newBits; + } + } + + /** + * @param i bit to get + * @return true iff bit i is set + */ + public boolean get(int i) { + return (bits[i >> 5] & (1 << (i & 0x1F))) != 0; + } + + /** + * Sets bit i. + * + * @param i bit to set + */ + public void set(int i) { + bits[i >> 5] |= 1 << (i & 0x1F); + } + + /** + * Flips bit i. + * + * @param i bit to set + */ + public void flip(int i) { + bits[i >> 5] ^= 1 << (i & 0x1F); + } + + /** + * Sets a block of 32 bits, starting at bit i. + * + * @param i first bit to set + * @param newBits the new value of the next 32 bits. Note again that the least-significant bit + * corresponds to bit i, the next-least-significant to i+1, and so on. + */ + public void setBulk(int i, int newBits) { + bits[i >> 5] = newBits; + } + + /** + * Clears all bits (sets to false). + */ + public void clear() { + int max = bits.length; + for (int i = 0; i < max; i++) { + bits[i] = 0; + } + } + + /** + * Efficient method to check if a range of bits is set, or not set. + * + * @param start start of range, inclusive. + * @param end end of range, exclusive + * @param value if true, checks that bits in range are set, otherwise checks that they are not set + * @return true iff all bits are set or not set in range, according to value argument + * @throws IllegalArgumentException if end is less than or equal to start + */ + public boolean isRange(int start, int end, boolean value) { + if (end < start) { + throw new IllegalArgumentException(); + } + if (end == start) { + return true; // empty range matches + } + end--; // will be easier to treat this as the last actually set bit -- inclusive + int firstInt = start >> 5; + int lastInt = end >> 5; + for (int i = firstInt; i <= lastInt; i++) { + int firstBit = i > firstInt ? 0 : start & 0x1F; + int lastBit = i < lastInt ? 31 : end & 0x1F; + int mask; + if (firstBit == 0 && lastBit == 31) { + mask = -1; + } else { + mask = 0; + for (int j = firstBit; j <= lastBit; j++) { + mask |= 1 << j; + } + } + + // Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is, + // equals the mask, or we're looking for 0s and the masked portion is not all 0s + if ((bits[i] & mask) != (value ? mask : 0)) { + return false; + } + } + return true; + } + + public void appendBit(boolean bit) { + ensureCapacity(size + 1); + if (bit) { + bits[size >> 5] |= (1 << (size & 0x1F)); + } + size++; + } + + /** + * Appends the least-significant bits, from value, in order from most-significant to + * least-significant. For example, appending 6 bits from 0x000001E will append the bits + * 0, 1, 1, 1, 1, 0 in that order. + */ + public void appendBits(int value, int numBits) { + if (numBits < 0 || numBits > 32) { + throw new IllegalArgumentException("Num bits must be between 0 and 32"); + } + ensureCapacity(size + numBits); + for (int numBitsLeft = numBits; numBitsLeft > 0; numBitsLeft--) { + appendBit(((value >> (numBitsLeft - 1)) & 0x01) == 1); + } + } + + public void appendBitArray(BitArray other) { + int otherSize = other.getSize(); + ensureCapacity(size + otherSize); + for (int i = 0; i < otherSize; i++) { + appendBit(other.get(i)); + } + } + + public void xor(BitArray other) { + if (bits.length != other.bits.length) { + throw new IllegalArgumentException("Sizes don't match"); + } + for (int i = 0; i < bits.length; i++) { + // The last byte could be incomplete (i.e. not have 8 bits in + // it) but there is no problem since 0 XOR 0 == 0. + bits[i] ^= other.bits[i]; + } + } + + /** + * + * @param bitOffset first bit to start writing + * @param array array to write into. Bytes are written most-significant byte first. This is the opposite + * of the internal representation, which is exposed by {@link #getBitArray()} + * @param offset position in array to start writing + * @param numBytes how many bytes to write + */ + public void toBytes(int bitOffset, byte[] array, int offset, int numBytes) { + for (int i = 0; i < numBytes; i++) { + int theByte = 0; + for (int j = 0; j < 8; j++) { + if (get(bitOffset)) { + theByte |= 1 << (7 - j); + } + bitOffset++; + } + array[offset + i] = (byte) theByte; + } + } + + /** + * @return underlying array of ints. The first element holds the first 32 bits, and the least + * significant bit is bit 0. + */ + public int[] getBitArray() { + return bits; + } + + /** + * Reverses all bits in the array. + */ + public void reverse() { + int[] newBits = new int[bits.length]; + int size = this.size; + for (int i = 0; i < size; i++) { + if (get(size - i - 1)) { + newBits[i >> 5] |= 1 << (i & 0x1F); + } + } + bits = newBits; + } + + private static int[] makeArray(int size) { + return new int[(size + 31) >> 5]; + } + + public String toString() { + StringBuffer result = new StringBuffer(size); + for (int i = 0; i < size; i++) { + if ((i & 0x07) == 0) { + result.append(' '); + } + result.append(get(i) ? 'X' : '.'); + } + return result.toString(); + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/common/BitMatrix.java b/src/com/google/zxing/common/BitMatrix.java new file mode 100644 index 000000000..26cd5997a --- /dev/null +++ b/src/com/google/zxing/common/BitMatrix.java @@ -0,0 +1,226 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common; + +/** + *

Represents a 2D matrix of bits. In function arguments below, and throughout the common + * module, x is the column position, and y is the row position. The ordering is always x, y. + * The origin is at the top-left.

+ * + *

Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins + * with a new int. This is done intentionally so that we can copy out a row into a BitArray very + * efficiently.

+ * + *

The ordering of bits is row-major. Within each int, the least significant bits are used first, + * meaning they represent lower x values. This is compatible with BitArray's implementation.

+ * + * @author Sean Owen + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class BitMatrix { + + // TODO: Just like BitArray, these need to be public so ProGuard can inline them. + public final int width; + public final int height; + public final int rowSize; + public final int[] bits; + + // A helper to construct a square matrix. + public BitMatrix(int dimension) { + this(dimension, dimension); + } + + public BitMatrix(int width, int height) { + if (width < 1 || height < 1) { + throw new IllegalArgumentException("Both dimensions must be greater than 0"); + } + this.width = width; + this.height = height; + this.rowSize = (width + 31) >> 5; + bits = new int[rowSize * height]; + } + + /** + *

Gets the requested bit, where true means black.

+ * + * @param x The horizontal component (i.e. which column) + * @param y The vertical component (i.e. which row) + * @return value of given bit in matrix + */ + public boolean get(int x, int y) { + int offset = y * rowSize + (x >> 5); + return ((bits[offset] >>> (x & 0x1f)) & 1) != 0; + } + + /** + *

Sets the given bit to true.

+ * + * @param x The horizontal component (i.e. which column) + * @param y The vertical component (i.e. which row) + */ + public void set(int x, int y) { + int offset = y * rowSize + (x >> 5); + bits[offset] |= 1 << (x & 0x1f); + } + + /** + *

Flips the given bit.

+ * + * @param x The horizontal component (i.e. which column) + * @param y The vertical component (i.e. which row) + */ + public void flip(int x, int y) { + int offset = y * rowSize + (x >> 5); + bits[offset] ^= 1 << (x & 0x1f); + } + + /** + * Clears all bits (sets to false). + */ + public void clear() { + int max = bits.length; + for (int i = 0; i < max; i++) { + bits[i] = 0; + } + } + + /** + *

Sets a square region of the bit matrix to true.

+ * + * @param left The horizontal position to begin at (inclusive) + * @param top The vertical position to begin at (inclusive) + * @param width The width of the region + * @param height The height of the region + */ + public void setRegion(int left, int top, int width, int height) { + if (top < 0 || left < 0) { + throw new IllegalArgumentException("Left and top must be nonnegative"); + } + if (height < 1 || width < 1) { + throw new IllegalArgumentException("Height and width must be at least 1"); + } + int right = left + width; + int bottom = top + height; + if (bottom > this.height || right > this.width) { + throw new IllegalArgumentException("The region must fit inside the matrix"); + } + for (int y = top; y < bottom; y++) { + int offset = y * rowSize; + for (int x = left; x < right; x++) { + bits[offset + (x >> 5)] |= 1 << (x & 0x1f); + } + } + } + + /** + * A fast method to retrieve one row of data from the matrix as a BitArray. + * + * @param y The row to retrieve + * @param row An optional caller-allocated BitArray, will be allocated if null or too small + * @return The resulting BitArray - this reference should always be used even when passing + * your own row + */ + public BitArray getRow(int y, BitArray row) { + if (row == null || row.getSize() < width) { + row = new BitArray(width); + } + int offset = y * rowSize; + for (int x = 0; x < rowSize; x++) { + row.setBulk(x << 5, bits[offset + x]); + } + return row; + } + + /** + * This is useful in detecting a corner of a 'pure' barcode. + * + * @return {x,y} coordinate of top-left-most 1 bit, or null if it is all white + */ + public int[] getTopLeftOnBit() { + int bitsOffset = 0; + while (bitsOffset < bits.length && bits[bitsOffset] == 0) { + bitsOffset++; + } + if (bitsOffset == bits.length) { + return null; + } + int y = bitsOffset / rowSize; + int x = (bitsOffset % rowSize) << 5; + + int theBits = bits[bitsOffset]; + int bit = 0; + while ((theBits << (31-bit)) == 0) { + bit++; + } + x += bit; + return new int[] {x, y}; + } + + /** + * @return The width of the matrix + */ + public int getWidth() { + return width; + } + + /** + * @return The height of the matrix + */ + public int getHeight() { + return height; + } + + public boolean equals(Object o) { + if (!(o instanceof BitMatrix)) { + return false; + } + BitMatrix other = (BitMatrix) o; + if (width != other.width || height != other.height || + rowSize != other.rowSize || bits.length != other.bits.length) { + return false; + } + for (int i = 0; i < bits.length; i++) { + if (bits[i] != other.bits[i]) { + return false; + } + } + return true; + } + + public int hashCode() { + int hash = width; + hash = 31 * hash + width; + hash = 31 * hash + height; + hash = 31 * hash + rowSize; + for (int i = 0; i < bits.length; i++) { + hash = 31 * hash + bits[i]; + } + return hash; + } + + public String toString() { + StringBuffer result = new StringBuffer(height * (width + 1)); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + result.append(get(x, y) ? "X " : " "); + } + result.append('\n'); + } + return result.toString(); + } + +} diff --git a/src/com/google/zxing/common/BitSource.java b/src/com/google/zxing/common/BitSource.java new file mode 100644 index 000000000..e5d7b1c87 --- /dev/null +++ b/src/com/google/zxing/common/BitSource.java @@ -0,0 +1,97 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common; + +/** + *

This provides an easy abstraction to read bits at a time from a sequence of bytes, where the + * number of bits read is not often a multiple of 8.

+ * + *

This class is thread-safe but not reentrant. Unless the caller modifies the bytes array + * it passed in, in which case all bets are off.

+ * + * @author Sean Owen + */ +public final class BitSource { + + private final byte[] bytes; + private int byteOffset; + private int bitOffset; + + /** + * @param bytes bytes from which this will read bits. Bits will be read from the first byte first. + * Bits are read within a byte from most-significant to least-significant bit. + */ + public BitSource(byte[] bytes) { + this.bytes = bytes; + } + + /** + * @param numBits number of bits to read + * @return int representing the bits read. The bits will appear as the least-significant + * bits of the int + * @throws IllegalArgumentException if numBits isn't in [1,32] + */ + public int readBits(int numBits) { + if (numBits < 1 || numBits > 32) { + throw new IllegalArgumentException(); + } + + int result = 0; + + // First, read remainder from current byte + if (bitOffset > 0) { + int bitsLeft = 8 - bitOffset; + int toRead = numBits < bitsLeft ? numBits : bitsLeft; + int bitsToNotRead = bitsLeft - toRead; + int mask = (0xFF >> (8 - toRead)) << bitsToNotRead; + result = (bytes[byteOffset] & mask) >> bitsToNotRead; + numBits -= toRead; + bitOffset += toRead; + if (bitOffset == 8) { + bitOffset = 0; + byteOffset++; + } + } + + // Next read whole bytes + if (numBits > 0) { + while (numBits >= 8) { + result = (result << 8) | (bytes[byteOffset] & 0xFF); + byteOffset++; + numBits -= 8; + } + + // Finally read a partial byte + if (numBits > 0) { + int bitsToNotRead = 8 - numBits; + int mask = (0xFF >> bitsToNotRead) << bitsToNotRead; + result = (result << numBits) | ((bytes[byteOffset] & mask) >> bitsToNotRead); + bitOffset += numBits; + } + } + + return result; + } + + /** + * @return number of bits that can be read successfully + */ + public int available() { + return 8 * (bytes.length - byteOffset) - bitOffset; + } + +} diff --git a/src/com/google/zxing/common/CharacterSetECI.java b/src/com/google/zxing/common/CharacterSetECI.java new file mode 100644 index 000000000..26a151888 --- /dev/null +++ b/src/com/google/zxing/common/CharacterSetECI.java @@ -0,0 +1,110 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common; + +import java.util.Hashtable; + +/** + * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 + * of ISO 18004. + * + * @author Sean Owen + */ +public final class CharacterSetECI extends ECI { + + private static Hashtable VALUE_TO_ECI; + private static Hashtable NAME_TO_ECI; + + private static void initialize() { + VALUE_TO_ECI = new Hashtable(29); + NAME_TO_ECI = new Hashtable(29); + // TODO figure out if these values are even right! + addCharacterSet(0, "Cp437"); + addCharacterSet(1, new String[] {"ISO8859_1", "ISO-8859-1"}); + addCharacterSet(2, "Cp437"); + addCharacterSet(3, new String[] {"ISO8859_1", "ISO-8859-1"}); + addCharacterSet(4, "ISO8859_2"); + addCharacterSet(5, "ISO8859_3"); + addCharacterSet(6, "ISO8859_4"); + addCharacterSet(7, "ISO8859_5"); + addCharacterSet(8, "ISO8859_6"); + addCharacterSet(9, "ISO8859_7"); + addCharacterSet(10, "ISO8859_8"); + addCharacterSet(11, "ISO8859_9"); + addCharacterSet(12, "ISO8859_10"); + addCharacterSet(13, "ISO8859_11"); + addCharacterSet(15, "ISO8859_13"); + addCharacterSet(16, "ISO8859_14"); + addCharacterSet(17, "ISO8859_15"); + addCharacterSet(18, "ISO8859_16"); + addCharacterSet(20, new String[] {"SJIS", "Shift_JIS"}); + } + + private final String encodingName; + + private CharacterSetECI(int value, String encodingName) { + super(value); + this.encodingName = encodingName; + } + + public String getEncodingName() { + return encodingName; + } + + private static void addCharacterSet(int value, String encodingName) { + CharacterSetECI eci = new CharacterSetECI(value, encodingName); + VALUE_TO_ECI.put(new Integer(value), eci); // can't use valueOf + NAME_TO_ECI.put(encodingName, eci); + } + + private static void addCharacterSet(int value, String[] encodingNames) { + CharacterSetECI eci = new CharacterSetECI(value, encodingNames[0]); + VALUE_TO_ECI.put(new Integer(value), eci); // can't use valueOf + for (int i = 0; i < encodingNames.length; i++) { + NAME_TO_ECI.put(encodingNames[i], eci); + } + } + + /** + * @param value character set ECI value + * @return {@link CharacterSetECI} representing ECI of given value, or null if it is legal but + * unsupported + * @throws IllegalArgumentException if ECI value is invalid + */ + public static CharacterSetECI getCharacterSetECIByValue(int value) { + if (VALUE_TO_ECI == null) { + initialize(); + } + if (value < 0 || value >= 900) { + throw new IllegalArgumentException("Bad ECI value: " + value); + } + return (CharacterSetECI) VALUE_TO_ECI.get(new Integer(value)); + } + + /** + * @param name character set ECI encoding name + * @return {@link CharacterSetECI} representing ECI for character encoding, or null if it is legal + * but unsupported + */ + public static CharacterSetECI getCharacterSetECIByName(String name) { + if (NAME_TO_ECI == null) { + initialize(); + } + return (CharacterSetECI) NAME_TO_ECI.get(name); + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/common/DecoderResult.java b/src/com/google/zxing/common/DecoderResult.java new file mode 100644 index 000000000..6e6659b46 --- /dev/null +++ b/src/com/google/zxing/common/DecoderResult.java @@ -0,0 +1,63 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common; + +import java.util.Vector; + +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +/** + *

Encapsulates the result of decoding a matrix of bits. This typically + * applies to 2D barcode formats. For now it contains the raw bytes obtained, + * as well as a String interpretation of those bytes, if applicable.

+ * + * @author Sean Owen + */ +public final class DecoderResult { + + private final byte[] rawBytes; + private final String text; + private final Vector byteSegments; + private final ErrorCorrectionLevel ecLevel; + + public DecoderResult(byte[] rawBytes, String text, Vector byteSegments, ErrorCorrectionLevel ecLevel) { + if (rawBytes == null && text == null) { + throw new IllegalArgumentException(); + } + this.rawBytes = rawBytes; + this.text = text; + this.byteSegments = byteSegments; + this.ecLevel = ecLevel; + } + + public byte[] getRawBytes() { + return rawBytes; + } + + public String getText() { + return text; + } + + public Vector getByteSegments() { + return byteSegments; + } + + public ErrorCorrectionLevel getECLevel() { + return ecLevel; + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/common/DetectorResult.java b/src/com/google/zxing/common/DetectorResult.java new file mode 100644 index 000000000..5e3786a35 --- /dev/null +++ b/src/com/google/zxing/common/DetectorResult.java @@ -0,0 +1,46 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common; + +import com.google.zxing.ResultPoint; + +/** + *

Encapsulates the result of detecting a barcode in an image. This includes the raw + * matrix of black/white pixels corresponding to the barcode, and possibly points of interest + * in the image, like the location of finder patterns or corners of the barcode in the image.

+ * + * @author Sean Owen + */ +public final class DetectorResult { + + private final BitMatrix bits; + private final ResultPoint[] points; + + public DetectorResult(BitMatrix bits, ResultPoint[] points) { + this.bits = bits; + this.points = points; + } + + public BitMatrix getBits() { + return bits; + } + + public ResultPoint[] getPoints() { + return points; + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/common/ECI.java b/src/com/google/zxing/common/ECI.java new file mode 100644 index 000000000..9d725dbc3 --- /dev/null +++ b/src/com/google/zxing/common/ECI.java @@ -0,0 +1,52 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common; + +/** + * Superclass of classes encapsulating types ECIs, according to "Extended Channel Interpretations" + * 5.3 of ISO 18004. + * + * @author Sean Owen + */ +public abstract class ECI { + + private final int value; + + ECI(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + /** + * @param value ECI value + * @return {@link ECI} representing ECI of given value, or null if it is legal but unsupported + * @throws IllegalArgumentException if ECI value is invalid + */ + public static ECI getECIByValue(int value) { + if (value < 0 || value > 999999) { + throw new IllegalArgumentException("Bad ECI value: " + value); + } + if (value < 900) { // Character set ECIs use 000000 - 000899 + return CharacterSetECI.getCharacterSetECIByValue(value); + } + return null; + } + +} diff --git a/src/com/google/zxing/common/StringUtils.java b/src/com/google/zxing/common/StringUtils.java new file mode 100644 index 000000000..d3a7c2b6c --- /dev/null +++ b/src/com/google/zxing/common/StringUtils.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2010 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common; + +import java.util.Hashtable; + +import com.google.zxing.DecodeHintType; + +/** + * Common string-related functions. + * + * @author Sean Owen + */ +public final class StringUtils { + + private static final String PLATFORM_DEFAULT_ENCODING = + System.getProperty("file.encoding"); + public static final String SHIFT_JIS = "SJIS"; + private static final String EUC_JP = "EUC_JP"; + private static final String UTF8 = "UTF8"; + private static final String ISO88591 = "ISO8859_1"; + private static final boolean ASSUME_SHIFT_JIS = + SHIFT_JIS.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING) || + EUC_JP.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING); + + private StringUtils() {} + + /** + * @param bytes bytes encoding a string, whose encoding should be guessed + * @param hints decode hints if applicable + * @return name of guessed encoding; at the moment will only guess one of: + * {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform + * default encoding if none of these can possibly be correct + */ + public static String guessEncoding(byte[] bytes, Hashtable hints) { + if (hints != null) { + String characterSet = (String) hints.get(DecodeHintType.CHARACTER_SET); + if (characterSet != null) { + return characterSet; + } + } + // Does it start with the UTF-8 byte order mark? then guess it's UTF-8 + if (bytes.length > 3 && + bytes[0] == (byte) 0xEF && + bytes[1] == (byte) 0xBB && + bytes[2] == (byte) 0xBF) { + return UTF8; + } + // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS, + // which should be by far the most common encodings. ISO-8859-1 + // should not have bytes in the 0x80 - 0x9F range, while Shift_JIS + // uses this as a first byte of a two-byte character. If we see this + // followed by a valid second byte in Shift_JIS, assume it is Shift_JIS. + // If we see something else in that second byte, we'll make the risky guess + // that it's UTF-8. + int length = bytes.length; + boolean canBeISO88591 = true; + boolean canBeShiftJIS = true; + boolean canBeUTF8 = true; + int utf8BytesLeft = 0; + int maybeDoubleByteCount = 0; + int maybeSingleByteKatakanaCount = 0; + boolean sawLatin1Supplement = false; + boolean sawUTF8Start = false; + boolean lastWasPossibleDoubleByteStart = false; + + for (int i = 0; + i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8); + i++) { + + int value = bytes[i] & 0xFF; + + // UTF-8 stuff + if (value >= 0x80 && value <= 0xBF) { + if (utf8BytesLeft > 0) { + utf8BytesLeft--; + } + } else { + if (utf8BytesLeft > 0) { + canBeUTF8 = false; + } + if (value >= 0xC0 && value <= 0xFD) { + sawUTF8Start = true; + int valueCopy = value; + while ((valueCopy & 0x40) != 0) { + utf8BytesLeft++; + valueCopy <<= 1; + } + } + } + + // ISO-8859-1 stuff + + if ((value == 0xC2 || value == 0xC3) && i < length - 1) { + // This is really a poor hack. The slightly more exotic characters people might want to put in + // a QR Code, by which I mean the Latin-1 supplement characters (e.g. u-umlaut) have encodings + // that start with 0xC2 followed by [0xA0,0xBF], or start with 0xC3 followed by [0x80,0xBF]. + int nextValue = bytes[i + 1] & 0xFF; + if (nextValue <= 0xBF && + ((value == 0xC2 && nextValue >= 0xA0) || (value == 0xC3 && nextValue >= 0x80))) { + sawLatin1Supplement = true; + } + } + if (value >= 0x7F && value <= 0x9F) { + canBeISO88591 = false; + } + + // Shift_JIS stuff + + if (value >= 0xA1 && value <= 0xDF) { + // count the number of characters that might be a Shift_JIS single-byte Katakana character + if (!lastWasPossibleDoubleByteStart) { + maybeSingleByteKatakanaCount++; + } + } + if (!lastWasPossibleDoubleByteStart && + ((value >= 0xF0 && value <= 0xFF) || value == 0x80 || value == 0xA0)) { + canBeShiftJIS = false; + } + if (((value >= 0x81 && value <= 0x9F) || (value >= 0xE0 && value <= 0xEF))) { + // These start double-byte characters in Shift_JIS. Let's see if it's followed by a valid + // second byte. + if (lastWasPossibleDoubleByteStart) { + // If we just checked this and the last byte for being a valid double-byte + // char, don't check starting on this byte. If this and the last byte + // formed a valid pair, then this shouldn't be checked to see if it starts + // a double byte pair of course. + lastWasPossibleDoubleByteStart = false; + } else { + // ... otherwise do check to see if this plus the next byte form a valid + // double byte pair encoding a character. + lastWasPossibleDoubleByteStart = true; + if (i >= bytes.length - 1) { + canBeShiftJIS = false; + } else { + int nextValue = bytes[i + 1] & 0xFF; + if (nextValue < 0x40 || nextValue > 0xFC) { + canBeShiftJIS = false; + } else { + maybeDoubleByteCount++; + } + // There is some conflicting information out there about which bytes can follow which in + // double-byte Shift_JIS characters. The rule above seems to be the one that matches practice. + } + } + } else { + lastWasPossibleDoubleByteStart = false; + } + } + if (utf8BytesLeft > 0) { + canBeUTF8 = false; + } + + // Easy -- if assuming Shift_JIS and no evidence it can't be, done + if (canBeShiftJIS && ASSUME_SHIFT_JIS) { + return SHIFT_JIS; + } + if (canBeUTF8 && sawUTF8Start) { + return UTF8; + } + // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough. The crude heuristic is: + // - If we saw + // - at least 3 bytes that starts a double-byte value (bytes that are rare in ISO-8859-1), or + // - over 5% of bytes could be single-byte Katakana (also rare in ISO-8859-1), + // - and, saw no sequences that are invalid in Shift_JIS, then we conclude Shift_JIS + if (canBeShiftJIS && (maybeDoubleByteCount >= 3 || 20 * maybeSingleByteKatakanaCount > length)) { + return SHIFT_JIS; + } + // Otherwise, we default to ISO-8859-1 unless we know it can't be + if (!sawLatin1Supplement && canBeISO88591) { + return ISO88591; + } + // Otherwise, we take a wild guess with platform encoding + return PLATFORM_DEFAULT_ENCODING; + } + +} diff --git a/src/com/google/zxing/common/reedsolomon/GF256.java b/src/com/google/zxing/common/reedsolomon/GF256.java new file mode 100644 index 000000000..9c5827527 --- /dev/null +++ b/src/com/google/zxing/common/reedsolomon/GF256.java @@ -0,0 +1,139 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common.reedsolomon; + +/** + *

This class contains utility methods for performing mathematical operations over + * the Galois Field GF(256). Operations use a given primitive polynomial in calculations.

+ * + *

Throughout this package, elements of GF(256) are represented as an int + * for convenience and speed (but at the cost of memory). + * Only the bottom 8 bits are really used.

+ * + * @author Sean Owen + */ +public final class GF256 { + + public static final GF256 QR_CODE_FIELD = new GF256(0x011D); // x^8 + x^4 + x^3 + x^2 + 1 + public static final GF256 DATA_MATRIX_FIELD = new GF256(0x012D); // x^8 + x^5 + x^3 + x^2 + 1 + + private final int[] expTable; + private final int[] logTable; + private final GF256Poly zero; + private final GF256Poly one; + + /** + * Create a representation of GF(256) using the given primitive polynomial. + * + * @param primitive irreducible polynomial whose coefficients are represented by + * the bits of an int, where the least-significant bit represents the constant + * coefficient + */ + private GF256(int primitive) { + expTable = new int[256]; + logTable = new int[256]; + int x = 1; + for (int i = 0; i < 256; i++) { + expTable[i] = x; + x <<= 1; // x = x * 2; we're assuming the generator alpha is 2 + if (x >= 0x100) { + x ^= primitive; + } + } + for (int i = 0; i < 255; i++) { + logTable[expTable[i]] = i; + } + // logTable[0] == 0 but this should never be used + zero = new GF256Poly(this, new int[]{0}); + one = new GF256Poly(this, new int[]{1}); + } + + GF256Poly getZero() { + return zero; + } + + GF256Poly getOne() { + return one; + } + + /** + * @return the monomial representing coefficient * x^degree + */ + GF256Poly buildMonomial(int degree, int coefficient) { + if (degree < 0) { + throw new IllegalArgumentException(); + } + if (coefficient == 0) { + return zero; + } + int[] coefficients = new int[degree + 1]; + coefficients[0] = coefficient; + return new GF256Poly(this, coefficients); + } + + /** + * Implements both addition and subtraction -- they are the same in GF(256). + * + * @return sum/difference of a and b + */ + static int addOrSubtract(int a, int b) { + return a ^ b; + } + + /** + * @return 2 to the power of a in GF(256) + */ + int exp(int a) { + return expTable[a]; + } + + /** + * @return base 2 log of a in GF(256) + */ + int log(int a) { + if (a == 0) { + throw new IllegalArgumentException(); + } + return logTable[a]; + } + + /** + * @return multiplicative inverse of a + */ + int inverse(int a) { + if (a == 0) { + throw new ArithmeticException(); + } + return expTable[255 - logTable[a]]; + } + + /** + * @param a + * @param b + * @return product of a and b in GF(256) + */ + int multiply(int a, int b) { + if (a == 0 || b == 0) { + return 0; + } + int logSum = logTable[a] + logTable[b]; + // index is a sped-up alternative to logSum % 255 since sum + // is in [0,510]. Thanks to jmsachs for the idea + return expTable[(logSum & 0xFF) + (logSum >>> 8)]; + } + +} diff --git a/src/com/google/zxing/common/reedsolomon/GF256Poly.java b/src/com/google/zxing/common/reedsolomon/GF256Poly.java new file mode 100644 index 000000000..8da7f931f --- /dev/null +++ b/src/com/google/zxing/common/reedsolomon/GF256Poly.java @@ -0,0 +1,263 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common.reedsolomon; + +/** + *

Represents a polynomial whose coefficients are elements of GF(256). + * Instances of this class are immutable.

+ * + *

Much credit is due to William Rucklidge since portions of this code are an indirect + * port of his C++ Reed-Solomon implementation.

+ * + * @author Sean Owen + */ +final class GF256Poly { + + private final GF256 field; + private final int[] coefficients; + + /** + * @param field the {@link GF256} instance representing the field to use + * to perform computations + * @param coefficients coefficients as ints representing elements of GF(256), arranged + * from most significant (highest-power term) coefficient to least significant + * @throws IllegalArgumentException if argument is null or empty, + * or if leading coefficient is 0 and this is not a + * constant polynomial (that is, it is not the monomial "0") + */ + GF256Poly(GF256 field, int[] coefficients) { + if (coefficients == null || coefficients.length == 0) { + throw new IllegalArgumentException(); + } + this.field = field; + int coefficientsLength = coefficients.length; + if (coefficientsLength > 1 && coefficients[0] == 0) { + // Leading term must be non-zero for anything except the constant polynomial "0" + int firstNonZero = 1; + while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) { + firstNonZero++; + } + if (firstNonZero == coefficientsLength) { + this.coefficients = field.getZero().coefficients; + } else { + this.coefficients = new int[coefficientsLength - firstNonZero]; + System.arraycopy(coefficients, + firstNonZero, + this.coefficients, + 0, + this.coefficients.length); + } + } else { + this.coefficients = coefficients; + } + } + + int[] getCoefficients() { + return coefficients; + } + + /** + * @return degree of this polynomial + */ + int getDegree() { + return coefficients.length - 1; + } + + /** + * @return true iff this polynomial is the monomial "0" + */ + boolean isZero() { + return coefficients[0] == 0; + } + + /** + * @return coefficient of x^degree term in this polynomial + */ + int getCoefficient(int degree) { + return coefficients[coefficients.length - 1 - degree]; + } + + /** + * @return evaluation of this polynomial at a given point + */ + int evaluateAt(int a) { + if (a == 0) { + // Just return the x^0 coefficient + return getCoefficient(0); + } + int size = coefficients.length; + if (a == 1) { + // Just the sum of the coefficients + int result = 0; + for (int i = 0; i < size; i++) { + result = GF256.addOrSubtract(result, coefficients[i]); + } + return result; + } + int result = coefficients[0]; + for (int i = 1; i < size; i++) { + result = GF256.addOrSubtract(field.multiply(a, result), coefficients[i]); + } + return result; + } + + GF256Poly addOrSubtract(GF256Poly other) { + if (!field.equals(other.field)) { + throw new IllegalArgumentException("GF256Polys do not have same GF256 field"); + } + if (isZero()) { + return other; + } + if (other.isZero()) { + return this; + } + + int[] smallerCoefficients = this.coefficients; + int[] largerCoefficients = other.coefficients; + if (smallerCoefficients.length > largerCoefficients.length) { + int[] temp = smallerCoefficients; + smallerCoefficients = largerCoefficients; + largerCoefficients = temp; + } + int[] sumDiff = new int[largerCoefficients.length]; + int lengthDiff = largerCoefficients.length - smallerCoefficients.length; + // Copy high-order terms only found in higher-degree polynomial's coefficients + System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff); + + for (int i = lengthDiff; i < largerCoefficients.length; i++) { + sumDiff[i] = GF256.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); + } + + return new GF256Poly(field, sumDiff); + } + + GF256Poly multiply(GF256Poly other) { + if (!field.equals(other.field)) { + throw new IllegalArgumentException("GF256Polys do not have same GF256 field"); + } + if (isZero() || other.isZero()) { + return field.getZero(); + } + int[] aCoefficients = this.coefficients; + int aLength = aCoefficients.length; + int[] bCoefficients = other.coefficients; + int bLength = bCoefficients.length; + int[] product = new int[aLength + bLength - 1]; + for (int i = 0; i < aLength; i++) { + int aCoeff = aCoefficients[i]; + for (int j = 0; j < bLength; j++) { + product[i + j] = GF256.addOrSubtract(product[i + j], + field.multiply(aCoeff, bCoefficients[j])); + } + } + return new GF256Poly(field, product); + } + + GF256Poly multiply(int scalar) { + if (scalar == 0) { + return field.getZero(); + } + if (scalar == 1) { + return this; + } + int size = coefficients.length; + int[] product = new int[size]; + for (int i = 0; i < size; i++) { + product[i] = field.multiply(coefficients[i], scalar); + } + return new GF256Poly(field, product); + } + + GF256Poly multiplyByMonomial(int degree, int coefficient) { + if (degree < 0) { + throw new IllegalArgumentException(); + } + if (coefficient == 0) { + return field.getZero(); + } + int size = coefficients.length; + int[] product = new int[size + degree]; + for (int i = 0; i < size; i++) { + product[i] = field.multiply(coefficients[i], coefficient); + } + return new GF256Poly(field, product); + } + + GF256Poly[] divide(GF256Poly other) { + if (!field.equals(other.field)) { + throw new IllegalArgumentException("GF256Polys do not have same GF256 field"); + } + if (other.isZero()) { + throw new IllegalArgumentException("Divide by 0"); + } + + GF256Poly quotient = field.getZero(); + GF256Poly remainder = this; + + int denominatorLeadingTerm = other.getCoefficient(other.getDegree()); + int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm); + + while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) { + int degreeDifference = remainder.getDegree() - other.getDegree(); + int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm); + GF256Poly term = other.multiplyByMonomial(degreeDifference, scale); + GF256Poly iterationQuotient = field.buildMonomial(degreeDifference, scale); + quotient = quotient.addOrSubtract(iterationQuotient); + remainder = remainder.addOrSubtract(term); + } + + return new GF256Poly[] { quotient, remainder }; + } + + public String toString() { + StringBuffer result = new StringBuffer(8 * getDegree()); + for (int degree = getDegree(); degree >= 0; degree--) { + int coefficient = getCoefficient(degree); + if (coefficient != 0) { + if (coefficient < 0) { + result.append(" - "); + coefficient = -coefficient; + } else { + if (result.length() > 0) { + result.append(" + "); + } + } + if (degree == 0 || coefficient != 1) { + int alphaPower = field.log(coefficient); + if (alphaPower == 0) { + result.append('1'); + } else if (alphaPower == 1) { + result.append('a'); + } else { + result.append("a^"); + result.append(alphaPower); + } + } + if (degree != 0) { + if (degree == 1) { + result.append('x'); + } else { + result.append("x^"); + result.append(degree); + } + } + } + } + return result.toString(); + } + +} diff --git a/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java b/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java new file mode 100644 index 000000000..5ce80c867 --- /dev/null +++ b/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java @@ -0,0 +1,75 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common.reedsolomon; + +import java.util.Vector; + +/** + *

Implements Reed-Solomon enbcoding, as the name implies.

+ * + * @author Sean Owen + * @author William Rucklidge + */ +public final class ReedSolomonEncoder { + + private final GF256 field; + private final Vector cachedGenerators; + + public ReedSolomonEncoder(GF256 field) { + if (!GF256.QR_CODE_FIELD.equals(field)) { + throw new IllegalArgumentException("Only QR Code is supported at this time"); + } + this.field = field; + this.cachedGenerators = new Vector(); + cachedGenerators.addElement(new GF256Poly(field, new int[] { 1 })); + } + + private GF256Poly buildGenerator(int degree) { + if (degree >= cachedGenerators.size()) { + GF256Poly lastGenerator = (GF256Poly) cachedGenerators.elementAt(cachedGenerators.size() - 1); + for (int d = cachedGenerators.size(); d <= degree; d++) { + GF256Poly nextGenerator = lastGenerator.multiply(new GF256Poly(field, new int[] { 1, field.exp(d - 1) })); + cachedGenerators.addElement(nextGenerator); + lastGenerator = nextGenerator; + } + } + return (GF256Poly) cachedGenerators.elementAt(degree); + } + + public void encode(int[] toEncode, int ecBytes) { + if (ecBytes == 0) { + throw new IllegalArgumentException("No error correction bytes"); + } + int dataBytes = toEncode.length - ecBytes; + if (dataBytes <= 0) { + throw new IllegalArgumentException("No data bytes provided"); + } + GF256Poly generator = buildGenerator(ecBytes); + int[] infoCoefficients = new int[dataBytes]; + System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes); + GF256Poly info = new GF256Poly(field, infoCoefficients); + info = info.multiplyByMonomial(ecBytes, 1); + GF256Poly remainder = info.divide(generator)[1]; + int[] coefficients = remainder.getCoefficients(); + int numZeroCoefficients = ecBytes - coefficients.length; + for (int i = 0; i < numZeroCoefficients; i++) { + toEncode[dataBytes + i] = 0; + } + System.arraycopy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.length); + } + +} diff --git a/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java b/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java new file mode 100644 index 000000000..d5b45a612 --- /dev/null +++ b/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.common.reedsolomon; + +/** + *

Thrown when an exception occurs during Reed-Solomon decoding, such as when + * there are too many errors to correct.

+ * + * @author Sean Owen + */ +public final class ReedSolomonException extends Exception { + + public ReedSolomonException(String message) { + super(message); + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/datamatrix/decoder/BitMatrixParser.java b/src/com/google/zxing/datamatrix/decoder/BitMatrixParser.java new file mode 100644 index 000000000..c33839ba3 --- /dev/null +++ b/src/com/google/zxing/datamatrix/decoder/BitMatrixParser.java @@ -0,0 +1,446 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.datamatrix.decoder; + +import com.google.zxing.FormatException; +import com.google.zxing.common.BitMatrix; + +/** + * @author bbrown@google.com (Brian Brown) + */ +final class BitMatrixParser { + + private final BitMatrix mappingBitMatrix; + private final BitMatrix readMappingMatrix; + private final Version version; + + /** + * @param bitMatrix {@link BitMatrix} to parse + * @throws FormatException if dimension is < 10 or > 144 or not 0 mod 2 + */ + BitMatrixParser(BitMatrix bitMatrix) throws FormatException { + int dimension = bitMatrix.getHeight(); + if (dimension < 10 || dimension > 144 || (dimension & 0x01) != 0) { + throw FormatException.getFormatInstance(); + } + + version = readVersion(bitMatrix); + this.mappingBitMatrix = extractDataRegion(bitMatrix); + // TODO(bbrown): Make this work for rectangular symbols + this.readMappingMatrix = new BitMatrix(this.mappingBitMatrix.getHeight()); + } + + /** + *

Creates the version object based on the dimension of the original bit matrix from + * the datamatrix code.

+ * + *

See ISO 16022:2006 Table 7 - ECC 200 symbol attributes

+ * + * @param bitMatrix Original {@link BitMatrix} including alignment patterns + * @return {@link Version} encapsulating the Data Matrix Code's "version" + * @throws FormatException if the dimensions of the mapping matrix are not valid + * Data Matrix dimensions. + */ + Version readVersion(BitMatrix bitMatrix) throws FormatException { + + if (version != null) { + return version; + } + + // TODO(bbrown): make this work for rectangular dimensions as well. + int numRows = bitMatrix.getHeight(); + int numColumns = numRows; + + return Version.getVersionForDimensions(numRows, numColumns); + } + + /** + *

Reads the bits in the {@link BitMatrix} representing the mapping matrix (No alignment patterns) + * in the correct order in order to reconstitute the codewords bytes contained within the + * Data Matrix Code.

+ * + * @return bytes encoded within the Data Matrix Code + * @throws FormatException if the exact number of bytes expected is not read + */ + byte[] readCodewords() throws FormatException { + + byte[] result = new byte[version.getTotalCodewords()]; + int resultOffset = 0; + + int row = 4; + int column = 0; + // TODO(bbrown): Data Matrix can be rectangular, assuming square for now + int numRows = mappingBitMatrix.getHeight(); + int numColumns = numRows; + + boolean corner1Read = false; + boolean corner2Read = false; + boolean corner3Read = false; + boolean corner4Read = false; + + // Read all of the codewords + do { + // Check the four corner cases + if ((row == numRows) && (column == 0) && !corner1Read) { + result[resultOffset++] = (byte) readCorner1(numRows, numColumns); + row -= 2; + column +=2; + corner1Read = true; + } else if ((row == numRows-2) && (column == 0) && ((numColumns & 0x03) != 0) && !corner2Read) { + result[resultOffset++] = (byte) readCorner2(numRows, numColumns); + row -= 2; + column +=2; + corner2Read = true; + } else if ((row == numRows+4) && (column == 2) && ((numColumns & 0x07) == 0) && !corner3Read) { + result[resultOffset++] = (byte) readCorner3(numRows, numColumns); + row -= 2; + column +=2; + corner3Read = true; + } else if ((row == numRows-2) && (column == 0) && ((numColumns & 0x07) == 4) && !corner4Read) { + result[resultOffset++] = (byte) readCorner4(numRows, numColumns); + row -= 2; + column +=2; + corner4Read = true; + } else { + // Sweep upward diagonally to the right + do { + if ((row < numRows) && (column >= 0) && !readMappingMatrix.get(column, row)) { + result[resultOffset++] = (byte) readUtah(row, column, numRows, numColumns); + } + row -= 2; + column +=2; + } while ((row >= 0) && (column < numColumns)); + row += 1; + column +=3; + + // Sweep downward diagonally to the left + do { + if ((row >= 0) && (column < numColumns) && !readMappingMatrix.get(column, row)) { + result[resultOffset++] = (byte) readUtah(row, column, numRows, numColumns); + } + row += 2; + column -=2; + } while ((row < numRows) && (column >= 0)); + row += 3; + column +=1; + } + } while ((row < numRows) || (column < numColumns)); + + if (resultOffset != version.getTotalCodewords()) { + throw FormatException.getFormatInstance(); + } + return result; + } + + /** + *

Reads a bit of the mapping matrix accounting for boundary wrapping.

+ * + * @param row Row to read in the mapping matrix + * @param column Column to read in the mapping matrix + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return value of the given bit in the mapping matrix + */ + boolean readModule(int row, int column, int numRows, int numColumns) { + // Adjust the row and column indices based on boundary wrapping + if (row < 0) { + row += numRows; + column += 4 - ((numRows + 4) & 0x07); + } + if (column < 0) { + column += numColumns; + row += 4 - ((numColumns + 4) & 0x07); + } + readMappingMatrix.set(column, row); + return mappingBitMatrix.get(column, row); + } + + /** + *

Reads the 8 bits of the standard Utah-shaped pattern.

+ * + *

See ISO 16022:2006, 5.8.1 Figure 6

+ * + * @param row Current row in the mapping matrix, anchored at the 8th bit (LSB) of the pattern + * @param column Current column in the mapping matrix, anchored at the 8th bit (LSB) of the pattern + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the utah shape + */ + int readUtah(int row, int column, int numRows, int numColumns) { + int currentByte = 0; + if (readModule(row - 2, column - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row - 2, column - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row - 1, column - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row - 1, column - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row - 1, column, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row, column - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row, column - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row, column, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + + /** + *

Reads the 8 bits of the special corner condition 1.

+ * + *

See ISO 16022:2006, Figure F.3

+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 1 + */ + int readCorner1(int numRows, int numColumns) { + int currentByte = 0; + if (readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 1, 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 1, 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(2, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(3, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + + /** + *

Reads the 8 bits of the special corner condition 2.

+ * + *

See ISO 16022:2006, Figure F.4

+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 2 + */ + int readCorner2(int numRows, int numColumns) { + int currentByte = 0; + if (readModule(numRows - 3, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 2, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 4, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 3, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + + /** + *

Reads the 8 bits of the special corner condition 3.

+ * + *

See ISO 16022:2006, Figure F.5

+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 3 + */ + int readCorner3(int numRows, int numColumns) { + int currentByte = 0; + if (readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 3, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 3, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + + /** + *

Reads the 8 bits of the special corner condition 4.

+ * + *

See ISO 16022:2006, Figure F.6

+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 4 + */ + int readCorner4(int numRows, int numColumns) { + int currentByte = 0; + if (readModule(numRows - 3, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 2, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(2, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(3, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + + /** + *

Extracts the data region from a {@link BitMatrix} that contains + * alignment patterns.

+ * + * @param bitMatrix Original {@link BitMatrix} with alignment patterns + * @return BitMatrix that has the alignment patterns removed + */ + BitMatrix extractDataRegion(BitMatrix bitMatrix) { + int symbolSizeRows = version.getSymbolSizeRows(); + int symbolSizeColumns = version.getSymbolSizeColumns(); + + // TODO(bbrown): Make this work with rectangular codes + if (bitMatrix.getHeight() != symbolSizeRows) { + throw new IllegalArgumentException("Dimension of bitMarix must match the version size"); + } + + int dataRegionSizeRows = version.getDataRegionSizeRows(); + int dataRegionSizeColumns = version.getDataRegionSizeColumns(); + + int numDataRegionsRow = symbolSizeRows / dataRegionSizeRows; + int numDataRegionsColumn = symbolSizeColumns / dataRegionSizeColumns; + + int sizeDataRegionRow = numDataRegionsRow * dataRegionSizeRows; + //int sizeDataRegionColumn = numDataRegionsColumn * dataRegionSizeColumns; + + // TODO(bbrown): Make this work with rectangular codes + BitMatrix bitMatrixWithoutAlignment = new BitMatrix(sizeDataRegionRow); + for (int dataRegionRow = 0; dataRegionRow < numDataRegionsRow; ++dataRegionRow) { + int dataRegionRowOffset = dataRegionRow * dataRegionSizeRows; + for (int dataRegionColumn = 0; dataRegionColumn < numDataRegionsColumn; ++dataRegionColumn) { + int dataRegionColumnOffset = dataRegionColumn * dataRegionSizeColumns; + for (int i = 0; i < dataRegionSizeRows; ++i) { + int readRowOffset = dataRegionRow * (dataRegionSizeRows + 2) + 1 + i; + int writeRowOffset = dataRegionRowOffset + i; + for (int j = 0; j < dataRegionSizeColumns; ++j) { + int readColumnOffset = dataRegionColumn * (dataRegionSizeColumns + 2) + 1 + j; + if (bitMatrix.get(readColumnOffset, readRowOffset)) { + int writeColumnOffset = dataRegionColumnOffset + j; + bitMatrixWithoutAlignment.set(writeColumnOffset, writeRowOffset); + } + } + } + } + } + return bitMatrixWithoutAlignment; + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/datamatrix/decoder/DataBlock.java b/src/com/google/zxing/datamatrix/decoder/DataBlock.java new file mode 100644 index 000000000..1e605ac9d --- /dev/null +++ b/src/com/google/zxing/datamatrix/decoder/DataBlock.java @@ -0,0 +1,118 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.datamatrix.decoder; + +/** + *

Encapsulates a block of data within a Data Matrix Code. Data Matrix Codes may split their data into + * multiple blocks, each of which is a unit of data and error-correction codewords. Each + * is represented by an instance of this class.

+ * + * @author bbrown@google.com (Brian Brown) + */ +final class DataBlock { + + private final int numDataCodewords; + private final byte[] codewords; + + private DataBlock(int numDataCodewords, byte[] codewords) { + this.numDataCodewords = numDataCodewords; + this.codewords = codewords; + } + + /** + *

When Data Matrix Codes use multiple data blocks, they actually interleave the bytes of each of them. + * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This + * method will separate the data into original blocks.

+ * + * @param rawCodewords bytes as read directly from the Data Matrix Code + * @param version version of the Data Matrix Code + * @return {@link DataBlock}s containing original bytes, "de-interleaved" from representation in the + * Data Matrix Code + */ + static DataBlock[] getDataBlocks(byte[] rawCodewords, + Version version) { + // Figure out the number and size of data blocks used by this version + Version.ECBlocks ecBlocks = version.getECBlocks(); + + // First count the total number of data blocks + int totalBlocks = 0; + Version.ECB[] ecBlockArray = ecBlocks.getECBlocks(); + for (int i = 0; i < ecBlockArray.length; i++) { + totalBlocks += ecBlockArray[i].getCount(); + } + + // Now establish DataBlocks of the appropriate size and number of data codewords + DataBlock[] result = new DataBlock[totalBlocks]; + int numResultBlocks = 0; + for (int j = 0; j < ecBlockArray.length; j++) { + Version.ECB ecBlock = ecBlockArray[j]; + for (int i = 0; i < ecBlock.getCount(); i++) { + int numDataCodewords = ecBlock.getDataCodewords(); + int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords; + result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]); + } + } + + // All blocks have the same amount of data, except that the last n + // (where n may be 0) have 1 less byte. Figure out where these start. + // TODO(bbrown): There is only one case where there is a difference for Data Matrix for size 144 + int longerBlocksTotalCodewords = result[0].codewords.length; + //int shorterBlocksTotalCodewords = longerBlocksTotalCodewords - 1; + + int longerBlocksNumDataCodewords = longerBlocksTotalCodewords - ecBlocks.getECCodewords(); + int shorterBlocksNumDataCodewords = longerBlocksNumDataCodewords - 1; + // The last elements of result may be 1 element shorter for 144 matrix + // first fill out as many elements as all of them have minus 1 + int rawCodewordsOffset = 0; + for (int i = 0; i < shorterBlocksNumDataCodewords; i++) { + for (int j = 0; j < numResultBlocks; j++) { + result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; + } + } + + // Fill out the last data block in the longer ones + boolean specialVersion = version.getVersionNumber() == 24; + int numLongerBlocks = specialVersion ? 8 : numResultBlocks; + for (int j = 0; j < numLongerBlocks; j++) { + result[j].codewords[longerBlocksNumDataCodewords - 1] = rawCodewords[rawCodewordsOffset++]; + } + + // Now add in error correction blocks + int max = result[0].codewords.length; + for (int i = longerBlocksNumDataCodewords; i < max; i++) { + for (int j = 0; j < numResultBlocks; j++) { + int iOffset = (specialVersion && j > 7) ? i - 1 : i; + result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; + } + } + + if (rawCodewordsOffset != rawCodewords.length) { + throw new IllegalArgumentException(); + } + + return result; + } + + int getNumDataCodewords() { + return numDataCodewords; + } + + byte[] getCodewords() { + return codewords; + } + +} diff --git a/src/com/google/zxing/datamatrix/decoder/Version.java b/src/com/google/zxing/datamatrix/decoder/Version.java new file mode 100644 index 000000000..45f9e3553 --- /dev/null +++ b/src/com/google/zxing/datamatrix/decoder/Version.java @@ -0,0 +1,242 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.datamatrix.decoder; + +import com.google.zxing.FormatException; + +/** + * The Version object encapsulates attributes about a particular + * size Data Matrix Code. + * + * @author bbrown@google.com (Brian Brown) + */ +public final class Version { + + private static final Version[] VERSIONS = buildVersions(); + + private final int versionNumber; + private final int symbolSizeRows; + private final int symbolSizeColumns; + private final int dataRegionSizeRows; + private final int dataRegionSizeColumns; + private final ECBlocks ecBlocks; + private final int totalCodewords; + + private Version(int versionNumber, + int symbolSizeRows, + int symbolSizeColumns, + int dataRegionSizeRows, + int dataRegionSizeColumns, + ECBlocks ecBlocks) { + this.versionNumber = versionNumber; + this.symbolSizeRows = symbolSizeRows; + this.symbolSizeColumns = symbolSizeColumns; + this.dataRegionSizeRows = dataRegionSizeRows; + this.dataRegionSizeColumns = dataRegionSizeColumns; + this.ecBlocks = ecBlocks; + + // Calculate the total number of codewords + int total = 0; + int ecCodewords = ecBlocks.getECCodewords(); + ECB[] ecbArray = ecBlocks.getECBlocks(); + for (int i = 0; i < ecbArray.length; i++) { + ECB ecBlock = ecbArray[i]; + total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords); + } + this.totalCodewords = total; + } + + public int getVersionNumber() { + return versionNumber; + } + + public int getSymbolSizeRows() { + return symbolSizeRows; + } + + public int getSymbolSizeColumns() { + return symbolSizeColumns; + } + + public int getDataRegionSizeRows() { + return dataRegionSizeRows; + } + + public int getDataRegionSizeColumns() { + return dataRegionSizeColumns; + } + + public int getTotalCodewords() { + return totalCodewords; + } + + ECBlocks getECBlocks() { + return ecBlocks; + } + + /** + *

Deduces version information from Data Matrix dimensions.

+ * + * @param numRows Number of rows in modules + * @param numColumns Number of columns in modules + * @return {@link Version} for a Data Matrix Code of those dimensions + * @throws FormatException if dimensions do correspond to a valid Data Matrix size + */ + public static Version getVersionForDimensions(int numRows, int numColumns) throws FormatException { + if ((numRows & 0x01) != 0 || (numColumns & 0x01) != 0) { + throw FormatException.getFormatInstance(); + } + + // TODO(bbrown): This is doing a linear search through the array of versions. + // If we interleave the rectangular versions with the square versions we could + // do a binary search. + int numVersions = VERSIONS.length; + for (int i = 0; i < numVersions; ++i){ + Version version = VERSIONS[i]; + if (version.symbolSizeRows == numRows && version.symbolSizeColumns == numColumns) { + return version; + } + } + + throw FormatException.getFormatInstance(); + } + + /** + *

Encapsulates a set of error-correction blocks in one symbol version. Most versions will + * use blocks of differing sizes within one version, so, this encapsulates the parameters for + * each set of blocks. It also holds the number of error-correction codewords per block since it + * will be the same across all blocks within one version.

+ */ + static final class ECBlocks { + private final int ecCodewords; + private final ECB[] ecBlocks; + + private ECBlocks(int ecCodewords, ECB ecBlocks) { + this.ecCodewords = ecCodewords; + this.ecBlocks = new ECB[] { ecBlocks }; + } + + private ECBlocks(int ecCodewords, ECB ecBlocks1, ECB ecBlocks2) { + this.ecCodewords = ecCodewords; + this.ecBlocks = new ECB[] { ecBlocks1, ecBlocks2 }; + } + + int getECCodewords() { + return ecCodewords; + } + + ECB[] getECBlocks() { + return ecBlocks; + } + } + + /** + *

Encapsualtes the parameters for one error-correction block in one symbol version. + * This includes the number of data codewords, and the number of times a block with these + * parameters is used consecutively in the Data Matrix code version's format.

+ */ + static final class ECB { + private final int count; + private final int dataCodewords; + + private ECB(int count, int dataCodewords) { + this.count = count; + this.dataCodewords = dataCodewords; + } + + int getCount() { + return count; + } + + int getDataCodewords() { + return dataCodewords; + } + } + + public String toString() { + return String.valueOf(versionNumber); + } + + /** + * See ISO 16022:2006 5.5.1 Table 7 + */ + private static Version[] buildVersions() { + return new Version[]{ + new Version(1, 10, 10, 8, 8, + new ECBlocks(5, new ECB(1, 3))), + new Version(2, 12, 12, 10, 10, + new ECBlocks(7, new ECB(1, 5))), + new Version(3, 14, 14, 12, 12, + new ECBlocks(10, new ECB(1, 8))), + new Version(4, 16, 16, 14, 14, + new ECBlocks(12, new ECB(1, 12))), + new Version(5, 18, 18, 16, 16, + new ECBlocks(14, new ECB(1, 18))), + new Version(6, 20, 20, 18, 18, + new ECBlocks(18, new ECB(1, 22))), + new Version(7, 22, 22, 20, 20, + new ECBlocks(20, new ECB(1, 30))), + new Version(8, 24, 24, 22, 22, + new ECBlocks(24, new ECB(1, 36))), + new Version(9, 26, 26, 24, 24, + new ECBlocks(28, new ECB(1, 44))), + new Version(10, 32, 32, 14, 14, + new ECBlocks(36, new ECB(1, 62))), + new Version(11, 36, 36, 16, 16, + new ECBlocks(42, new ECB(1, 86))), + new Version(12, 40, 40, 18, 18, + new ECBlocks(48, new ECB(1, 114))), + new Version(13, 44, 44, 20, 20, + new ECBlocks(56, new ECB(1, 144))), + new Version(14, 48, 48, 22, 22, + new ECBlocks(68, new ECB(1, 174))), + new Version(15, 52, 52, 24, 24, + new ECBlocks(42, new ECB(2, 102))), + new Version(16, 64, 64, 14, 14, + new ECBlocks(56, new ECB(2, 140))), + new Version(17, 72, 72, 16, 16, + new ECBlocks(36, new ECB(4, 92))), + new Version(18, 80, 80, 18, 18, + new ECBlocks(48, new ECB(4, 114))), + new Version(19, 88, 88, 20, 20, + new ECBlocks(56, new ECB(4, 144))), + new Version(20, 96, 96, 22, 22, + new ECBlocks(68, new ECB(4, 174))), + new Version(21, 104, 104, 24, 24, + new ECBlocks(56, new ECB(6, 136))), + new Version(22, 120, 120, 18, 18, + new ECBlocks(68, new ECB(6, 175))), + new Version(23, 132, 132, 20, 20, + new ECBlocks(62, new ECB(8, 163))), + new Version(24, 144, 144, 22, 22, + new ECBlocks(62, new ECB(8, 156), new ECB(2, 155))), + new Version(25, 8, 18, 6, 16, + new ECBlocks(7, new ECB(1, 5))), + new Version(26, 8, 32, 6, 14, + new ECBlocks(11, new ECB(1, 10))), + new Version(27, 12, 26, 10, 24, + new ECBlocks(14, new ECB(1, 16))), + new Version(28, 12, 36, 10, 16, + new ECBlocks(18, new ECB(1, 22))), + new Version(29, 16, 36, 10, 16, + new ECBlocks(24, new ECB(1, 32))), + new Version(30, 16, 48, 14, 22, + new ECBlocks(28, new ECB(1, 49))) + }; + } + +} diff --git a/src/com/google/zxing/qrcode/QRCodeWriter.java b/src/com/google/zxing/qrcode/QRCodeWriter.java new file mode 100644 index 000000000..bb20008d1 --- /dev/null +++ b/src/com/google/zxing/qrcode/QRCodeWriter.java @@ -0,0 +1,106 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode; + +import java.util.Hashtable; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.Writer; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.google.zxing.qrcode.encoder.ByteMatrix; +import com.google.zxing.qrcode.encoder.Encoder; +import com.google.zxing.qrcode.encoder.QRCode; + +/** + * This object renders a QR Code as a BitMatrix 2D array of greyscale values. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class QRCodeWriter implements Writer { + + private static final int QUIET_ZONE_SIZE = 4; + + public BitMatrix encode(String contents, BarcodeFormat format, int multiple) throws WriterException { + + return encode(contents, format, multiple, null); + } + + public BitMatrix encode(String contents, BarcodeFormat format, int multiple, Hashtable hints) throws WriterException { + + if (contents == null || contents.length() == 0) { + throw new IllegalArgumentException("Found empty contents"); + } + + if (format != BarcodeFormat.QR_CODE) { + throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format); + } + + ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L; + if (hints != null) { + ErrorCorrectionLevel requestedECLevel = (ErrorCorrectionLevel) hints.get(EncodeHintType.ERROR_CORRECTION); + if (requestedECLevel != null) { + errorCorrectionLevel = requestedECLevel; + } + } + + QRCode code = new QRCode(); + Encoder.encode(contents, errorCorrectionLevel, hints, code); + return renderResult(code, multiple); + } + + // Note that the input matrix uses 0 == white, 1 == black, while the output + // matrix uses + // 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap). + private static BitMatrix renderResult(QRCode code, int multiple) { + ByteMatrix input = code.getMatrix(); + int inputWidth = input.getWidth(); + int inputHeight = input.getHeight(); + int qrWidth = inputWidth + (QUIET_ZONE_SIZE << 1); + int qrHeight = inputHeight + (QUIET_ZONE_SIZE << 1); + // int outputWidth = Math.max(width, qrWidth); + // int outputHeight = Math.max(height, qrHeight); + + // int multiple = Math.min(outputWidth / qrWidth, outputHeight / + // qrHeight); + // Padding includes both the quiet zone and the extra white pixels to + // accommodate the requested + // dimensions. For example, if input is 25x25 the QR will be 33x33 + // including the quiet zone. + // If the requested size is 200x160, the multiple will be 4, for a QR of + // 132x132. These will + // handle all the padding from 100x100 (the actual QR) up to 200x160. + int leftPadding = QUIET_ZONE_SIZE * multiple; + int topPadding = QUIET_ZONE_SIZE * multiple; + + BitMatrix output = new BitMatrix(qrWidth * multiple, qrHeight * multiple); + + for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) { + // Write the contents of this row of the barcode + for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { + if (input.get(inputX, inputY) == 1) { + output.setRegion(outputX, outputY, multiple, multiple); + } + } + } + + return output; + } + +} diff --git a/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java b/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java new file mode 100644 index 000000000..dfe6fd351 --- /dev/null +++ b/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java @@ -0,0 +1,203 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +import com.google.zxing.FormatException; +import com.google.zxing.common.BitMatrix; + +/** + * @author Sean Owen + */ +final class BitMatrixParser { + + private final BitMatrix bitMatrix; + private Version parsedVersion; + private FormatInformation parsedFormatInfo; + + /** + * @param bitMatrix {@link BitMatrix} to parse + * @throws FormatException if dimension is not >= 21 and 1 mod 4 + */ + BitMatrixParser(BitMatrix bitMatrix) throws FormatException { + int dimension = bitMatrix.getHeight(); + if (dimension < 21 || (dimension & 0x03) != 1) { + throw FormatException.getFormatInstance(); + } + this.bitMatrix = bitMatrix; + } + + /** + *

Reads format information from one of its two locations within the QR Code.

+ * + * @return {@link FormatInformation} encapsulating the QR Code's format info + * @throws FormatException if both format information locations cannot be parsed as + * the valid encoding of format information + */ + FormatInformation readFormatInformation() throws FormatException { + + if (parsedFormatInfo != null) { + return parsedFormatInfo; + } + + // Read top-left format info bits + int formatInfoBits1 = 0; + for (int i = 0; i < 6; i++) { + formatInfoBits1 = copyBit(i, 8, formatInfoBits1); + } + // .. and skip a bit in the timing pattern ... + formatInfoBits1 = copyBit(7, 8, formatInfoBits1); + formatInfoBits1 = copyBit(8, 8, formatInfoBits1); + formatInfoBits1 = copyBit(8, 7, formatInfoBits1); + // .. and skip a bit in the timing pattern ... + for (int j = 5; j >= 0; j--) { + formatInfoBits1 = copyBit(8, j, formatInfoBits1); + } + + // Read the top-right/bottom-left pattern too + int dimension = bitMatrix.getHeight(); + int formatInfoBits2 = 0; + int iMin = dimension - 8; + for (int i = dimension - 1; i >= iMin; i--) { + formatInfoBits2 = copyBit(i, 8, formatInfoBits2); + } + for (int j = dimension - 7; j < dimension; j++) { + formatInfoBits2 = copyBit(8, j, formatInfoBits2); + } + + parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits1, formatInfoBits2); + if (parsedFormatInfo != null) { + return parsedFormatInfo; + } + throw FormatException.getFormatInstance(); + } + + /** + *

Reads version information from one of its two locations within the QR Code.

+ * + * @return {@link Version} encapsulating the QR Code's version + * @throws FormatException if both version information locations cannot be parsed as + * the valid encoding of version information + */ + Version readVersion() throws FormatException { + + if (parsedVersion != null) { + return parsedVersion; + } + + int dimension = bitMatrix.getHeight(); + + int provisionalVersion = (dimension - 17) >> 2; + if (provisionalVersion <= 6) { + return Version.getVersionForNumber(provisionalVersion); + } + + // Read top-right version info: 3 wide by 6 tall + int versionBits = 0; + int ijMin = dimension - 11; + for (int j = 5; j >= 0; j--) { + for (int i = dimension - 9; i >= ijMin; i--) { + versionBits = copyBit(i, j, versionBits); + } + } + + parsedVersion = Version.decodeVersionInformation(versionBits); + if (parsedVersion != null && parsedVersion.getDimensionForVersion() == dimension) { + return parsedVersion; + } + + // Hmm, failed. Try bottom left: 6 wide by 3 tall + versionBits = 0; + for (int i = 5; i >= 0; i--) { + for (int j = dimension - 9; j >= ijMin; j--) { + versionBits = copyBit(i, j, versionBits); + } + } + + parsedVersion = Version.decodeVersionInformation(versionBits); + if (parsedVersion != null && parsedVersion.getDimensionForVersion() == dimension) { + return parsedVersion; + } + throw FormatException.getFormatInstance(); + } + + private int copyBit(int i, int j, int versionBits) { + return bitMatrix.get(i, j) ? (versionBits << 1) | 0x1 : versionBits << 1; + } + + /** + *

Reads the bits in the {@link BitMatrix} representing the finder pattern in the + * correct order in order to reconstitute the codewords bytes contained within the + * QR Code.

+ * + * @return bytes encoded within the QR Code + * @throws FormatException if the exact number of bytes expected is not read + */ + byte[] readCodewords() throws FormatException { + + FormatInformation formatInfo = readFormatInformation(); + Version version = readVersion(); + + // Get the data mask for the format used in this QR Code. This will exclude + // some bits from reading as we wind through the bit matrix. + DataMask dataMask = DataMask.forReference((int) formatInfo.getDataMask()); + int dimension = bitMatrix.getHeight(); + dataMask.unmaskBitMatrix(bitMatrix, dimension); + + BitMatrix functionPattern = version.buildFunctionPattern(); + + boolean readingUp = true; + byte[] result = new byte[version.getTotalCodewords()]; + int resultOffset = 0; + int currentByte = 0; + int bitsRead = 0; + // Read columns in pairs, from right to left + for (int j = dimension - 1; j > 0; j -= 2) { + if (j == 6) { + // Skip whole column with vertical alignment pattern; + // saves time and makes the other code proceed more cleanly + j--; + } + // Read alternatingly from bottom to top then top to bottom + for (int count = 0; count < dimension; count++) { + int i = readingUp ? dimension - 1 - count : count; + for (int col = 0; col < 2; col++) { + // Ignore bits covered by the function pattern + if (!functionPattern.get(j - col, i)) { + // Read a bit + bitsRead++; + currentByte <<= 1; + if (bitMatrix.get(j - col, i)) { + currentByte |= 1; + } + // If we've made a whole byte, save it off + if (bitsRead == 8) { + result[resultOffset++] = (byte) currentByte; + bitsRead = 0; + currentByte = 0; + } + } + } + } + readingUp ^= true; // readingUp = !readingUp; // switch directions + } + if (resultOffset != version.getTotalCodewords()) { + throw FormatException.getFormatInstance(); + } + return result; + } + +} \ No newline at end of file diff --git a/src/com/google/zxing/qrcode/decoder/DataBlock.java b/src/com/google/zxing/qrcode/decoder/DataBlock.java new file mode 100644 index 000000000..215f6c0c2 --- /dev/null +++ b/src/com/google/zxing/qrcode/decoder/DataBlock.java @@ -0,0 +1,123 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +/** + *

Encapsulates a block of data within a QR Code. QR Codes may split their data into + * multiple blocks, each of which is a unit of data and error-correction codewords. Each + * is represented by an instance of this class.

+ * + * @author Sean Owen + */ +final class DataBlock { + + private final int numDataCodewords; + private final byte[] codewords; + + private DataBlock(int numDataCodewords, byte[] codewords) { + this.numDataCodewords = numDataCodewords; + this.codewords = codewords; + } + + /** + *

When QR Codes use multiple data blocks, they are actually interleaved. + * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This + * method will separate the data into original blocks.

+ * + * @param rawCodewords bytes as read directly from the QR Code + * @param version version of the QR Code + * @param ecLevel error-correction level of the QR Code + * @return {@link DataBlock}s containing original bytes, "de-interleaved" from representation in the + * QR Code + */ + static DataBlock[] getDataBlocks(byte[] rawCodewords, + Version version, + ErrorCorrectionLevel ecLevel) { + + if (rawCodewords.length != version.getTotalCodewords()) { + throw new IllegalArgumentException(); + } + + // Figure out the number and size of data blocks used by this version and + // error correction level + Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); + + // First count the total number of data blocks + int totalBlocks = 0; + Version.ECB[] ecBlockArray = ecBlocks.getECBlocks(); + for (int i = 0; i < ecBlockArray.length; i++) { + totalBlocks += ecBlockArray[i].getCount(); + } + + // Now establish DataBlocks of the appropriate size and number of data codewords + DataBlock[] result = new DataBlock[totalBlocks]; + int numResultBlocks = 0; + for (int j = 0; j < ecBlockArray.length; j++) { + Version.ECB ecBlock = ecBlockArray[j]; + for (int i = 0; i < ecBlock.getCount(); i++) { + int numDataCodewords = ecBlock.getDataCodewords(); + int numBlockCodewords = ecBlocks.getECCodewordsPerBlock() + numDataCodewords; + result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]); + } + } + + // All blocks have the same amount of data, except that the last n + // (where n may be 0) have 1 more byte. Figure out where these start. + int shorterBlocksTotalCodewords = result[0].codewords.length; + int longerBlocksStartAt = result.length - 1; + while (longerBlocksStartAt >= 0) { + int numCodewords = result[longerBlocksStartAt].codewords.length; + if (numCodewords == shorterBlocksTotalCodewords) { + break; + } + longerBlocksStartAt--; + } + longerBlocksStartAt++; + + int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewordsPerBlock(); + // The last elements of result may be 1 element longer; + // first fill out as many elements as all of them have + int rawCodewordsOffset = 0; + for (int i = 0; i < shorterBlocksNumDataCodewords; i++) { + for (int j = 0; j < numResultBlocks; j++) { + result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; + } + } + // Fill out the last data block in the longer ones + for (int j = longerBlocksStartAt; j < numResultBlocks; j++) { + result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++]; + } + // Now add in error correction blocks + int max = result[0].codewords.length; + for (int i = shorterBlocksNumDataCodewords; i < max; i++) { + for (int j = 0; j < numResultBlocks; j++) { + int iOffset = j < longerBlocksStartAt ? i : i + 1; + result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; + } + } + return result; + } + + int getNumDataCodewords() { + return numDataCodewords; + } + + byte[] getCodewords() { + return codewords; + } + +} diff --git a/src/com/google/zxing/qrcode/decoder/DataMask.java b/src/com/google/zxing/qrcode/decoder/DataMask.java new file mode 100644 index 000000000..48036b1bf --- /dev/null +++ b/src/com/google/zxing/qrcode/decoder/DataMask.java @@ -0,0 +1,155 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +import com.google.zxing.common.BitMatrix; + +/** + *

Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations + * of this class can un-mask a raw BitMatrix. For simplicity, they will unmask the entire BitMatrix, + * including areas used for finder patterns, timing patterns, etc. These areas should be unused + * after the point they are unmasked anyway.

+ * + *

Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position + * and j is row position. In fact, as the text says, i is row position and j is column position.

+ * + * @author Sean Owen + */ +abstract class DataMask { + + /** + * See ISO 18004:2006 6.8.1 + */ + private static final DataMask[] DATA_MASKS = { + new DataMask000(), + new DataMask001(), + new DataMask010(), + new DataMask011(), + new DataMask100(), + new DataMask101(), + new DataMask110(), + new DataMask111(), + }; + + private DataMask() { + } + + /** + *

Implementations of this method reverse the data masking process applied to a QR Code and + * make its bits ready to read.

+ * + * @param bits representation of QR Code bits + * @param dimension dimension of QR Code, represented by bits, being unmasked + */ + final void unmaskBitMatrix(BitMatrix bits, int dimension) { + for (int i = 0; i < dimension; i++) { + for (int j = 0; j < dimension; j++) { + if (isMasked(i, j)) { + bits.flip(j, i); + } + } + } + } + + abstract boolean isMasked(int i, int j); + + /** + * @param reference a value between 0 and 7 indicating one of the eight possible + * data mask patterns a QR Code may use + * @return {@link DataMask} encapsulating the data mask pattern + */ + static DataMask forReference(int reference) { + if (reference < 0 || reference > 7) { + throw new IllegalArgumentException(); + } + return DATA_MASKS[reference]; + } + + /** + * 000: mask bits for which (x + y) mod 2 == 0 + */ + private static class DataMask000 extends DataMask { + boolean isMasked(int i, int j) { + return ((i + j) & 0x01) == 0; + } + } + + /** + * 001: mask bits for which x mod 2 == 0 + */ + private static class DataMask001 extends DataMask { + boolean isMasked(int i, int j) { + return (i & 0x01) == 0; + } + } + + /** + * 010: mask bits for which y mod 3 == 0 + */ + private static class DataMask010 extends DataMask { + boolean isMasked(int i, int j) { + return j % 3 == 0; + } + } + + /** + * 011: mask bits for which (x + y) mod 3 == 0 + */ + private static class DataMask011 extends DataMask { + boolean isMasked(int i, int j) { + return (i + j) % 3 == 0; + } + } + + /** + * 100: mask bits for which (x/2 + y/3) mod 2 == 0 + */ + private static class DataMask100 extends DataMask { + boolean isMasked(int i, int j) { + return (((i >>> 1) + (j /3)) & 0x01) == 0; + } + } + + /** + * 101: mask bits for which xy mod 2 + xy mod 3 == 0 + */ + private static class DataMask101 extends DataMask { + boolean isMasked(int i, int j) { + int temp = i * j; + return (temp & 0x01) + (temp % 3) == 0; + } + } + + /** + * 110: mask bits for which (xy mod 2 + xy mod 3) mod 2 == 0 + */ + private static class DataMask110 extends DataMask { + boolean isMasked(int i, int j) { + int temp = i * j; + return (((temp & 0x01) + (temp % 3)) & 0x01) == 0; + } + } + + /** + * 111: mask bits for which ((x+y)mod 2 + xy mod 3) mod 2 == 0 + */ + private static class DataMask111 extends DataMask { + boolean isMasked(int i, int j) { + return ((((i + j) & 0x01) + ((i * j) % 3)) & 0x01) == 0; + } + } +} diff --git a/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java b/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java new file mode 100644 index 000000000..400611afe --- /dev/null +++ b/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java @@ -0,0 +1,86 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +/** + *

See ISO 18004:2006, 6.5.1. This enum encapsulates the four error correction levels + * defined by the QR code standard.

+ * + * @author Sean Owen + */ +public final class ErrorCorrectionLevel { + + // No, we can't use an enum here. J2ME doesn't support it. + + /** + * L = ~7% correction + */ + public static final ErrorCorrectionLevel L = new ErrorCorrectionLevel(0, 0x01, "L"); + /** + * M = ~15% correction + */ + public static final ErrorCorrectionLevel M = new ErrorCorrectionLevel(1, 0x00, "M"); + /** + * Q = ~25% correction + */ + public static final ErrorCorrectionLevel Q = new ErrorCorrectionLevel(2, 0x03, "Q"); + /** + * H = ~30% correction + */ + public static final ErrorCorrectionLevel H = new ErrorCorrectionLevel(3, 0x02, "H"); + + private static final ErrorCorrectionLevel[] FOR_BITS = {M, L, H, Q}; + + private final int ordinal; + private final int bits; + private final String name; + + private ErrorCorrectionLevel(int ordinal, int bits, String name) { + this.ordinal = ordinal; + this.bits = bits; + this.name = name; + } + + public int ordinal() { + return ordinal; + } + + public int getBits() { + return bits; + } + + public String getName() { + return name; + } + + public String toString() { + return name; + } + + /** + * @param bits int containing the two bits encoding a QR Code's error correction level + * @return {@link ErrorCorrectionLevel} representing the encoded error correction level + */ + public static ErrorCorrectionLevel forBits(int bits) { + if (bits < 0 || bits >= FOR_BITS.length) { + throw new IllegalArgumentException(); + } + return FOR_BITS[bits]; + } + + +} diff --git a/src/com/google/zxing/qrcode/decoder/FormatInformation.java b/src/com/google/zxing/qrcode/decoder/FormatInformation.java new file mode 100644 index 000000000..1b76b0de5 --- /dev/null +++ b/src/com/google/zxing/qrcode/decoder/FormatInformation.java @@ -0,0 +1,171 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +/** + *

Encapsulates a QR Code's format information, including the data mask used and + * error correction level.

+ * + * @author Sean Owen + * @see DataMask + * @see ErrorCorrectionLevel + */ +final class FormatInformation { + + private static final int FORMAT_INFO_MASK_QR = 0x5412; + + /** + * See ISO 18004:2006, Annex C, Table C.1 + */ + private static final int[][] FORMAT_INFO_DECODE_LOOKUP = { + {0x5412, 0x00}, + {0x5125, 0x01}, + {0x5E7C, 0x02}, + {0x5B4B, 0x03}, + {0x45F9, 0x04}, + {0x40CE, 0x05}, + {0x4F97, 0x06}, + {0x4AA0, 0x07}, + {0x77C4, 0x08}, + {0x72F3, 0x09}, + {0x7DAA, 0x0A}, + {0x789D, 0x0B}, + {0x662F, 0x0C}, + {0x6318, 0x0D}, + {0x6C41, 0x0E}, + {0x6976, 0x0F}, + {0x1689, 0x10}, + {0x13BE, 0x11}, + {0x1CE7, 0x12}, + {0x19D0, 0x13}, + {0x0762, 0x14}, + {0x0255, 0x15}, + {0x0D0C, 0x16}, + {0x083B, 0x17}, + {0x355F, 0x18}, + {0x3068, 0x19}, + {0x3F31, 0x1A}, + {0x3A06, 0x1B}, + {0x24B4, 0x1C}, + {0x2183, 0x1D}, + {0x2EDA, 0x1E}, + {0x2BED, 0x1F}, + }; + + /** + * Offset i holds the number of 1 bits in the binary representation of i + */ + private static final int[] BITS_SET_IN_HALF_BYTE = + {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + + private final ErrorCorrectionLevel errorCorrectionLevel; + private final byte dataMask; + + private FormatInformation(int formatInfo) { + // Bits 3,4 + errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03); + // Bottom 3 bits + dataMask = (byte) (formatInfo & 0x07); + } + + static int numBitsDiffering(int a, int b) { + a ^= b; // a now has a 1 bit exactly where its bit differs with b's + // Count bits set quickly with a series of lookups: + return BITS_SET_IN_HALF_BYTE[a & 0x0F] + + BITS_SET_IN_HALF_BYTE[(a >>> 4 & 0x0F)] + + BITS_SET_IN_HALF_BYTE[(a >>> 8 & 0x0F)] + + BITS_SET_IN_HALF_BYTE[(a >>> 12 & 0x0F)] + + BITS_SET_IN_HALF_BYTE[(a >>> 16 & 0x0F)] + + BITS_SET_IN_HALF_BYTE[(a >>> 20 & 0x0F)] + + BITS_SET_IN_HALF_BYTE[(a >>> 24 & 0x0F)] + + BITS_SET_IN_HALF_BYTE[(a >>> 28 & 0x0F)]; + } + + /** + * @param maskedFormatInfo1 format info indicator, with mask still applied + * @param maskedFormatInfo2 second copy of same info; both are checked at the same time + * to establish best match + * @return information about the format it specifies, or null + * if doesn't seem to match any known pattern + */ + static FormatInformation decodeFormatInformation(int maskedFormatInfo1, int maskedFormatInfo2) { + FormatInformation formatInfo = doDecodeFormatInformation(maskedFormatInfo1, maskedFormatInfo2); + if (formatInfo != null) { + return formatInfo; + } + // Should return null, but, some QR codes apparently + // do not mask this info. Try again by actually masking the pattern + // first + return doDecodeFormatInformation(maskedFormatInfo1 ^ FORMAT_INFO_MASK_QR, + maskedFormatInfo2 ^ FORMAT_INFO_MASK_QR); + } + + private static FormatInformation doDecodeFormatInformation(int maskedFormatInfo1, int maskedFormatInfo2) { + // Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing + int bestDifference = Integer.MAX_VALUE; + int bestFormatInfo = 0; + for (int i = 0; i < FORMAT_INFO_DECODE_LOOKUP.length; i++) { + int[] decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i]; + int targetInfo = decodeInfo[0]; + if (targetInfo == maskedFormatInfo1 || targetInfo == maskedFormatInfo2) { + // Found an exact match + return new FormatInformation(decodeInfo[1]); + } + int bitsDifference = numBitsDiffering(maskedFormatInfo1, targetInfo); + if (bitsDifference < bestDifference) { + bestFormatInfo = decodeInfo[1]; + bestDifference = bitsDifference; + } + if (maskedFormatInfo1 != maskedFormatInfo2) { + // also try the other option + bitsDifference = numBitsDiffering(maskedFormatInfo2, targetInfo); + if (bitsDifference < bestDifference) { + bestFormatInfo = decodeInfo[1]; + bestDifference = bitsDifference; + } + } + } + // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits + // differing means we found a match + if (bestDifference <= 3) { + return new FormatInformation(bestFormatInfo); + } + return null; + } + + ErrorCorrectionLevel getErrorCorrectionLevel() { + return errorCorrectionLevel; + } + + byte getDataMask() { + return dataMask; + } + + public int hashCode() { + return (errorCorrectionLevel.ordinal() << 3) | (int) dataMask; + } + + public boolean equals(Object o) { + if (!(o instanceof FormatInformation)) { + return false; + } + FormatInformation other = (FormatInformation) o; + return this.errorCorrectionLevel == other.errorCorrectionLevel && + this.dataMask == other.dataMask; + } + +} diff --git a/src/com/google/zxing/qrcode/decoder/Mode.java b/src/com/google/zxing/qrcode/decoder/Mode.java new file mode 100644 index 000000000..24117dcbb --- /dev/null +++ b/src/com/google/zxing/qrcode/decoder/Mode.java @@ -0,0 +1,112 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +/** + *

See ISO 18004:2006, 6.4.1, Tables 2 and 3. This enum encapsulates the various modes in which + * data can be encoded to bits in the QR code standard.

+ * + * @author Sean Owen + */ +public final class Mode { + + // No, we can't use an enum here. J2ME doesn't support it. + + public static final Mode TERMINATOR = new Mode(new int[]{0, 0, 0}, 0x00, "TERMINATOR"); // Not really a mode... + public static final Mode NUMERIC = new Mode(new int[]{10, 12, 14}, 0x01, "NUMERIC"); + public static final Mode ALPHANUMERIC = new Mode(new int[]{9, 11, 13}, 0x02, "ALPHANUMERIC"); + public static final Mode STRUCTURED_APPEND = new Mode(new int[]{0, 0, 0}, 0x03, "STRUCTURED_APPEND"); // Not supported + public static final Mode BYTE = new Mode(new int[]{8, 16, 16}, 0x04, "BYTE"); + public static final Mode ECI = new Mode(null, 0x07, "ECI"); // character counts don't apply + public static final Mode KANJI = new Mode(new int[]{8, 10, 12}, 0x08, "KANJI"); + public static final Mode FNC1_FIRST_POSITION = new Mode(null, 0x05, "FNC1_FIRST_POSITION"); + public static final Mode FNC1_SECOND_POSITION = new Mode(null, 0x09, "FNC1_SECOND_POSITION"); + + private final int[] characterCountBitsForVersions; + private final int bits; + private final String name; + + private Mode(int[] characterCountBitsForVersions, int bits, String name) { + this.characterCountBitsForVersions = characterCountBitsForVersions; + this.bits = bits; + this.name = name; + } + + /** + * @param bits four bits encoding a QR Code data mode + * @return {@link Mode} encoded by these bits + * @throws IllegalArgumentException if bits do not correspond to a known mode + */ + public static Mode forBits(int bits) { + switch (bits) { + case 0x0: + return TERMINATOR; + case 0x1: + return NUMERIC; + case 0x2: + return ALPHANUMERIC; + case 0x3: + return STRUCTURED_APPEND; + case 0x4: + return BYTE; + case 0x5: + return FNC1_FIRST_POSITION; + case 0x7: + return ECI; + case 0x8: + return KANJI; + case 0x9: + return FNC1_SECOND_POSITION; + default: + throw new IllegalArgumentException(); + } + } + + /** + * @param version version in question + * @return number of bits used, in this QR Code symbol {@link Version}, to encode the + * count of characters that will follow encoded in this {@link Mode} + */ + public int getCharacterCountBits(Version version) { + if (characterCountBitsForVersions == null) { + throw new IllegalArgumentException("Character count doesn't apply to this mode"); + } + int number = version.getVersionNumber(); + int offset; + if (number <= 9) { + offset = 0; + } else if (number <= 26) { + offset = 1; + } else { + offset = 2; + } + return characterCountBitsForVersions[offset]; + } + + public int getBits() { + return bits; + } + + public String getName() { + return name; + } + + public String toString() { + return name; + } + +} diff --git a/src/com/google/zxing/qrcode/decoder/Version.java b/src/com/google/zxing/qrcode/decoder/Version.java new file mode 100644 index 000000000..e4960050c --- /dev/null +++ b/src/com/google/zxing/qrcode/decoder/Version.java @@ -0,0 +1,586 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.decoder; + +import com.google.zxing.FormatException; +import com.google.zxing.common.BitMatrix; + +/** + * See ISO 18004:2006 Annex D + * + * @author Sean Owen + */ +public final class Version { + + /** + * See ISO 18004:2006 Annex D. + * Element i represents the raw version bits that specify version i + 7 + */ + private static final int[] VERSION_DECODE_INFO = { + 0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, + 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78, + 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, + 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB, + 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, + 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B, + 0x2542E, 0x26A64, 0x27541, 0x28C69 + }; + + private static final Version[] VERSIONS = buildVersions(); + + private final int versionNumber; + private final int[] alignmentPatternCenters; + private final ECBlocks[] ecBlocks; + private final int totalCodewords; + + private Version(int versionNumber, + int[] alignmentPatternCenters, + ECBlocks ecBlocks1, + ECBlocks ecBlocks2, + ECBlocks ecBlocks3, + ECBlocks ecBlocks4) { + this.versionNumber = versionNumber; + this.alignmentPatternCenters = alignmentPatternCenters; + this.ecBlocks = new ECBlocks[]{ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4}; + int total = 0; + int ecCodewords = ecBlocks1.getECCodewordsPerBlock(); + ECB[] ecbArray = ecBlocks1.getECBlocks(); + for (int i = 0; i < ecbArray.length; i++) { + ECB ecBlock = ecbArray[i]; + total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords); + } + this.totalCodewords = total; + } + + public int getVersionNumber() { + return versionNumber; + } + + public int[] getAlignmentPatternCenters() { + return alignmentPatternCenters; + } + + public int getTotalCodewords() { + return totalCodewords; + } + + public int getDimensionForVersion() { + return 17 + 4 * versionNumber; + } + + public ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) { + return ecBlocks[ecLevel.ordinal()]; + } + + /** + *

Deduces version information purely from QR Code dimensions.

+ * + * @param dimension dimension in modules + * @return {@link Version} for a QR Code of that dimension + * @throws FormatException if dimension is not 1 mod 4 + */ + public static Version getProvisionalVersionForDimension(int dimension) throws FormatException { + if (dimension % 4 != 1) { + throw FormatException.getFormatInstance(); + } + try { + return getVersionForNumber((dimension - 17) >> 2); + } catch (IllegalArgumentException iae) { + throw FormatException.getFormatInstance(); + } + } + + public static Version getVersionForNumber(int versionNumber) { + if (versionNumber < 1 || versionNumber > 40) { + throw new IllegalArgumentException(); + } + return VERSIONS[versionNumber - 1]; + } + + static Version decodeVersionInformation(int versionBits) { + int bestDifference = Integer.MAX_VALUE; + int bestVersion = 0; + for (int i = 0; i < VERSION_DECODE_INFO.length; i++) { + int targetVersion = VERSION_DECODE_INFO[i]; + // Do the version info bits match exactly? done. + if (targetVersion == versionBits) { + return getVersionForNumber(i + 7); + } + // Otherwise see if this is the closest to a real version info bit string + // we have seen so far + int bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion); + if (bitsDifference < bestDifference) { + bestVersion = i + 7; + bestDifference = bitsDifference; + } + } + // We can tolerate up to 3 bits of error since no two version info codewords will + // differ in less than 8 bits. + if (bestDifference <= 3) { + return getVersionForNumber(bestVersion); + } + // If we didn't find a close enough match, fail + return null; + } + + /** + * See ISO 18004:2006 Annex E + */ + BitMatrix buildFunctionPattern() { + int dimension = getDimensionForVersion(); + BitMatrix bitMatrix = new BitMatrix(dimension); + + // Top left finder pattern + separator + format + bitMatrix.setRegion(0, 0, 9, 9); + // Top right finder pattern + separator + format + bitMatrix.setRegion(dimension - 8, 0, 8, 9); + // Bottom left finder pattern + separator + format + bitMatrix.setRegion(0, dimension - 8, 9, 8); + + // Alignment patterns + int max = alignmentPatternCenters.length; + for (int x = 0; x < max; x++) { + int i = alignmentPatternCenters[x] - 2; + for (int y = 0; y < max; y++) { + if ((x == 0 && (y == 0 || y == max - 1)) || (x == max - 1 && y == 0)) { + // No alignment patterns near the three finder paterns + continue; + } + bitMatrix.setRegion(alignmentPatternCenters[y] - 2, i, 5, 5); + } + } + + // Vertical timing pattern + bitMatrix.setRegion(6, 9, 1, dimension - 17); + // Horizontal timing pattern + bitMatrix.setRegion(9, 6, dimension - 17, 1); + + if (versionNumber > 6) { + // Version info, top right + bitMatrix.setRegion(dimension - 11, 0, 3, 6); + // Version info, bottom left + bitMatrix.setRegion(0, dimension - 11, 6, 3); + } + + return bitMatrix; + } + + /** + *

Encapsulates a set of error-correction blocks in one symbol version. Most versions will + * use blocks of differing sizes within one version, so, this encapsulates the parameters for + * each set of blocks. It also holds the number of error-correction codewords per block since it + * will be the same across all blocks within one version.

+ */ + public static final class ECBlocks { + private final int ecCodewordsPerBlock; + private final ECB[] ecBlocks; + + ECBlocks(int ecCodewordsPerBlock, ECB ecBlocks) { + this.ecCodewordsPerBlock = ecCodewordsPerBlock; + this.ecBlocks = new ECB[]{ecBlocks}; + } + + ECBlocks(int ecCodewordsPerBlock, ECB ecBlocks1, ECB ecBlocks2) { + this.ecCodewordsPerBlock = ecCodewordsPerBlock; + this.ecBlocks = new ECB[]{ecBlocks1, ecBlocks2}; + } + + public int getECCodewordsPerBlock() { + return ecCodewordsPerBlock; + } + + public int getNumBlocks() { + int total = 0; + for (int i = 0; i < ecBlocks.length; i++) { + total += ecBlocks[i].getCount(); + } + return total; + } + + public int getTotalECCodewords() { + return ecCodewordsPerBlock * getNumBlocks(); + } + + public ECB[] getECBlocks() { + return ecBlocks; + } + } + + /** + *

Encapsualtes the parameters for one error-correction block in one symbol version. + * This includes the number of data codewords, and the number of times a block with these + * parameters is used consecutively in the QR code version's format.

+ */ + public static final class ECB { + private final int count; + private final int dataCodewords; + + ECB(int count, int dataCodewords) { + this.count = count; + this.dataCodewords = dataCodewords; + } + + public int getCount() { + return count; + } + + public int getDataCodewords() { + return dataCodewords; + } + } + + public String toString() { + return String.valueOf(versionNumber); + } + + /** + * See ISO 18004:2006 6.5.1 Table 9 + */ + private static Version[] buildVersions() { + return new Version[]{ + new Version(1, new int[]{}, + new ECBlocks(7, new ECB(1, 19)), + new ECBlocks(10, new ECB(1, 16)), + new ECBlocks(13, new ECB(1, 13)), + new ECBlocks(17, new ECB(1, 9))), + new Version(2, new int[]{6, 18}, + new ECBlocks(10, new ECB(1, 34)), + new ECBlocks(16, new ECB(1, 28)), + new ECBlocks(22, new ECB(1, 22)), + new ECBlocks(28, new ECB(1, 16))), + new Version(3, new int[]{6, 22}, + new ECBlocks(15, new ECB(1, 55)), + new ECBlocks(26, new ECB(1, 44)), + new ECBlocks(18, new ECB(2, 17)), + new ECBlocks(22, new ECB(2, 13))), + new Version(4, new int[]{6, 26}, + new ECBlocks(20, new ECB(1, 80)), + new ECBlocks(18, new ECB(2, 32)), + new ECBlocks(26, new ECB(2, 24)), + new ECBlocks(16, new ECB(4, 9))), + new Version(5, new int[]{6, 30}, + new ECBlocks(26, new ECB(1, 108)), + new ECBlocks(24, new ECB(2, 43)), + new ECBlocks(18, new ECB(2, 15), + new ECB(2, 16)), + new ECBlocks(22, new ECB(2, 11), + new ECB(2, 12))), + new Version(6, new int[]{6, 34}, + new ECBlocks(18, new ECB(2, 68)), + new ECBlocks(16, new ECB(4, 27)), + new ECBlocks(24, new ECB(4, 19)), + new ECBlocks(28, new ECB(4, 15))), + new Version(7, new int[]{6, 22, 38}, + new ECBlocks(20, new ECB(2, 78)), + new ECBlocks(18, new ECB(4, 31)), + new ECBlocks(18, new ECB(2, 14), + new ECB(4, 15)), + new ECBlocks(26, new ECB(4, 13), + new ECB(1, 14))), + new Version(8, new int[]{6, 24, 42}, + new ECBlocks(24, new ECB(2, 97)), + new ECBlocks(22, new ECB(2, 38), + new ECB(2, 39)), + new ECBlocks(22, new ECB(4, 18), + new ECB(2, 19)), + new ECBlocks(26, new ECB(4, 14), + new ECB(2, 15))), + new Version(9, new int[]{6, 26, 46}, + new ECBlocks(30, new ECB(2, 116)), + new ECBlocks(22, new ECB(3, 36), + new ECB(2, 37)), + new ECBlocks(20, new ECB(4, 16), + new ECB(4, 17)), + new ECBlocks(24, new ECB(4, 12), + new ECB(4, 13))), + new Version(10, new int[]{6, 28, 50}, + new ECBlocks(18, new ECB(2, 68), + new ECB(2, 69)), + new ECBlocks(26, new ECB(4, 43), + new ECB(1, 44)), + new ECBlocks(24, new ECB(6, 19), + new ECB(2, 20)), + new ECBlocks(28, new ECB(6, 15), + new ECB(2, 16))), + new Version(11, new int[]{6, 30, 54}, + new ECBlocks(20, new ECB(4, 81)), + new ECBlocks(30, new ECB(1, 50), + new ECB(4, 51)), + new ECBlocks(28, new ECB(4, 22), + new ECB(4, 23)), + new ECBlocks(24, new ECB(3, 12), + new ECB(8, 13))), + new Version(12, new int[]{6, 32, 58}, + new ECBlocks(24, new ECB(2, 92), + new ECB(2, 93)), + new ECBlocks(22, new ECB(6, 36), + new ECB(2, 37)), + new ECBlocks(26, new ECB(4, 20), + new ECB(6, 21)), + new ECBlocks(28, new ECB(7, 14), + new ECB(4, 15))), + new Version(13, new int[]{6, 34, 62}, + new ECBlocks(26, new ECB(4, 107)), + new ECBlocks(22, new ECB(8, 37), + new ECB(1, 38)), + new ECBlocks(24, new ECB(8, 20), + new ECB(4, 21)), + new ECBlocks(22, new ECB(12, 11), + new ECB(4, 12))), + new Version(14, new int[]{6, 26, 46, 66}, + new ECBlocks(30, new ECB(3, 115), + new ECB(1, 116)), + new ECBlocks(24, new ECB(4, 40), + new ECB(5, 41)), + new ECBlocks(20, new ECB(11, 16), + new ECB(5, 17)), + new ECBlocks(24, new ECB(11, 12), + new ECB(5, 13))), + new Version(15, new int[]{6, 26, 48, 70}, + new ECBlocks(22, new ECB(5, 87), + new ECB(1, 88)), + new ECBlocks(24, new ECB(5, 41), + new ECB(5, 42)), + new ECBlocks(30, new ECB(5, 24), + new ECB(7, 25)), + new ECBlocks(24, new ECB(11, 12), + new ECB(7, 13))), + new Version(16, new int[]{6, 26, 50, 74}, + new ECBlocks(24, new ECB(5, 98), + new ECB(1, 99)), + new ECBlocks(28, new ECB(7, 45), + new ECB(3, 46)), + new ECBlocks(24, new ECB(15, 19), + new ECB(2, 20)), + new ECBlocks(30, new ECB(3, 15), + new ECB(13, 16))), + new Version(17, new int[]{6, 30, 54, 78}, + new ECBlocks(28, new ECB(1, 107), + new ECB(5, 108)), + new ECBlocks(28, new ECB(10, 46), + new ECB(1, 47)), + new ECBlocks(28, new ECB(1, 22), + new ECB(15, 23)), + new ECBlocks(28, new ECB(2, 14), + new ECB(17, 15))), + new Version(18, new int[]{6, 30, 56, 82}, + new ECBlocks(30, new ECB(5, 120), + new ECB(1, 121)), + new ECBlocks(26, new ECB(9, 43), + new ECB(4, 44)), + new ECBlocks(28, new ECB(17, 22), + new ECB(1, 23)), + new ECBlocks(28, new ECB(2, 14), + new ECB(19, 15))), + new Version(19, new int[]{6, 30, 58, 86}, + new ECBlocks(28, new ECB(3, 113), + new ECB(4, 114)), + new ECBlocks(26, new ECB(3, 44), + new ECB(11, 45)), + new ECBlocks(26, new ECB(17, 21), + new ECB(4, 22)), + new ECBlocks(26, new ECB(9, 13), + new ECB(16, 14))), + new Version(20, new int[]{6, 34, 62, 90}, + new ECBlocks(28, new ECB(3, 107), + new ECB(5, 108)), + new ECBlocks(26, new ECB(3, 41), + new ECB(13, 42)), + new ECBlocks(30, new ECB(15, 24), + new ECB(5, 25)), + new ECBlocks(28, new ECB(15, 15), + new ECB(10, 16))), + new Version(21, new int[]{6, 28, 50, 72, 94}, + new ECBlocks(28, new ECB(4, 116), + new ECB(4, 117)), + new ECBlocks(26, new ECB(17, 42)), + new ECBlocks(28, new ECB(17, 22), + new ECB(6, 23)), + new ECBlocks(30, new ECB(19, 16), + new ECB(6, 17))), + new Version(22, new int[]{6, 26, 50, 74, 98}, + new ECBlocks(28, new ECB(2, 111), + new ECB(7, 112)), + new ECBlocks(28, new ECB(17, 46)), + new ECBlocks(30, new ECB(7, 24), + new ECB(16, 25)), + new ECBlocks(24, new ECB(34, 13))), + new Version(23, new int[]{6, 30, 54, 78, 102}, + new ECBlocks(30, new ECB(4, 121), + new ECB(5, 122)), + new ECBlocks(28, new ECB(4, 47), + new ECB(14, 48)), + new ECBlocks(30, new ECB(11, 24), + new ECB(14, 25)), + new ECBlocks(30, new ECB(16, 15), + new ECB(14, 16))), + new Version(24, new int[]{6, 28, 54, 80, 106}, + new ECBlocks(30, new ECB(6, 117), + new ECB(4, 118)), + new ECBlocks(28, new ECB(6, 45), + new ECB(14, 46)), + new ECBlocks(30, new ECB(11, 24), + new ECB(16, 25)), + new ECBlocks(30, new ECB(30, 16), + new ECB(2, 17))), + new Version(25, new int[]{6, 32, 58, 84, 110}, + new ECBlocks(26, new ECB(8, 106), + new ECB(4, 107)), + new ECBlocks(28, new ECB(8, 47), + new ECB(13, 48)), + new ECBlocks(30, new ECB(7, 24), + new ECB(22, 25)), + new ECBlocks(30, new ECB(22, 15), + new ECB(13, 16))), + new Version(26, new int[]{6, 30, 58, 86, 114}, + new ECBlocks(28, new ECB(10, 114), + new ECB(2, 115)), + new ECBlocks(28, new ECB(19, 46), + new ECB(4, 47)), + new ECBlocks(28, new ECB(28, 22), + new ECB(6, 23)), + new ECBlocks(30, new ECB(33, 16), + new ECB(4, 17))), + new Version(27, new int[]{6, 34, 62, 90, 118}, + new ECBlocks(30, new ECB(8, 122), + new ECB(4, 123)), + new ECBlocks(28, new ECB(22, 45), + new ECB(3, 46)), + new ECBlocks(30, new ECB(8, 23), + new ECB(26, 24)), + new ECBlocks(30, new ECB(12, 15), + new ECB(28, 16))), + new Version(28, new int[]{6, 26, 50, 74, 98, 122}, + new ECBlocks(30, new ECB(3, 117), + new ECB(10, 118)), + new ECBlocks(28, new ECB(3, 45), + new ECB(23, 46)), + new ECBlocks(30, new ECB(4, 24), + new ECB(31, 25)), + new ECBlocks(30, new ECB(11, 15), + new ECB(31, 16))), + new Version(29, new int[]{6, 30, 54, 78, 102, 126}, + new ECBlocks(30, new ECB(7, 116), + new ECB(7, 117)), + new ECBlocks(28, new ECB(21, 45), + new ECB(7, 46)), + new ECBlocks(30, new ECB(1, 23), + new ECB(37, 24)), + new ECBlocks(30, new ECB(19, 15), + new ECB(26, 16))), + new Version(30, new int[]{6, 26, 52, 78, 104, 130}, + new ECBlocks(30, new ECB(5, 115), + new ECB(10, 116)), + new ECBlocks(28, new ECB(19, 47), + new ECB(10, 48)), + new ECBlocks(30, new ECB(15, 24), + new ECB(25, 25)), + new ECBlocks(30, new ECB(23, 15), + new ECB(25, 16))), + new Version(31, new int[]{6, 30, 56, 82, 108, 134}, + new ECBlocks(30, new ECB(13, 115), + new ECB(3, 116)), + new ECBlocks(28, new ECB(2, 46), + new ECB(29, 47)), + new ECBlocks(30, new ECB(42, 24), + new ECB(1, 25)), + new ECBlocks(30, new ECB(23, 15), + new ECB(28, 16))), + new Version(32, new int[]{6, 34, 60, 86, 112, 138}, + new ECBlocks(30, new ECB(17, 115)), + new ECBlocks(28, new ECB(10, 46), + new ECB(23, 47)), + new ECBlocks(30, new ECB(10, 24), + new ECB(35, 25)), + new ECBlocks(30, new ECB(19, 15), + new ECB(35, 16))), + new Version(33, new int[]{6, 30, 58, 86, 114, 142}, + new ECBlocks(30, new ECB(17, 115), + new ECB(1, 116)), + new ECBlocks(28, new ECB(14, 46), + new ECB(21, 47)), + new ECBlocks(30, new ECB(29, 24), + new ECB(19, 25)), + new ECBlocks(30, new ECB(11, 15), + new ECB(46, 16))), + new Version(34, new int[]{6, 34, 62, 90, 118, 146}, + new ECBlocks(30, new ECB(13, 115), + new ECB(6, 116)), + new ECBlocks(28, new ECB(14, 46), + new ECB(23, 47)), + new ECBlocks(30, new ECB(44, 24), + new ECB(7, 25)), + new ECBlocks(30, new ECB(59, 16), + new ECB(1, 17))), + new Version(35, new int[]{6, 30, 54, 78, 102, 126, 150}, + new ECBlocks(30, new ECB(12, 121), + new ECB(7, 122)), + new ECBlocks(28, new ECB(12, 47), + new ECB(26, 48)), + new ECBlocks(30, new ECB(39, 24), + new ECB(14, 25)), + new ECBlocks(30, new ECB(22, 15), + new ECB(41, 16))), + new Version(36, new int[]{6, 24, 50, 76, 102, 128, 154}, + new ECBlocks(30, new ECB(6, 121), + new ECB(14, 122)), + new ECBlocks(28, new ECB(6, 47), + new ECB(34, 48)), + new ECBlocks(30, new ECB(46, 24), + new ECB(10, 25)), + new ECBlocks(30, new ECB(2, 15), + new ECB(64, 16))), + new Version(37, new int[]{6, 28, 54, 80, 106, 132, 158}, + new ECBlocks(30, new ECB(17, 122), + new ECB(4, 123)), + new ECBlocks(28, new ECB(29, 46), + new ECB(14, 47)), + new ECBlocks(30, new ECB(49, 24), + new ECB(10, 25)), + new ECBlocks(30, new ECB(24, 15), + new ECB(46, 16))), + new Version(38, new int[]{6, 32, 58, 84, 110, 136, 162}, + new ECBlocks(30, new ECB(4, 122), + new ECB(18, 123)), + new ECBlocks(28, new ECB(13, 46), + new ECB(32, 47)), + new ECBlocks(30, new ECB(48, 24), + new ECB(14, 25)), + new ECBlocks(30, new ECB(42, 15), + new ECB(32, 16))), + new Version(39, new int[]{6, 26, 54, 82, 110, 138, 166}, + new ECBlocks(30, new ECB(20, 117), + new ECB(4, 118)), + new ECBlocks(28, new ECB(40, 47), + new ECB(7, 48)), + new ECBlocks(30, new ECB(43, 24), + new ECB(22, 25)), + new ECBlocks(30, new ECB(10, 15), + new ECB(67, 16))), + new Version(40, new int[]{6, 30, 58, 86, 114, 142, 170}, + new ECBlocks(30, new ECB(19, 118), + new ECB(6, 119)), + new ECBlocks(28, new ECB(18, 47), + new ECB(31, 48)), + new ECBlocks(30, new ECB(34, 24), + new ECB(34, 25)), + new ECBlocks(30, new ECB(20, 15), + new ECB(61, 16))) + }; + } + +} diff --git a/src/com/google/zxing/qrcode/encoder/BlockPair.java b/src/com/google/zxing/qrcode/encoder/BlockPair.java new file mode 100644 index 000000000..5714d9c3a --- /dev/null +++ b/src/com/google/zxing/qrcode/encoder/BlockPair.java @@ -0,0 +1,37 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +final class BlockPair { + + private final byte[] dataBytes; + private final byte[] errorCorrectionBytes; + + BlockPair(byte[] data, byte[] errorCorrection) { + dataBytes = data; + errorCorrectionBytes = errorCorrection; + } + + public byte[] getDataBytes() { + return dataBytes; + } + + public byte[] getErrorCorrectionBytes() { + return errorCorrectionBytes; + } + +} diff --git a/src/com/google/zxing/qrcode/encoder/ByteMatrix.java b/src/com/google/zxing/qrcode/encoder/ByteMatrix.java new file mode 100644 index 000000000..eb248a26c --- /dev/null +++ b/src/com/google/zxing/qrcode/encoder/ByteMatrix.java @@ -0,0 +1,97 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +/** + * A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + * unsigned container, it's up to you to do byteValue & 0xff at each location. + * + * JAVAPORT: The original code was a 2D array of ints, but since it only ever gets assigned + * -1, 0, and 1, I'm going to use less memory and go with bytes. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class ByteMatrix { + + private final byte[][] bytes; + private final int width; + private final int height; + + public ByteMatrix(int width, int height) { + bytes = new byte[height][width]; + this.width = width; + this.height = height; + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } + + public byte get(int x, int y) { + return bytes[y][x]; + } + + public byte[][] getArray() { + return bytes; + } + + public void set(int x, int y, byte value) { + bytes[y][x] = value; + } + + public void set(int x, int y, int value) { + bytes[y][x] = (byte) value; + } + + public void set(int x, int y, boolean value) { + bytes[y][x] = (byte) (value ? 1 : 0); + } + + public void clear(byte value) { + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + bytes[y][x] = value; + } + } + } + + public String toString() { + StringBuffer result = new StringBuffer(2 * width * height + 2); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + switch (bytes[y][x]) { + case 0: + result.append(" 0"); + break; + case 1: + result.append(" 1"); + break; + default: + result.append(" "); + break; + } + } + result.append('\n'); + } + return result.toString(); + } + +} diff --git a/src/com/google/zxing/qrcode/encoder/Encoder.java b/src/com/google/zxing/qrcode/encoder/Encoder.java new file mode 100644 index 000000000..589148853 --- /dev/null +++ b/src/com/google/zxing/qrcode/encoder/Encoder.java @@ -0,0 +1,557 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +import java.io.UnsupportedEncodingException; +import java.util.Hashtable; +import java.util.Vector; + +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitArray; +import com.google.zxing.common.CharacterSetECI; +import com.google.zxing.common.ECI; +import com.google.zxing.common.reedsolomon.GF256; +import com.google.zxing.common.reedsolomon.ReedSolomonEncoder; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.google.zxing.qrcode.decoder.Mode; +import com.google.zxing.qrcode.decoder.Version; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +public final class Encoder { + + // The original table is defined in the table 5 of JISX0510:2004 (p.19). + private static final int[] ALPHANUMERIC_TABLE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f + }; + + static final String DEFAULT_BYTE_MODE_ENCODING = "ISO-8859-1"; + + private Encoder() { + } + + // The mask penalty calculation is complicated. See Table 21 of JISX0510:2004 (p.45) for details. + // Basically it applies four rules and summate all penalties. + private static int calculateMaskPenalty(ByteMatrix matrix) { + int penalty = 0; + penalty += MaskUtil.applyMaskPenaltyRule1(matrix); + penalty += MaskUtil.applyMaskPenaltyRule2(matrix); + penalty += MaskUtil.applyMaskPenaltyRule3(matrix); + penalty += MaskUtil.applyMaskPenaltyRule4(matrix); + return penalty; + } + + /** + * Encode "bytes" with the error correction level "ecLevel". The encoding mode will be chosen + * internally by chooseMode(). On success, store the result in "qrCode". + * + * We recommend you to use QRCode.EC_LEVEL_L (the lowest level) for + * "getECLevel" since our primary use is to show QR code on desktop screens. We don't need very + * strong error correction for this purpose. + * + * Note that there is no way to encode bytes in MODE_KANJI. We might want to add EncodeWithMode() + * with which clients can specify the encoding mode. For now, we don't need the functionality. + */ + public static void encode(String content, ErrorCorrectionLevel ecLevel, QRCode qrCode) + throws WriterException { + encode(content, ecLevel, null, qrCode); + } + + public static void encode(String content, ErrorCorrectionLevel ecLevel, Hashtable hints, + QRCode qrCode) throws WriterException { + + String encoding = hints == null ? null : (String) hints.get(EncodeHintType.CHARACTER_SET); + if (encoding == null) { + encoding = DEFAULT_BYTE_MODE_ENCODING; + } + + // Step 1: Choose the mode (encoding). + Mode mode = chooseMode(content, encoding); + + // Step 2: Append "bytes" into "dataBits" in appropriate encoding. + BitArray dataBits = new BitArray(); + appendBytes(content, mode, dataBits, encoding); + // Step 3: Initialize QR code that can contain "dataBits". + int numInputBytes = dataBits.getSizeInBytes(); + initQRCode(numInputBytes, ecLevel, mode, qrCode); + + // Step 4: Build another bit vector that contains header and data. + BitArray headerAndDataBits = new BitArray(); + + // Step 4.5: Append ECI message if applicable + if (mode == Mode.BYTE && !DEFAULT_BYTE_MODE_ENCODING.equals(encoding)) { + CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding); + if (eci != null) { + appendECI(eci, headerAndDataBits); + } + } + + appendModeInfo(mode, headerAndDataBits); + + int numLetters = mode.equals(Mode.BYTE) ? dataBits.getSizeInBytes() : content.length(); + appendLengthInfo(numLetters, qrCode.getVersion(), mode, headerAndDataBits); + headerAndDataBits.appendBitArray(dataBits); + + // Step 5: Terminate the bits properly. + terminateBits(qrCode.getNumDataBytes(), headerAndDataBits); + + // Step 6: Interleave data bits with error correction code. + BitArray finalBits = new BitArray(); + interleaveWithECBytes(headerAndDataBits, qrCode.getNumTotalBytes(), qrCode.getNumDataBytes(), + qrCode.getNumRSBlocks(), finalBits); + + // Step 7: Choose the mask pattern and set to "qrCode". + ByteMatrix matrix = new ByteMatrix(qrCode.getMatrixWidth(), qrCode.getMatrixWidth()); + qrCode.setMaskPattern(chooseMaskPattern(finalBits, qrCode.getECLevel(), qrCode.getVersion(), + matrix)); + + // Step 8. Build the matrix and set it to "qrCode". + MatrixUtil.buildMatrix(finalBits, qrCode.getECLevel(), qrCode.getVersion(), + qrCode.getMaskPattern(), matrix); + qrCode.setMatrix(matrix); + // Step 9. Make sure we have a valid QR Code. + if (!qrCode.isValid()) { + throw new WriterException("Invalid QR code: " + qrCode.toString()); + } + } + + /** + * @return the code point of the table used in alphanumeric mode or + * -1 if there is no corresponding code in the table. + */ + static int getAlphanumericCode(int code) { + if (code < ALPHANUMERIC_TABLE.length) { + return ALPHANUMERIC_TABLE[code]; + } + return -1; + } + + public static Mode chooseMode(String content) { + return chooseMode(content, null); + } + + /** + * Choose the best mode by examining the content. Note that 'encoding' is used as a hint; + * if it is Shift_JIS, and the input is only double-byte Kanji, then we return {@link Mode#KANJI}. + */ + public static Mode chooseMode(String content, String encoding) { + if ("Shift_JIS".equals(encoding)) { + // Choose Kanji mode if all input are double-byte characters + return isOnlyDoubleByteKanji(content) ? Mode.KANJI : Mode.BYTE; + } + boolean hasNumeric = false; + boolean hasAlphanumeric = false; + for (int i = 0; i < content.length(); ++i) { + char c = content.charAt(i); + if (c >= '0' && c <= '9') { + hasNumeric = true; + } else if (getAlphanumericCode(c) != -1) { + hasAlphanumeric = true; + } else { + return Mode.BYTE; + } + } + if (hasAlphanumeric) { + return Mode.ALPHANUMERIC; + } else if (hasNumeric) { + return Mode.NUMERIC; + } + return Mode.BYTE; + } + + private static boolean isOnlyDoubleByteKanji(String content) { + byte[] bytes; + try { + bytes = content.getBytes("Shift_JIS"); + } catch (UnsupportedEncodingException uee) { + return false; + } + int length = bytes.length; + if (length % 2 != 0) { + return false; + } + for (int i = 0; i < length; i += 2) { + int byte1 = bytes[i] & 0xFF; + if ((byte1 < 0x81 || byte1 > 0x9F) && (byte1 < 0xE0 || byte1 > 0xEB)) { + return false; + } + } + return true; + } + + private static int chooseMaskPattern(BitArray bits, ErrorCorrectionLevel ecLevel, int version, + ByteMatrix matrix) throws WriterException { + + int minPenalty = Integer.MAX_VALUE; // Lower penalty is better. + int bestMaskPattern = -1; + // We try all mask patterns to choose the best one. + for (int maskPattern = 0; maskPattern < QRCode.NUM_MASK_PATTERNS; maskPattern++) { + MatrixUtil.buildMatrix(bits, ecLevel, version, maskPattern, matrix); + int penalty = calculateMaskPenalty(matrix); + if (penalty < minPenalty) { + minPenalty = penalty; + bestMaskPattern = maskPattern; + } + } + return bestMaskPattern; + } + + /** + * Initialize "qrCode" according to "numInputBytes", "ecLevel", and "mode". On success, + * modify "qrCode". + */ + private static void initQRCode(int numInputBytes, ErrorCorrectionLevel ecLevel, Mode mode, + QRCode qrCode) throws WriterException { + qrCode.setECLevel(ecLevel); + qrCode.setMode(mode); + + // In the following comments, we use numbers of Version 7-H. + for (int versionNum = 1; versionNum <= 40; versionNum++) { + Version version = Version.getVersionForNumber(versionNum); + // numBytes = 196 + int numBytes = version.getTotalCodewords(); + // getNumECBytes = 130 + Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); + int numEcBytes = ecBlocks.getTotalECCodewords(); + // getNumRSBlocks = 5 + int numRSBlocks = ecBlocks.getNumBlocks(); + // getNumDataBytes = 196 - 130 = 66 + int numDataBytes = numBytes - numEcBytes; + // We want to choose the smallest version which can contain data of "numInputBytes" + some + // extra bits for the header (mode info and length info). The header can be three bytes + // (precisely 4 + 16 bits) at most. Hence we do +3 here. + if (numDataBytes >= numInputBytes + 3) { + // Yay, we found the proper rs block info! + qrCode.setVersion(versionNum); + qrCode.setNumTotalBytes(numBytes); + qrCode.setNumDataBytes(numDataBytes); + qrCode.setNumRSBlocks(numRSBlocks); + // getNumECBytes = 196 - 66 = 130 + qrCode.setNumECBytes(numEcBytes); + // matrix width = 21 + 6 * 4 = 45 + qrCode.setMatrixWidth(version.getDimensionForVersion()); + return; + } + } + throw new WriterException("Cannot find proper rs block info (input data too big?)"); + } + + /** + * Terminate bits as described in 8.4.8 and 8.4.9 of JISX0510:2004 (p.24). + */ + static void terminateBits(int numDataBytes, BitArray bits) throws WriterException { + int capacity = numDataBytes << 3; + if (bits.getSize() > capacity) { + throw new WriterException("data bits cannot fit in the QR Code" + bits.getSize() + " > " + + capacity); + } + for (int i = 0; i < 4 && bits.getSize() < capacity; ++i) { + bits.appendBit(false); + } + // Append termination bits. See 8.4.8 of JISX0510:2004 (p.24) for details. + // If the last byte isn't 8-bit aligned, we'll add padding bits. + int numBitsInLastByte = bits.getSize() & 0x07; + if (numBitsInLastByte > 0) { + for (int i = numBitsInLastByte; i < 8; i++) { + bits.appendBit(false); + } + } + // If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24). + int numPaddingBytes = numDataBytes - bits.getSizeInBytes(); + for (int i = 0; i < numPaddingBytes; ++i) { + bits.appendBits(((i & 0x01) == 0) ? 0xEC : 0x11, 8); + } + if (bits.getSize() != capacity) { + throw new WriterException("Bits size does not equal capacity"); + } + } + + /** + * Get number of data bytes and number of error correction bytes for block id "blockID". Store + * the result in "numDataBytesInBlock", and "numECBytesInBlock". See table 12 in 8.5.1 of + * JISX0510:2004 (p.30) + */ + static void getNumDataBytesAndNumECBytesForBlockID(int numTotalBytes, int numDataBytes, + int numRSBlocks, int blockID, int[] numDataBytesInBlock, + int[] numECBytesInBlock) throws WriterException { + if (blockID >= numRSBlocks) { + throw new WriterException("Block ID too large"); + } + // numRsBlocksInGroup2 = 196 % 5 = 1 + int numRsBlocksInGroup2 = numTotalBytes % numRSBlocks; + // numRsBlocksInGroup1 = 5 - 1 = 4 + int numRsBlocksInGroup1 = numRSBlocks - numRsBlocksInGroup2; + // numTotalBytesInGroup1 = 196 / 5 = 39 + int numTotalBytesInGroup1 = numTotalBytes / numRSBlocks; + // numTotalBytesInGroup2 = 39 + 1 = 40 + int numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1; + // numDataBytesInGroup1 = 66 / 5 = 13 + int numDataBytesInGroup1 = numDataBytes / numRSBlocks; + // numDataBytesInGroup2 = 13 + 1 = 14 + int numDataBytesInGroup2 = numDataBytesInGroup1 + 1; + // numEcBytesInGroup1 = 39 - 13 = 26 + int numEcBytesInGroup1 = numTotalBytesInGroup1 - numDataBytesInGroup1; + // numEcBytesInGroup2 = 40 - 14 = 26 + int numEcBytesInGroup2 = numTotalBytesInGroup2 - numDataBytesInGroup2; + // Sanity checks. + // 26 = 26 + if (numEcBytesInGroup1 != numEcBytesInGroup2) { + throw new WriterException("EC bytes mismatch"); + } + // 5 = 4 + 1. + if (numRSBlocks != numRsBlocksInGroup1 + numRsBlocksInGroup2) { + throw new WriterException("RS blocks mismatch"); + } + // 196 = (13 + 26) * 4 + (14 + 26) * 1 + if (numTotalBytes != + ((numDataBytesInGroup1 + numEcBytesInGroup1) * + numRsBlocksInGroup1) + + ((numDataBytesInGroup2 + numEcBytesInGroup2) * + numRsBlocksInGroup2)) { + throw new WriterException("Total bytes mismatch"); + } + + if (blockID < numRsBlocksInGroup1) { + numDataBytesInBlock[0] = numDataBytesInGroup1; + numECBytesInBlock[0] = numEcBytesInGroup1; + } else { + numDataBytesInBlock[0] = numDataBytesInGroup2; + numECBytesInBlock[0] = numEcBytesInGroup2; + } + } + + /** + * Interleave "bits" with corresponding error correction bytes. On success, store the result in + * "result". The interleave rule is complicated. See 8.6 of JISX0510:2004 (p.37) for details. + */ + static void interleaveWithECBytes(BitArray bits, int numTotalBytes, + int numDataBytes, int numRSBlocks, BitArray result) throws WriterException { + + // "bits" must have "getNumDataBytes" bytes of data. + if (bits.getSizeInBytes() != numDataBytes) { + throw new WriterException("Number of bits and data bytes does not match"); + } + + // Step 1. Divide data bytes into blocks and generate error correction bytes for them. We'll + // store the divided data bytes blocks and error correction bytes blocks into "blocks". + int dataBytesOffset = 0; + int maxNumDataBytes = 0; + int maxNumEcBytes = 0; + + // Since, we know the number of reedsolmon blocks, we can initialize the vector with the number. + Vector blocks = new Vector(numRSBlocks); + + for (int i = 0; i < numRSBlocks; ++i) { + int[] numDataBytesInBlock = new int[1]; + int[] numEcBytesInBlock = new int[1]; + getNumDataBytesAndNumECBytesForBlockID( + numTotalBytes, numDataBytes, numRSBlocks, i, + numDataBytesInBlock, numEcBytesInBlock); + + int size = numDataBytesInBlock[0]; + byte[] dataBytes = new byte[size]; + bits.toBytes(8*dataBytesOffset, dataBytes, 0, size); + byte[] ecBytes = generateECBytes(dataBytes, numEcBytesInBlock[0]); + blocks.addElement(new BlockPair(dataBytes, ecBytes)); + + maxNumDataBytes = Math.max(maxNumDataBytes, size); + maxNumEcBytes = Math.max(maxNumEcBytes, ecBytes.length); + dataBytesOffset += numDataBytesInBlock[0]; + } + if (numDataBytes != dataBytesOffset) { + throw new WriterException("Data bytes does not match offset"); + } + + // First, place data blocks. + for (int i = 0; i < maxNumDataBytes; ++i) { + for (int j = 0; j < blocks.size(); ++j) { + byte[] dataBytes = ((BlockPair) blocks.elementAt(j)).getDataBytes(); + if (i < dataBytes.length) { + result.appendBits(dataBytes[i], 8); + } + } + } + // Then, place error correction blocks. + for (int i = 0; i < maxNumEcBytes; ++i) { + for (int j = 0; j < blocks.size(); ++j) { + byte[] ecBytes = ((BlockPair) blocks.elementAt(j)).getErrorCorrectionBytes(); + if (i < ecBytes.length) { + result.appendBits(ecBytes[i], 8); + } + } + } + if (numTotalBytes != result.getSizeInBytes()) { // Should be same. + throw new WriterException("Interleaving error: " + numTotalBytes + " and " + + result.getSizeInBytes() + " differ."); + } + } + + static byte[] generateECBytes(byte[] dataBytes, int numEcBytesInBlock) { + int numDataBytes = dataBytes.length; + int[] toEncode = new int[numDataBytes + numEcBytesInBlock]; + for (int i = 0; i < numDataBytes; i++) { + toEncode[i] = dataBytes[i] & 0xFF; + } + new ReedSolomonEncoder(GF256.QR_CODE_FIELD).encode(toEncode, numEcBytesInBlock); + + byte[] ecBytes = new byte[numEcBytesInBlock]; + for (int i = 0; i < numEcBytesInBlock; i++) { + ecBytes[i] = (byte) toEncode[numDataBytes + i]; + } + return ecBytes; + } + + /** + * Append mode info. On success, store the result in "bits". + */ + static void appendModeInfo(Mode mode, BitArray bits) { + bits.appendBits(mode.getBits(), 4); + } + + + /** + * Append length info. On success, store the result in "bits". + */ + static void appendLengthInfo(int numLetters, int version, Mode mode, BitArray bits) + throws WriterException { + int numBits = mode.getCharacterCountBits(Version.getVersionForNumber(version)); + if (numLetters > ((1 << numBits) - 1)) { + throw new WriterException(numLetters + "is bigger than" + ((1 << numBits) - 1)); + } + bits.appendBits(numLetters, numBits); + } + + /** + * Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits". + */ + static void appendBytes(String content, Mode mode, BitArray bits, String encoding) + throws WriterException { + if (mode.equals(Mode.NUMERIC)) { + appendNumericBytes(content, bits); + } else if (mode.equals(Mode.ALPHANUMERIC)) { + appendAlphanumericBytes(content, bits); + } else if (mode.equals(Mode.BYTE)) { + append8BitBytes(content, bits, encoding); + } else if (mode.equals(Mode.KANJI)) { + appendKanjiBytes(content, bits); + } else { + throw new WriterException("Invalid mode: " + mode); + } + } + + static void appendNumericBytes(String content, BitArray bits) { + int length = content.length(); + int i = 0; + while (i < length) { + int num1 = content.charAt(i) - '0'; + if (i + 2 < length) { + // Encode three numeric letters in ten bits. + int num2 = content.charAt(i + 1) - '0'; + int num3 = content.charAt(i + 2) - '0'; + bits.appendBits(num1 * 100 + num2 * 10 + num3, 10); + i += 3; + } else if (i + 1 < length) { + // Encode two numeric letters in seven bits. + int num2 = content.charAt(i + 1) - '0'; + bits.appendBits(num1 * 10 + num2, 7); + i += 2; + } else { + // Encode one numeric letter in four bits. + bits.appendBits(num1, 4); + i++; + } + } + } + + static void appendAlphanumericBytes(String content, BitArray bits) throws WriterException { + int length = content.length(); + int i = 0; + while (i < length) { + int code1 = getAlphanumericCode(content.charAt(i)); + if (code1 == -1) { + throw new WriterException(); + } + if (i + 1 < length) { + int code2 = getAlphanumericCode(content.charAt(i + 1)); + if (code2 == -1) { + throw new WriterException(); + } + // Encode two alphanumeric letters in 11 bits. + bits.appendBits(code1 * 45 + code2, 11); + i += 2; + } else { + // Encode one alphanumeric letter in six bits. + bits.appendBits(code1, 6); + i++; + } + } + } + + static void append8BitBytes(String content, BitArray bits, String encoding) + throws WriterException { + byte[] bytes; + try { + bytes = content.getBytes(encoding); + } catch (UnsupportedEncodingException uee) { + throw new WriterException(uee.toString()); + } + for (int i = 0; i < bytes.length; ++i) { + bits.appendBits(bytes[i], 8); + } + } + + static void appendKanjiBytes(String content, BitArray bits) throws WriterException { + byte[] bytes; + try { + bytes = content.getBytes("Shift_JIS"); + } catch (UnsupportedEncodingException uee) { + throw new WriterException(uee.toString()); + } + int length = bytes.length; + for (int i = 0; i < length; i += 2) { + int byte1 = bytes[i] & 0xFF; + int byte2 = bytes[i + 1] & 0xFF; + int code = (byte1 << 8) | byte2; + int subtracted = -1; + if (code >= 0x8140 && code <= 0x9ffc) { + subtracted = code - 0x8140; + } else if (code >= 0xe040 && code <= 0xebbf) { + subtracted = code - 0xc140; + } + if (subtracted == -1) { + throw new WriterException("Invalid byte sequence"); + } + int encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff); + bits.appendBits(encoded, 13); + } + } + + private static void appendECI(ECI eci, BitArray bits) { + bits.appendBits(Mode.ECI.getBits(), 4); + // This is correct for values up to 127, which is all we need now. + bits.appendBits(eci.getValue(), 8); + } + +} diff --git a/src/com/google/zxing/qrcode/encoder/MaskUtil.java b/src/com/google/zxing/qrcode/encoder/MaskUtil.java new file mode 100644 index 000000000..c7f3c48ad --- /dev/null +++ b/src/com/google/zxing/qrcode/encoder/MaskUtil.java @@ -0,0 +1,217 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +public final class MaskUtil { + + private MaskUtil() { + // do nothing + } + + // Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and + // give penalty to them. Example: 00000 or 11111. + public static int applyMaskPenaltyRule1(ByteMatrix matrix) { + return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false); + } + + // Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give + // penalty to them. + public static int applyMaskPenaltyRule2(ByteMatrix matrix) { + int penalty = 0; + byte[][] array = matrix.getArray(); + int width = matrix.getWidth(); + int height = matrix.getHeight(); + for (int y = 0; y < height - 1; ++y) { + for (int x = 0; x < width - 1; ++x) { + int value = array[y][x]; + if (value == array[y][x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1]) { + penalty += 3; + } + } + } + return penalty; + } + + // Apply mask penalty rule 3 and return the penalty. Find consecutive cells of 00001011101 or + // 10111010000, and give penalty to them. If we find patterns like 000010111010000, we give + // penalties twice (i.e. 40 * 2). + public static int applyMaskPenaltyRule3(ByteMatrix matrix) { + int penalty = 0; + byte[][] array = matrix.getArray(); + int width = matrix.getWidth(); + int height = matrix.getHeight(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // Tried to simplify following conditions but failed. + if (x + 6 < width && + array[y][x] == 1 && + array[y][x + 1] == 0 && + array[y][x + 2] == 1 && + array[y][x + 3] == 1 && + array[y][x + 4] == 1 && + array[y][x + 5] == 0 && + array[y][x + 6] == 1 && + ((x + 10 < width && + array[y][x + 7] == 0 && + array[y][x + 8] == 0 && + array[y][x + 9] == 0 && + array[y][x + 10] == 0) || + (x - 4 >= 0 && + array[y][x - 1] == 0 && + array[y][x - 2] == 0 && + array[y][x - 3] == 0 && + array[y][x - 4] == 0))) { + penalty += 40; + } + if (y + 6 < height && + array[y][x] == 1 && + array[y + 1][x] == 0 && + array[y + 2][x] == 1 && + array[y + 3][x] == 1 && + array[y + 4][x] == 1 && + array[y + 5][x] == 0 && + array[y + 6][x] == 1 && + ((y + 10 < height && + array[y + 7][x] == 0 && + array[y + 8][x] == 0 && + array[y + 9][x] == 0 && + array[y + 10][x] == 0) || + (y - 4 >= 0 && + array[y - 1][x] == 0 && + array[y - 2][x] == 0 && + array[y - 3][x] == 0 && + array[y - 4][x] == 0))) { + penalty += 40; + } + } + } + return penalty; + } + + // Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give + // penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance. Examples: + // - 0% => 100 + // - 40% => 20 + // - 45% => 10 + // - 50% => 0 + // - 55% => 10 + // - 55% => 20 + // - 100% => 100 + public static int applyMaskPenaltyRule4(ByteMatrix matrix) { + int numDarkCells = 0; + byte[][] array = matrix.getArray(); + int width = matrix.getWidth(); + int height = matrix.getHeight(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + if (array[y][x] == 1) { + numDarkCells += 1; + } + } + } + int numTotalCells = matrix.getHeight() * matrix.getWidth(); + double darkRatio = (double) numDarkCells / numTotalCells; + return Math.abs((int) (darkRatio * 100 - 50)) / 5 * 10; + } + + // Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask + // pattern conditions. + public static boolean getDataMaskBit(int maskPattern, int x, int y) { + if (!QRCode.isValidMaskPattern(maskPattern)) { + throw new IllegalArgumentException("Invalid mask pattern"); + } + int intermediate, temp; + switch (maskPattern) { + case 0: + intermediate = (y + x) & 0x1; + break; + case 1: + intermediate = y & 0x1; + break; + case 2: + intermediate = x % 3; + break; + case 3: + intermediate = (y + x) % 3; + break; + case 4: + intermediate = ((y >>> 1) + (x / 3)) & 0x1; + break; + case 5: + temp = y * x; + intermediate = (temp & 0x1) + (temp % 3); + break; + case 6: + temp = y * x; + intermediate = (((temp & 0x1) + (temp % 3)) & 0x1); + break; + case 7: + temp = y * x; + intermediate = (((temp % 3) + ((y + x) & 0x1)) & 0x1); + break; + default: + throw new IllegalArgumentException("Invalid mask pattern: " + maskPattern); + } + return intermediate == 0; + } + + // Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both + // vertical and horizontal orders respectively. + private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, boolean isHorizontal) { + int penalty = 0; + int numSameBitCells = 0; + int prevBit = -1; + // Horizontal mode: + // for (int i = 0; i < matrix.height(); ++i) { + // for (int j = 0; j < matrix.width(); ++j) { + // int bit = matrix.get(i, j); + // Vertical mode: + // for (int i = 0; i < matrix.width(); ++i) { + // for (int j = 0; j < matrix.height(); ++j) { + // int bit = matrix.get(j, i); + int iLimit = isHorizontal ? matrix.getHeight() : matrix.getWidth(); + int jLimit = isHorizontal ? matrix.getWidth() : matrix.getHeight(); + byte[][] array = matrix.getArray(); + for (int i = 0; i < iLimit; ++i) { + for (int j = 0; j < jLimit; ++j) { + int bit = isHorizontal ? array[i][j] : array[j][i]; + if (bit == prevBit) { + numSameBitCells += 1; + // Found five repetitive cells with the same color (bit). + // We'll give penalty of 3. + if (numSameBitCells == 5) { + penalty += 3; + } else if (numSameBitCells > 5) { + // After five repetitive cells, we'll add the penalty one + // by one. + penalty += 1; + } + } else { + numSameBitCells = 1; // Include the cell itself. + prevBit = bit; + } + } + numSameBitCells = 0; // Clear at each row/column. + } + return penalty; + } + +} diff --git a/src/com/google/zxing/qrcode/encoder/MatrixUtil.java b/src/com/google/zxing/qrcode/encoder/MatrixUtil.java new file mode 100644 index 000000000..76bdb1899 --- /dev/null +++ b/src/com/google/zxing/qrcode/encoder/MatrixUtil.java @@ -0,0 +1,524 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +import com.google.zxing.WriterException; +import com.google.zxing.common.BitArray; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +public final class MatrixUtil { + + private MatrixUtil() { + // do nothing + } + + private static final int[][] POSITION_DETECTION_PATTERN = { + {1, 1, 1, 1, 1, 1, 1}, + {1, 0, 0, 0, 0, 0, 1}, + {1, 0, 1, 1, 1, 0, 1}, + {1, 0, 1, 1, 1, 0, 1}, + {1, 0, 1, 1, 1, 0, 1}, + {1, 0, 0, 0, 0, 0, 1}, + {1, 1, 1, 1, 1, 1, 1}, + }; + + private static final int[][] HORIZONTAL_SEPARATION_PATTERN = { + {0, 0, 0, 0, 0, 0, 0, 0}, + }; + + private static final int[][] VERTICAL_SEPARATION_PATTERN = { + {0}, {0}, {0}, {0}, {0}, {0}, {0}, + }; + + private static final int[][] POSITION_ADJUSTMENT_PATTERN = { + {1, 1, 1, 1, 1}, + {1, 0, 0, 0, 1}, + {1, 0, 1, 0, 1}, + {1, 0, 0, 0, 1}, + {1, 1, 1, 1, 1}, + }; + + // From Appendix E. Table 1, JIS0510X:2004 (p 71). The table was double-checked by komatsu. + private static final int[][] POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = { + {-1, -1, -1, -1, -1, -1, -1}, // Version 1 + { 6, 18, -1, -1, -1, -1, -1}, // Version 2 + { 6, 22, -1, -1, -1, -1, -1}, // Version 3 + { 6, 26, -1, -1, -1, -1, -1}, // Version 4 + { 6, 30, -1, -1, -1, -1, -1}, // Version 5 + { 6, 34, -1, -1, -1, -1, -1}, // Version 6 + { 6, 22, 38, -1, -1, -1, -1}, // Version 7 + { 6, 24, 42, -1, -1, -1, -1}, // Version 8 + { 6, 26, 46, -1, -1, -1, -1}, // Version 9 + { 6, 28, 50, -1, -1, -1, -1}, // Version 10 + { 6, 30, 54, -1, -1, -1, -1}, // Version 11 + { 6, 32, 58, -1, -1, -1, -1}, // Version 12 + { 6, 34, 62, -1, -1, -1, -1}, // Version 13 + { 6, 26, 46, 66, -1, -1, -1}, // Version 14 + { 6, 26, 48, 70, -1, -1, -1}, // Version 15 + { 6, 26, 50, 74, -1, -1, -1}, // Version 16 + { 6, 30, 54, 78, -1, -1, -1}, // Version 17 + { 6, 30, 56, 82, -1, -1, -1}, // Version 18 + { 6, 30, 58, 86, -1, -1, -1}, // Version 19 + { 6, 34, 62, 90, -1, -1, -1}, // Version 20 + { 6, 28, 50, 72, 94, -1, -1}, // Version 21 + { 6, 26, 50, 74, 98, -1, -1}, // Version 22 + { 6, 30, 54, 78, 102, -1, -1}, // Version 23 + { 6, 28, 54, 80, 106, -1, -1}, // Version 24 + { 6, 32, 58, 84, 110, -1, -1}, // Version 25 + { 6, 30, 58, 86, 114, -1, -1}, // Version 26 + { 6, 34, 62, 90, 118, -1, -1}, // Version 27 + { 6, 26, 50, 74, 98, 122, -1}, // Version 28 + { 6, 30, 54, 78, 102, 126, -1}, // Version 29 + { 6, 26, 52, 78, 104, 130, -1}, // Version 30 + { 6, 30, 56, 82, 108, 134, -1}, // Version 31 + { 6, 34, 60, 86, 112, 138, -1}, // Version 32 + { 6, 30, 58, 86, 114, 142, -1}, // Version 33 + { 6, 34, 62, 90, 118, 146, -1}, // Version 34 + { 6, 30, 54, 78, 102, 126, 150}, // Version 35 + { 6, 24, 50, 76, 102, 128, 154}, // Version 36 + { 6, 28, 54, 80, 106, 132, 158}, // Version 37 + { 6, 32, 58, 84, 110, 136, 162}, // Version 38 + { 6, 26, 54, 82, 110, 138, 166}, // Version 39 + { 6, 30, 58, 86, 114, 142, 170}, // Version 40 + }; + + // Type info cells at the left top corner. + private static final int[][] TYPE_INFO_COORDINATES = { + {8, 0}, + {8, 1}, + {8, 2}, + {8, 3}, + {8, 4}, + {8, 5}, + {8, 7}, + {8, 8}, + {7, 8}, + {5, 8}, + {4, 8}, + {3, 8}, + {2, 8}, + {1, 8}, + {0, 8}, + }; + + // From Appendix D in JISX0510:2004 (p. 67) + private static final int VERSION_INFO_POLY = 0x1f25; // 1 1111 0010 0101 + + // From Appendix C in JISX0510:2004 (p.65). + private static final int TYPE_INFO_POLY = 0x537; + private static final int TYPE_INFO_MASK_PATTERN = 0x5412; + + // Set all cells to -1. -1 means that the cell is empty (not set yet). + // + // JAVAPORT: We shouldn't need to do this at all. The code should be rewritten to begin encoding + // with the ByteMatrix initialized all to zero. + public static void clearMatrix(ByteMatrix matrix) { + matrix.clear((byte) -1); + } + + // Build 2D matrix of QR Code from "dataBits" with "ecLevel", "version" and "getMaskPattern". On + // success, store the result in "matrix" and return true. + public static void buildMatrix(BitArray dataBits, ErrorCorrectionLevel ecLevel, int version, + int maskPattern, ByteMatrix matrix) throws WriterException { + clearMatrix(matrix); + embedBasicPatterns(version, matrix); + // Type information appear with any version. + embedTypeInfo(ecLevel, maskPattern, matrix); + // Version info appear if version >= 7. + maybeEmbedVersionInfo(version, matrix); + // Data should be embedded at end. + embedDataBits(dataBits, maskPattern, matrix); + } + + // Embed basic patterns. On success, modify the matrix and return true. + // The basic patterns are: + // - Position detection patterns + // - Timing patterns + // - Dark dot at the left bottom corner + // - Position adjustment patterns, if need be + public static void embedBasicPatterns(int version, ByteMatrix matrix) throws WriterException { + // Let's get started with embedding big squares at corners. + embedPositionDetectionPatternsAndSeparators(matrix); + // Then, embed the dark dot at the left bottom corner. + embedDarkDotAtLeftBottomCorner(matrix); + + // Position adjustment patterns appear if version >= 2. + maybeEmbedPositionAdjustmentPatterns(version, matrix); + // Timing patterns should be embedded after position adj. patterns. + embedTimingPatterns(matrix); + } + + // Embed type information. On success, modify the matrix. + public static void embedTypeInfo(ErrorCorrectionLevel ecLevel, int maskPattern, ByteMatrix matrix) + throws WriterException { + BitArray typeInfoBits = new BitArray(); + makeTypeInfoBits(ecLevel, maskPattern, typeInfoBits); + + for (int i = 0; i < typeInfoBits.getSize(); ++i) { + // Place bits in LSB to MSB order. LSB (least significant bit) is the last value in + // "typeInfoBits". + boolean bit = typeInfoBits.get(typeInfoBits.getSize() - 1 - i); + + // Type info bits at the left top corner. See 8.9 of JISX0510:2004 (p.46). + int x1 = TYPE_INFO_COORDINATES[i][0]; + int y1 = TYPE_INFO_COORDINATES[i][1]; + matrix.set(x1, y1, bit); + + if (i < 8) { + // Right top corner. + int x2 = matrix.getWidth() - i - 1; + int y2 = 8; + matrix.set(x2, y2, bit); + } else { + // Left bottom corner. + int x2 = 8; + int y2 = matrix.getHeight() - 7 + (i - 8); + matrix.set(x2, y2, bit); + } + } + } + + // Embed version information if need be. On success, modify the matrix and return true. + // See 8.10 of JISX0510:2004 (p.47) for how to embed version information. + public static void maybeEmbedVersionInfo(int version, ByteMatrix matrix) throws WriterException { + if (version < 7) { // Version info is necessary if version >= 7. + return; // Don't need version info. + } + BitArray versionInfoBits = new BitArray(); + makeVersionInfoBits(version, versionInfoBits); + + int bitIndex = 6 * 3 - 1; // It will decrease from 17 to 0. + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 3; ++j) { + // Place bits in LSB (least significant bit) to MSB order. + boolean bit = versionInfoBits.get(bitIndex); + bitIndex--; + // Left bottom corner. + matrix.set(i, matrix.getHeight() - 11 + j, bit); + // Right bottom corner. + matrix.set(matrix.getHeight() - 11 + j, i, bit); + } + } + } + + // Embed "dataBits" using "getMaskPattern". On success, modify the matrix and return true. + // For debugging purposes, it skips masking process if "getMaskPattern" is -1. + // See 8.7 of JISX0510:2004 (p.38) for how to embed data bits. + public static void embedDataBits(BitArray dataBits, int maskPattern, ByteMatrix matrix) + throws WriterException { + int bitIndex = 0; + int direction = -1; + // Start from the right bottom cell. + int x = matrix.getWidth() - 1; + int y = matrix.getHeight() - 1; + while (x > 0) { + // Skip the vertical timing pattern. + if (x == 6) { + x -= 1; + } + while (y >= 0 && y < matrix.getHeight()) { + for (int i = 0; i < 2; ++i) { + int xx = x - i; + // Skip the cell if it's not empty. + if (!isEmpty(matrix.get(xx, y))) { + continue; + } + boolean bit; + if (bitIndex < dataBits.getSize()) { + bit = dataBits.get(bitIndex); + ++bitIndex; + } else { + // Padding bit. If there is no bit left, we'll fill the left cells with 0, as described + // in 8.4.9 of JISX0510:2004 (p. 24). + bit = false; + } + + // Skip masking if mask_pattern is -1. + if (maskPattern != -1) { + if (MaskUtil.getDataMaskBit(maskPattern, xx, y)) { + bit = !bit; + } + } + matrix.set(xx, y, bit); + } + y += direction; + } + direction = -direction; // Reverse the direction. + y += direction; + x -= 2; // Move to the left. + } + // All bits should be consumed. + if (bitIndex != dataBits.getSize()) { + throw new WriterException("Not all bits consumed: " + bitIndex + '/' + dataBits.getSize()); + } + } + + // Return the position of the most significant bit set (to one) in the "value". The most + // significant bit is position 32. If there is no bit set, return 0. Examples: + // - findMSBSet(0) => 0 + // - findMSBSet(1) => 1 + // - findMSBSet(255) => 8 + public static int findMSBSet(int value) { + int numDigits = 0; + while (value != 0) { + value >>>= 1; + ++numDigits; + } + return numDigits; + } + + // Calculate BCH (Bose-Chaudhuri-Hocquenghem) code for "value" using polynomial "poly". The BCH + // code is used for encoding type information and version information. + // Example: Calculation of version information of 7. + // f(x) is created from 7. + // - 7 = 000111 in 6 bits + // - f(x) = x^2 + x^1 + x^0 + // g(x) is given by the standard (p. 67) + // - g(x) = x^12 + x^11 + x^10 + x^9 + x^8 + x^5 + x^2 + 1 + // Multiply f(x) by x^(18 - 6) + // - f'(x) = f(x) * x^(18 - 6) + // - f'(x) = x^14 + x^13 + x^12 + // Calculate the remainder of f'(x) / g(x) + // x^2 + // __________________________________________________ + // g(x) )x^14 + x^13 + x^12 + // x^14 + x^13 + x^12 + x^11 + x^10 + x^7 + x^4 + x^2 + // -------------------------------------------------- + // x^11 + x^10 + x^7 + x^4 + x^2 + // + // The remainder is x^11 + x^10 + x^7 + x^4 + x^2 + // Encode it in binary: 110010010100 + // The return value is 0xc94 (1100 1001 0100) + // + // Since all coefficients in the polynomials are 1 or 0, we can do the calculation by bit + // operations. We don't care if cofficients are positive or negative. + public static int calculateBCHCode(int value, int poly) { + // If poly is "1 1111 0010 0101" (version info poly), msbSetInPoly is 13. We'll subtract 1 + // from 13 to make it 12. + int msbSetInPoly = findMSBSet(poly); + value <<= msbSetInPoly - 1; + // Do the division business using exclusive-or operations. + while (findMSBSet(value) >= msbSetInPoly) { + value ^= poly << (findMSBSet(value) - msbSetInPoly); + } + // Now the "value" is the remainder (i.e. the BCH code) + return value; + } + + // Make bit vector of type information. On success, store the result in "bits" and return true. + // Encode error correction level and mask pattern. See 8.9 of + // JISX0510:2004 (p.45) for details. + public static void makeTypeInfoBits(ErrorCorrectionLevel ecLevel, int maskPattern, BitArray bits) + throws WriterException { + if (!QRCode.isValidMaskPattern(maskPattern)) { + throw new WriterException("Invalid mask pattern"); + } + int typeInfo = (ecLevel.getBits() << 3) | maskPattern; + bits.appendBits(typeInfo, 5); + + int bchCode = calculateBCHCode(typeInfo, TYPE_INFO_POLY); + bits.appendBits(bchCode, 10); + + BitArray maskBits = new BitArray(); + maskBits.appendBits(TYPE_INFO_MASK_PATTERN, 15); + bits.xor(maskBits); + + if (bits.getSize() != 15) { // Just in case. + throw new WriterException("should not happen but we got: " + bits.getSize()); + } + } + + // Make bit vector of version information. On success, store the result in "bits" and return true. + // See 8.10 of JISX0510:2004 (p.45) for details. + public static void makeVersionInfoBits(int version, BitArray bits) throws WriterException { + bits.appendBits(version, 6); + int bchCode = calculateBCHCode(version, VERSION_INFO_POLY); + bits.appendBits(bchCode, 12); + + if (bits.getSize() != 18) { // Just in case. + throw new WriterException("should not happen but we got: " + bits.getSize()); + } + } + + // Check if "value" is empty. + private static boolean isEmpty(int value) { + return value == -1; + } + + // Check if "value" is valid. + private static boolean isValidValue(int value) { + return (value == -1 || // Empty. + value == 0 || // Light (white). + value == 1); // Dark (black). + } + + private static void embedTimingPatterns(ByteMatrix matrix) throws WriterException { + // -8 is for skipping position detection patterns (size 7), and two horizontal/vertical + // separation patterns (size 1). Thus, 8 = 7 + 1. + for (int i = 8; i < matrix.getWidth() - 8; ++i) { + int bit = (i + 1) % 2; + // Horizontal line. + if (!isValidValue(matrix.get(i, 6))) { + throw new WriterException(); + } + if (isEmpty(matrix.get(i, 6))) { + matrix.set(i, 6, bit); + } + // Vertical line. + if (!isValidValue(matrix.get(6, i))) { + throw new WriterException(); + } + if (isEmpty(matrix.get(6, i))) { + matrix.set(6, i, bit); + } + } + } + + // Embed the lonely dark dot at left bottom corner. JISX0510:2004 (p.46) + private static void embedDarkDotAtLeftBottomCorner(ByteMatrix matrix) throws WriterException { + if (matrix.get(8, matrix.getHeight() - 8) == 0) { + throw new WriterException(); + } + matrix.set(8, matrix.getHeight() - 8, 1); + } + + private static void embedHorizontalSeparationPattern(int xStart, int yStart, + ByteMatrix matrix) throws WriterException { + // We know the width and height. + if (HORIZONTAL_SEPARATION_PATTERN[0].length != 8 || HORIZONTAL_SEPARATION_PATTERN.length != 1) { + throw new WriterException("Bad horizontal separation pattern"); + } + for (int x = 0; x < 8; ++x) { + if (!isEmpty(matrix.get(xStart + x, yStart))) { + throw new WriterException(); + } + matrix.set(xStart + x, yStart, HORIZONTAL_SEPARATION_PATTERN[0][x]); + } + } + + private static void embedVerticalSeparationPattern(int xStart, int yStart, + ByteMatrix matrix) throws WriterException { + // We know the width and height. + if (VERTICAL_SEPARATION_PATTERN[0].length != 1 || VERTICAL_SEPARATION_PATTERN.length != 7) { + throw new WriterException("Bad vertical separation pattern"); + } + for (int y = 0; y < 7; ++y) { + if (!isEmpty(matrix.get(xStart, yStart + y))) { + throw new WriterException(); + } + matrix.set(xStart, yStart + y, VERTICAL_SEPARATION_PATTERN[y][0]); + } + } + + // Note that we cannot unify the function with embedPositionDetectionPattern() despite they are + // almost identical, since we cannot write a function that takes 2D arrays in different sizes in + // C/C++. We should live with the fact. + private static void embedPositionAdjustmentPattern(int xStart, int yStart, + ByteMatrix matrix) throws WriterException { + // We know the width and height. + if (POSITION_ADJUSTMENT_PATTERN[0].length != 5 || POSITION_ADJUSTMENT_PATTERN.length != 5) { + throw new WriterException("Bad position adjustment"); + } + for (int y = 0; y < 5; ++y) { + for (int x = 0; x < 5; ++x) { + if (!isEmpty(matrix.get(xStart + x, yStart + y))) { + throw new WriterException(); + } + matrix.set(xStart + x, yStart + y, POSITION_ADJUSTMENT_PATTERN[y][x]); + } + } + } + + private static void embedPositionDetectionPattern(int xStart, int yStart, + ByteMatrix matrix) throws WriterException { + // We know the width and height. + if (POSITION_DETECTION_PATTERN[0].length != 7 || POSITION_DETECTION_PATTERN.length != 7) { + throw new WriterException("Bad position detection pattern"); + } + for (int y = 0; y < 7; ++y) { + for (int x = 0; x < 7; ++x) { + if (!isEmpty(matrix.get(xStart + x, yStart + y))) { + throw new WriterException(); + } + matrix.set(xStart + x, yStart + y, POSITION_DETECTION_PATTERN[y][x]); + } + } + } + + // Embed position detection patterns and surrounding vertical/horizontal separators. + private static void embedPositionDetectionPatternsAndSeparators(ByteMatrix matrix) throws WriterException { + // Embed three big squares at corners. + int pdpWidth = POSITION_DETECTION_PATTERN[0].length; + // Left top corner. + embedPositionDetectionPattern(0, 0, matrix); + // Right top corner. + embedPositionDetectionPattern(matrix.getWidth() - pdpWidth, 0, matrix); + // Left bottom corner. + embedPositionDetectionPattern(0, matrix.getWidth() - pdpWidth, matrix); + + // Embed horizontal separation patterns around the squares. + int hspWidth = HORIZONTAL_SEPARATION_PATTERN[0].length; + // Left top corner. + embedHorizontalSeparationPattern(0, hspWidth - 1, matrix); + // Right top corner. + embedHorizontalSeparationPattern(matrix.getWidth() - hspWidth, + hspWidth - 1, matrix); + // Left bottom corner. + embedHorizontalSeparationPattern(0, matrix.getWidth() - hspWidth, matrix); + + // Embed vertical separation patterns around the squares. + int vspSize = VERTICAL_SEPARATION_PATTERN.length; + // Left top corner. + embedVerticalSeparationPattern(vspSize, 0, matrix); + // Right top corner. + embedVerticalSeparationPattern(matrix.getHeight() - vspSize - 1, 0, matrix); + // Left bottom corner. + embedVerticalSeparationPattern(vspSize, matrix.getHeight() - vspSize, + matrix); + } + + // Embed position adjustment patterns if need be. + private static void maybeEmbedPositionAdjustmentPatterns(int version, ByteMatrix matrix) + throws WriterException { + if (version < 2) { // The patterns appear if version >= 2 + return; + } + int index = version - 1; + int[] coordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index]; + int numCoordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index].length; + for (int i = 0; i < numCoordinates; ++i) { + for (int j = 0; j < numCoordinates; ++j) { + int y = coordinates[i]; + int x = coordinates[j]; + if (x == -1 || y == -1) { + continue; + } + // If the cell is unset, we embed the position adjustment pattern here. + if (isEmpty(matrix.get(x, y))) { + // -2 is necessary since the x/y coordinates point to the center of the pattern, not the + // left top corner. + embedPositionAdjustmentPattern(x - 2, y - 2, matrix); + } + } + } + } + +} diff --git a/src/com/google/zxing/qrcode/encoder/QRCode.java b/src/com/google/zxing/qrcode/encoder/QRCode.java new file mode 100644 index 000000000..05c818513 --- /dev/null +++ b/src/com/google/zxing/qrcode/encoder/QRCode.java @@ -0,0 +1,239 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.qrcode.encoder; + +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; +import com.google.zxing.qrcode.decoder.Mode; + +/** + * @author satorux@google.com (Satoru Takabayashi) - creator + * @author dswitkin@google.com (Daniel Switkin) - ported from C++ + */ +public final class QRCode { + + public static final int NUM_MASK_PATTERNS = 8; + + private Mode mode; + private ErrorCorrectionLevel ecLevel; + private int version; + private int matrixWidth; + private int maskPattern; + private int numTotalBytes; + private int numDataBytes; + private int numECBytes; + private int numRSBlocks; + private ByteMatrix matrix; + + public QRCode() { + mode = null; + ecLevel = null; + version = -1; + matrixWidth = -1; + maskPattern = -1; + numTotalBytes = -1; + numDataBytes = -1; + numECBytes = -1; + numRSBlocks = -1; + matrix = null; + } + + // Mode of the QR Code. + public Mode getMode() { + return mode; + } + + // Error correction level of the QR Code. + public ErrorCorrectionLevel getECLevel() { + return ecLevel; + } + + // Version of the QR Code. The bigger size, the bigger version. + public int getVersion() { + return version; + } + + // ByteMatrix width of the QR Code. + public int getMatrixWidth() { + return matrixWidth; + } + + // Mask pattern of the QR Code. + public int getMaskPattern() { + return maskPattern; + } + + // Number of total bytes in the QR Code. + public int getNumTotalBytes() { + return numTotalBytes; + } + + // Number of data bytes in the QR Code. + public int getNumDataBytes() { + return numDataBytes; + } + + // Number of error correction bytes in the QR Code. + public int getNumECBytes() { + return numECBytes; + } + + // Number of Reedsolomon blocks in the QR Code. + public int getNumRSBlocks() { + return numRSBlocks; + } + + // ByteMatrix data of the QR Code. + public ByteMatrix getMatrix() { + return matrix; + } + + + // Return the value of the module (cell) pointed by "x" and "y" in the matrix of the QR Code. They + // call cells in the matrix "modules". 1 represents a black cell, and 0 represents a white cell. + public int at(int x, int y) { + // The value must be zero or one. + int value = matrix.get(x, y); + if (!(value == 0 || value == 1)) { + // this is really like an assert... not sure what better exception to use? + throw new RuntimeException("Bad value"); + } + return value; + } + + // Checks all the member variables are set properly. Returns true on success. Otherwise, returns + // false. + public boolean isValid() { + return + // First check if all version are not uninitialized. + mode != null && + ecLevel != null && + version != -1 && + matrixWidth != -1 && + maskPattern != -1 && + numTotalBytes != -1 && + numDataBytes != -1 && + numECBytes != -1 && + numRSBlocks != -1 && + // Then check them in other ways.. + isValidMaskPattern(maskPattern) && + numTotalBytes == numDataBytes + numECBytes && + // ByteMatrix stuff. + matrix != null && + matrixWidth == matrix.getWidth() && + // See 7.3.1 of JISX0510:2004 (p.5). + matrix.getWidth() == matrix.getHeight(); // Must be square. + } + + // Return debug String. + public String toString() { + StringBuffer result = new StringBuffer(200); + result.append("<<\n"); + result.append(" mode: "); + result.append(mode); + result.append("\n ecLevel: "); + result.append(ecLevel); + result.append("\n version: "); + result.append(version); + result.append("\n matrixWidth: "); + result.append(matrixWidth); + result.append("\n maskPattern: "); + result.append(maskPattern); + result.append("\n numTotalBytes: "); + result.append(numTotalBytes); + result.append("\n numDataBytes: "); + result.append(numDataBytes); + result.append("\n numECBytes: "); + result.append(numECBytes); + result.append("\n numRSBlocks: "); + result.append(numRSBlocks); + if (matrix == null) { + result.append("\n matrix: null\n"); + } else { + result.append("\n matrix:\n"); + result.append(matrix.toString()); + } + result.append(">>\n"); + return result.toString(); + } + + public void setMode(Mode value) { + mode = value; + } + + public void setECLevel(ErrorCorrectionLevel value) { + ecLevel = value; + } + + public void setVersion(int value) { + version = value; + } + + public void setMatrixWidth(int value) { + matrixWidth = value; + } + + public void setMaskPattern(int value) { + maskPattern = value; + } + + public void setNumTotalBytes(int value) { + numTotalBytes = value; + } + + public void setNumDataBytes(int value) { + numDataBytes = value; + } + + public void setNumECBytes(int value) { + numECBytes = value; + } + + public void setNumRSBlocks(int value) { + numRSBlocks = value; + } + + // This takes ownership of the 2D array. + public void setMatrix(ByteMatrix value) { + matrix = value; + } + + // Check if "mask_pattern" is valid. + public static boolean isValidMaskPattern(int maskPattern) { + return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS; + } + + // Return true if the all values in the matrix are binary numbers. + // + // JAVAPORT: This is going to be super expensive and unnecessary, we should not call this in + // production. I'm leaving it because it may be useful for testing. It should be removed entirely + // if ByteMatrix is changed never to contain a -1. + /* + private static boolean EverythingIsBinary(final ByteMatrix matrix) { + for (int y = 0; y < matrix.height(); ++y) { + for (int x = 0; x < matrix.width(); ++x) { + int value = matrix.get(y, x); + if (!(value == 0 || value == 1)) { + // Found non zero/one value. + return false; + } + } + } + return true; + } + */ + +} diff --git a/src/net/sourceforge/plantuml/AbstractPSystem.java b/src/net/sourceforge/plantuml/AbstractPSystem.java index 270a4d5d2..d071c4e5b 100644 --- a/src/net/sourceforge/plantuml/AbstractPSystem.java +++ b/src/net/sourceforge/plantuml/AbstractPSystem.java @@ -28,12 +28,18 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6002 $ + * Revision $Revision: 6302 $ * */ package net.sourceforge.plantuml; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; import java.util.Date; +import java.util.List; import java.util.Properties; import net.sourceforge.plantuml.version.Version; @@ -78,4 +84,22 @@ public abstract class AbstractPSystem implements PSystem { return 1; } + public List exportDiagrams(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { + if (suggestedFile.exists() && suggestedFile.isDirectory()) { + throw new IllegalArgumentException("File is a directory " + suggestedFile); + } + OutputStream os = null; + try { + os = new FileOutputStream(suggestedFile); + this.exportDiagram(os, null, 0, fileFormat); + } finally { + if (os != null) { + os.close(); + } + } + return Arrays.asList(suggestedFile); + } + + + } diff --git a/src/net/sourceforge/plantuml/BlockUml.java b/src/net/sourceforge/plantuml/BlockUml.java index 781a573c3..981d64e9d 100644 --- a/src/net/sourceforge/plantuml/BlockUml.java +++ b/src/net/sourceforge/plantuml/BlockUml.java @@ -45,15 +45,15 @@ public class BlockUml { private final List data; private PSystem system; - private static final Pattern pattern1 = Pattern.compile("^@startuml\\s+\"?(.*?)\"?$"); + private static final Pattern patternFilename = Pattern.compile("^@start[-\\w.]+\\s+\"?(.*?)\"?$"); BlockUml(String... strings) { this(Arrays.asList(strings)); } - BlockUml(List strings) { + public BlockUml(List strings) { final String s0 = strings.get(0).trim(); - if (s0.startsWith("@startuml") == false) { + if (s0.startsWith("@start") == false) { throw new IllegalArgumentException(); } this.data = new ArrayList(strings); @@ -63,16 +63,23 @@ public class BlockUml { if (OptionFlags.getInstance().isWord()) { return null; } - final Matcher m = pattern1.matcher(data.get(0).trim()); + final Matcher m = patternFilename.matcher(data.get(0).trim()); final boolean ok = m.find(); if (ok == false) { return null; } - return m.group(1); + final String result = m.group(1); + for (int i = 0; i < result.length(); i++) { + final char c = result.charAt(i); + if ("<>|".indexOf(c) != -1) { + return null; + } + } + return result; } public PSystem getSystem() throws IOException, InterruptedException { - if (system==null) { + if (system == null) { createSystem(); } return system; @@ -80,7 +87,7 @@ public class BlockUml { private void createSystem() throws IOException, InterruptedException { system = new PSystemBuilder().createPSystem(data); - + } } diff --git a/src/net/sourceforge/plantuml/BlockUmlBuilder.java b/src/net/sourceforge/plantuml/BlockUmlBuilder.java index 864890432..724cf59b2 100644 --- a/src/net/sourceforge/plantuml/BlockUmlBuilder.java +++ b/src/net/sourceforge/plantuml/BlockUmlBuilder.java @@ -64,27 +64,17 @@ final public class BlockUmlBuilder { } } - public static boolean isArobaseEnduml(String s) { - s = s.trim(); - return s.equals("@enduml") || s.startsWith("@enduml "); - } - - public static boolean isArobaseStartuml(String s) { - s = s.trim(); - return s.equals("@startuml") || s.startsWith("@startuml "); - } - private void init(Preprocessor includer, List config) throws IOException { String s = null; List current = null; while ((s = includer.readLine()) != null) { - if (isArobaseStartuml(s)) { + if (StartUtils.isArobaseStartDiagram(s)) { current = new ArrayList(); } if (current != null) { current.add(s); } - if (isArobaseEnduml(s) && current != null) { + if (StartUtils.isArobaseEndDiagram(s) && current != null) { current.addAll(1, config); blocks.add(new BlockUml(current)); current = null; diff --git a/src/net/sourceforge/plantuml/ColorParam.java b/src/net/sourceforge/plantuml/ColorParam.java index df0998a3f..b89060580 100644 --- a/src/net/sourceforge/plantuml/ColorParam.java +++ b/src/net/sourceforge/plantuml/ColorParam.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5823 $ + * Revision $Revision: 6475 $ * */ package net.sourceforge.plantuml; @@ -82,15 +82,17 @@ public enum ColorParam { sequenceActorBackground(true), sequenceActorBorder, + sequenceGroupBorder, sequenceGroupBackground(true), + sequenceReferenceBackground(true), sequenceDividerBackground(true), sequenceLifeLineBackground(true), sequenceLifeLineBorder, sequenceParticipantBackground(true), sequenceParticipantBorder, sequenceArrow, - sequenceEngloberLine, - sequenceEngloberBackground(true), + sequenceBoxBorder, + sequenceBoxBackground(true), iconPrivate, iconPrivateBackground, diff --git a/src/net/sourceforge/plantuml/DiagramType.java b/src/net/sourceforge/plantuml/DiagramType.java new file mode 100644 index 000000000..eb393baf8 --- /dev/null +++ b/src/net/sourceforge/plantuml/DiagramType.java @@ -0,0 +1,54 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 4768 $ + * + */ +package net.sourceforge.plantuml; + +public enum DiagramType { + UML, DITAA, DOT, PROJECT, UNKNOWN; + + static DiagramType getTypeFromArobaseStart(String s) { + if (s.startsWith("@startuml")) { + return UML; + } + if (s.startsWith("@startdot")) { + return DOT; + } + if (s.startsWith("@startditaa")) { + return DITAA; + } + if (s.startsWith("@startproject")) { + return PROJECT; + } + return UNKNOWN; + } +} diff --git a/src/net/sourceforge/plantuml/EmbededDiagram.java b/src/net/sourceforge/plantuml/EmbededDiagram.java new file mode 100644 index 000000000..825f0a950 --- /dev/null +++ b/src/net/sourceforge/plantuml/EmbededDiagram.java @@ -0,0 +1,63 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 5982 $ + * + */ +package net.sourceforge.plantuml; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class EmbededDiagram implements CharSequence { + + private final List system; + + public EmbededDiagram(List system) { + this.system = new ArrayList(system); + } + + public int length() { + return toString().length(); + } + + public char charAt(int index) { + return toString().charAt(index); + } + + public CharSequence subSequence(int start, int end) { + return toString().subSequence(start, end); + } + + public final List getLines() { + return Collections.unmodifiableList(system); + } +} diff --git a/src/net/sourceforge/plantuml/FontParam.java b/src/net/sourceforge/plantuml/FontParam.java index e654bf9df..8773e1ec9 100644 --- a/src/net/sourceforge/plantuml/FontParam.java +++ b/src/net/sourceforge/plantuml/FontParam.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5942 $ + * Revision $Revision: 6475 $ * */ package net.sourceforge.plantuml; @@ -55,11 +55,12 @@ public enum FontParam { PACKAGE(14, Font.PLAIN, "black", null), SEQUENCE_ACTOR(13, Font.PLAIN, "black", null), SEQUENCE_ARROW(13, Font.PLAIN, "black", null), - SEQUENCE_ENGLOBER(13, Font.BOLD, "black", null), + SEQUENCE_BOX(13, Font.BOLD, "black", null), SEQUENCE_DIVIDER(13, Font.BOLD, "black", null), + SEQUENCE_REFERENCE(13, Font.PLAIN, "black", null), SEQUENCE_DELAY(11, Font.PLAIN, "black", null), - SEQUENCE_GROUPING(11, Font.BOLD, "black", null), - SEQUENCE_GROUPING_HEADER(13, Font.BOLD, "black", null), + SEQUENCE_GROUP(11, Font.BOLD, "black", null), + SEQUENCE_GROUP_HEADER(13, Font.BOLD, "black", null), SEQUENCE_PARTICIPANT(13, Font.PLAIN, "black", null), SEQUENCE_TITLE(13, Font.BOLD, "black", null), STATE(14, Font.PLAIN, "black", null), diff --git a/src/net/sourceforge/plantuml/Option.java b/src/net/sourceforge/plantuml/Option.java index f736a75db..ab9e910fb 100644 --- a/src/net/sourceforge/plantuml/Option.java +++ b/src/net/sourceforge/plantuml/Option.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6187 $ + * Revision $Revision: 6448 $ * */ package net.sourceforge.plantuml; @@ -172,6 +172,8 @@ public class Option { OptionPrint.printVersion(); } else if (s.startsWith("-D")) { manageDefine(s.substring(2)); + } else if (s.startsWith("-S")) { + manageSkinParam(s.substring(2)); } else if (s.equalsIgnoreCase("-testdot")) { OptionPrint.printTestDot(); } else if (s.equalsIgnoreCase("-about") || s.equalsIgnoreCase("-author") || s.equalsIgnoreCase("-authors")) { @@ -211,6 +213,21 @@ public class Option { } } + private void manageSkinParam(String s) { + final Pattern p = Pattern.compile("^(\\w+)(?:=(.*))?$"); + final Matcher m = p.matcher(s); + if (m.find()) { + skinParam(m.group(1), m.group(2)); + } + } + + private void skinParam(String var, String value) { + if (var != null && value != null) { + config.add("skinparamlocked " + var + " " + value); + } + + } + public final File getOutputDir() { return outputDir; } diff --git a/src/net/sourceforge/plantuml/OptionPrint.java b/src/net/sourceforge/plantuml/OptionPrint.java index 37d1dab4f..d217b2fd0 100644 --- a/src/net/sourceforge/plantuml/OptionPrint.java +++ b/src/net/sourceforge/plantuml/OptionPrint.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6007 $ + * Revision $Revision: 6448 $ * */ package net.sourceforge.plantuml; @@ -56,7 +56,7 @@ public class OptionPrint { System.err.println("Usage: java -jar plantuml.jar [options] -gui"); System.err.println("\t(to execute the GUI)"); - System.err.println(" or java -jar plantuml.jar [options] [files/dirs]"); + System.err.println(" or java -jar plantuml.jar [options] [file/dir] [file/dir] [file/dir]"); System.err.println("\t(to process files or directories)"); System.err.println(); System.err.println("You can use the following wildcards in files/dirs:"); @@ -67,7 +67,14 @@ public class OptionPrint { System.err.println("where options include:"); System.err.println(" -gui\t\tTo run the graphical user interface"); System.err.println(" -tsvg\t\tTo generate images using SVG format"); + System.err.println(" -teps\t\tTo generate images using EPS format"); + System.err.println(" -txmi\t\tTo generate XMI file for classes diagrams"); + System.err.println(" -tdot\t\tTo generate DOT intermediate file"); + System.err.println(" -ttxt\t\tTo generate images with ASCII art"); + System.err.println(" -tutxt\t\tTo generate images with ASCII art using Unicode characters"); System.err.println(" -o[utput] \"dir\"\tTo generate images in the specified directory"); + System.err.println(" -DVAR1=value\tTo set a preprocessing variable as if '!define VAR1 value' were used"); + System.err.println(" -Sparam1=value\tTo set a skin parameter as if 'skinparam param1 value' were used"); System.err.println(" -config \"file\"\tTo read the provided config file before each diagram"); System.err.println(" -charset xxx\tTo use a specific charset (default is " + charset + ")"); System.err.println(" -e[x]clude pattern\tTo exclude files that match the provided pattern"); @@ -84,6 +91,9 @@ public class OptionPrint { System.err.println(" -p[ipe]\t\tTo use stdin for PlantUML source and stdout for PNG/SVG generation"); System.err.println(" -computeurl\t\tTo compute the encoded URL of a PlantUML source file"); System.err.println(" -decodeurl\t\tTo retrieve the PlantUML source from an encoded URL"); + System.err.println(" -syntax\t\tTo report any syntax error from standard input without generating images"); + System.err.println(" -language\t\tTo print the list of PlantUML keywords"); + System.err.println(" -pattern\t\tTo print the list of Regular Expression used by PlantUML"); System.err.println(); System.err.println("If needed, you can setup the environment variable GRAPHVIZ_DOT."); exit(); diff --git a/src/net/sourceforge/plantuml/PSystem.java b/src/net/sourceforge/plantuml/PSystem.java index cab71b11d..86140bf75 100644 --- a/src/net/sourceforge/plantuml/PSystem.java +++ b/src/net/sourceforge/plantuml/PSystem.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5794 $ + * Revision $Revision: 6302 $ * */ package net.sourceforge.plantuml; @@ -40,9 +40,9 @@ import java.util.List; public interface PSystem { - List createFiles(File suggestedFile, FileFormatOption fileFormatOption) throws IOException, InterruptedException; + List exportDiagrams(File suggestedFile, FileFormatOption fileFormatOption) throws IOException, InterruptedException; - void createFile(OutputStream os, int index, FileFormatOption fileFormatOption) throws IOException; + void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormatOption) throws IOException; int getNbImages(); diff --git a/src/net/sourceforge/plantuml/PSystemBuilder.java b/src/net/sourceforge/plantuml/PSystemBuilder.java index 8ffd84187..dca80fc64 100644 --- a/src/net/sourceforge/plantuml/PSystemBuilder.java +++ b/src/net/sourceforge/plantuml/PSystemBuilder.java @@ -43,6 +43,8 @@ import net.sourceforge.plantuml.activitydiagram2.ActivityDiagramFactory2; import net.sourceforge.plantuml.classdiagram.ClassDiagramFactory; import net.sourceforge.plantuml.componentdiagram.ComponentDiagramFactory; import net.sourceforge.plantuml.compositediagram.CompositeDiagramFactory; +import net.sourceforge.plantuml.directdot.PSystemDotFactory; +import net.sourceforge.plantuml.ditaa.PSystemDitaaFactory; import net.sourceforge.plantuml.eggs.PSystemEggFactory; import net.sourceforge.plantuml.eggs.PSystemLostFactory; import net.sourceforge.plantuml.eggs.PSystemPathFactory; @@ -51,6 +53,7 @@ import net.sourceforge.plantuml.objectdiagram.ObjectDiagramFactory; import net.sourceforge.plantuml.oregon.PSystemOregonFactory; import net.sourceforge.plantuml.postit.PostIdDiagramFactory; import net.sourceforge.plantuml.printskin.PrintSkinFactory; +import net.sourceforge.plantuml.project.PSystemProjectFactory; import net.sourceforge.plantuml.sequencediagram.SequenceDiagramFactory; import net.sourceforge.plantuml.statediagram.StateDiagramFactory; import net.sourceforge.plantuml.sudoku.PSystemSudokuFactory; @@ -75,16 +78,26 @@ public class PSystemBuilder { factories.add(new PostIdDiagramFactory()); factories.add(new PrintSkinFactory()); factories.add(new PSystemVersionFactory()); + factories.add(new PSystemDotFactory(DiagramType.DOT)); + factories.add(new PSystemDotFactory(DiagramType.UML)); + factories.add(new PSystemDitaaFactory(DiagramType.DITAA)); + factories.add(new PSystemDitaaFactory(DiagramType.UML)); factories.add(new PSystemSudokuFactory()); factories.add(new PSystemEggFactory()); factories.add(new PSystemRIPFactory()); factories.add(new PSystemLostFactory()); factories.add(new PSystemPathFactory()); factories.add(new PSystemOregonFactory()); + factories.add(new PSystemProjectFactory()); + final UmlSource umlSource = new UmlSource(strings); + final DiagramType diagramType = umlSource.getDiagramType(); final List errors = new ArrayList(); for (PSystemFactory systemFactory : factories) { - final PSystem sys = new PSystemSingleBuilder(new UmlSource(strings), systemFactory).getPSystem(); + if (diagramType != systemFactory.getDiagramType()) { + continue; + } + final PSystem sys = new PSystemSingleBuilder(umlSource, systemFactory).getPSystem(); if (isOk(sys)) { return sys; } diff --git a/src/net/sourceforge/plantuml/PSystemError.java b/src/net/sourceforge/plantuml/PSystemError.java index ab0a57252..658794385 100644 --- a/src/net/sourceforge/plantuml/PSystemError.java +++ b/src/net/sourceforge/plantuml/PSystemError.java @@ -28,17 +28,14 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5999 $ + * Revision $Revision: 6453 $ */ package net.sourceforge.plantuml; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; @@ -79,24 +76,24 @@ public class PSystemError extends AbstractPSystem { this(source, Collections.singletonList(singleError)); } - public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, - InterruptedException { - if (suggestedFile.exists() && suggestedFile.isDirectory()) { - throw new IllegalArgumentException("File is a directory " + suggestedFile); - } - OutputStream os = null; - try { - os = new FileOutputStream(suggestedFile); - getPngError().writeImage(os, getMetadata(), fileFormat); - } finally { - if (os != null) { - os.close(); - } - } - return Arrays.asList(suggestedFile); - } +// public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, +// InterruptedException { +// if (suggestedFile.exists() && suggestedFile.isDirectory()) { +// throw new IllegalArgumentException("File is a directory " + suggestedFile); +// } +// OutputStream os = null; +// try { +// os = new FileOutputStream(suggestedFile); +// getPngError().writeImage(os, getMetadata(), fileFormat); +// } finally { +// if (os != null) { +// os.close(); +// } +// } +// return Arrays.asList(suggestedFile); +// } - public void createFile(OutputStream os, int index, FileFormatOption fileFormat) throws IOException { + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormat) throws IOException { getPngError().writeImage(os, getMetadata(), fileFormat); } diff --git a/src/net/sourceforge/plantuml/PSystemFactory.java b/src/net/sourceforge/plantuml/PSystemFactory.java index d79aeef58..b17f70f5b 100644 --- a/src/net/sourceforge/plantuml/PSystemFactory.java +++ b/src/net/sourceforge/plantuml/PSystemFactory.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 3824 $ + * Revision $Revision: 6341 $ * */ package net.sourceforge.plantuml; @@ -38,5 +38,7 @@ public interface PSystemFactory { PSystem getSystem(); void reset(); + + DiagramType getDiagramType(); } diff --git a/src/net/sourceforge/plantuml/PSystemSingleBuilder.java b/src/net/sourceforge/plantuml/PSystemSingleBuilder.java index d6694bee4..7a378a591 100644 --- a/src/net/sourceforge/plantuml/PSystemSingleBuilder.java +++ b/src/net/sourceforge/plantuml/PSystemSingleBuilder.java @@ -73,7 +73,7 @@ final public class PSystemSingleBuilder { public PSystemSingleBuilder(UmlSource s, PSystemFactory systemFactory) throws IOException { this.source = s; it = s.iterator(); - if (BlockUmlBuilder.isArobaseStartuml(next()) == false) { + if (StartUtils.isArobaseStartDiagram(next()) == false) { throw new UnsupportedOperationException(); } @@ -90,7 +90,7 @@ final public class PSystemSingleBuilder { systemFactory.reset(); while (hasNext()) { final String s = next(); - if (BlockUmlBuilder.isArobaseEnduml(s)) { + if (StartUtils.isArobaseEndDiagram(s)) { if (source.getSize() == 2) { assert false; sys = buildEmptyError(); @@ -130,7 +130,7 @@ final public class PSystemSingleBuilder { systemFactory.reset(); while (hasNext()) { final String s = next(); - if (BlockUmlBuilder.isArobaseEnduml(s)) { + if (StartUtils.isArobaseEndDiagram(s)) { final String err = ((AbstractUmlSystemCommandFactory) systemFactory).checkFinalError(); if (err != null) { sys = buildEmptyError(err); @@ -201,7 +201,7 @@ final public class PSystemSingleBuilder { lines.add(init); while (hasNext()) { final String s = next(); - if (BlockUmlBuilder.isArobaseEnduml(s)) { + if (StartUtils.isArobaseEndDiagram(s)) { return false; } lines.add(s); diff --git a/src/net/sourceforge/plantuml/SkinParam.java b/src/net/sourceforge/plantuml/SkinParam.java index 3b9b243f5..1388bb693 100644 --- a/src/net/sourceforge/plantuml/SkinParam.java +++ b/src/net/sourceforge/plantuml/SkinParam.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6228 $ + * Revision $Revision: 6448 $ * */ package net.sourceforge.plantuml; @@ -240,7 +240,7 @@ public class SkinParam implements ISkinParam { public static Collection getPossibleValues() { final Set result = new TreeSet(); result.add("Monochrome"); - result.add("BackgroundColor"); + // result.add("BackgroundColor"); result.add("CircledCharacterRadius"); result.add("ClassAttributeIconSize"); result.add("DefaultFontName"); @@ -254,9 +254,17 @@ public class SkinParam implements ISkinParam { result.add(h + "FontSize"); result.add(h + "FontColor"); } + for (ColorParam p : EnumSet.allOf(ColorParam.class)) { + final String h = capitalize(p.name()); + result.add(h + "Color"); + } return Collections.unmodifiableSet(result); } + private static String capitalize(String name) { + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + public int getDpi() { final String value = getValue("dpi"); if (value != null && value.matches("\\d+")) { @@ -286,8 +294,7 @@ public class SkinParam implements ISkinParam { } return DotSplines.SPLINES; } - - + public GraphvizLayoutStrategy getStrategy() { final String value = getValue("layout"); if ("neato".equalsIgnoreCase(value)) { @@ -305,5 +312,4 @@ public class SkinParam implements ISkinParam { return GraphvizLayoutStrategy.DOT; } - } diff --git a/src/net/sourceforge/plantuml/SourceFileReader.java b/src/net/sourceforge/plantuml/SourceFileReader.java index 8328e042e..48805214c 100644 --- a/src/net/sourceforge/plantuml/SourceFileReader.java +++ b/src/net/sourceforge/plantuml/SourceFileReader.java @@ -106,7 +106,7 @@ public class SourceFileReader { final File suggested = new File(outputDirectory, newName); suggested.getParentFile().mkdirs(); - for (File f : blockUml.getSystem().createFiles(suggested, fileFormatOption)) { + for (File f : blockUml.getSystem().exportDiagrams(suggested, fileFormatOption)) { final String desc = "[" + file.getName() + "] " + blockUml.getSystem().getDescription(); final GeneratedImage generatedImage = new GeneratedImage(f, desc); result.add(generatedImage); diff --git a/src/net/sourceforge/plantuml/SourceStringReader.java b/src/net/sourceforge/plantuml/SourceStringReader.java index f09c77e87..6d247ff0a 100644 --- a/src/net/sourceforge/plantuml/SourceStringReader.java +++ b/src/net/sourceforge/plantuml/SourceStringReader.java @@ -92,7 +92,7 @@ public class SourceStringReader { final PSystem system = b.getSystem(); final int nbInSystem = system.getNbImages(); if (numImage < nbInSystem) { - system.createFile(os, numImage, fileFormatOption); + system.exportDiagram(os, null, numImage, fileFormatOption); return system.getDescription(); } numImage -= nbInSystem; diff --git a/src/net/sourceforge/plantuml/StartUtils.java b/src/net/sourceforge/plantuml/StartUtils.java new file mode 100644 index 000000000..a856096e7 --- /dev/null +++ b/src/net/sourceforge/plantuml/StartUtils.java @@ -0,0 +1,49 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6110 $ + * + */ +package net.sourceforge.plantuml; + + +public class StartUtils { + + public static boolean isArobaseStartDiagram(String s) { + s = s.trim(); + return s.startsWith("@start"); + } + + public static boolean isArobaseEndDiagram(String s) { + s = s.trim(); + return s.startsWith("@end"); + } + +} diff --git a/src/net/sourceforge/plantuml/UmlDiagram.java b/src/net/sourceforge/plantuml/UmlDiagram.java index 0f1da7689..bca870381 100644 --- a/src/net/sourceforge/plantuml/UmlDiagram.java +++ b/src/net/sourceforge/plantuml/UmlDiagram.java @@ -28,15 +28,35 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6097 $ + * Revision $Revision: 6382 $ * */ package net.sourceforge.plantuml; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Hashtable; import java.util.List; +import net.sourceforge.plantuml.code.Compression; +import net.sourceforge.plantuml.code.CompressionZlib; import net.sourceforge.plantuml.graphic.HorizontalAlignement; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; + public abstract class UmlDiagram extends AbstractPSystem implements PSystem { private boolean rotation; @@ -151,4 +171,134 @@ public abstract class UmlDiagram extends AbstractPSystem implements PSystem { this.hideUnlinkedData = hideUnlinkedData; } + final public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormatOption) + throws IOException { + List flashcodes = null; + try { + if ("split".equalsIgnoreCase(getSkinParam().getValue("flashcode")) + && fileFormatOption.getFileFormat() == FileFormat.PNG) { + final String s = getSource().getPlainString(); + flashcodes = exportSplitCompress(s); + } else if ("compress".equalsIgnoreCase(getSkinParam().getValue("flashcode")) + && fileFormatOption.getFileFormat() == FileFormat.PNG) { + final String s = getSource().getPlainString(); + flashcodes = exportFlashcodeCompress(s); + } else if (getSkinParam().getValue("flashcode") != null + && fileFormatOption.getFileFormat() == FileFormat.PNG) { + final String s = getSource().getPlainString(); + flashcodes = exportFlashcodeSimple(s); + } + } catch (WriterException e) { + Log.error("Cannot generate flashcode"); + e.printStackTrace(); + flashcodes = null; + } + exportDiagramInternal(os, cmap, index, fileFormatOption, flashcodes); + } + + protected abstract void exportDiagramInternal(OutputStream os, StringBuilder cmap, int index, + FileFormatOption fileFormatOption, List flashcodes) throws IOException; + + final protected void exportCmap(File suggestedFile, final StringBuilder cmap) throws FileNotFoundException { + final File cmapFile = new File(changeName(suggestedFile.getAbsolutePath())); + PrintWriter pw = null; + try { + pw = new PrintWriter(cmapFile); + pw.print(cmap.toString()); + pw.close(); + } finally { + if (pw != null) { + pw.close(); + } + } + } + + static String changeName(String name) { + return name.replaceAll("(?i)\\.\\w{3}$", ".cmapx"); + } + + + + + private List exportFlashcodeSimple(String s) throws IOException, WriterException { + final QRCodeWriter writer = new QRCodeWriter(); + final int multiple = 1; + final Hashtable hints = new Hashtable(); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); + final BitMatrix bit = writer.encode(s, BarcodeFormat.QR_CODE, multiple); + final BufferedImage im = MatrixToImageWriter.toBufferedImage(bit); + return Arrays.asList(im); + } + + private List exportFlashcodeCompress(String s) throws IOException, WriterException { + final QRCodeWriter writer = new QRCodeWriter(); + final int multiple = 1; + final Hashtable hints = new Hashtable(); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); + + final Compression comp = new CompressionZlib(); + final byte data[] = comp.compress(s.getBytes("UTF-8")); + + // Encoder.DEFAULT_BYTE_MODE_ENCODING + final BitMatrix bit = writer.encode(new String(data, "ISO-8859-1"), BarcodeFormat.QR_CODE, multiple); + final BufferedImage im = MatrixToImageWriter.toBufferedImage(bit); + return Arrays.asList(im); + } + + private List exportSplitCompress(String s) throws IOException, WriterException { + final QRCodeWriter writer = new QRCodeWriter(); + final int multiple = 1; + final Hashtable hints = new Hashtable(); + hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); + + final Compression comp = new CompressionZlib(); + final byte data[] = comp.compress(s.getBytes("UTF-8")); + + final List result = new ArrayList(); + + final List blocs = new ArrayList(); + for (int i = 0; i < 4; i++) { + blocs.add(getSplited(data, i, 4)); + } + + blocs.add(xor(blocs)); + + for (byte d[] : blocs) { + // Encoder.DEFAULT_BYTE_MODE_ENCODING + final BitMatrix bit = writer.encode(new String(d, "ISO-8859-1"), BarcodeFormat.QR_CODE, multiple); + result.add(MatrixToImageWriter.toBufferedImage(bit)); + } + + return Collections.unmodifiableList(result); + } + + static byte[] xor(List blocs) { + final byte result[] = new byte[blocs.get(0).length]; + for (int i = 0; i < result.length; i++) { + result[i] = xor(blocs, i); + } + return result; + } + + static byte xor(List blocs, int nb) { + byte result = 0; + for (byte[] bloc : blocs) { + result = (byte) (result ^ bloc[nb]); + } + return result; + } + + static byte[] getSplited(byte[] data, int n, int total) { + final int size = (data.length + total - 1) / total; + assert size * total >= data.length; + final byte result[] = new byte[size + 1]; + result[0] = (byte) (1 << n); + for (int i = 0; (i < size) && (n * total + i < data.length); i++) { + result[i + 1] = data[n * total + i]; + } + return result; + } + + + } diff --git a/src/net/sourceforge/plantuml/UmlSource.java b/src/net/sourceforge/plantuml/UmlSource.java index 535f52e96..ec9f12186 100644 --- a/src/net/sourceforge/plantuml/UmlSource.java +++ b/src/net/sourceforge/plantuml/UmlSource.java @@ -33,37 +33,27 @@ */ package net.sourceforge.plantuml; -import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; final public class UmlSource { - final private List source = new ArrayList(); - - @Deprecated - public UmlSource(UmlSource start) { - this.source.addAll(start.source); - } + final private List source; public UmlSource(List source) { - this.source.addAll(source); + this.source = Collections.unmodifiableList(new ArrayList(source)); } - @Deprecated - public UmlSource() { + public DiagramType getDiagramType() { + return DiagramType.getTypeFromArobaseStart(source.get(0)); } public Iterator iterator() { return source.iterator(); } - @Deprecated - public void append(String s) { - source.add(s); - } - public String getPlainString() { final StringBuilder sb = new StringBuilder(); for (String s : source) { @@ -83,10 +73,10 @@ final public class UmlSource { public boolean isEmpty() { for (String s : source) { - if (BlockUmlBuilder.isArobaseStartuml(s)) { + if (StartUtils.isArobaseStartDiagram(s)) { continue; } - if (BlockUmlBuilder.isArobaseEnduml(s)) { + if (StartUtils.isArobaseEndDiagram(s)) { continue; } if (s.matches("\\s*'.*")) { diff --git a/src/net/sourceforge/plantuml/Url.java b/src/net/sourceforge/plantuml/Url.java new file mode 100644 index 000000000..a49a5deaf --- /dev/null +++ b/src/net/sourceforge/plantuml/Url.java @@ -0,0 +1,63 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6396 $ + * + */ +package net.sourceforge.plantuml; + +public class Url { + + private final String url; + private final String tooltip; + + public Url(String url, String tooltip) { + this.url = url; + if (tooltip == null) { + this.tooltip = url; + } else { + this.tooltip = tooltip; + } + } + + public final String getUrl() { + return url; + } + + public final String getTooltip() { + return tooltip; + } + + @Override + public String toString() { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/net/sourceforge/plantuml/activitydiagram/command/CommandLinkActivity.java b/src/net/sourceforge/plantuml/activitydiagram/command/CommandLinkActivity.java index 32e011ae7..3a34fe133 100644 --- a/src/net/sourceforge/plantuml/activitydiagram/command/CommandLinkActivity.java +++ b/src/net/sourceforge/plantuml/activitydiagram/command/CommandLinkActivity.java @@ -70,7 +70,7 @@ public class CommandLinkActivity extends SingleLineCommand2 { new RegexLeaf("\\s*"), // new RegexLeaf("BACKCOLOR", "(#\\w+)?"), // new RegexLeaf("\\s*"), // - new RegexLeaf("ARROW", "([=-]+(?:left|right|up|down|le?|ri?|up?|do?)?[=-]*\\>)"), // + new RegexLeaf("ARROW", "([=-]+(?:\\*|left|right|up|down|le?|ri?|up?|do?)?[=-]*\\>)"), // new RegexLeaf("\\s*"), // new RegexLeaf("BRACKET", "(?:\\[([^\\]*]+[^\\]]*)\\])?"), // new RegexLeaf("\\s*"), // @@ -114,9 +114,15 @@ public class CommandLinkActivity extends SingleLineCommand2 { final String linkLabel = arg2.get("BRACKET").get(0); final String arrow = StringUtils.manageArrowForCuca(arg2.get("ARROW").get(0)); - final int lenght = arrow.length() - 1; + int lenght = arrow.length() - 1; + if (arg2.get("ARROW").get(0).contains("*")) { + lenght = 2; + } Link link = new Link(entity1, entity2, new LinkType(LinkDecor.ARROW, LinkDecor.NONE), linkLabel, lenght); + if (arg2.get("ARROW").get(0).contains("*")) { + link.setConstraint(false); + } final Direction direction = StringUtils.getArrowDirection(arg2.get("ARROW").get(0)); if (direction == Direction.LEFT || direction == Direction.UP) { link = link.getInv(); diff --git a/src/net/sourceforge/plantuml/activitydiagram2/ActivityDiagram2.java b/src/net/sourceforge/plantuml/activitydiagram2/ActivityDiagram2.java index 2455c0c4f..1222b5eba 100644 --- a/src/net/sourceforge/plantuml/activitydiagram2/ActivityDiagram2.java +++ b/src/net/sourceforge/plantuml/activitydiagram2/ActivityDiagram2.java @@ -53,12 +53,13 @@ import net.sourceforge.plantuml.cucadiagram.IEntity; import net.sourceforge.plantuml.cucadiagram.Link; import net.sourceforge.plantuml.cucadiagram.LinkDecor; import net.sourceforge.plantuml.cucadiagram.LinkType; +import net.sourceforge.plantuml.cucadiagram.dot.DotMaker; public class ActivityDiagram2 extends CucaDiagram { private Collection waitings = new LinkedHashSet(); private ConditionalContext2 currentContext; - private int futureLength = 2; + // private int futureLength = 2; private String futureLabel = null; private final Collection pendingLabels = new HashSet(); @@ -91,31 +92,46 @@ public class ActivityDiagram2 extends CucaDiagram { } + private final Map bars = new HashMap(); + public void bar(String bar) { + if (bars.containsKey(bar)) { + final IEntity existingBar = bars.get(bar); + for (Iterator it = waitings.iterator(); it.hasNext();) { + final IEntity w = it.next(); + if (w.getType() == EntityType.SYNCHRO_BAR) { + it.remove(); + } + } + afterAdd(existingBar); + return; + } + if (waitings.size() == 0) { // throw new IllegalStateException(bar); } label(bar); final Entity act = createEntity(getAutoCode(), bar, EntityType.SYNCHRO_BAR); + bars.put(bar, act); afterAdd(act); } - private void afterAdd(final IEntity act) { + private void afterAdd(final IEntity dest) { for (IEntity last : this.waitings) { // System.err.println("last=" + last); // System.err.println("act=" + act); - this.addLink(new Link(last, act, new LinkType(LinkDecor.ARROW, LinkDecor.NONE), futureLabel, futureLength)); + this.addLink(new Link(last, dest, new LinkType(LinkDecor.ARROW, LinkDecor.NONE), futureLabel, 2)); futureLabel = null; } for (String p : pendingLabels) { - labels.put(p, act); + labels.put(p, dest); } pendingLabels.clear(); this.waitings.clear(); - this.waitings.add(act); - this.futureLength = 2; + this.waitings.add(dest); + // this.futureLength = 2; } public IEntity getLastEntityConsulted() { @@ -137,22 +153,25 @@ public class ActivityDiagram2 extends CucaDiagram { } public void startIf(String test, String when) { - final IEntity br = createEntity(getAutoCode(), "", EntityType.BRANCH); + final IEntity br = createEntity(getAutoCode(), test, EntityType.BRANCH); + if (DotMaker.MODE_BRANCHE_CLUSTER) { + test = null; + } currentContext = new ConditionalContext2(currentContext, br, Direction.DOWN, when); for (IEntity last : this.waitings) { - if (test == null) { - // this.addLink(new Link(last, br, new LinkType(LinkDecor.ARROW, - // LinkDecor.NONE), test, futureLength)); - throw new IllegalArgumentException(); - } else { - this.addLink(new Link(last, br, new LinkType(LinkDecor.ARROW, LinkDecor.NONE), this.futureLabel, - futureLength, null, test, getLabeldistance(), getLabelangle())); - } + // if (test == null) { + // // this.addLink(new Link(last, br, new LinkType(LinkDecor.ARROW, + // // LinkDecor.NONE), test, futureLength)); + // throw new IllegalArgumentException(); + // } else { + this.addLink(new Link(last, br, new LinkType(LinkDecor.ARROW, LinkDecor.NONE), this.futureLabel, 2, null, + test, getLabeldistance(), getLabelangle())); + // } test = null; } this.waitings.clear(); this.waitings.add(br); - this.futureLength = 2; + // this.futureLength = 2; this.futureLabel = when; } @@ -161,15 +180,15 @@ public class ActivityDiagram2 extends CucaDiagram { // } public void endif() { - final boolean hasElse = currentContext.isHasElse(); -// System.err.println("CALL endif hasElse " + hasElse); + // final boolean hasElse = currentContext.isHasElse(); + // System.err.println("CALL endif hasElse " + hasElse); this.waitings.addAll(currentContext.getPendings()); currentContext = currentContext.getParent(); // if (currentContext == null) { // System.err.println("after endif " + currentContext); // } else { // System.err.println("after endif " + currentContext.getPendings()); - // } + // } } public void else2(String when) { @@ -187,7 +206,10 @@ public class ActivityDiagram2 extends CucaDiagram { if (pending.getLinkLabel() != null) { this.futureLabel = pending.getLinkLabel(); } + final List olds = new ArrayList(waitings); + waitings.clear(); waitings.add(pending.getEntityFrom()); + waitings.addAll(olds); it.remove(); } } @@ -202,10 +224,16 @@ public class ActivityDiagram2 extends CucaDiagram { if (dest == null) { this.pendingLinks.add(new PendingLink(last, gotoLabel, this.futureLabel)); } else { - this.addLink(new Link(last, dest, new LinkType(LinkDecor.ARROW, LinkDecor.NONE), this.futureLabel, - this.futureLength)); + // final Link link = new Link(last, dest, new LinkType(LinkDecor.ARROW, LinkDecor.NONE), + // this.futureLabel, + // this.futureLength); + final Link link = new Link(last, dest, new LinkType(LinkDecor.ARROW, LinkDecor.NONE), this.futureLabel, + 2); + link.setConstraint(false); + this.addLink(link); } } + this.futureLabel = null; // System.err.println("Avant fin goto, waitings=" + waitings); this.waitings.clear(); // currentContext.clearPendingsButFirst(); diff --git a/src/net/sourceforge/plantuml/activitydiagram2/ConditionalContext2.java b/src/net/sourceforge/plantuml/activitydiagram2/ConditionalContext2.java index 397e1b873..7fd51bd90 100644 --- a/src/net/sourceforge/plantuml/activitydiagram2/ConditionalContext2.java +++ b/src/net/sourceforge/plantuml/activitydiagram2/ConditionalContext2.java @@ -78,7 +78,7 @@ public class ConditionalContext2 { } public void clearPendingsButFirst() { - System.err.println("ConditionalContext2::clearPendingsButFirst"); + //System.err.println("ConditionalContext2::clearPendingsButFirst"); this.pendings.clear(); pendings.add(branch); } @@ -90,7 +90,7 @@ public class ConditionalContext2 { throw new IllegalStateException(); } this.hasElse = true; - System.err.println("pend=" + pendings); + //System.err.println("pend=" + pendings); if (pendings.size() == 0) { throw new IllegalStateException(); } diff --git a/src/net/sourceforge/plantuml/activitydiagram2/PendingLink.java b/src/net/sourceforge/plantuml/activitydiagram2/PendingLink.java index 4fa8cfe41..8b77ce61b 100644 --- a/src/net/sourceforge/plantuml/activitydiagram2/PendingLink.java +++ b/src/net/sourceforge/plantuml/activitydiagram2/PendingLink.java @@ -59,4 +59,9 @@ public class PendingLink { return linkLabel; } + @Override + public String toString() { + return entityFrom + " -> " + gotoLabel + " " + linkLabel; + } + } diff --git a/src/net/sourceforge/plantuml/activitydiagram2/command/CommandNewActivity2.java b/src/net/sourceforge/plantuml/activitydiagram2/command/CommandNewActivity2.java index 29acecf2a..c5820d995 100644 --- a/src/net/sourceforge/plantuml/activitydiagram2/command/CommandNewActivity2.java +++ b/src/net/sourceforge/plantuml/activitydiagram2/command/CommandNewActivity2.java @@ -42,7 +42,7 @@ import net.sourceforge.plantuml.command.SingleLineCommand; public class CommandNewActivity2 extends SingleLineCommand { public CommandNewActivity2(ActivityDiagram2 diagram) { - super(diagram, "(?i)^[\"<](.+)[\">]$"); + super(diagram, "(?i)^\\s*[-*]\\s*([^\"\\s].*)$"); } @Override diff --git a/src/net/sourceforge/plantuml/activitydiagram2/command/CommandNewMultilinesActivity2.java b/src/net/sourceforge/plantuml/activitydiagram2/command/CommandNewMultilinesActivity2.java index a88039a16..33e9eea79 100644 --- a/src/net/sourceforge/plantuml/activitydiagram2/command/CommandNewMultilinesActivity2.java +++ b/src/net/sourceforge/plantuml/activitydiagram2/command/CommandNewMultilinesActivity2.java @@ -43,7 +43,7 @@ import net.sourceforge.plantuml.command.CommandMultilines; public class CommandNewMultilinesActivity2 extends CommandMultilines { public CommandNewMultilinesActivity2(final ActivityDiagram2 system) { - super(system, "(?i)^[\"<].*$", "(?i)^.*[\">]$"); + super(system, "(?i)^\\s*[-*]\\s*\"\\s*.*$", "(?i)^.*\\s*\"\\s*$"); } public final CommandExecutionResult execute(List lines) { @@ -55,6 +55,12 @@ public class CommandNewMultilinesActivity2 extends CommandMultilines { + enum Mode { + EXTENDS, IMPLEMENTS + }; + public CommandCreateEntityClass2(ClassDiagram diagram) { super(diagram, getRegexConcat()); } private static RegexConcat getRegexConcat() { - return new RegexConcat(new RegexLeaf("^"), - new RegexLeaf("TYPE", "(interface|enum|abstract\\s+class|abstract|class)\\s+"), - new RegexOr( - new RegexLeaf("NAME1", "(?:\"([^\"]+)\"\\s+as\\s+)?(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*)"), - new RegexLeaf("NAME2", "(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*)\\s+as\\s+\"([^\"]+)\""), - new RegexLeaf("NAME3", "\"([^\"]+)\"")), - new RegexLeaf("STEREO", "(?:\\s*([\\<\\[]{2}.*[\\>\\]]{2}))?"), + return new RegexConcat(new RegexLeaf("^"), // + new RegexLeaf("TYPE",// + "(interface|enum|abstract\\s+class|abstract|class)\\s+"), // + new RegexOr(new RegexLeaf("NAME1", + "(?:\"([^\"]+)\"\\s+as\\s+)?(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*)"), // + new RegexLeaf("NAME2", "(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*)\\s+as\\s+\"([^\"]+)\""), // + new RegexLeaf("NAME3", "\"([^\"]+)\"")), // + new RegexLeaf("STEREO", "(?:\\s*([\\<\\[]{2}.*[\\>\\]]{2}))?"), // + new RegexLeaf("EXTENDS", "(\\s+(extends|implements)\\s+(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*))?"), // new RegexLeaf("$")); } @@ -91,9 +101,34 @@ public class CommandCreateEntityClass2 extends SingleLineCommand2 entity.setStereotype(new Stereotype(stereotype, getSystem().getSkinParam().getCircledCharacterRadius(), getSystem().getSkinParam().getFont(FontParam.CIRCLED_CHARACTER, null))); } + + manageExtends(getSystem(), arg, entity); + return CommandExecutionResult.ok(); } + public static void manageExtends(ClassDiagram system, Map arg, final Entity entity) { + if (arg.get("EXTENDS").get(1) != null) { + final Mode mode = arg.get("EXTENDS").get(1).equalsIgnoreCase("extends") ? Mode.EXTENDS : Mode.IMPLEMENTS; + final String other = arg.get("EXTENDS").get(2); + EntityType type2 = EntityType.CLASS; + if (mode == Mode.IMPLEMENTS) { + type2 = EntityType.INTERFACE; + } + if (mode == Mode.EXTENDS && entity.getType() == EntityType.INTERFACE) { + type2 = EntityType.INTERFACE; + } + final IEntity cl2 = system.getOrCreateClass(other, type2); + LinkType typeLink = new LinkType(LinkDecor.NONE, LinkDecor.EXTENDS); + if (type2 == EntityType.INTERFACE && entity.getType() != EntityType.INTERFACE) { + typeLink = typeLink.getDashed(); + } + final Link link = new Link(cl2, entity, typeLink, null, 2, null, null, system.getLabeldistance(), + system.getLabelangle()); + system.addLink(link); + } + } + // @Override // protected CommandExecutionResult executeArg(List arg) { // final String arg0 = arg.get(0).toUpperCase(); diff --git a/src/net/sourceforge/plantuml/classdiagram/command/CommandCreateEntityClassMultilines2.java b/src/net/sourceforge/plantuml/classdiagram/command/CommandCreateEntityClassMultilines2.java index a8bd14951..54b7bb3e0 100644 --- a/src/net/sourceforge/plantuml/classdiagram/command/CommandCreateEntityClassMultilines2.java +++ b/src/net/sourceforge/plantuml/classdiagram/command/CommandCreateEntityClassMultilines2.java @@ -55,19 +55,19 @@ public class CommandCreateEntityClassMultilines2 extends CommandMultilines2\\]]{2}))?"), + return new RegexConcat(new RegexLeaf("^"), // + new RegexLeaf("TYPE", "(interface|enum|abstract\\s+class|abstract|class)\\s+"), // + new RegexOr( // + new RegexLeaf("NAME1", "(?:\"([^\"]+)\"\\s+as\\s+)?(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*)"), // + new RegexLeaf("NAME2", "(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*)\\s+as\\s+\"([^\"]+)\""), // + new RegexLeaf("NAME3", "\"([^\"]+)\"")), // + new RegexLeaf("STEREO", "(?:\\s*([\\<\\[]{2}.*[\\>\\]]{2}))?"), // + new RegexLeaf("EXTENDS", "(\\s+(extends|implements)\\s+(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*))?"), // new RegexLeaf("\\s*\\{\\s*$")); } - public CommandExecutionResult execute(List lines) { StringUtils.trim(lines, true); final Map line0 = getStartingPattern().matcher(lines.get(0).trim()); @@ -82,11 +82,14 @@ public class CommandCreateEntityClassMultilines2 extends CommandMultilines2 arg) { - + final EntityType type = EntityType.getEntityType(arg.get("TYPE").get(0).toUpperCase()); final String code; final String display; diff --git a/src/net/sourceforge/plantuml/classdiagram/command/CommandLinkClass2.java b/src/net/sourceforge/plantuml/classdiagram/command/CommandLinkClass2.java index 7f63db8f7..dafb02342 100644 --- a/src/net/sourceforge/plantuml/classdiagram/command/CommandLinkClass2.java +++ b/src/net/sourceforge/plantuml/classdiagram/command/CommandLinkClass2.java @@ -72,8 +72,9 @@ final public class CommandLinkClass2 extends SingleLineCommand2*+]|\\|[>\\]])?)"), + new RegexOr( + new RegexLeaf("LEFT_TO_RIGHT", + "(([-=.]+)(?:(left|right|up|down|le?|ri?|up?|do?)(?=[-=.]))?([-=.]*)(o +|[\\]>*+]|\\|[>\\]])?)"), new RegexLeaf("RIGHT_TO_LEFT", "(( +o|[\\[<*+]|[<\\[]\\|)?([-=.]*)(left|right|up|down|le?|ri?|up?|do?)?([-=.]+))"), new RegexLeaf("NAV_AGREG_OR_COMPO_INV", @@ -88,7 +89,11 @@ final public class CommandLinkClass2 extends SingleLineCommand2\\>)?"), new RegexLeaf("COUPLE2", "\\(\\s*(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*)\\s*,\\s*(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*)\\s*\\)")), - new RegexLeaf("\\s*"), new RegexLeaf("LABEL_LINK", "(?::\\s*([^\"]+))?$")); + // new RegexLeaf("\\s*"), new RegexLeaf("LABEL_LINK", + // "(?::\\s*([^\"]+))?$")); + new RegexLeaf("\\s*"), new RegexOr(null, true, new RegexLeaf("LABEL_LINK", ":\\s*([^\"]+)"), + new RegexLeaf("LABEL_LINK_XT", ":\\s*(\"[^\"]*\")?\\s*([^\"]*)\\s*(\"[^\"]*\")?")), + new RegexLeaf("$")); } private static String optionalKeywords(UmlDiagramType type) { @@ -160,8 +165,22 @@ final public class CommandLinkClass2 extends SingleLineCommand2 arg) { - final String ent1 = arg.get("ENT1").get(1); - final String ent2 = arg.get("ENT2").get(1); + final String ent1 = StringUtils.eventuallyRemoveStartingAndEndingDoubleQuote(arg.get("ENT1").get(1)); + final String ent2 = StringUtils.eventuallyRemoveStartingAndEndingDoubleQuote(arg.get("ENT2").get(1)); final Group cl1 = getSystem().getGroup(ent1); final Group cl2 = getSystem().getGroup(ent2); @@ -210,9 +243,11 @@ final public class CommandLinkClass2 extends SingleLineCommand2 { + + public CommandLinkLollipop2(AbstractClassOrObjectDiagram diagram) { + super(diagram, getRegexConcat(diagram.getUmlDiagramType())); + } + + static RegexConcat getRegexConcat(UmlDiagramType umlDiagramType) { + return new RegexConcat(new RegexLeaf("HEADER", "^(?:@([\\d.]+)\\s+)?"), // + new RegexLeaf("ENT1", "(?:" + optionalKeywords(umlDiagramType) + "\\s+)?" + + "(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*|\"[^\"]+\")\\s*(\\<\\<.*\\>\\>)?"), // + new RegexLeaf("\\s*"), // + new RegexLeaf("FIRST_LABEL", "(?:\"([^\"]+)\")?"), // + new RegexLeaf("\\s*"), // + new RegexOr(new RegexLeaf("LOL_THEN_ENT", "\\(\\)([-=.]+)"), // + new RegexLeaf("ENT_THEN_LOL", "([-=.]+)\\(\\)")), // + new RegexLeaf("\\s*"), // + new RegexLeaf("SECOND_LABEL", "(?:\"([^\"]+)\")?"), // + new RegexLeaf("\\s*"), // + new RegexLeaf("ENT2", "(?:" + optionalKeywords(umlDiagramType) + "\\s+)?" + + "(\\.?[\\p{L}0-9_]+(?:\\.[\\p{L}0-9_]+)*|\"[^\"]+\")\\s*(\\<\\<.*\\>\\>)?"), // + new RegexLeaf("\\s*"), // + new RegexOr(null, true, // + new RegexLeaf("LABEL_LINK", ":\\s*([^\"]+)"), // + new RegexLeaf("LABEL_LINK_XT", ":\\s*(\"[^\"]*\")?\\s*([^\"]*)\\s*(\"[^\"]*\")?")), // + new RegexLeaf("$")); + } + + private static String optionalKeywords(UmlDiagramType type) { + if (type == UmlDiagramType.CLASS) { + return "(interface|enum|abstract\\s+class|abstract|class)"; + } + if (type == UmlDiagramType.OBJECT) { + return "(object)"; + } + throw new IllegalArgumentException(); + } + + @Override + protected CommandExecutionResult executeArg(Map arg) { + + final String ent1 = arg.get("ENT1").get(1); + final String ent2 = arg.get("ENT2").get(1); + + final Entity cl1; + final Entity cl2; + final Entity normalEntity; + + final String suffix = "lol" + UniqueSequence.getValue(); + if (arg.get("LOL_THEN_ENT").get(0) != null) { + cl2 = (Entity) getSystem().getOrCreateClass(ent2); + cl1 = getSystem().createEntity(cl2.getCode() + suffix, ent1, EntityType.LOLLIPOP); + normalEntity = cl2; + } else { + assert arg.get("ENT_THEN_LOL").get(0) != null; + cl1 = (Entity) getSystem().getOrCreateClass(ent1); + cl2 = getSystem().createEntity(cl1.getCode() + suffix, ent2, EntityType.LOLLIPOP); + normalEntity = cl1; + } + + final LinkType linkType = getLinkType(arg); + final String queue = getQueue(arg); + + int length = queue.length(); + if (length == 1 && getSystem().getNbOfHozizontalLollipop(normalEntity) > 1) { + length++; + } + + String firstLabel = arg.get("FIRST_LABEL").get(0); + String secondLabel = arg.get("SECOND_LABEL").get(0); + + String labelLink = null; + + if (arg.get("LABEL_LINK").get(0) != null) { + labelLink = arg.get("LABEL_LINK").get(0); + } else if (arg.get("LABEL_LINK_XT").get(0) != null || arg.get("LABEL_LINK_XT").get(1) != null + || arg.get("LABEL_LINK_XT").get(2) != null) { + labelLink = arg.get("LABEL_LINK_XT").get(1); + firstLabel = merge(firstLabel, arg.get("LABEL_LINK_XT").get(0)); + secondLabel = merge(arg.get("LABEL_LINK_XT").get(2), secondLabel); + } + + final Link link = new Link(cl1, cl2, linkType, labelLink, length, firstLabel, secondLabel, getSystem() + .getLabeldistance(), getSystem().getLabelangle()); + getSystem().resetPragmaLabel(); + addLink(link, arg.get("HEADER").get(0)); + + return CommandExecutionResult.ok(); + } + + private String merge(String a, String b) { + if (a == null && b == null) { + return null; + } + if (a == null && b != null) { + return StringUtils.eventuallyRemoveStartingAndEndingDoubleQuote(b); + } + if (b == null && a != null) { + return StringUtils.eventuallyRemoveStartingAndEndingDoubleQuote(a); + } + return StringUtils.eventuallyRemoveStartingAndEndingDoubleQuote(a) + "\\n" + + StringUtils.eventuallyRemoveStartingAndEndingDoubleQuote(b); + } + + private void addLink(Link link, String weight) { + getSystem().addLink(link); + if (weight == null) { + final LinkType type = link.getType(); + // --|> highest + // --*, -->, --o normal + // ..*, ..>, ..o lowest + // if (type.isDashed() == false) { + // if (type.contains(LinkDecor.EXTENDS)) { + // link.setWeight(3); + // } + // if (type.contains(LinkDecor.ARROW) || + // type.contains(LinkDecor.COMPOSITION) + // || type.contains(LinkDecor.AGREGATION)) { + // link.setWeight(2); + // } + // } + } else { + link.setWeight(Double.parseDouble(weight)); + } + } + + private LinkType getLinkType(Map arg) { + return new LinkType(LinkDecor.NONE, LinkDecor.NONE); + } + + private String getQueue(Map arg) { + if (arg.get("LOL_THEN_ENT").get(0) != null) { + return arg.get("LOL_THEN_ENT").get(0).trim(); + } + if (arg.get("ENT_THEN_LOL").get(0) != null) { + return arg.get("ENT_THEN_LOL").get(0).trim(); + } + throw new IllegalArgumentException(); + } + +} diff --git a/src/net/sourceforge/plantuml/classdiagram/command/CommandUrl.java b/src/net/sourceforge/plantuml/classdiagram/command/CommandUrl.java index 2aecc8495..5c07fcb29 100644 --- a/src/net/sourceforge/plantuml/classdiagram/command/CommandUrl.java +++ b/src/net/sourceforge/plantuml/classdiagram/command/CommandUrl.java @@ -35,23 +35,32 @@ package net.sourceforge.plantuml.classdiagram.command; import java.util.List; -import net.sourceforge.plantuml.classdiagram.ClassDiagram; +import net.sourceforge.plantuml.Url; +import net.sourceforge.plantuml.classdiagram.AbstractEntityDiagram; import net.sourceforge.plantuml.command.CommandExecutionResult; import net.sourceforge.plantuml.command.SingleLineCommand; import net.sourceforge.plantuml.cucadiagram.Entity; -public class CommandUrl extends SingleLineCommand { +public class CommandUrl extends SingleLineCommand { - public CommandUrl(ClassDiagram classDiagram) { - super(classDiagram, "(?i)^url\\s*(?:of|for)?\\s+([\\p{L}0-9_.]+|\"[^\"]+\")\\s+(?:is)?\\s*\\[\\[(.*)\\]\\]$"); + public CommandUrl(AbstractEntityDiagram diagram) { + super(diagram, + "(?i)^url\\s*(?:of|for)?\\s+([\\p{L}0-9_.]+|\"[^\"]+\")\\s+(?:is)?\\s*\\[\\[([^|]*)(?:\\|([^|]*))?\\]\\]$"); } @Override protected CommandExecutionResult executeArg(List arg) { final String code = arg.get(0); - final String url = arg.get(1); + String url = arg.get(1); + final String title = arg.get(2); final Entity entity = (Entity) getSystem().getOrCreateClass(code); - entity.setUrl(url); + if (url.startsWith("http:") == false && url.startsWith("https:") == false) { + final String top = getSystem().getSkinParam().getValue("topurl"); + if (top != null) { + url = top + url; + } + } + entity.setUrl(new Url(url, title)); return CommandExecutionResult.ok(); } diff --git a/src/net/sourceforge/plantuml/code/ArobaseStringCompressor.java b/src/net/sourceforge/plantuml/code/ArobaseStringCompressor.java index 702734f16..6f1ae4774 100644 --- a/src/net/sourceforge/plantuml/code/ArobaseStringCompressor.java +++ b/src/net/sourceforge/plantuml/code/ArobaseStringCompressor.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5980 $ + * Revision $Revision: 6362 $ * */ package net.sourceforge.plantuml.code; @@ -50,7 +50,16 @@ public class ArobaseStringCompressor implements StringCompressor { } public String decompress(String s) throws IOException { - return clean(s); + String result = clean(s); + if (result.startsWith("@start")) { + return result; + } + result = "@startuml\n" + result; + if (result.endsWith("\n") == false) { + result += "\n"; + } + result += "@enduml"; + return result; } private String clean(String s) { @@ -70,5 +79,4 @@ public class ArobaseStringCompressor implements StringCompressor { return s; } - } \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/command/AbstractCommandMultilinesNoteEntity.java b/src/net/sourceforge/plantuml/command/AbstractCommandMultilinesNoteEntity.java index f92a4d12e..d5fb44f21 100644 --- a/src/net/sourceforge/plantuml/command/AbstractCommandMultilinesNoteEntity.java +++ b/src/net/sourceforge/plantuml/command/AbstractCommandMultilinesNoteEntity.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5751 $ + * Revision $Revision: 6486 $ * */ package net.sourceforge.plantuml.command; @@ -37,6 +37,7 @@ import java.util.List; import net.sourceforge.plantuml.StringUtils; import net.sourceforge.plantuml.UniqueSequence; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.classdiagram.AbstractEntityDiagram; import net.sourceforge.plantuml.cucadiagram.Entity; import net.sourceforge.plantuml.cucadiagram.EntityType; @@ -44,6 +45,7 @@ import net.sourceforge.plantuml.cucadiagram.IEntity; import net.sourceforge.plantuml.cucadiagram.Link; import net.sourceforge.plantuml.cucadiagram.LinkDecor; import net.sourceforge.plantuml.cucadiagram.LinkType; +import net.sourceforge.plantuml.sequencediagram.Note; public abstract class AbstractCommandMultilinesNoteEntity extends CommandMultilines { @@ -58,11 +60,20 @@ public abstract class AbstractCommandMultilinesNoteEntity extends CommandMultili final IEntity cl1 = getSystem().getOrCreateClass(line0.get(1)); - final List strings = StringUtils.removeEmptyColumns(lines.subList(1, lines.size() - 1)); + List strings = StringUtils.removeEmptyColumns(lines.subList(1, lines.size() - 1)); + Url url = null; + if (strings.size() > 0) { + url = Note.extractUrl(strings.get(0)); + } + if (url != null) { + strings = strings.subList(1, strings.size()); + } + final String s = StringUtils.getMergedLines(strings); final Entity note = getSystem().createEntity("GMN" + UniqueSequence.getValue(), s, EntityType.NOTE); note.setSpecificBackcolor(line0.get(2)); + note.setUrl(url); final Position position = Position.valueOf(pos.toUpperCase()).withRankdir(getSystem().getRankdir()); final Link link; diff --git a/src/net/sourceforge/plantuml/command/AbstractUmlSystemCommandFactory.java b/src/net/sourceforge/plantuml/command/AbstractUmlSystemCommandFactory.java index 6e61fba1f..ed172a0cb 100644 --- a/src/net/sourceforge/plantuml/command/AbstractUmlSystemCommandFactory.java +++ b/src/net/sourceforge/plantuml/command/AbstractUmlSystemCommandFactory.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6214 $ + * Revision $Revision: 6423 $ * */ package net.sourceforge.plantuml.command; @@ -38,11 +38,19 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import net.sourceforge.plantuml.DiagramType; import net.sourceforge.plantuml.UmlDiagram; public abstract class AbstractUmlSystemCommandFactory implements PSystemCommandFactory { + private final DiagramType type; + protected AbstractUmlSystemCommandFactory() { + this(DiagramType.UML); + } + + protected AbstractUmlSystemCommandFactory(DiagramType type) { + this.type = type; reset(); } @@ -120,4 +128,8 @@ public abstract class AbstractUmlSystemCommandFactory implements PSystemCommandF } + final public DiagramType getDiagramType() { + return type; + } + } diff --git a/src/net/sourceforge/plantuml/command/CommandComment.java b/src/net/sourceforge/plantuml/command/CommandComment.java index 7588bc48f..d4b1b46b9 100644 --- a/src/net/sourceforge/plantuml/command/CommandComment.java +++ b/src/net/sourceforge/plantuml/command/CommandComment.java @@ -35,11 +35,11 @@ package net.sourceforge.plantuml.command; import java.util.List; -import net.sourceforge.plantuml.UmlDiagram; +import net.sourceforge.plantuml.PSystem; -public class CommandComment extends SingleLineCommand { +public class CommandComment extends SingleLineCommand { - public CommandComment(UmlDiagram diagram) { + public CommandComment(PSystem diagram) { super(diagram, "(?i)^\\s*('.*||/'.*'/\\s*)$"); } diff --git a/src/net/sourceforge/plantuml/command/CommandNope.java b/src/net/sourceforge/plantuml/command/CommandNope.java index 25f338d07..cd0287456 100644 --- a/src/net/sourceforge/plantuml/command/CommandNope.java +++ b/src/net/sourceforge/plantuml/command/CommandNope.java @@ -35,11 +35,11 @@ package net.sourceforge.plantuml.command; import java.util.List; -import net.sourceforge.plantuml.UmlDiagram; +import net.sourceforge.plantuml.PSystem; -public class CommandNope extends SingleLineCommand { +public class CommandNope extends SingleLineCommand { - public CommandNope(UmlDiagram diagram) { + public CommandNope(PSystem diagram) { super(diagram, "(?i)^\\s*$"); } diff --git a/src/net/sourceforge/plantuml/command/CommandSkinParam.java b/src/net/sourceforge/plantuml/command/CommandSkinParam.java index 3c9341398..0c6b3d1bc 100644 --- a/src/net/sourceforge/plantuml/command/CommandSkinParam.java +++ b/src/net/sourceforge/plantuml/command/CommandSkinParam.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5769 $ + * Revision $Revision: 6448 $ * */ package net.sourceforge.plantuml.command; @@ -40,12 +40,13 @@ import net.sourceforge.plantuml.UmlDiagram; public class CommandSkinParam extends SingleLineCommand { public CommandSkinParam(UmlDiagram diagram) { - super(diagram, "(?i)^skinparam\\s+([\\w.]*(?:\\<\\<.*\\>\\>)?[\\w.]*)\\s+([^{}]*)$"); + super(diagram, "(?i)^(skinparam|skinparamlocked)\\s+([\\w.]*(?:\\<\\<.*\\>\\>)?[\\w.]*)\\s+([^{}]*)$"); } @Override protected CommandExecutionResult executeArg(List arg) { - getSystem().setParam(arg.get(0), arg.get(1)); + boolean locked = arg.get(0).endsWith("locked"); + getSystem().setParam(arg.get(1), arg.get(2)); return CommandExecutionResult.ok(); } diff --git a/src/net/sourceforge/plantuml/command/SingleLineCommand.java b/src/net/sourceforge/plantuml/command/SingleLineCommand.java index 599e87926..23ec4f09a 100644 --- a/src/net/sourceforge/plantuml/command/SingleLineCommand.java +++ b/src/net/sourceforge/plantuml/command/SingleLineCommand.java @@ -28,14 +28,12 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6186 $ + * Revision $Revision: 6280 $ * */ package net.sourceforge.plantuml.command; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -44,8 +42,6 @@ import net.sourceforge.plantuml.StringUtils; public abstract class SingleLineCommand implements Command { - private static final Set printed = new HashSet(); - private final S system; private final Pattern pattern; @@ -60,10 +56,6 @@ public abstract class SingleLineCommand implements Command { throw new IllegalArgumentException("Bad pattern " + pattern); } - if (printed.add(pattern) == true) { - // System.out.println(pattern); - } - this.system = system; this.pattern = Pattern.compile(pattern); } diff --git a/src/net/sourceforge/plantuml/componentdiagram/ComponentDiagramFactory.java b/src/net/sourceforge/plantuml/componentdiagram/ComponentDiagramFactory.java index 071dc9c60..a38c60c8f 100644 --- a/src/net/sourceforge/plantuml/componentdiagram/ComponentDiagramFactory.java +++ b/src/net/sourceforge/plantuml/componentdiagram/ComponentDiagramFactory.java @@ -28,11 +28,12 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5463 $ + * Revision $Revision: 6396 $ * */ package net.sourceforge.plantuml.componentdiagram; +import net.sourceforge.plantuml.classdiagram.command.CommandUrl; import net.sourceforge.plantuml.command.AbstractUmlSystemCommandFactory; import net.sourceforge.plantuml.command.CommandCreateNote; import net.sourceforge.plantuml.command.CommandEndPackage; @@ -71,6 +72,7 @@ public class ComponentDiagramFactory extends AbstractUmlSystemCommandFactory { addCommand(new CommandNoteEntity(system)); addCommand(new CommandCreateNote(system)); + addCommand(new CommandUrl(system)); addCommand(new CommandCreateComponent(system)); addCommand(new CommandCreateCircleInterface(system)); addCommand(new CommandCreateActorInComponent(system)); diff --git a/src/net/sourceforge/plantuml/cucadiagram/CucaDiagram.java b/src/net/sourceforge/plantuml/cucadiagram/CucaDiagram.java index a050ebabb..c693899b3 100644 --- a/src/net/sourceforge/plantuml/cucadiagram/CucaDiagram.java +++ b/src/net/sourceforge/plantuml/cucadiagram/CucaDiagram.java @@ -28,15 +28,18 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6229 $ + * Revision $Revision: 6453 $ * */ package net.sourceforge.plantuml.cucadiagram; +import java.awt.image.BufferedImage; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -52,13 +55,13 @@ import java.util.TreeMap; import net.sourceforge.plantuml.FileFormat; import net.sourceforge.plantuml.FileFormatOption; import net.sourceforge.plantuml.Log; -import net.sourceforge.plantuml.OptionFlags; import net.sourceforge.plantuml.UmlDiagram; import net.sourceforge.plantuml.UmlDiagramType; import net.sourceforge.plantuml.cucadiagram.dot.CucaDiagramFileMaker; import net.sourceforge.plantuml.cucadiagram.dot.CucaDiagramFileMakerBeta; import net.sourceforge.plantuml.cucadiagram.dot.CucaDiagramPngMaker3; import net.sourceforge.plantuml.cucadiagram.dot.CucaDiagramTxtMaker; +import net.sourceforge.plantuml.png.PngSplitter; import net.sourceforge.plantuml.skin.VisibilityModifier; import net.sourceforge.plantuml.xmi.CucaDiagramXmiMaker; @@ -272,33 +275,37 @@ public abstract class CucaDiagram extends UmlDiagram implements GroupHierarchy, abstract protected List getDotStrings(); - final public List createFiles(File suggestedFile, FileFormatOption fileFormatOption) throws IOException, - InterruptedException { + // final public List createFiles(File suggestedFile, FileFormatOption + // fileFormatOption) throws IOException, + // InterruptedException { + // + // final FileFormat fileFormat = fileFormatOption.getFileFormat(); + // + // if (fileFormat == FileFormat.ATXT || fileFormat == FileFormat.UTXT) { + // return createFilesTxt(suggestedFile, fileFormat); + // } + // + // if (fileFormat.name().startsWith("XMI")) { + // return createFilesXmi(suggestedFile, fileFormat); + // } + // + // if (OptionFlags.getInstance().useJavaInsteadOfDot()) { + // return createPng2(suggestedFile); + // } + // if (getUmlDiagramType() == UmlDiagramType.COMPOSITE || (BETA && + // getUmlDiagramType() == UmlDiagramType.CLASS)) { + // final CucaDiagramFileMakerBeta maker = new + // CucaDiagramFileMakerBeta(this); + // return maker.createFile(suggestedFile, getDotStrings(), fileFormat); + // } + // final CucaDiagramFileMaker maker = new CucaDiagramFileMaker(this); + // return maker.createFile(suggestedFile, getDotStrings(), + // fileFormatOption); + // } - final FileFormat fileFormat = fileFormatOption.getFileFormat(); - - if (fileFormat == FileFormat.ATXT || fileFormat == FileFormat.UTXT) { - return createFilesTxt(suggestedFile, fileFormat); - } - - if (fileFormat.name().startsWith("XMI")) { - return createFilesXmi(suggestedFile, fileFormat); - } - - if (OptionFlags.getInstance().useJavaInsteadOfDot()) { - return createPng2(suggestedFile); - } - if (getUmlDiagramType() == UmlDiagramType.COMPOSITE || (BETA && getUmlDiagramType() == UmlDiagramType.CLASS)) { - final CucaDiagramFileMakerBeta maker = new CucaDiagramFileMakerBeta(this); - return maker.createFile(suggestedFile, getDotStrings(), fileFormat); - } - final CucaDiagramFileMaker maker = new CucaDiagramFileMaker(this); - return maker.createFile(suggestedFile, getDotStrings(), fileFormatOption); - } - - private List createFilesXmi(File suggestedFile, FileFormat fileFormat) throws IOException { + private void createFilesXmi(OutputStream suggestedFile, FileFormat fileFormat) throws IOException { final CucaDiagramXmiMaker maker = new CucaDiagramXmiMaker(this, fileFormat); - return maker.createFiles(suggestedFile); + maker.createFiles(suggestedFile); } private List createFilesTxt(File suggestedFile, FileFormat fileFormat) throws IOException { @@ -308,13 +315,64 @@ public abstract class CucaDiagram extends UmlDiagram implements GroupHierarchy, public static boolean BETA; - final public void createFile(OutputStream os, int index, FileFormatOption fileFormatOption) throws IOException { + @Override + public List exportDiagrams(File suggestedFile, FileFormatOption fileFormat) throws IOException, + InterruptedException { + if (suggestedFile.exists() && suggestedFile.isDirectory()) { + throw new IllegalArgumentException("File is a directory " + suggestedFile); + } + + final StringBuilder cmap = new StringBuilder(); + OutputStream os = null; + try { + os = new FileOutputStream(suggestedFile); + this.exportDiagram(os, cmap, 0, fileFormat); + } finally { + if (os != null) { + os.close(); + } + } + List result = Arrays.asList(suggestedFile); + + if (this.hasUrl() && cmap.length() > 0) { + exportCmap(suggestedFile, cmap); + } + + if (fileFormat.getFileFormat() == FileFormat.PNG) { + result = new PngSplitter(suggestedFile, this.getHorizontalPages(), this.getVerticalPages(), this + .getMetadata(), this.getDpi(fileFormat)).getFiles(); + } + return result; + + } + + @Override + final protected void exportDiagramInternal(OutputStream os, StringBuilder cmap, int index, + FileFormatOption fileFormatOption, List flashcodes) throws IOException { final FileFormat fileFormat = fileFormatOption.getFileFormat(); if (fileFormat == FileFormat.ATXT || fileFormat == FileFormat.UTXT) { createFilesTxt(os, index, fileFormat); return; } + if (fileFormat.name().startsWith("XMI")) { + createFilesXmi(os, fileFormat); + return; + } + // + // if (OptionFlags.getInstance().useJavaInsteadOfDot()) { + // return createPng2(suggestedFile); + // } + if (getUmlDiagramType() == UmlDiagramType.COMPOSITE || (BETA && getUmlDiagramType() == UmlDiagramType.CLASS)) { + final CucaDiagramFileMakerBeta maker = new CucaDiagramFileMakerBeta(this); + try { + maker.createFile(os, getDotStrings(), fileFormat); + } catch (InterruptedException e) { + throw new IOException(e.toString()); + } + return; + } + if (getUmlDiagramType() == UmlDiagramType.COMPOSITE) { final CucaDiagramFileMakerBeta maker = new CucaDiagramFileMakerBeta(this); try { @@ -325,9 +383,12 @@ public abstract class CucaDiagram extends UmlDiagram implements GroupHierarchy, } return; } - final CucaDiagramFileMaker maker = new CucaDiagramFileMaker(this); + final CucaDiagramFileMaker maker = new CucaDiagramFileMaker(this, flashcodes); try { - maker.createFile(os, getDotStrings(), fileFormatOption); + final String cmapResult = maker.createFile(os, getDotStrings(), fileFormatOption); + if (cmapResult != null && cmap != null) { + cmap.append(cmapResult); + } } catch (InterruptedException e) { Log.error(e.toString()); throw new IOException(e.toString()); diff --git a/src/net/sourceforge/plantuml/cucadiagram/Entity.java b/src/net/sourceforge/plantuml/cucadiagram/Entity.java index 1833f5267..031a4a30b 100644 --- a/src/net/sourceforge/plantuml/cucadiagram/Entity.java +++ b/src/net/sourceforge/plantuml/cucadiagram/Entity.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6169 $ + * Revision $Revision: 6482 $ * */ package net.sourceforge.plantuml.cucadiagram; @@ -41,6 +41,7 @@ import java.util.List; import java.util.Set; import net.sourceforge.plantuml.UniqueSequence; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.cucadiagram.dot.DrawFile; import net.sourceforge.plantuml.graphic.HtmlColor; import net.sourceforge.plantuml.skin.VisibilityModifier; @@ -62,7 +63,7 @@ public class Entity implements IEntity { private Group container; private DrawFile imageFile; - private String url; + private Url url2; private boolean top; @@ -129,7 +130,7 @@ public class Entity implements IEntity { } public List getMethodsToDisplay() { - if (hides==null || hides.size() == 0) { + if (hides == null || hides.size() == 0) { return Collections.unmodifiableList(methods); } final List result = new ArrayList(); @@ -142,7 +143,7 @@ public class Entity implements IEntity { } public List getFieldsToDisplay() { - if (hides==null || hides.size() == 0) { + if (hides == null || hides.size() == 0) { return Collections.unmodifiableList(fields); } final List result = new ArrayList(); @@ -237,13 +238,12 @@ public class Entity implements IEntity { this.specificBackcolor = HtmlColor.getColorIfValid(s); } - public final String getUrl() { - return url; - // return "http://www.google.com"; + public final Url getUrl() { + return url2; } - public final void setUrl(String url) { - this.url = url; + public final void setUrl(Url url) { + this.url2 = url; } @Override @@ -280,4 +280,15 @@ public class Entity implements IEntity { } return null; } + + private boolean nearDecoration = false; + + public final boolean hasNearDecoration() { + return nearDecoration; + } + + public final void setNearDecoration(boolean nearDecoration) { + this.nearDecoration = nearDecoration; + } + } diff --git a/src/net/sourceforge/plantuml/cucadiagram/EntityUtils.java b/src/net/sourceforge/plantuml/cucadiagram/EntityUtils.java index 4411f8c32..fc46589a6 100644 --- a/src/net/sourceforge/plantuml/cucadiagram/EntityUtils.java +++ b/src/net/sourceforge/plantuml/cucadiagram/EntityUtils.java @@ -37,6 +37,7 @@ import java.io.File; import java.io.IOException; import java.util.List; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.cucadiagram.dot.DrawFile; import net.sourceforge.plantuml.graphic.HtmlColor; @@ -75,7 +76,7 @@ public abstract class EntityUtils { return ent.getUid(); } - public String getUrl() { + public Url getUrl() { return ent.getUrl(); } @@ -126,6 +127,14 @@ public abstract class EntityUtils { ent.setTop(top); } + public boolean hasNearDecoration() { + return ent.hasNearDecoration(); + } + + public void setNearDecoration(boolean nearDecoration) { + ent.setNearDecoration(nearDecoration); + } + }; } diff --git a/src/net/sourceforge/plantuml/cucadiagram/IEntity.java b/src/net/sourceforge/plantuml/cucadiagram/IEntity.java index 1af23a863..cf124dd4e 100644 --- a/src/net/sourceforge/plantuml/cucadiagram/IEntity.java +++ b/src/net/sourceforge/plantuml/cucadiagram/IEntity.java @@ -38,6 +38,7 @@ import java.io.IOException; import java.util.List; import net.sourceforge.plantuml.SpecificBackcolorable; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.cucadiagram.dot.DrawFile; public interface IEntity extends Imaged, SpecificBackcolorable { @@ -50,7 +51,7 @@ public interface IEntity extends Imaged, SpecificBackcolorable { public String getUid(); - public String getUrl(); + public Url getUrl(); public List getFieldsToDisplay(); @@ -67,6 +68,9 @@ public interface IEntity extends Imaged, SpecificBackcolorable { public boolean isTop(); public void setTop(boolean top); - + + public boolean hasNearDecoration(); + + public void setNearDecoration(boolean nearDecoration); } diff --git a/src/net/sourceforge/plantuml/cucadiagram/Link.java b/src/net/sourceforge/plantuml/cucadiagram/Link.java index ec47ca9e1..15ca31596 100644 --- a/src/net/sourceforge/plantuml/cucadiagram/Link.java +++ b/src/net/sourceforge/plantuml/cucadiagram/Link.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6152 $ + * Revision $Revision: 6356 $ * */ package net.sourceforge.plantuml.cucadiagram; @@ -68,6 +68,7 @@ public class Link implements Imaged { private final String labelangle; private HtmlColor specificColor; + private boolean constraint = true; public Link(IEntity cl1, IEntity cl2, LinkType type, String label, int length) { this(cl1, cl2, type, label, length, null, null, null, null, null); @@ -96,6 +97,12 @@ public class Link implements Imaged { this.labeldistance = labeldistance; this.labelangle = labelangle; this.specificColor = specificColor; + if (qualifier1 != null) { + cl1.setNearDecoration(true); + } + if (qualifier2 != null) { + cl2.setNearDecoration(true); + } } public Link getInv() { @@ -314,4 +321,12 @@ public class Link implements Imaged { this.specificColor = HtmlColor.getColorIfValid(s); } + public final boolean isConstraint() { + return constraint; + } + + public final void setConstraint(boolean constraint) { + this.constraint = constraint; + } + } diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/BorderMode.java b/src/net/sourceforge/plantuml/cucadiagram/dot/BorderMode.java new file mode 100644 index 000000000..05720bcae --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/BorderMode.java @@ -0,0 +1,44 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + + +enum BorderMode { + + NO_BORDER, + NO_BORDER_CELLSPACING, + BORDER_1_WITH_COLOR, + BORDER_1_WITHOUT_COLOR; + +} diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/CucaDiagramFileMaker.java b/src/net/sourceforge/plantuml/cucadiagram/dot/CucaDiagramFileMaker.java index ac13ac999..d7c4786df 100644 --- a/src/net/sourceforge/plantuml/cucadiagram/dot/CucaDiagramFileMaker.java +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/CucaDiagramFileMaker.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6241 $ + * Revision $Revision: 6474 $ * */ package net.sourceforge.plantuml.cucadiagram.dot; @@ -47,6 +47,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -79,6 +80,7 @@ import net.sourceforge.plantuml.cucadiagram.IEntity; import net.sourceforge.plantuml.cucadiagram.Imaged; import net.sourceforge.plantuml.cucadiagram.Link; import net.sourceforge.plantuml.cucadiagram.Stereotype; +import net.sourceforge.plantuml.eps.EpsGraphics; import net.sourceforge.plantuml.eps.EpsStrategy; import net.sourceforge.plantuml.eps.EpsTitler; import net.sourceforge.plantuml.eps.SvgToEpsConverter; @@ -89,11 +91,11 @@ import net.sourceforge.plantuml.graphic.HtmlColor; import net.sourceforge.plantuml.graphic.StringBounder; import net.sourceforge.plantuml.graphic.StringBounderUtils; import net.sourceforge.plantuml.graphic.VerticalPosition; +import net.sourceforge.plantuml.png.PngFlashcoder; import net.sourceforge.plantuml.png.PngIO; import net.sourceforge.plantuml.png.PngRotation; import net.sourceforge.plantuml.png.PngScaler; import net.sourceforge.plantuml.png.PngSizer; -import net.sourceforge.plantuml.png.PngSplitter; import net.sourceforge.plantuml.png.PngTitler; import net.sourceforge.plantuml.skin.CircleInterface; import net.sourceforge.plantuml.skin.Component; @@ -110,6 +112,7 @@ import net.sourceforge.plantuml.ugraphic.svg.UGraphicSvg; public final class CucaDiagramFileMaker { private final CucaDiagram diagram; + private final List flashcodes; private final StaticFilesMap staticFilesMap; private final Rose rose = new Rose(); @@ -120,9 +123,10 @@ public final class CucaDiagramFileMaker { stringBounder = StringBounderUtils.asStringBounder(builder.getGraphics2D()); } - public CucaDiagramFileMaker(CucaDiagram diagram) throws IOException { + public CucaDiagramFileMaker(CucaDiagram diagram, List flashcodes) throws IOException { HtmlColor.setForceMonochrome(diagram.getSkinParam().isMonochrome()); this.diagram = diagram; + this.flashcodes = flashcodes; if (diagram.getUmlDiagramType() == UmlDiagramType.CLASS || diagram.getUmlDiagramType() == UmlDiagramType.OBJECT) { this.staticFilesMap = new StaticFilesMap(diagram.getSkinParam(), diagram.getDpiFactor(null)); } else { @@ -130,42 +134,6 @@ public final class CucaDiagramFileMaker { } } - static String changeName(String name) { - return name.replaceAll("(?i)\\.png$", ".cmapx"); - } - - public List createFile(File suggested, List dotStrings, FileFormatOption fileFormatOption) - throws IOException, InterruptedException { - - final FileFormat fileFormat = fileFormatOption.getFileFormat(); - OutputStream os = null; - try { - os = new FileOutputStream(suggested); - final String cmap = createFile(os, dotStrings, fileFormatOption); - if (diagram.hasUrl() && fileFormat == FileFormat.PNG) { - final File cmapFile = new File(changeName(suggested.getAbsolutePath())); - final PrintWriter pw = new PrintWriter(cmapFile); - pw.print(cmap); - pw.close(); - } - } finally { - if (os != null) { - os.close(); - } - } - - if (fileFormat == FileFormat.PNG) { - final List result = new PngSplitter(suggested, diagram.getHorizontalPages(), diagram - .getVerticalPages(), diagram.getMetadata(), diagram.getDpi(fileFormatOption)).getFiles(); - for (File f : result) { - Log.info("Creating file: " + f); - } - return result; - } - Log.info("Creating file: " + suggested); - return Arrays.asList(suggested); - } - static private void traceDotString(String dotString) throws IOException { final File f = new File("dottmpfile" + UniqueSequence.getValue() + ".tmp"); PrintWriter pw = null; @@ -216,28 +184,19 @@ public final class CucaDiagramFileMaker { private String createSvg(OutputStream os, List dotStrings, FileFormatOption fileFormatOption) throws IOException, InterruptedException { + final StringBuilder cmap = new StringBuilder(); try { deltaY = 0; - final GraphvizMaker dotMaker = populateImagesAndCreateGraphvizMaker(dotStrings, fileFormatOption); - final String dotString = dotMaker.createDotString(); - - if (OptionFlags.getInstance().isKeepTmpFiles()) { - traceDotString(dotString); - } - - // final boolean isUnderline = dotMaker.isUnderline(); - final Graphviz graphviz = GraphvizUtils.create(dotString, "svg"); - - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - graphviz.createPng(baos); - baos.close(); - dotMaker.clean(); - - String svg = new String(baos.toByteArray(), "UTF-8").replace('\\', '/'); + String svg = getSvgData(dotStrings, fileFormatOption, cmap); final Dimension2D dim = getDimensionSvg(svg); if (dim != null) { - svg = removeSvgXmlHeader(svg); + + double supH = getTitleSvgHeight(); + supH += getHeaderSvgHeight(); + supH += getFooterSvgHeight(); + + svg = removeSvgXmlHeader(svg, dim.getWidth(), dim.getHeight() + supH); svg = addTitleSvg(svg, dim.getWidth(), dim.getHeight()); svg = addHeaderSvg(svg, dim.getWidth(), dim.getHeight()); @@ -304,14 +263,73 @@ public final class CucaDiagramFileMaker { // cleanTemporaryFiles(diagram.entities().values()); // cleanTemporaryFiles(diagram.getLinks()); } + if (cmap.length() > 0) { + return translateXY(cmap.toString(), 0, (int) Math.round(deltaY)); + } return null; } - private static String removeSvgXmlHeader(String svg) { - svg = svg - .replaceFirst( - "(?i)]*>", - ""); + static String translateXY(String cmap, int deltaX, int deltaY) { + if (deltaY == 0) { + return cmap; + } + final Pattern p = Pattern.compile("coords=\"(\\d+),(\\d+),(\\d+),(\\d+)\""); + final Matcher m = p.matcher(cmap); + + final StringBuffer sb = new StringBuffer(); + while (m.find()) { + final int x1 = Integer.parseInt(m.group(1)) + deltaX; + final int y1 = Integer.parseInt(m.group(2)) + deltaY; + final int x2 = Integer.parseInt(m.group(3)) + deltaX; + final int y2 = Integer.parseInt(m.group(4)) + deltaY; + m.appendReplacement(sb, "coords=\"" + x1 + "," + y1 + "," + x2 + "," + y2 + "\""); + } + m.appendTail(sb); + return sb.toString(); + } + + private String getSvgData(List dotStrings, FileFormatOption fileFormatOption, StringBuilder cmap) + throws IOException, InterruptedException, UnsupportedEncodingException { + final GraphvizMaker dotMaker = populateImagesAndCreateGraphvizMaker(dotStrings, fileFormatOption); + final String dotString = dotMaker.createDotString(); + + if (OptionFlags.getInstance().isKeepTmpFiles()) { + traceDotString(dotString); + } + // if (diagram.hasUrl()) { + // final Graphviz graphviz = GraphvizUtils.create(dotString, "cmapx", + // "svg"); + // + // final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // graphviz.createPng(baos); + // baos.close(); + // dotMaker.clean(); + // + // final String cmapAndSvg = new String(baos.toByteArray(), + // "UTF-8").replace('\\', '/'); + // final int x = cmapAndSvg.indexOf(""; + svg = svg.replaceFirst("(?i)]*>", newString); return svg; } @@ -412,6 +430,8 @@ public final class CucaDiagramFileMaker { throws IOException, InterruptedException { final StringBuilder cmap = new StringBuilder(); + double supX = 0; + double supY = 0; try { final GraphvizMaker dotMaker = populateImagesAndCreateGraphvizMaker(dotStrings, fileFormatOption); final String dotString = dotMaker.createDotString(); @@ -448,6 +468,12 @@ public final class CucaDiagramFileMaker { } final Color background = diagram.getSkinParam().getBackgroundColor().getColor(); + + supY = getTitlePngHeight(stringBounder); + supY += getHeaderPngHeight(stringBounder); + + supX = getOffsetX(stringBounder, im.getWidth()); + im = addTitle(im, background); im = addFooter(im, background); im = addHeader(im, background); @@ -457,6 +483,9 @@ public final class CucaDiagramFileMaker { im = PngRotation.process(im); } im = PngSizer.process(im, diagram.getMinwidth()); + + im = addFlashcode(im, background); + PngIO.write(im, os, diagram.getMetadata(), diagram.getDpi(fileFormatOption)); } finally { cleanTemporaryFiles(diagram.entities().values()); @@ -464,11 +493,18 @@ public final class CucaDiagramFileMaker { } if (cmap.length() > 0) { - return cmap.toString(); + return translateXY(cmap.toString(), (int) Math.round(supX), (int) Math.round(supY)); } return null; } + private BufferedImage addFlashcode(BufferedImage im, Color background) { + if (flashcodes == null) { + return im; + } + return new PngFlashcoder(flashcodes).processImage(im, background); + } + private String createDot(OutputStream os, List dotStrings, FileFormatOption fileFormatOption) throws IOException, InterruptedException { final GraphvizMaker dotMaker = populateImagesAndCreateGraphvizMaker(dotStrings, fileFormatOption); @@ -575,6 +611,24 @@ public final class CucaDiagramFileMaker { return pngTitler.processImage(im, background, 3); } + private double getTitlePngHeight(StringBounder stringBounder) throws IOException { + final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.TITLE, null).getColor(); + final String fontFamily = getSkinParam().getFontFamily(FontParam.TITLE, null); + final int fontSize = getSkinParam().getFontSize(FontParam.TITLE, null); + final PngTitler pngTitler = new PngTitler(titleColor, diagram.getTitle(), fontSize, fontFamily, + HorizontalAlignement.CENTER, VerticalPosition.TOP); + return pngTitler.getOffsetY(stringBounder); + } + + private double getOffsetX(StringBounder stringBounder, double imWidth) throws IOException { + final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.TITLE, null).getColor(); + final String fontFamily = getSkinParam().getFontFamily(FontParam.TITLE, null); + final int fontSize = getSkinParam().getFontSize(FontParam.TITLE, null); + final PngTitler pngTitler = new PngTitler(titleColor, diagram.getTitle(), fontSize, fontFamily, + HorizontalAlignement.CENTER, VerticalPosition.TOP); + return pngTitler.getOffsetX(imWidth, stringBounder); + } + private String addTitleSvg(String svg, double width, double height) throws IOException { final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.TITLE, null).getColor(); final String fontFamily = getSkinParam().getFontFamily(FontParam.TITLE, null); @@ -586,6 +640,16 @@ public final class CucaDiagramFileMaker { return svgTitler.addTitleSvg(svg, width, height); } + private double getTitleSvgHeight() throws IOException { + final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.TITLE, null).getColor(); + final String fontFamily = getSkinParam().getFontFamily(FontParam.TITLE, null); + final int fontSize = getSkinParam().getFontSize(FontParam.TITLE, null); + + final SvgTitler svgTitler = new SvgTitler(titleColor, diagram.getTitle(), fontSize, fontFamily, + HorizontalAlignement.CENTER, VerticalPosition.TOP, 3); + return svgTitler.getHeight(); + } + private String addTitleEps(String eps) throws IOException { final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.TITLE, null).getColor(); final String fontFamily = getSkinParam().getFontFamily(FontParam.TITLE, null); @@ -616,8 +680,6 @@ public final class CucaDiagramFileMaker { return epsTitler.addTitleEps(eps); } - - private String addHeaderSvg(String svg, double width, double height) throws IOException { final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.HEADER, null).getColor(); final String fontFamily = getSkinParam().getFontFamily(FontParam.HEADER, null); @@ -628,6 +690,15 @@ public final class CucaDiagramFileMaker { return svgTitler.addTitleSvg(svg, width, height); } + private double getHeaderSvgHeight() throws IOException { + final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.HEADER, null).getColor(); + final String fontFamily = getSkinParam().getFontFamily(FontParam.HEADER, null); + final int fontSize = getSkinParam().getFontSize(FontParam.HEADER, null); + final SvgTitler svgTitler = new SvgTitler(titleColor, diagram.getHeader(), fontSize, fontFamily, diagram + .getHeaderAlignement(), VerticalPosition.TOP, 3); + return svgTitler.getHeight(); + } + private String addFooterSvg(String svg, double width, double height) throws IOException { final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.FOOTER, null).getColor(); final String fontFamily = getSkinParam().getFontFamily(FontParam.FOOTER, null); @@ -637,6 +708,15 @@ public final class CucaDiagramFileMaker { return svgTitler.addTitleSvg(svg, width, height + deltaY); } + private double getFooterSvgHeight() throws IOException { + final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.FOOTER, null).getColor(); + final String fontFamily = getSkinParam().getFontFamily(FontParam.FOOTER, null); + final int fontSize = getSkinParam().getFontSize(FontParam.FOOTER, null); + final SvgTitler svgTitler = new SvgTitler(titleColor, diagram.getFooter(), fontSize, fontFamily, diagram + .getFooterAlignement(), VerticalPosition.BOTTOM, 3); + return svgTitler.getHeight(); + } + private BufferedImage addFooter(BufferedImage im, final Color background) { final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.FOOTER, null).getColor(); final String fontFamily = getSkinParam().getFontFamily(FontParam.FOOTER, null); @@ -655,6 +735,15 @@ public final class CucaDiagramFileMaker { return pngTitler.processImage(im, background, 3); } + private double getHeaderPngHeight(StringBounder stringBounder) throws IOException { + final Color titleColor = diagram.getSkinParam().getFontHtmlColor(FontParam.HEADER, null).getColor(); + final String fontFamily = getSkinParam().getFontFamily(FontParam.HEADER, null); + final int fontSize = getSkinParam().getFontSize(FontParam.HEADER, null); + final PngTitler pngTitler = new PngTitler(titleColor, diagram.getHeader(), fontSize, fontFamily, diagram + .getHeaderAlignement(), VerticalPosition.TOP); + return pngTitler.getOffsetY(stringBounder); + } + private void cleanTemporaryFiles(final Collection imageFiles) throws IOException { if (OptionFlags.getInstance().isKeepTmpFiles() == false) { for (Imaged entity : imageFiles) { @@ -842,8 +931,6 @@ public final class CucaDiagramFileMaker { final Lazy lpng = new Lazy() { public File getNow() throws IOException { final EmptyImageBuilder builder = new EmptyImageBuilder(stickMan.getPreferredWidth(null) * dpiFactor, - // stickMan.getPreferredHeight(null) * dpiFactor, dpiFactor > 1 - // ? Color.BLUE : background); stickMan.getPreferredHeight(null) * dpiFactor, background); final BufferedImage im = builder.getBufferedImage(); @@ -920,10 +1007,11 @@ public final class CucaDiagramFileMaker { final boolean isUnderline = dotMaker.isUnderline(); String eps = new String(baos.toByteArray(), "UTF-8"); + eps = cleanStrangeCharacter(eps); if (isUnderline) { eps = new UnderlineTrickEps(eps).getString(); } - + if (diagram.getTitle() != null) { eps = addTitleEps(eps); } @@ -975,7 +1063,6 @@ public final class CucaDiagramFileMaker { // mImage.appendTail(sb); // svg = sb.toString(); - } finally { // cleanTemporaryFiles(diagram.entities().values()); // cleanTemporaryFiles(diagram.getLinks()); @@ -1041,4 +1128,26 @@ public final class CucaDiagramFileMaker { } } + static String cleanStrangeCharacter(String eps) { + final StringBuilder sb = new StringBuilder(); + final StringTokenizer st = new StringTokenizer(eps, "\r\n"); + while (st.hasMoreTokens()) { + String s = st.nextToken(); + if (s.equals(EpsGraphics.END_OF_FILE)) { + sb.append(s); + sb.append("\n"); + s = st.nextToken(); + if (s.equalsIgnoreCase("grestore") == false) { + s = st.nextToken(); + if (s.equalsIgnoreCase("grestore") == false) { + throw new IllegalStateException(); + } + } + } + sb.append(s); + sb.append("\n"); + } + return sb.toString(); + } + } diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/CucaDiagramTxtMaker.java b/src/net/sourceforge/plantuml/cucadiagram/dot/CucaDiagramTxtMaker.java index 471ec364c..1464b4f05 100644 --- a/src/net/sourceforge/plantuml/cucadiagram/dot/CucaDiagramTxtMaker.java +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/CucaDiagramTxtMaker.java @@ -48,10 +48,10 @@ import java.util.Map; import net.sourceforge.plantuml.FileFormat; import net.sourceforge.plantuml.StringUtils; -import net.sourceforge.plantuml.cucadiagram.Member; import net.sourceforge.plantuml.cucadiagram.CucaDiagram; import net.sourceforge.plantuml.cucadiagram.Entity; import net.sourceforge.plantuml.cucadiagram.Link; +import net.sourceforge.plantuml.cucadiagram.Member; import net.sourceforge.plantuml.posimo.Block; import net.sourceforge.plantuml.posimo.Cluster; import net.sourceforge.plantuml.posimo.GraphvizSolverB; diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/DotCommon.java b/src/net/sourceforge/plantuml/cucadiagram/dot/DotCommon.java new file mode 100644 index 000000000..bf6f1cd43 --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/DotCommon.java @@ -0,0 +1,301 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import javax.imageio.ImageIO; + +import net.sourceforge.plantuml.ColorParam; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FontParam; +import net.sourceforge.plantuml.cucadiagram.EntityPortion; +import net.sourceforge.plantuml.cucadiagram.EntityType; +import net.sourceforge.plantuml.cucadiagram.Group; +import net.sourceforge.plantuml.cucadiagram.IEntity; +import net.sourceforge.plantuml.cucadiagram.Member; +import net.sourceforge.plantuml.cucadiagram.Stereotype; +import net.sourceforge.plantuml.graphic.HtmlColor; +import net.sourceforge.plantuml.skin.rose.Rose; + +abstract class DotCommon { + + private final DotData data; + private final FileFormat fileFormat; + private boolean underline; + + private final Rose rose = new Rose(); + + + + DotCommon(FileFormat fileFormat, DotData data) { + this.fileFormat = fileFormat; + this.data = data; + } + + protected final Stereotype getStereotype(IEntity entity) { + if (data.showPortion(EntityPortion.STEREOTYPE, entity) == false) { + return null; + } + return entity.getStereotype(); + } + + protected final boolean isThereLabel(final Stereotype stereotype) { + return stereotype != null && stereotype.getLabel() != null; + } + + protected final double getMagicFactorForImageDpi() { + return 10500 / 100000.0; + } + + protected final void appendLabelAndStereotype(IEntity entity, final StringBuilder sb, boolean classes) { + final Stereotype stereotype = getStereotype(entity); + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + if (isThereLabel(stereotype)) { + sb.append("
"); + sb.append(manageHtmlIB(stereotype.getLabel(), classes ? FontParam.CLASS_STEREOTYPE + : FontParam.OBJECT_STEREOTYPE, stereo)); + sb.append("
"); + } + String display = entity.getDisplay(); + final boolean italic = entity.getType() == EntityType.ABSTRACT_CLASS + || entity.getType() == EntityType.INTERFACE; + if (italic) { + display = "" + display; + } + sb.append(manageHtmlIB(display, classes ? FontParam.CLASS : FontParam.OBJECT, stereo)); + } + + protected final String manageHtmlIB(String s, FontParam param, String stereotype) { + s = unicode(s); + final int fontSize = data.getSkinParam().getFontSize(param, stereotype); + final int style = data.getSkinParam().getFontStyle(param, stereotype); + final String fontFamily = data.getSkinParam().getFontFamily(param, stereotype); + final DotExpression dotExpression = new DotExpression(s, fontSize, getFontHtmlColor(param, stereotype), + fontFamily, style, fileFormat); + final String result = dotExpression.getDotHtml(); + if (dotExpression.isUnderline()) { + underline = true; + } + return result; + } + + protected final HtmlColor getFontHtmlColor(FontParam fontParam, String stereotype) { + return data.getSkinParam().getFontHtmlColor(fontParam, stereotype); + } + + static String unicode(String s) { + final StringBuilder result = new StringBuilder(); + for (char c : s.toCharArray()) { + if (c > 127 || c == '&') { + final int i = c; + result.append("&#" + i + ";"); + } else { + result.append(c); + } + } + return result.toString(); + } + + protected final void addTdImageBugB1983(final StringBuilder sb, final String absolutePath) throws IOException { + // http://www.graphviz.org/bugs/b1983.html + final BufferedImage im = ImageIO.read(new File(absolutePath)); + final int height = im.getHeight(); + final int width = im.getWidth(); + final double f = 1.0 / data.getDpiFactor(); + final int w = (int) (width * f); + final int h = (int) (height * f); + final int w2 = (int) (width * getMagicFactorForImageDpi()); + final int h2 = (int) (height * getMagicFactorForImageDpi()); + sb.append(getTdHeaderForDpi(w, h)); + sb.append(""); + sb.append(""); + sb.append(getTdHeaderForDpi(w2, h2)); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("
"); + sb.append(""); + } + + protected final String getTdHeaderForDpi(final double w, final double h) { + // return ""; + return ""; + } + + public final boolean isUnderline() { + return underline; + } + + protected final DotData getData() { + return data; + } + + protected final FileFormat getFileFormat() { + return fileFormat; + } + + protected final void setUnderline(boolean underline) { + this.underline = underline; + } + + protected final int getLongestMethods(IEntity entity) { + int result = 0; + for (Member att : entity.getMethodsToDisplay()) { + final int size = att.getDisplayWithVisibilityChar().length(); + if (size > result) { + result = size; + } + } + return result; + + } + + protected final int getLongestField(IEntity entity) { + int result = 0; + for (Member att : entity.getFieldsToDisplay()) { + final int size = att.getDisplayWithVisibilityChar().length(); + if (size > result) { + result = size; + } + } + return result; + } + + protected final String getWitdh55() { + if (getData().getDpi() == 96) { + return "WIDTH=\"55\""; + } + return "WIDTH=\"55\""; + } + + protected final int computeSpring(final int current, final int maximum, int maxSpring) { + if (maximum <= current) { + return 0; + } + final int spring = maximum - current; + if (spring > maxSpring) { + return maxSpring; + } + return spring; + } + + protected final int getLonguestHeader(IEntity entity) { + int result = entity.getDisplay().length(); + final Stereotype stereotype = getStereotype(entity); + if (isThereLabel(stereotype)) { + final int size = stereotype.getLabel().length(); + if (size > result) { + result = size; + } + } + return result; + } + + protected final String getColorString(ColorParam colorParam, String stereotype) { + return "\"" + rose.getHtmlColor(getData().getSkinParam(), colorParam, stereotype).getAsHtml() + "\""; + } + + protected final int getLongestFieldOrAttribute(IEntity entity) { + return Math.max(getLongestField(entity), getLongestMethods(entity)); + } + + protected final boolean hasStatic(Collection attributes) { + for (Member att : attributes) { + if (att.isStatic()) { + return true; + } + } + return false; + } + + protected final String manageHtmlIBspecial(Member att, FontParam param, boolean hasStatic, String backColor, + boolean withVisibilityChar) { + String prefix = ""; + if (hasStatic) { + prefix = "_"; + } + if (att.isAbstract()) { + return prefix + manageHtmlIB("" + att.getDisplay(withVisibilityChar), param, null); + } + if (att.isStatic()) { + return manageHtmlIB("" + att.getDisplay(withVisibilityChar), param, null); + } + return prefix + manageHtmlIB(att.getDisplay(withVisibilityChar), param, null); + } + + final protected String getBackColorAroundEntity(IEntity entity) { + String backColor = getSpecificBackColor(entity); + if (backColor == null) { + backColor = getColorString(ColorParam.background, null); + } + return backColor; + } + + private String getSpecificBackColor(IEntity entity) { + final Group parent = entity.getParent(); + if (parent == null) { + return null; + } + if (parent.getBackColor() == null) { + return null; + } + return "\"" + parent.getBackColor().getAsHtml() + "\""; + } + + final protected void appendImageAsTD(StringBuilder sb, String circleAbsolutePath) throws IOException { + if (circleAbsolutePath.endsWith(".png")) { + if (getData().getDpi() == 96) { + final BufferedImage im = ImageIO.read(new File(circleAbsolutePath)); + final int height = im.getHeight(); + final int width = im.getWidth(); + sb.append(""); + } else { + addTdImageBugB1983(sb, circleAbsolutePath); + } + } else if (circleAbsolutePath.endsWith(".eps")) { + sb.append(""); + } + } + + + + + +} diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/DotMaker.java b/src/net/sourceforge/plantuml/cucadiagram/dot/DotMaker.java index 418e4a478..70dd05cec 100644 --- a/src/net/sourceforge/plantuml/cucadiagram/dot/DotMaker.java +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/DotMaker.java @@ -28,14 +28,13 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6230 $ + * Revision $Revision: 6482 $ * */ package net.sourceforge.plantuml.cucadiagram.dot; import java.awt.Color; import java.awt.Font; -import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -45,8 +44,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import javax.imageio.ImageIO; - import net.sourceforge.plantuml.ColorParam; import net.sourceforge.plantuml.FileFormat; import net.sourceforge.plantuml.FileUtils; @@ -56,8 +53,8 @@ import net.sourceforge.plantuml.OptionFlags; import net.sourceforge.plantuml.SignatureUtils; import net.sourceforge.plantuml.StringUtils; import net.sourceforge.plantuml.UmlDiagramType; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.cucadiagram.Entity; -import net.sourceforge.plantuml.cucadiagram.EntityPortion; import net.sourceforge.plantuml.cucadiagram.EntityType; import net.sourceforge.plantuml.cucadiagram.Group; import net.sourceforge.plantuml.cucadiagram.GroupType; @@ -73,20 +70,14 @@ import net.sourceforge.plantuml.graphic.HtmlColor; import net.sourceforge.plantuml.graphic.TextBlock; import net.sourceforge.plantuml.graphic.TextBlockUtils; import net.sourceforge.plantuml.skin.UDrawable; -import net.sourceforge.plantuml.skin.VisibilityModifier; -import net.sourceforge.plantuml.skin.rose.Rose; import net.sourceforge.plantuml.ugraphic.UGraphic; import net.sourceforge.plantuml.ugraphic.eps.UGraphicEps; -final public class DotMaker implements GraphvizMaker { - - private final DotData data; +final public class DotMaker extends DotCommon implements GraphvizMaker { private static boolean isJunit = false; private final List dotStrings; - private boolean underline = false; - private final Rose rose = new Rose(); private static String lastDotSignature; @@ -106,7 +97,7 @@ final public class DotMaker implements GraphvizMaker { } public DotMaker(DotData data, List dotStrings, FileFormat fileFormat) { - this.data = data; + super(fileFormat, data); this.dotStrings = dotStrings; this.fileFormat = fileFormat; if (data.getSkinParam().classAttributeIconSize() > 0) { @@ -124,7 +115,7 @@ final public class DotMaker implements GraphvizMaker { initPrintWriter(sb); printGroups(sb, null); printEntities(sb, getUnpackagedEntities()); - printLinks(sb, data.getLinks()); + printLinks(sb, getData().getLinks()); printRanking(sb); sb.append("}"); @@ -150,16 +141,14 @@ final public class DotMaker implements GraphvizMaker { private void initPrintWriter(StringBuilder sb) { - Log.info("Entities = " + data.getEntities().size()); - final boolean huge = data.getEntities().size() > 800; + Log.info("Entities = " + getData().getEntities().size()); + final boolean huge = getData().getEntities().size() > 800; sb.append("digraph unix {"); - // if (isJunit == false) { for (String s : dotStrings) { sb.append(s); } - // } - sb.append("bgcolor=\"" + data.getSkinParam().getBackgroundColor().getAsHtml() + "\";"); + sb.append("bgcolor=\"" + getData().getSkinParam().getBackgroundColor().getAsHtml() + "\";"); if (huge) { sb.append("size=\"400,400;\""); } else { @@ -168,8 +157,8 @@ final public class DotMaker implements GraphvizMaker { } // sb.append("ordering=out;"); sb.append("compound=true;"); - final DotSplines dotSplines = data.getSkinParam().getDotSplines(); - final GraphvizLayoutStrategy strategy = data.getSkinParam().getStrategy(); + final DotSplines dotSplines = getData().getSkinParam().getDotSplines(); + final GraphvizLayoutStrategy strategy = getData().getSkinParam().getStrategy(); if (dotSplines == DotSplines.ORTHO) { sb.append("splines=ortho;"); } else if (dotSplines == DotSplines.POLYLINE) { @@ -177,31 +166,31 @@ final public class DotMaker implements GraphvizMaker { } else if (strategy != GraphvizLayoutStrategy.DOT) { sb.append("splines=true;"); } - -// if (strategy == GraphvizLayoutStrategy.NEATO) { -// sb.append("overlap=false;"); -// } + + // if (strategy == GraphvizLayoutStrategy.NEATO) { + // sb.append("overlap=false;"); + // } if (strategy != GraphvizLayoutStrategy.DOT) { sb.append("layout=" + strategy.name().toLowerCase() + ";"); sb.append("overlap=false;"); } - + sb.append("remincross=true;"); sb.append("searchsize=500;"); - if (data.getRankdir() == Rankdir.LEFT_TO_RIGHT) { + if (getData().getRankdir() == Rankdir.LEFT_TO_RIGHT) { sb.append("rankdir=LR;"); } - if (data.getDpi() != 96) { - sb.append("dpi=" + data.getDpi() + ";"); + if (getData().getDpi() != 96) { + sb.append("dpi=" + getData().getDpi() + ";"); sb.append("imagescale=both;"); } } private Collection getUnpackagedEntities() { final List result = new ArrayList(); - for (IEntity ent : data.getEntities().values()) { - if (ent.getParent() == data.getTopParent()) { + for (IEntity ent : getData().getEntities().values()) { + if (ent.getParent() == getData().getTopParent()) { result.add(ent); } } @@ -209,8 +198,8 @@ final public class DotMaker implements GraphvizMaker { } private void printGroups(StringBuilder sb, Group parent) throws IOException { - for (Group g : data.getGroupHierarchy().getChildrenGroups(parent)) { - if (data.isEmpty(g) && g.getType() == GroupType.PACKAGE) { + for (Group g : getData().getGroupHierarchy().getChildrenGroups(parent)) { + if (getData().isEmpty(g) && g.getType() == GroupType.PACKAGE) { final IEntity folder = new Entity(g.getUid(), g.getCode(), g.getDisplay(), EntityType.EMPTY_PACKAGE, null, null); printEntity(sb, folder); @@ -239,8 +228,8 @@ final public class DotMaker implements GraphvizMaker { sb.append("subgraph " + g.getUid() + " {"); // sb.append("margin=10;"); - sb.append("fontsize=\"" + data.getSkinParam().getFontSize(getFontParamForGroup(), stereo) + "\";"); - final String fontFamily = data.getSkinParam().getFontFamily(getFontParamForGroup(), stereo); + sb.append("fontsize=\"" + getData().getSkinParam().getFontSize(getFontParamForGroup(), stereo) + "\";"); + final String fontFamily = getData().getSkinParam().getFontFamily(getFontParamForGroup(), stereo); if (fontFamily != null) { sb.append("fontname=\"" + fontFamily + "\";"); } @@ -248,7 +237,7 @@ final public class DotMaker implements GraphvizMaker { if (g.getDisplay() != null) { sb.append("label=<" + manageHtmlIB(g.getDisplay(), getFontParamForGroup(), stereo) + ">;"); } - final String fontColor = data.getSkinParam().getFontHtmlColor(getFontParamForGroup(), stereo).getAsHtml(); + final String fontColor = getData().getSkinParam().getFontHtmlColor(getFontParamForGroup(), stereo).getAsHtml(); sb.append("fontcolor=\"" + fontColor + "\";"); if (getGroupBackColor(g) != null) { @@ -265,7 +254,7 @@ final public class DotMaker implements GraphvizMaker { printGroups(sb, g); this.printEntities(sb, g.entities().values()); - for (Link link : data.getLinks()) { + for (Link link : getData().getLinks()) { eventuallySameRank(sb, g, link); } sb.append("}"); @@ -274,8 +263,8 @@ final public class DotMaker implements GraphvizMaker { private HtmlColor getGroupBackColor(Group g) { HtmlColor value = g.getBackColor(); if (value == null) { - value = data.getSkinParam().getHtmlColor(ColorParam.packageBackground, null); - // value = rose.getHtmlColor(this.data.getSkinParam(), + value = getData().getSkinParam().getHtmlColor(ColorParam.packageBackground, null); + // value = rose.getHtmlColor(this.getData().getSkinParam(), // ColorParam.packageBackground); } return value; @@ -296,9 +285,9 @@ final public class DotMaker implements GraphvizMaker { sb.append("style=solid;"); // sb.append("margin=10;"); - final List autolinks = data.getAutoLinks(g); - final List toEdgeLinks = data.getToEdgeLinks(g); - final List fromEdgeLinks = data.getFromEdgeLinks(g); + final List autolinks = getData().getAutoLinks(g); + final List toEdgeLinks = getData().getToEdgeLinks(g); + final List fromEdgeLinks = getData().getFromEdgeLinks(g); final boolean autoLabel = autolinks.size() == 1; final List nodesHiddenUidOut = getNodesHiddenUidOut(g); @@ -355,8 +344,8 @@ final public class DotMaker implements GraphvizMaker { } // sb.append(g.getUid() + "min->" + g.getUid() + "max;"); - sb.append("fontsize=\"" + data.getSkinParam().getFontSize(getFontParamForGroup(), null) + "\";"); - final String fontFamily = data.getSkinParam().getFontFamily(getFontParamForGroup(), null); + sb.append("fontsize=\"" + getData().getSkinParam().getFontSize(getFontParamForGroup(), null) + "\";"); + final String fontFamily = getData().getSkinParam().getFontFamily(getFontParamForGroup(), null); if (fontFamily != null) { sb.append("fontname=\"" + fontFamily + "\";"); } @@ -374,7 +363,7 @@ final public class DotMaker implements GraphvizMaker { sb.append("label=<" + label + ">;"); } - final String fontColor = data.getSkinParam().getFontHtmlColor(getFontParamForGroup(), null).getAsHtml(); + final String fontColor = getData().getSkinParam().getFontHtmlColor(getFontParamForGroup(), null).getAsHtml(); sb.append("fontcolor=\"" + fontColor + "\";"); final HtmlColor groupBackColor = getGroupBackColor(g); if (groupBackColor != null) { @@ -409,7 +398,7 @@ final public class DotMaker implements GraphvizMaker { printGroups(sb, g); this.printEntities(sb, g.entities().values()); - for (Link link : data.getLinks()) { + for (Link link : getData().getLinks()) { eventuallySameRank(sb, g, link); } @@ -482,7 +471,7 @@ final public class DotMaker implements GraphvizMaker { } private FontParam getFontParamForGroup() { - if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + if (getData().getUmlDiagramType() == UmlDiagramType.STATE) { return FontParam.STATE; } return FontParam.PACKAGE; @@ -540,6 +529,9 @@ final public class DotMaker implements GraphvizMaker { if (link.getLabelangle() != null) { decoration.append("labelangle=" + link.getLabelangle() + ","); } + if (link.isConstraint() == false) { + decoration.append("constraint=false,"); + } final DrawFile noteLink = link.getImageFile(); @@ -575,9 +567,6 @@ final public class DotMaker implements GraphvizMaker { decoration.append(",style=invis"); } - // if (len == 1) { - // decoration.append(",constraint=false"); - // } final String lenString = len >= 3 ? ",minlen=" + (len - 1) : ""; if (link.getEntity1().getType() == EntityType.GROUP) { @@ -589,15 +578,26 @@ final public class DotMaker implements GraphvizMaker { decoration.append(",lhead=" + link.getEntity2().getParent().getUid() + "v"); } - sb.append(uid1 + " -> " + uid2); + final boolean margin1 = MODE_MARGIN && link.getEntity1().hasNearDecoration(); + final boolean margin2 = MODE_MARGIN && link.getEntity2().hasNearDecoration(); + + sb.append(uid1); + if (margin1) { + sb.append(":h"); + } + sb.append(" -> "); + sb.append(uid2); + if (margin2) { + sb.append(":h"); + } sb.append(decoration); sb.append(lenString + "];"); - eventuallySameRank(sb, data.getTopParent(), link); + eventuallySameRank(sb, getData().getTopParent(), link); } private List getNodesHiddenUidOut(Group g) { final List result = new ArrayList(); - for (Link link : data.getLinks()) { + for (Link link : getData().getLinks()) { if (link.getEntity1().getParent() == link.getEntity2().getParent()) { continue; } @@ -610,7 +610,7 @@ final public class DotMaker implements GraphvizMaker { private List getNodesHiddenUidIn(Group g) { final List result = new ArrayList(); - for (Link link : data.getLinks()) { + for (Link link : getData().getLinks()) { if (link.getEntity1().getParent() == link.getEntity2().getParent()) { continue; } @@ -622,7 +622,7 @@ final public class DotMaker implements GraphvizMaker { } private String getHiddenNodeUid(Group g, Link link) { - if (data.isEmpty(g) && g.getType() == GroupType.PACKAGE) { + if (getData().isEmpty(g) && g.getType() == GroupType.PACKAGE) { return g.getUid(); } return g.getUid() + "_" + link.getUid(); @@ -638,9 +638,9 @@ final public class DotMaker implements GraphvizMaker { decoration.append(","); decoration.append("fontcolor=" + getFontColorString(getArrowFontParam(), null) + ","); - decoration.append("fontsize=\"" + data.getSkinParam().getFontSize(getArrowFontParam(), null) + "\","); + decoration.append("fontsize=\"" + getData().getSkinParam().getFontSize(getArrowFontParam(), null) + "\","); - final String fontName = data.getSkinParam().getFontFamily(getArrowFontParam(), null); + final String fontName = getData().getSkinParam().getFontFamily(getArrowFontParam(), null); if (fontName != null) { decoration.append("fontname=\"" + fontName + "\","); } @@ -674,51 +674,43 @@ final public class DotMaker implements GraphvizMaker { } private FontParam getArrowFontParam() { - if (data.getUmlDiagramType() == UmlDiagramType.CLASS) { + if (getData().getUmlDiagramType() == UmlDiagramType.CLASS) { return FontParam.CLASS_ARROW; - } else if (data.getUmlDiagramType() == UmlDiagramType.OBJECT) { + } else if (getData().getUmlDiagramType() == UmlDiagramType.OBJECT) { return FontParam.OBJECT_ARROW; - } else if (data.getUmlDiagramType() == UmlDiagramType.USECASE) { + } else if (getData().getUmlDiagramType() == UmlDiagramType.USECASE) { return FontParam.USECASE_ARROW; - } else if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + } else if (getData().getUmlDiagramType() == UmlDiagramType.ACTIVITY) { return FontParam.ACTIVITY_ARROW; - } else if (data.getUmlDiagramType() == UmlDiagramType.COMPONENT) { + } else if (getData().getUmlDiagramType() == UmlDiagramType.COMPONENT) { return FontParam.COMPONENT_ARROW; - } else if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + } else if (getData().getUmlDiagramType() == UmlDiagramType.STATE) { return FontParam.STATE_ARROW; } throw new IllegalStateException(); } private ColorParam getArrowColorParam() { - if (data.getUmlDiagramType() == UmlDiagramType.CLASS) { + if (getData().getUmlDiagramType() == UmlDiagramType.CLASS) { return ColorParam.classArrow; - } else if (data.getUmlDiagramType() == UmlDiagramType.OBJECT) { + } else if (getData().getUmlDiagramType() == UmlDiagramType.OBJECT) { return ColorParam.objectArrow; - } else if (data.getUmlDiagramType() == UmlDiagramType.USECASE) { + } else if (getData().getUmlDiagramType() == UmlDiagramType.USECASE) { return ColorParam.usecaseArrow; - } else if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + } else if (getData().getUmlDiagramType() == UmlDiagramType.ACTIVITY) { return ColorParam.activityArrow; - } else if (data.getUmlDiagramType() == UmlDiagramType.COMPONENT) { + } else if (getData().getUmlDiagramType() == UmlDiagramType.COMPONENT) { return ColorParam.componentArrow; - } else if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + } else if (getData().getUmlDiagramType() == UmlDiagramType.STATE) { return ColorParam.stateArrow; } throw new IllegalStateException(); } - private String getColorString(ColorParam colorParam, String stereotype) { - return "\"" + rose.getHtmlColor(data.getSkinParam(), colorParam, stereotype).getAsHtml() + "\""; - } - private String getFontColorString(FontParam fontParam, String stereotype) { return "\"" + getFontHtmlColor(fontParam, stereotype).getAsHtml() + "\""; } - private HtmlColor getFontHtmlColor(FontParam fontParam, String stereotype) { - return data.getSkinParam().getFontHtmlColor(fontParam, stereotype); - } - private void eventuallySameRank(StringBuilder sb, Group entityPackage, Link link) { final int len = link.getLength(); if (len == 1 && link.getEntity1().getParent() == entityPackage @@ -733,41 +725,55 @@ final public class DotMaker implements GraphvizMaker { } } + private boolean MODE_LOLLIPOP_BETA = false; + private void printEntities(StringBuilder sb, Collection entities) throws IOException { final Set lollipops = new HashSet(); final Set lollipopsFriends = new HashSet(); for (IEntity entity : entities) { if (entity.getType() == EntityType.LOLLIPOP) { lollipops.add(entity); - lollipopsFriends.add(getConnectedToLollipop(entity)); + if (MODE_LOLLIPOP_BETA == false) { + lollipopsFriends.add(getConnectedToLollipop(entity)); + } } } - for (IEntity entity : entities) { - if (lollipops.contains(entity) || lollipopsFriends.contains(entity)) { - continue; - } - printEntity(sb, entity); - } - for (IEntity ent : lollipopsFriends) { - sb.append("subgraph cluster" + ent.getUid() + "lol {"); - sb.append("style=invis;"); - sb.append("label=\"\";"); - printEntity(sb, ent); - for (IEntity lollipop : getAllLollipop(ent)) { - final Link link = getLinkLollipop(lollipop, ent); - final String headOrTail = getHeadOrTail(lollipop, link); - printEntity(sb, lollipop, headOrTail); - printLink(sb, link); + if (MODE_LOLLIPOP_BETA) { + for (IEntity entity : entities) { + if (lollipops.contains(entity)) { + continue; + } + printEntity(sb, entity); + } + } else { + for (IEntity entity : entities) { + if (lollipops.contains(entity) || lollipopsFriends.contains(entity)) { + continue; + } + printEntity(sb, entity); + } + + for (IEntity ent : lollipopsFriends) { + sb.append("subgraph cluster" + ent.getUid() + "lol {"); + sb.append("style=invis;"); + sb.append("label=\"\";"); + printEntity(sb, ent); + for (IEntity lollipop : getAllLollipop(ent)) { + final Link link = getLinkLollipop(lollipop, ent); + final String headOrTail = getHeadOrTail(lollipop, link); + printEntity(sb, lollipop, headOrTail); + printLink(sb, link); + } + sb.append("}"); } - sb.append("}"); } } private Collection getAllLollipop(IEntity entity) { final Collection result = new ArrayList(); - for (IEntity lollipop : data.getAllLinkedDirectedTo(entity)) { + for (IEntity lollipop : getData().getAllLinkedDirectedTo(entity)) { if (lollipop.getType() == EntityType.LOLLIPOP) { result.add(lollipop); } @@ -777,7 +783,7 @@ final public class DotMaker implements GraphvizMaker { private IEntity getConnectedToLollipop(IEntity lollipop) { assert lollipop.getType() == EntityType.LOLLIPOP; - final Collection linked = data.getAllLinkedDirectedTo(lollipop); + final Collection linked = getData().getAllLinkedDirectedTo(lollipop); if (linked.size() != 1) { throw new IllegalStateException("size=" + linked.size()); } @@ -786,7 +792,7 @@ final public class DotMaker implements GraphvizMaker { private Link getLinkLollipop(IEntity lollipop, IEntity ent) { assert lollipop.getType() == EntityType.LOLLIPOP; - for (Link link : data.getLinks()) { + for (Link link : getData().getLinks()) { if (link.isBetween(lollipop, ent)) { return link; } @@ -806,48 +812,54 @@ final public class DotMaker implements GraphvizMaker { sb.append(entity.getUid() + " -> " + entity.getUid() + "[color=" + colorBack + ",arrowtail=none,arrowhead=none," + headOrTail + "=<" + labelLo + ">];"); } else { - throw new IllegalStateException(type.toString() + " " + data.getUmlDiagramType()); + throw new IllegalStateException(type.toString() + " " + getData().getUmlDiagramType()); } } + static final boolean MODE_MARGIN = true; + static public final boolean MODE_BRANCHE_CLUSTER = true; + private void printEntity(StringBuilder sb, IEntity entity) throws IOException { final EntityType type = entity.getType(); final String label = getLabel(entity); if (type == EntityType.GROUP) { return; } + boolean closeBracket = false; final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); if (type == EntityType.ABSTRACT_CLASS || type == EntityType.CLASS || type == EntityType.INTERFACE || type == EntityType.ENUM) { - String dec = " [fontcolor=" + getFontColorString(FontParam.CLASS, stereo) + ",margin=0,fillcolor=" - + getColorString(ColorParam.classBackground, stereo) + ",color=" - + getColorString(ColorParam.classBorder, stereo) + ",style=filled,shape=box," + label; - if (this.data.hasUrl() && entity.getUrl() != null) { - dec += ",URL=\"" + entity.getUrl() + "\""; + String dec; + if (MODE_MARGIN && entity.hasNearDecoration() || MODE_LOLLIPOP_BETA) { + dec = " [fontcolor=" + getFontColorString(FontParam.CLASS, stereo) + "color=" + + getBackColorAroundEntity(entity) + ",margin=0,style=filled,shape=plaintext," + label; + } else { + dec = " [fontcolor=" + getFontColorString(FontParam.CLASS, stereo) + ",margin=0,fillcolor=" + + getColorString(ColorParam.classBackground, stereo) + ",color=" + + getColorString(ColorParam.classBorder, stereo) + ",style=filled,shape=box," + label; + } - dec += "];"; sb.append(entity.getUid() + dec); } else if (type == EntityType.OBJECT) { sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.CLASS, stereo) + ",margin=0,fillcolor=" + getColorString(ColorParam.classBackground, stereo) + ",color=" - + getColorString(ColorParam.classBorder, stereo) + ",style=filled,shape=record," + label + "];"); + + getColorString(ColorParam.classBorder, stereo) + ",style=filled,shape=record," + label); } else if (type == EntityType.USECASE) { sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.USECASE, stereo) + ",fillcolor=" + getColorString(ColorParam.usecaseBackground, stereo) + ",color=" - + getColorString(ColorParam.usecaseBorder, stereo) + ",style=filled," + label + "];"); + + getColorString(ColorParam.usecaseBorder, stereo) + ",style=filled," + label); } else if (type == EntityType.ACTOR) { sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.USECASE_ACTOR, stereo) - + ",margin=0,shape=plaintext," + label + "];"); + + ",margin=0,shape=plaintext," + label); } else if (type == EntityType.CIRCLE_INTERFACE) { - sb.append(entity.getUid() + " [margin=0,shape=plaintext," + label + "];"); + sb.append(entity.getUid() + " [margin=0,shape=plaintext," + label); } else if (type == EntityType.COMPONENT) { sb.append(entity.getUid() + " [margin=0.2,fontcolor=" + getFontColorString(FontParam.COMPONENT, stereo) + ",fillcolor=" + getColorString(ColorParam.componentBackground, stereo) + ",color=" - + getColorString(ColorParam.componentBorder, stereo) + ",style=filled,shape=component," + label - + "];"); - } else if (type == EntityType.NOTE && data.getDpi() != 96) { - sb.append(entity.getUid() + " [margin=0,pad=0,shape=plaintext,label=" + getLabelForNoteDpi(entity) + "];"); + + getColorString(ColorParam.componentBorder, stereo) + ",style=filled,shape=component," + label); + } else if (type == EntityType.NOTE && getData().getDpi() != 96) { + sb.append(entity.getUid() + " [margin=0,pad=0,shape=plaintext,label=" + getLabelForNoteDpi(entity)); } else if (type == EntityType.NOTE) { final DrawFile file = entity.getImageFile(); if (file == null) { @@ -858,19 +870,27 @@ final public class DotMaker implements GraphvizMaker { } final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file .getPngOrEps(fileFormat == FileFormat.EPS)); - sb.append(entity.getUid() + " [margin=0,pad=0," + label + ",shape=none,image=\"" + absolutePath + "\"];"); + sb.append(entity.getUid() + " [margin=0,pad=0," + label + ",shape=none,image=\"" + absolutePath + "\""); } else if (type == EntityType.ACTIVITY) { String shape = "octagon"; - if (data.getSkinParam().useOctagonForActivity() == false || entity.getImageFile() != null) { + if (getData().getSkinParam().useOctagonForActivity() == false || entity.getImageFile() != null) { shape = "rect"; } sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.ACTIVITY, stereo) + ",fillcolor=" + getBackColorOfEntity(entity) + ",color=" + getColorString(ColorParam.activityBorder, stereo) - + ",style=\"rounded,filled\",shape=" + shape + "," + label + "];"); + + ",style=\"rounded,filled\",shape=" + shape + "," + label); } else if (type == EntityType.BRANCH) { + if (MODE_BRANCHE_CLUSTER) { + sb.append("subgraph cluster" + entity.getUid() + "br {"); + sb.append("label=<" + manageHtmlIB(entity.getDisplay(), FontParam.ACTIVITY, null) + ">;"); + sb.append("color=" + getColorString(ColorParam.background, null) + ";"); + } sb.append(entity.getUid() + " [fillcolor=" + getBackColorOfEntity(entity) + ",color=" + getColorString(ColorParam.activityBorder, stereo) - + ",style=\"filled\",shape=diamond,height=.25,width=.25,label=\"\"];"); + + ",style=\"filled\",shape=diamond,height=.25,width=.25,label=\"\""); + if (MODE_BRANCHE_CLUSTER) { + closeBracket = true; + } // if (StringUtils.isNotEmpty(entity.getDisplay())) { // sb.append(entity.getUid() + "->" + entity.getUid() + // "[taillabel=\"" + entity.getDisplay() @@ -879,18 +899,19 @@ final public class DotMaker implements GraphvizMaker { } else if (type == EntityType.SYNCHRO_BAR) { final String color = getColorString(ColorParam.activityBar, null); sb.append(entity.getUid() + " [fillcolor=" + color + ",color=" + color + ",style=\"filled\"," - + "shape=rect,height=.08,width=1.30,label=\"\"];"); + + "shape=rect,height=.08,width=1.30,label=\"\""); } else if (type == EntityType.CIRCLE_START) { final String color = getColorString(getStartColorParam(), null); sb.append(entity.getUid() + " [fillcolor=" + color + ",color=" + color + ",style=\"filled\"," - + "shape=circle,width=.20,height=.20,label=\"\"];"); + + "shape=circle,width=.20,height=.20,label=\"\""); } else if (type == EntityType.CIRCLE_END) { final String color = getColorString(getEndColorParam(), null); sb.append(entity.getUid() + " [fillcolor=" + color + ",color=" + color + ",style=\"filled\"," - + "shape=doublecircle,width=.13,height=.13,label=\"\"];"); + + "shape=doublecircle,width=.13,height=.13,label=\"\""); } else if (type == EntityType.POINT_FOR_ASSOCIATION) { - sb.append(entity.getUid() + " [width=.05,shape=point,color=" + getColorString(ColorParam.classBorder, null) - + "];"); + sb + .append(entity.getUid() + " [width=.05,shape=point,color=" + + getColorString(ColorParam.classBorder, null)); } else if (type == EntityType.STATE) { sb.append(entity.getUid() + " [color=" + getColorString(ColorParam.stateBorder, stereo) + ",shape=record,style=\"rounded,filled\",color=" + getColorString(ColorParam.stateBorder, stereo)); @@ -899,9 +920,10 @@ final public class DotMaker implements GraphvizMaker { } else { sb.append(",fillcolor=" + getBackColorOfEntity(entity)); // sb.append(",fillcolor=\"" + - // data.getSkinParam().getBackgroundColor().getAsHtml() + "\""); + // getData().getSkinParam().getBackgroundColor().getAsHtml() + + // "\""); } - sb.append("," + label + "];"); + sb.append("," + label); } else if (type == EntityType.STATE_CONCURRENT) { final DrawFile file = entity.getImageFile(); if (file == null) { @@ -912,7 +934,7 @@ final public class DotMaker implements GraphvizMaker { } final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file.getPng()); sb.append(entity.getUid() + " [margin=1,pad=1," + label + ",style=dashed,shape=box,image=\"" + absolutePath - + "\"];"); + + "\""); } else if (type == EntityType.ACTIVITY_CONCURRENT) { final DrawFile file = entity.getImageFile(); if (file == null) { @@ -923,13 +945,24 @@ final public class DotMaker implements GraphvizMaker { } final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file.getPng()); sb.append(entity.getUid() + " [margin=0,pad=0," + label + ",style=dashed,shape=box,image=\"" + absolutePath - + "\"];"); + + "\""); } else if (type == EntityType.EMPTY_PACKAGE) { sb.append(entity.getUid() + " [margin=0.2,fontcolor=" + getFontColorString(FontParam.PACKAGE, null) + ",fillcolor=" + getColorString(ColorParam.packageBackground, null) + ",color=" - + getColorString(ColorParam.packageBorder, null) + ",style=filled,shape=tab," + label + "];"); + + getColorString(ColorParam.packageBorder, null) + ",style=filled,shape=tab," + label); } else { - throw new IllegalStateException(type.toString() + " " + data.getUmlDiagramType()); + throw new IllegalStateException(type.toString() + " " + getData().getUmlDiagramType()); + } + + if (this.getData().hasUrl() && entity.getUrl() != null) { + final Url url = entity.getUrl(); + sb.append(",URL=\"" + url.getUrl() + "\""); + sb.append(",tooltip=\"" + url.getTooltip() + "\""); + } + + sb.append("];"); + if (closeBracket) { + sb.append("}"); } if (entity.isTop()) { @@ -939,23 +972,23 @@ final public class DotMaker implements GraphvizMaker { } private ColorParam getEndColorParam() { - if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + if (getData().getUmlDiagramType() == UmlDiagramType.ACTIVITY) { return ColorParam.activityEnd; } - if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + if (getData().getUmlDiagramType() == UmlDiagramType.STATE) { return ColorParam.stateEnd; } - throw new IllegalStateException(data.getUmlDiagramType().toString()); + throw new IllegalStateException(getData().getUmlDiagramType().toString()); } private ColorParam getStartColorParam() { - if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + if (getData().getUmlDiagramType() == UmlDiagramType.ACTIVITY) { return ColorParam.activityStart; } - if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + if (getData().getUmlDiagramType() == UmlDiagramType.STATE) { return ColorParam.stateStart; } - throw new IllegalStateException(data.getUmlDiagramType().toString()); + throw new IllegalStateException(getData().getUmlDiagramType().toString()); } private String getHeadOrTail(IEntity lollipop, Link link) { @@ -990,7 +1023,7 @@ final public class DotMaker implements GraphvizMaker { final DrawFile drawFile = entity.getImageFile(); if (drawFile != null) { final String path = StringUtils.getPlateformDependentAbsolutePath(drawFile.getPng()); - final String bgcolor = "\"" + data.getSkinParam().getBackgroundColor().getAsHtml() + "\""; + final String bgcolor = "\"" + getData().getSkinParam().getBackgroundColor().getAsHtml() + "\""; final StringBuilder sb = new StringBuilder("label=<"); sb.append(""); sb.append(""); @@ -1009,6 +1042,8 @@ final public class DotMaker implements GraphvizMaker { return "label=" + getLabelForUsecase(entity); } else if (entity.getType() == EntityType.STATE) { return "label=" + getLabelForState(entity); + } else if (entity.getType() == EntityType.BRANCH) { + return "label=\"\""; } return "label=\"" + entity.getDisplay() + "\""; } @@ -1058,7 +1093,7 @@ final public class DotMaker implements GraphvizMaker { if (OptionFlags.PBBACK) { bgcolor = stateBgcolor; } else { - bgcolor = "\"" + data.getSkinParam().getBackgroundColor().getAsHtml() + "\""; + bgcolor = "\"" + getData().getSkinParam().getBackgroundColor().getAsHtml() + "\""; } // PBBACK @@ -1069,7 +1104,7 @@ final public class DotMaker implements GraphvizMaker { sb.append("
"); } - if (data.isHideEmptyDescription() == false && entity.getFieldsToDisplay().size() == 0 && cFile == null) { + if (getData().isHideEmptyDescription() == false && entity.getFieldsToDisplay().size() == 0 && cFile == null) { sb.append("|"); } @@ -1129,31 +1164,6 @@ final public class DotMaker implements GraphvizMaker { return sb.toString(); } - private void addTdImageBugB1983(final StringBuilder sb, final String absolutePath) throws IOException { - // http://www.graphviz.org/bugs/b1983.html - final BufferedImage im = ImageIO.read(new File(absolutePath)); - final int height = im.getHeight(); - final int width = im.getWidth(); - final double f = 1.0 / data.getDpiFactor(); - final int w = (int) (width * f); - final int h = (int) (height * f); - final int w2 = (int) (width * getMagicFactorForImageDpi()); - final int h2 = (int) (height * getMagicFactorForImageDpi()); - sb.append(getTdHeaderForDpi(w, h)); - sb.append(""); - sb.append(""); - sb.append(getTdHeaderForDpi(w2, h2)); - sb.append(""); - sb.append(""); - sb.append(""); - sb.append("
"); - sb.append(""); - } - - private double getMagicFactorForImageDpi() { - return 10500 / 100000.0; - } - private String getLabelForActor(IEntity entity) throws IOException { final String actorAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(entity.getImageFile() .getPngOrEps(fileFormat == FileFormat.EPS)); @@ -1165,7 +1175,7 @@ final public class DotMaker implements GraphvizMaker { sb.append("" + manageHtmlIB(stereotype.getLabel(), FontParam.USECASE_ACTOR_STEREOTYPE, stereo) + ""); } - if (data.getDpi() == 96) { + if (getData().getDpi() == 96) { sb.append(""); } else { sb.append(""); @@ -1190,7 +1200,7 @@ final public class DotMaker implements GraphvizMaker { + ""); } sb.append(""); - if (data.getDpi() == 96) { + if (getData().getDpi() == 96) { sb.append(""); } else { addTdImageBugB1983(sb, circleInterfaceAbsolutePath); @@ -1204,8 +1214,8 @@ final public class DotMaker implements GraphvizMaker { private String getLabelForLollipop(IEntity entity) throws IOException { final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); - final String circleInterfaceAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(data.getStaticImages( - EntityType.LOLLIPOP, stereo).getPngOrEps(fileFormat == FileFormat.EPS)); + final String circleInterfaceAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(getData() + .getStaticImages(EntityType.LOLLIPOP, stereo).getPngOrEps(fileFormat == FileFormat.EPS)); final Stereotype stereotype = getStereotype(entity); final StringBuilder sb = new StringBuilder("<"); @@ -1213,7 +1223,7 @@ final public class DotMaker implements GraphvizMaker { sb.append(""); } sb.append(""); - if (data.getDpi() == 96) { + if (getData().getDpi() == 96) { sb.append(""); } else { addTdImageBugB1983(sb, circleInterfaceAbsolutePath); @@ -1234,75 +1244,26 @@ final public class DotMaker implements GraphvizMaker { } private String getLabelForClassOrInterfaceOrEnumOld(IEntity entity) throws IOException { - DrawFile cFile = entity.getImageFile(); - if (cFile == null) { - final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); - cFile = data.getStaticImages(entity.getType(), stereo); + LabelBuilder builder = new LabelBuilderClassOld(getFileFormat(), getData(), entity); + if (MODE_LOLLIPOP_BETA) { + final DrawFile cFile = getData().getStaticImages(entity.getType(), null); + final String northPath = StringUtils.getPlateformDependentAbsolutePath(cFile + .getPngOrEps(getFileFormat() == FileFormat.EPS)); + final String southPath = northPath; + final String eastPath = northPath; + final String westPath = northPath; + builder = new LabelBuilderTableLollipopDecorator(getFileFormat(), getData(), entity, builder, northPath, + southPath, eastPath, westPath, getAllLollipop(entity)); + } else if (MODE_MARGIN && entity.hasNearDecoration()) { + builder = new LabelBuilderTableNineDecorator(getFileFormat(), getData(), entity, builder); } - if (cFile == null) { - throw new IllegalStateException(); - } - final String circleAbsolutePath; - if (data.showPortion(EntityPortion.CIRCLED_CHARACTER, entity)) { - circleAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(cFile - .getPngOrEps(fileFormat == FileFormat.EPS)); - } else { - circleAbsolutePath = null; - } - - final StringBuilder sb = new StringBuilder("<"); - - final boolean showFields = data.showPortion(EntityPortion.FIELD, entity); - final boolean showMethods = data.showPortion(EntityPortion.METHOD, entity); - - final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); - - if (showFields == false && showMethods == false) { - sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, 1, true, 1)); - } else { - sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.CLASS, null) + "
"); - sb.append(""); - - if (showFields) { - // if (fileFormat == FileFormat.EPS) { - // sb.append(addFieldsEps(entity.fields2(), true)); - // } else { - final boolean hasStatic = hasStatic(entity.getFieldsToDisplay()); - sb.append(""); - // } - } - if (showMethods) { - // if (fileFormat == FileFormat.EPS) { - // sb.append(addFieldsEps(entity.methods2(), true)); - // } else { - final boolean hasStatic = hasStatic(entity.getMethodsToDisplay()); - sb.append(""); - // } - } - sb.append("
"); - final int longuestFieldOrAttribute = getLongestFieldOrAttribute(entity); - final int longuestHeader = getLonguestHeader(entity); - final int spring = computeSpring(longuestHeader, longuestFieldOrAttribute, 30); - - sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, spring, true, 0)); - - sb.append("
"); - for (Member att : entity.getFieldsToDisplay()) { - sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, getColorString( - ColorParam.classBackground, stereo), true)); - sb.append("
"); - } - sb.append("
"); - for (Member att : entity.getMethodsToDisplay()) { - sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, getColorString( - ColorParam.classBackground, stereo), true)); - sb.append("
"); - } - sb.append("
"); + final StringBuilder sb = new StringBuilder(); + sb.append("<"); + builder.appendLabel(sb); + if (builder.isUnderline()) { + setUnderline(true); } sb.append(">"); - return sb.toString(); } @@ -1320,7 +1281,7 @@ final public class DotMaker implements GraphvizMaker { } texts.add(s); } - final Font font = data.getSkinParam().getFont(FontParam.CLASS_ATTRIBUTE, null); + final Font font = getData().getSkinParam().getFont(FontParam.CLASS_ATTRIBUTE, null); final Color color = getFontHtmlColor(FontParam.CLASS_ATTRIBUTE, null).getColor(); final TextBlock text = TextBlockUtils.create(texts, new FontConfiguration(font, color), HorizontalAlignement.LEFT); @@ -1338,164 +1299,19 @@ final public class DotMaker implements GraphvizMaker { + "" + "" + "" + ""; } - private boolean hasStatic(Collection attributes) { - for (Member att : attributes) { - if (att.isStatic()) { - return true; - } - } - return false; - } - private String getLabelForClassOrInterfaceOrEnumWithVisibilityImage(IEntity entity) throws IOException { - DrawFile cFile = entity.getImageFile(); - if (cFile == null) { - final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); - cFile = data.getStaticImages(entity.getType(), stereo); + LabelBuilder builder = new LabelBuilderClassWithVisibilityImage(fileFormat, getData(), entity); + if (MODE_MARGIN && entity.hasNearDecoration()) { + builder = new LabelBuilderTableNineDecorator(getFileFormat(), getData(), entity, builder); } - if (cFile == null) { - throw new IllegalStateException(); - } - final String circleAbsolutePath; - if (data.showPortion(EntityPortion.CIRCLED_CHARACTER, entity)) { - circleAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(cFile - .getPngOrEps(fileFormat == FileFormat.EPS)); - } else { - circleAbsolutePath = null; - } - - final boolean showFields = data.showPortion(EntityPortion.FIELD, entity); - final boolean showMethods = data.showPortion(EntityPortion.METHOD, entity); - - final StringBuilder sb = new StringBuilder("<"); - if (showFields == false && showMethods == false) { - sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, 1, true, 1)); - } else { - final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); - final int longuestHeader = getLonguestHeader(entity); - final int spring = computeSpring(longuestHeader, getLongestFieldOrAttribute(entity), 30); - final int springField = computeSpring(getLongestField(entity), Math.max(longuestHeader, - getLongestMethods(entity)), 30); - final int springMethod = computeSpring(getLongestMethods(entity), Math.max(longuestHeader, - getLongestField(entity)), 30); - - sb.append(""); - sb.append(""); - - if (showFields) { - sb.append(""); - } - if (showMethods) { - sb.append(""); - } - sb.append("
"); - - sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, spring, true, 0)); - sb.append("
"); - if (entity.getFieldsToDisplay().size() > 0) { - buildTableVisibility(entity, true, sb, springField); - } - sb.append("
"); - if (entity.getMethodsToDisplay().size() > 0) { - buildTableVisibility(entity, false, sb, springMethod); - } - sb.append("
"); + final StringBuilder sb = new StringBuilder(); + sb.append("<"); + builder.appendLabel(sb); + if (builder.isUnderline()) { + setUnderline(true); } sb.append(">"); - return sb.toString(); - - } - - private int computeSpring(final int current, final int maximum, int maxSpring) { - if (maximum <= current) { - return 0; - } - final int spring = maximum - current; - if (spring > maxSpring) { - return maxSpring; - } - return spring; - } - - private void buildTableVisibility(IEntity entity, boolean isField, final StringBuilder sb, int spring) - throws IOException { - sb.append(""); - - final boolean hasStatic = hasStatic(entity.getMethodsToDisplay()); - final boolean dpiNormal = data.getDpi() == 96; - for (Member att : isField ? entity.getFieldsToDisplay() : entity.getMethodsToDisplay()) { - sb.append(""); - if (dpiNormal) { - sb.append(""); - } - sb.append(""); - for (int i = 0; i < spring; i++) { - sb.append(""); - } - sb.append(""); - } - sb.append("
"); - } - String s = att.getDisplayWithVisibilityChar(); - final VisibilityModifier visibilityModifier = VisibilityModifier - .getVisibilityModifier(s.charAt(0), isField); - if (visibilityModifier != null) { - final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); - final String modifierFile = StringUtils.getPlateformDependentAbsolutePath(data.getVisibilityImages( - visibilityModifier, stereo).getPngOrEps(fileFormat == FileFormat.EPS)); - if (dpiNormal) { - sb.append(""); - } else { - addTdImageBugB1983(sb, modifierFile); - } - s = s.substring(1); - } - if (dpiNormal) { - sb.append(""); - sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, getColorString( - ColorParam.classBackground, null), false)); - sb.append("
"); - } - - private int getLonguestHeader(IEntity entity) { - int result = entity.getDisplay().length(); - final Stereotype stereotype = getStereotype(entity); - if (isThereLabel(stereotype)) { - final int size = stereotype.getLabel().length(); - if (size > result) { - result = size; - } - } - return result; - } - - private int getLongestFieldOrAttribute(IEntity entity) { - return Math.max(getLongestField(entity), getLongestMethods(entity)); - } - - private int getLongestMethods(IEntity entity) { - int result = 0; - for (Member att : entity.getMethodsToDisplay()) { - final int size = att.getDisplayWithVisibilityChar().length(); - if (size > result) { - result = size; - } - } - return result; - - } - - private int getLongestField(IEntity entity) { - int result = 0; - for (Member att : entity.getFieldsToDisplay()) { - final int size = att.getDisplayWithVisibilityChar().length(); - if (size > result) { - result = size; - } - } - return result; } private String getLabelForObject(IEntity entity) throws IOException { @@ -1506,266 +1322,25 @@ final public class DotMaker implements GraphvizMaker { } private String getLabelForObjectWithVisibilityImage(IEntity entity) throws IOException { - - final int longuestHeader = getLonguestHeader(entity); - final int spring = computeSpring(longuestHeader, getLongestFieldOrAttribute(entity), 30); - final int springField = computeSpring(getLongestField(entity), Math.max(longuestHeader, - getLongestMethods(entity)), 30); - - final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); - - final StringBuilder sb = new StringBuilder("<"); - sb.append(""); - sb.append(""); - sb.append("
"); - - sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, null, spring, false, 0)); - - sb.append("
"); - - if (entity.getFieldsToDisplay().size() == 0) { - sb.append(manageHtmlIB(" ", FontParam.OBJECT_ATTRIBUTE, stereo)); - } else { - buildTableVisibility(entity, true, sb, springField); + final LabelBuilder builder = new LabelBuilderObjectWithVisibilityImage(getFileFormat(), getData(), entity); + final StringBuilder sb = new StringBuilder(); + builder.appendLabel(sb); + if (builder.isUnderline()) { + setUnderline(true); } - - sb.append("
>"); - return sb.toString(); } private String getLabelForObjectOld(IEntity entity) throws IOException { - - final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); - - final StringBuilder sb = new StringBuilder("<"); - sb.append(""); - sb.append(""); - sb.append("
"); - - final int longuestFieldOrAttribute = getLongestFieldOrAttribute(entity); - final int longuestHeader = getLonguestHeader(entity); - final int spring = computeSpring(longuestHeader, longuestFieldOrAttribute, 30); - - sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, null, spring, false, 0)); - - sb.append("
"); - - if (entity.getFieldsToDisplay().size() == 0) { - sb.append(manageHtmlIB(" ", FontParam.OBJECT_ATTRIBUTE, stereo)); - } else { - for (Member att : entity.getFieldsToDisplay()) { - sb.append(manageHtmlIB(att.getDisplayWithVisibilityChar(), FontParam.OBJECT_ATTRIBUTE, stereo)); - sb.append("
"); - } - } - - sb.append("
>"); - - return sb.toString(); - } - - private String getWitdh55() { - if (data.getDpi() == 96) { - return "WIDTH=\"55\""; - } - return "WIDTH=\"55\""; - } - - private String manageHtmlIB(String s, FontParam param, String stereotype) { - s = unicode(s); - final int fontSize = data.getSkinParam().getFontSize(param, stereotype); - final int style = data.getSkinParam().getFontStyle(param, stereotype); - final String fontFamily = data.getSkinParam().getFontFamily(param, stereotype); - final DotExpression dotExpression = new DotExpression(s, fontSize, getFontHtmlColor(param, stereotype), - fontFamily, style, fileFormat); - final String result = dotExpression.getDotHtml(); - if (dotExpression.isUnderline()) { - underline = true; - } - return result; - } - - private String manageHtmlIBspecial(Member att, FontParam param, boolean hasStatic, String backColor, - boolean withVisibilityChar) { - String prefix = ""; - if (hasStatic) { - prefix = "_"; - } - if (att.isAbstract()) { - return prefix + manageHtmlIB("" + att.getDisplay(withVisibilityChar), param, null); - } - if (att.isStatic()) { - return manageHtmlIB("" + att.getDisplay(withVisibilityChar), param, null); - } - return prefix + manageHtmlIB(att.getDisplay(withVisibilityChar), param, null); - } - - private String manageSpace(int size) { - final DotExpression dotExpression = new DotExpression(" ", size, HtmlColor.getColorIfValid("white"), null, - Font.PLAIN, fileFormat); - final String result = dotExpression.getDotHtml(); - return result; - } - - static String unicode(String s) { - final StringBuilder result = new StringBuilder(); - for (char c : s.toCharArray()) { - if (c > 127 || c == '&') { - final int i = c; - result.append("&#" + i + ";"); - } else { - result.append(c); - } - } - return result.toString(); - } - - private String getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnumNoSpring(IEntity entity, - final String circleAbsolutePath, int cellSpacing, boolean classes) throws IOException { + final LabelBuilder builder = new LabelBuilderObjectOld(getFileFormat(), getData(), entity); final StringBuilder sb = new StringBuilder(); - sb.append(""); - sb.append(""); - if (circleAbsolutePath == null) { - sb.append(""); - } else { - addTdImageBugB1983(sb, circleAbsolutePath); - - } - sb.append("
"); - } else { - if (data.getDpi() == 96) { - sb.append(""); - sb.append(""); + builder.appendLabel(sb); + if (builder.isUnderline()) { + setUnderline(true); } - - appendLabelAndStereotype(entity, sb, classes); - sb.append("
"); return sb.toString(); - } - private String getTdHeaderForDpi(final double w, final double h) { - // return ""; - return ""; - } - - private String getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(IEntity entity, final String circleAbsolutePath, - int spring, boolean classes, int border) throws IOException { - if (spring == 0) { - return getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnumNoSpring(entity, circleAbsolutePath, 0, classes); - } - final StringBuilder sb = new StringBuilder(); - - sb - .append(""); - sb.append(""); - - for (int i = 0; i < spring; i++) { - sb.append(""); - } - - if (circleAbsolutePath != null) { - if (circleAbsolutePath.endsWith(".png")) { - if (data.getDpi() == 96) { - final BufferedImage im = ImageIO.read(new File(circleAbsolutePath)); - final int height = im.getHeight(); - final int width = im.getWidth(); - sb.append(""); - } else { - addTdImageBugB1983(sb, circleAbsolutePath); - } - } else if (circleAbsolutePath.endsWith(".eps")) { - sb.append(""); - } - } - - sb.append(""); - - for (int i = 0; i < spring; i++) { - sb.append(""); - } - sb.append("
"); - appendLabelAndStereotype(entity, sb, classes); - sb.append("
"); - return sb.toString(); - } - - private void appendLabelAndStereotype(IEntity entity, final StringBuilder sb, boolean classes) { - final Stereotype stereotype = getStereotype(entity); - final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); - if (isThereLabel(stereotype)) { - sb.append("
"); - sb.append(manageHtmlIB(stereotype.getLabel(), classes ? FontParam.CLASS_STEREOTYPE - : FontParam.OBJECT_STEREOTYPE, stereo)); - sb.append("
"); - } - String display = entity.getDisplay(); - final boolean italic = entity.getType() == EntityType.ABSTRACT_CLASS - || entity.getType() == EntityType.INTERFACE; - if (italic) { - display = "" + display; - } - sb.append(manageHtmlIB(display, classes ? FontParam.CLASS : FontParam.OBJECT, stereo)); - } - - private String getHtmlHeaderTableForClassOrInterfaceOrEnumNew(Entity entity, final String circleAbsolutePath) { - final StringBuilder sb = new StringBuilder(); - sb.append(""); - sb.append("
"); - - appendLabelAndStereotype(entity, sb, true); - sb.append("
"); - return sb.toString(); - } - - private boolean isThereLabel(final Stereotype stereotype) { - return stereotype != null && stereotype.getLabel() != null; - } - - private Stereotype getStereotype(IEntity entity) { - if (data.showPortion(EntityPortion.STEREOTYPE, entity) == false) { - return null; - } - return entity.getStereotype(); - } - - public final boolean isUnderline() { - return underline; - } - - private boolean workAroundDotBug() { - for (Link link : data.getLinks()) { - if (link.getLength() != 1) { - return false; - } - } - if (data.getUmlDiagramType() == UmlDiagramType.CLASS && allEntitiesAreClasses(data.getEntities().values())) { - return true; - } - for (IEntity ent : data.getEntities().values()) { - if (data.getAllLinkedTo(ent).size() == 0) { - return true; - } - } - return false; - } - - private boolean allEntitiesAreClasses(Collection entities) { - for (IEntity ent : entities) { - if (ent.getType() != EntityType.CLASS && ent.getType() != EntityType.ABSTRACT_CLASS - && ent.getType() != EntityType.INTERFACE && ent.getType() != EntityType.ENUM) { - return false; - } - } - return true; } private boolean isSpecialGroup(Group g) { @@ -1775,7 +1350,7 @@ final public class DotMaker implements GraphvizMaker { if (g.getType() == GroupType.CONCURRENT_STATE) { throw new IllegalStateException(); } - if (data.isThereLink(g)) { + if (getData().isThereLink(g)) { return true; } return false; diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/DotMaker2.java b/src/net/sourceforge/plantuml/cucadiagram/dot/DotMaker2.java new file mode 100644 index 000000000..ab2328eac --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/DotMaker2.java @@ -0,0 +1,1819 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6230 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.awt.Color; +import java.awt.Font; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.imageio.ImageIO; + +import net.sourceforge.plantuml.ColorParam; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FileUtils; +import net.sourceforge.plantuml.FontParam; +import net.sourceforge.plantuml.Log; +import net.sourceforge.plantuml.OptionFlags; +import net.sourceforge.plantuml.SignatureUtils; +import net.sourceforge.plantuml.StringUtils; +import net.sourceforge.plantuml.UmlDiagramType; +import net.sourceforge.plantuml.cucadiagram.Entity; +import net.sourceforge.plantuml.cucadiagram.EntityPortion; +import net.sourceforge.plantuml.cucadiagram.EntityType; +import net.sourceforge.plantuml.cucadiagram.Group; +import net.sourceforge.plantuml.cucadiagram.GroupType; +import net.sourceforge.plantuml.cucadiagram.IEntity; +import net.sourceforge.plantuml.cucadiagram.Link; +import net.sourceforge.plantuml.cucadiagram.LinkType; +import net.sourceforge.plantuml.cucadiagram.Member; +import net.sourceforge.plantuml.cucadiagram.Rankdir; +import net.sourceforge.plantuml.cucadiagram.Stereotype; +import net.sourceforge.plantuml.graphic.FontConfiguration; +import net.sourceforge.plantuml.graphic.HorizontalAlignement; +import net.sourceforge.plantuml.graphic.HtmlColor; +import net.sourceforge.plantuml.graphic.TextBlock; +import net.sourceforge.plantuml.graphic.TextBlockUtils; +import net.sourceforge.plantuml.skin.UDrawable; +import net.sourceforge.plantuml.skin.VisibilityModifier; +import net.sourceforge.plantuml.skin.rose.Rose; +import net.sourceforge.plantuml.ugraphic.UGraphic; +import net.sourceforge.plantuml.ugraphic.eps.UGraphicEps; + +final public class DotMaker2 implements GraphvizMaker { + + private final DotData data; + + private static boolean isJunit = false; + + private final List dotStrings; + private boolean underline = false; + private final Rose rose = new Rose(); + + private static String lastDotSignature; + + private final FileFormat fileFormat; + + private final boolean isVisibilityModifierPresent; + + // http://www.graphviz.org/bugs/b2114.html + private static final boolean TURN_AROUND_B2114 = false; + + private final Set hasAlreadyOneIncommingArrowLenghtOne; + + final private Set rankMin = new HashSet(); + + public static void goJunit() { + isJunit = true; + } + + public DotMaker2(DotData data, List dotStrings, FileFormat fileFormat) { + this.data = data; + this.dotStrings = dotStrings; + this.fileFormat = fileFormat; + if (data.getSkinParam().classAttributeIconSize() > 0) { + this.isVisibilityModifierPresent = data.isThereVisibilityImages(); + } else { + this.isVisibilityModifierPresent = false; + } + this.hasAlreadyOneIncommingArrowLenghtOne = TURN_AROUND_B2114 ? new HashSet() : null; + } + + public String createDotString() throws IOException { + + final StringBuilder sb = new StringBuilder(); + + initPrintWriter(sb); + printGroups(sb, null); + printEntities(sb, getUnpackagedEntities()); + printLinks(sb, data.getLinks()); + printRanking(sb); + sb.append("}"); + + // System.err.println(sb); + if (isJunit) { + lastDotSignature = SignatureUtils.getSignatureWithoutImgSrc(sb.toString()); + } + return sb.toString(); + } + + private void printRanking(StringBuilder sb) { + if (rankMin.size() == 0) { + return; + } + sb.append("{ rank = min;"); + for (String id : rankMin) { + sb.append(id); + sb.append(";"); + } + sb.append("}"); + + } + + private void initPrintWriter(StringBuilder sb) { + + Log.info("Entities = " + data.getEntities().size()); + final boolean huge = data.getEntities().size() > 800; + + sb.append("digraph unix {"); + // if (isJunit == false) { + for (String s : dotStrings) { + sb.append(s); + } + // } + sb.append("bgcolor=\"" + data.getSkinParam().getBackgroundColor().getAsHtml() + "\";"); + if (huge) { + sb.append("size=\"400,400;\""); + } else { + sb.append("ratio=auto;"); + // sb.append("concentrate=true;"); + } + // sb.append("ordering=out;"); + sb.append("compound=true;"); + final DotSplines dotSplines = data.getSkinParam().getDotSplines(); + final GraphvizLayoutStrategy strategy = data.getSkinParam().getStrategy(); + if (dotSplines == DotSplines.ORTHO) { + sb.append("splines=ortho;"); + } else if (dotSplines == DotSplines.POLYLINE) { + sb.append("splines=polyline;"); + } else if (strategy != GraphvizLayoutStrategy.DOT) { + sb.append("splines=true;"); + } + + // if (strategy == GraphvizLayoutStrategy.NEATO) { + // sb.append("overlap=false;"); + // } + if (strategy != GraphvizLayoutStrategy.DOT) { + sb.append("layout=" + strategy.name().toLowerCase() + ";"); + sb.append("overlap=false;"); + } + + sb.append("remincross=true;"); + sb.append("searchsize=500;"); + if (data.getRankdir() == Rankdir.LEFT_TO_RIGHT) { + sb.append("rankdir=LR;"); + } + + if (data.getDpi() != 96) { + sb.append("dpi=" + data.getDpi() + ";"); + sb.append("imagescale=both;"); + } + } + + private Collection getUnpackagedEntities() { + final List result = new ArrayList(); + for (IEntity ent : data.getEntities().values()) { + if (ent.getParent() == data.getTopParent()) { + result.add(ent); + } + } + return result; + } + + private void printGroups(StringBuilder sb, Group parent) throws IOException { + for (Group g : data.getGroupHierarchy().getChildrenGroups(parent)) { + if (data.isEmpty(g) && g.getType() == GroupType.PACKAGE) { + final IEntity folder = new Entity(g.getUid(), g.getCode(), g.getDisplay(), EntityType.EMPTY_PACKAGE, + null, null); + printEntity(sb, folder); + } else { + printGroup(sb, g); + } + } + } + + private void printGroup(StringBuilder sb, Group g) throws IOException { + if (g.getType() == GroupType.CONCURRENT_STATE) { + return; + } + + if (isSpecialGroup(g)) { + printGroupSpecial(sb, g); + } else { + printGroupNormal(sb, g); + } + } + + private void printGroupNormal(StringBuilder sb, Group g) throws IOException { + + final String stereo = g.getStereotype(); + + sb.append("subgraph " + g.getUid() + " {"); + // sb.append("margin=10;"); + + sb.append("fontsize=\"" + data.getSkinParam().getFontSize(getFontParamForGroup(), stereo) + "\";"); + final String fontFamily = data.getSkinParam().getFontFamily(getFontParamForGroup(), stereo); + if (fontFamily != null) { + sb.append("fontname=\"" + fontFamily + "\";"); + } + + if (g.getDisplay() != null) { + sb.append("label=<" + manageHtmlIB(g.getDisplay(), getFontParamForGroup(), stereo) + ">;"); + } + final String fontColor = data.getSkinParam().getFontHtmlColor(getFontParamForGroup(), stereo).getAsHtml(); + sb.append("fontcolor=\"" + fontColor + "\";"); + + if (getGroupBackColor(g) != null) { + sb.append("fillcolor=\"" + getGroupBackColor(g).getAsHtml() + "\";"); + } + + if (g.getType() == GroupType.STATE) { + sb.append("color=" + getColorString(ColorParam.stateBorder, stereo) + ";"); + } else { + sb.append("color=" + getColorString(ColorParam.packageBorder, stereo) + ";"); + } + sb.append("style=\"" + getStyle(g) + "\";"); + + printGroups(sb, g); + + this.printEntities(sb, g.entities().values()); + for (Link link : data.getLinks()) { + eventuallySameRank(sb, g, link); + } + sb.append("}"); + } + + private HtmlColor getGroupBackColor(Group g) { + HtmlColor value = g.getBackColor(); + if (value == null) { + value = data.getSkinParam().getHtmlColor(ColorParam.packageBackground, null); + // value = rose.getHtmlColor(this.data.getSkinParam(), + // ColorParam.packageBackground); + } + return value; + } + + private void printGroupSpecial(StringBuilder sb, Group g) throws IOException { + + sb.append("subgraph " + g.getUid() + "a {"); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("style=dotted;"); + sb.append("label=\"a\";"); + } else { + sb.append("style=invis;"); + sb.append("label=\"\";"); + } + + sb.append("subgraph " + g.getUid() + "v {"); + sb.append("style=solid;"); + // sb.append("margin=10;"); + + final List autolinks = data.getAutoLinks(g); + final List toEdgeLinks = data.getToEdgeLinks(g); + final List fromEdgeLinks = data.getFromEdgeLinks(g); + final boolean autoLabel = autolinks.size() == 1; + + final List nodesHiddenUidOut = getNodesHiddenUidOut(g); + final List nodesHiddenUidIn = getNodesHiddenUidIn(g); + final List nodesHiddenUid = new ArrayList(nodesHiddenUidOut); + nodesHiddenUid.addAll(nodesHiddenUidIn); + for (Link link : nodesHiddenUid) { + final String uid = getHiddenNodeUid(g, link); + // sb.append("subgraph " + g.getUid() + "k" + uid + " {"); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("style=dotted;"); + sb.append("label=\"k" + uid + "\";"); + } else { + sb.append("style=invis;"); + sb.append("label=\"\";"); + } + if (OptionFlags.getInstance().isDebugDot()) { + sb.append(uid + ";"); + } else { + sb.append(uid + " [shape=point,width=.01,style=invis,label=\"\"];"); + } + // sb.append("}"); // end of k + } + + for (int j = 1; j < nodesHiddenUidOut.size(); j++) { + for (int i = 0; i < j; i++) { + final Link linki = nodesHiddenUidOut.get(i); + final Link linkj = nodesHiddenUidOut.get(j); + if (linki.getEntity2() != linkj.getEntity2()) { + continue; + } + final String uidi = getHiddenNodeUid(g, linki); + final String uidj = getHiddenNodeUid(g, linkj); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append(uidi + "->" + uidj + ";"); + } else { + sb.append(uidi + "->" + uidj + " [style=invis,arrowtail=none,arrowhead=none];"); + } + + } + } + + if (autoLabel /* || toEdgeLinks.size() > 0 || fromEdgeLinks.size() > 0 */) { + if (OptionFlags.getInstance().isDebugDot()) { + sb.append(g.getUid() + "lmin;"); + sb.append(g.getUid() + "lmax;"); + sb.append(g.getUid() + "lmin->" + g.getUid() + "lmax [minlen=2]; "); + } else { + sb.append(g.getUid() + "lmin [shape=point,width=.01,style=invis,label=\"\"];"); + sb.append(g.getUid() + "lmax [shape=point,width=.01,style=invis,label=\"\"];"); + sb.append(g.getUid() + "lmin->" + g.getUid() + + "lmax [minlen=2,style=invis,arrowtail=none,arrowhead=none]; "); + } + } + // sb.append(g.getUid() + "min->" + g.getUid() + "max;"); + + sb.append("fontsize=\"" + data.getSkinParam().getFontSize(getFontParamForGroup(), null) + "\";"); + final String fontFamily = data.getSkinParam().getFontFamily(getFontParamForGroup(), null); + if (fontFamily != null) { + sb.append("fontname=\"" + fontFamily + "\";"); + } + + if (g.getDisplay() != null) { + final StringBuilder label = new StringBuilder(manageHtmlIB(g.getDisplay(), getFontParamForGroup(), null)); + if (g.getEntityCluster().getFieldsToDisplay().size() > 0) { + label.append("
"); + for (Member att : g.getEntityCluster().getFieldsToDisplay()) { + label.append(manageHtmlIB(" " + att.getDisplayWithVisibilityChar() + " ", + FontParam.STATE_ATTRIBUTE, null)); + label.append("
"); + } + } + sb.append("label=<" + label + ">;"); + } + + final String fontColor = data.getSkinParam().getFontHtmlColor(getFontParamForGroup(), null).getAsHtml(); + sb.append("fontcolor=\"" + fontColor + "\";"); + final HtmlColor groupBackColor = getGroupBackColor(g); + if (groupBackColor != null) { + sb.append("fillcolor=\"" + groupBackColor.getAsHtml() + "\";"); + } + if (g.getType() == GroupType.STATE) { + sb.append("color=" + getColorString(ColorParam.stateBorder, null) + ";"); + } else { + sb.append("color=" + getColorString(ColorParam.packageBorder, null) + ";"); + } + sb.append("style=\"" + getStyle(g) + "\";"); + + sb.append("subgraph " + g.getUid() + "i {"); + sb.append("label=\"i\";"); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("style=dotted;"); + sb.append("label=\"i\";"); + } else { + + if (groupBackColor == null) { + sb.append("style=invis;"); + } else { + final String colorBack = getColorString(ColorParam.background, null); + sb.append("fillcolor=" + colorBack + ";"); + sb.append("color=" + colorBack + ";"); + sb.append("style=\"filled,rounded\";"); + } + sb.append("label=\"\";"); + + } + + printGroups(sb, g); + + this.printEntities(sb, g.entities().values()); + for (Link link : data.getLinks()) { + eventuallySameRank(sb, g, link); + } + + for (int i = 0; i < fromEdgeLinks.size(); i++) { + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("eds" + i + ";"); + } else { + sb.append("eds" + i + " [shape=point,width=.01,style=invis,label=\"\"];"); + } + sb.append("eds" + i + " ->" + fromEdgeLinks.get(i).getEntity2().getUid() + + " [minlen=2,style=invis,arrowtail=none,arrowhead=none]; "); + + } + + sb.append("}"); // end of i + sb.append("}"); // end of v + + if (autoLabel) { + sb.append("subgraph " + g.getUid() + "l {"); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("style=dotted;"); + sb.append("label=\"l\";"); + } else { + sb.append("style=invis;"); + sb.append("label=\"\";"); + } + final String decorationColor = ",color=" + getColorString(getArrowColorParam(), null); + + sb.append(g.getUid() + "lab0 [shape=point,width=.01,label=\"\"" + decorationColor + "]"); + String autolabel = autolinks.get(0).getLabel(); + if (autolabel == null) { + autolabel = ""; + } + sb.append(g.getUid() + "lab1 [label=<" + manageHtmlIB(autolabel, getArrowFontParam(), null) + + ">,shape=plaintext,margin=0];"); + sb.append(g.getUid() + "lab0 -> " + g.getUid() + "lab1 [minlen=0,style=invis];"); + sb.append("}"); // end of l + + sb.append(g.getUid() + "lmin -> " + g.getUid() + "lab0 [ltail=" + g.getUid() + + "v,arrowtail=none,arrowhead=none" + decorationColor + "];"); + sb.append(g.getUid() + "lab0 -> " + g.getUid() + "lmax [lhead=" + g.getUid() + + "v,arrowtail=none,arrowhead=open" + decorationColor + "];"); + } + + for (int i = 0; i < fromEdgeLinks.size(); i++) { + sb.append("subgraph " + g.getUid() + "ed" + i + " {"); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("style=dotted;"); + sb.append("label=\"ed" + i + "\";"); + } else { + sb.append("style=invis;"); + sb.append("label=\"\";"); + } + final String decorationColor = ",color=" + getColorString(getArrowColorParam(), null); + String label = fromEdgeLinks.get(i).getLabel(); + if (label == null) { + label = ""; + } + + sb.append(g.getUid() + "fedge" + i + " [shape=point,width=.01,label=\"\"" + decorationColor + "]"); + sb.append("}"); // end of ed + sb.append("eds" + i + " -> " + g.getUid() + "fedge" + i + " [ltail=" + g.getUid() + + "v,arrowtail=none,arrowhead=none" + decorationColor + "];"); + sb.append(g.getUid() + "fedge" + i + " -> " + fromEdgeLinks.get(i).getEntity2().getUid() + + "[arrowtail=none,arrowhead=open" + decorationColor); + sb.append(",label=<" + manageHtmlIB(label, getArrowFontParam(), null) + ">];"); + + } + sb.append("}"); // end of a + } + + private FontParam getFontParamForGroup() { + if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + return FontParam.STATE; + } + return FontParam.PACKAGE; + } + + private String getStyle(Group g) { + final StringBuilder sb = new StringBuilder(); + if (g.isBold()) { + sb.append("bold"); + } else if (g.isDashed()) { + sb.append("dashed"); + } else { + sb.append("solid"); + + } + if (getGroupBackColor(g) != null) { + sb.append(",filled"); + } + if (g.isRounded()) { + sb.append(",rounded"); + } + return sb.toString(); + } + + private void printLinks(StringBuilder sb, List links) throws IOException { + for (Link link : appendPhantomLink(links)) { + final IEntity entity1 = link.getEntity1(); + final IEntity entity2 = link.getEntity2(); + if (entity1 == entity2 && entity1.getType() == EntityType.GROUP) { + continue; + } + if (entity1.getType() == EntityType.GROUP && entity2.getParent() == entity1.getParent()) { + continue; + } + if (entity2.getType() == EntityType.GROUP && entity1.getParent() == entity2.getParent()) { + continue; + } + if (entity1.getType() == EntityType.LOLLIPOP || entity2.getType() == EntityType.LOLLIPOP) { + continue; + } + // System.err.println("outing " + link); + printLink(sb, link); + } + } + + private void printLink(StringBuilder sb, Link link) throws IOException { + final StringBuilder decoration = getLinkDecoration(link); + + if (link.getWeight() > 1) { + decoration.append("weight=" + link.getWeight() + ","); + } + if (link.getLabeldistance() != null) { + decoration.append("labeldistance=" + link.getLabeldistance() + ","); + } + if (link.getLabelangle() != null) { + decoration.append("labelangle=" + link.getLabelangle() + ","); + } + + final DrawFile noteLink = link.getImageFile(); + + boolean hasLabel = false; + + if (link.getLabel() != null) { + decoration.append("label=<" + manageHtmlIB(link.getLabel(), getArrowFontParam(), null) + ">,"); + hasLabel = true; + } else if (noteLink != null) { + decoration + .append("label=<" + getHtmlForLinkNote(noteLink.getPngOrEps(fileFormat == FileFormat.EPS)) + ">,"); + hasLabel = true; + } + + if (link.getQualifier1() != null) { + decoration.append("taillabel=<" + manageHtmlIB(link.getQualifier1(), getArrowFontParam(), null) + ">,"); + } + if (link.getQualifier2() != null) { + decoration.append("headlabel=<" + manageHtmlIB(link.getQualifier2(), getArrowFontParam(), null) + ">,"); + } + final int len = link.getLength(); + String uid1 = link.getEntity1().getUid(); + String uid2 = link.getEntity2().getUid(); + LinkType typeToDraw = link.getType(); + if (TURN_AROUND_B2114 && len == 1 && hasAlreadyOneIncommingArrowLenghtOne.contains(uid2) && hasLabel) { + typeToDraw = typeToDraw.getInv(); + } + if (TURN_AROUND_B2114 && len == 1) { + hasAlreadyOneIncommingArrowLenghtOne.add(uid2); + } + decoration.append(typeToDraw.getSpecificDecoration()); + if (link.isInvis()) { + decoration.append(",style=invis"); + } + + // if (len == 1) { + // decoration.append(",constraint=false"); + // } + final String lenString = len >= 3 ? ",minlen=" + (len - 1) : ""; + + if (link.getEntity1().getType() == EntityType.GROUP) { + uid1 = getHiddenNodeUid(link.getEntity1().getParent(), link); + decoration.append(",ltail=" + link.getEntity1().getParent().getUid() + "v"); + } + if (link.getEntity2().getType() == EntityType.GROUP) { + uid2 = getHiddenNodeUid(link.getEntity2().getParent(), link); + decoration.append(",lhead=" + link.getEntity2().getParent().getUid() + "v"); + } + + sb.append(uid1 + ":here -> " + uid2 + ":here"); + sb.append(decoration); + sb.append(lenString + "];"); + eventuallySameRank(sb, data.getTopParent(), link); + } + + private List getNodesHiddenUidOut(Group g) { + final List result = new ArrayList(); + for (Link link : data.getLinks()) { + if (link.getEntity1().getParent() == link.getEntity2().getParent()) { + continue; + } + if (link.getEntity1().getType() == EntityType.GROUP && link.getEntity1().getParent() == g) { + result.add(link); + } + } + return Collections.unmodifiableList(result); + } + + private List getNodesHiddenUidIn(Group g) { + final List result = new ArrayList(); + for (Link link : data.getLinks()) { + if (link.getEntity1().getParent() == link.getEntity2().getParent()) { + continue; + } + if (link.getEntity2().getType() == EntityType.GROUP && link.getEntity2().getParent() == g) { + result.add(link); + } + } + return Collections.unmodifiableList(result); + } + + private String getHiddenNodeUid(Group g, Link link) { + if (data.isEmpty(g) && g.getType() == GroupType.PACKAGE) { + return g.getUid(); + } + return g.getUid() + "_" + link.getUid(); + } + + private StringBuilder getLinkDecoration(Link link) { + final StringBuilder decoration = new StringBuilder("[color="); + if (link.getSpecificColor() == null) { + decoration.append(getColorString(getArrowColorParam(), null)); + } else { + decoration.append("\"" + link.getSpecificColor().getAsHtml() + "\""); + } + decoration.append(","); + + decoration.append("fontcolor=" + getFontColorString(getArrowFontParam(), null) + ","); + decoration.append("fontsize=\"" + data.getSkinParam().getFontSize(getArrowFontParam(), null) + "\","); + + final String fontName = data.getSkinParam().getFontFamily(getArrowFontParam(), null); + if (fontName != null) { + decoration.append("fontname=\"" + fontName + "\","); + } + return decoration; + } + + private List appendPhantomLink(List links) { + final List result = new ArrayList(links); + for (Link link : links) { + if (link.getLength() != 1) { + continue; + } + final DrawFile noteLink = link.getImageFile(); + if (noteLink == null) { + continue; + } + final Link phantom = new Link(link.getEntity1(), link.getEntity2(), link.getType(), null, link.getLength()); + phantom.setInvis(true); + result.add(phantom); + } + return result; + } + + private String getHtmlForLinkNote(File image) { + final String circleInterfaceAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(image); + final StringBuilder sb = new StringBuilder(""); + sb.append(""); + sb.append("
"); + return sb.toString(); + + } + + private FontParam getArrowFontParam() { + if (data.getUmlDiagramType() == UmlDiagramType.CLASS) { + return FontParam.CLASS_ARROW; + } else if (data.getUmlDiagramType() == UmlDiagramType.OBJECT) { + return FontParam.OBJECT_ARROW; + } else if (data.getUmlDiagramType() == UmlDiagramType.USECASE) { + return FontParam.USECASE_ARROW; + } else if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + return FontParam.ACTIVITY_ARROW; + } else if (data.getUmlDiagramType() == UmlDiagramType.COMPONENT) { + return FontParam.COMPONENT_ARROW; + } else if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + return FontParam.STATE_ARROW; + } + throw new IllegalStateException(); + } + + private ColorParam getArrowColorParam() { + if (data.getUmlDiagramType() == UmlDiagramType.CLASS) { + return ColorParam.classArrow; + } else if (data.getUmlDiagramType() == UmlDiagramType.OBJECT) { + return ColorParam.objectArrow; + } else if (data.getUmlDiagramType() == UmlDiagramType.USECASE) { + return ColorParam.usecaseArrow; + } else if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + return ColorParam.activityArrow; + } else if (data.getUmlDiagramType() == UmlDiagramType.COMPONENT) { + return ColorParam.componentArrow; + } else if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + return ColorParam.stateArrow; + } + throw new IllegalStateException(); + } + + private String getColorString(ColorParam colorParam, String stereotype) { + return "\"" + rose.getHtmlColor(data.getSkinParam(), colorParam, stereotype).getAsHtml() + "\""; + } + + private String getFontColorString(FontParam fontParam, String stereotype) { + return "\"" + getFontHtmlColor(fontParam, stereotype).getAsHtml() + "\""; + } + + private HtmlColor getFontHtmlColor(FontParam fontParam, String stereotype) { + return data.getSkinParam().getFontHtmlColor(fontParam, stereotype); + } + + private void eventuallySameRank(StringBuilder sb, Group entityPackage, Link link) { + final int len = link.getLength(); + if (len == 1 && link.getEntity1().getParent() == entityPackage + && link.getEntity2().getParent() == entityPackage) { + if (link.getEntity1().getType() == EntityType.GROUP) { + throw new IllegalArgumentException(); + } + if (link.getEntity2().getType() == EntityType.GROUP) { + throw new IllegalArgumentException(); + } + sb.append("{rank=same; " + link.getEntity1().getUid() + "; " + link.getEntity2().getUid() + "}"); + } + } + + private void printEntities(StringBuilder sb, Collection entities) throws IOException { + final Set lollipops = new HashSet(); + final Set lollipopsFriends = new HashSet(); + for (IEntity entity : entities) { + if (entity.getType() == EntityType.LOLLIPOP) { + lollipops.add(entity); + lollipopsFriends.add(getConnectedToLollipop(entity)); + } + } + for (IEntity entity : entities) { + if (lollipops.contains(entity) || lollipopsFriends.contains(entity)) { + continue; + } + printEntity(sb, entity); + } + + for (IEntity ent : lollipopsFriends) { + sb.append("subgraph cluster" + ent.getUid() + "lol {"); + sb.append("style=invis;"); + sb.append("label=\"\";"); + printEntity(sb, ent); + for (IEntity lollipop : getAllLollipop(ent)) { + final Link link = getLinkLollipop(lollipop, ent); + final String headOrTail = getHeadOrTail(lollipop, link); + printEntity(sb, lollipop, headOrTail); + printLink(sb, link); + } + sb.append("}"); + } + + } + + private Collection getAllLollipop(IEntity entity) { + final Collection result = new ArrayList(); + for (IEntity lollipop : data.getAllLinkedDirectedTo(entity)) { + if (lollipop.getType() == EntityType.LOLLIPOP) { + result.add(lollipop); + } + } + return result; + } + + private IEntity getConnectedToLollipop(IEntity lollipop) { + assert lollipop.getType() == EntityType.LOLLIPOP; + final Collection linked = data.getAllLinkedDirectedTo(lollipop); + if (linked.size() != 1) { + throw new IllegalStateException("size=" + linked.size()); + } + return linked.iterator().next(); + } + + private Link getLinkLollipop(IEntity lollipop, IEntity ent) { + assert lollipop.getType() == EntityType.LOLLIPOP; + for (Link link : data.getLinks()) { + if (link.isBetween(lollipop, ent)) { + return link; + } + } + throw new IllegalArgumentException(); + } + + private void printEntity(StringBuilder sb, IEntity entity, String headOrTail) throws IOException { + final EntityType type = entity.getType(); + if (type == EntityType.LOLLIPOP) { + final String color1 = getColorString(ColorParam.classBackground, null); + final String color2 = getColorString(ColorParam.classBorder, null); + final String colorBack = getColorString(ColorParam.background, null); + final String labelLo = manageHtmlIB(entity.getDisplay(), FontParam.CLASS_ATTRIBUTE, null); + sb.append(entity.getUid() + " [fillcolor=" + color1 + ",color=" + color2 + ",style=\"filled\"," + + "shape=circle,width=0.12,height=0.12,label=\"\"];"); + sb.append(entity.getUid() + " -> " + entity.getUid() + "[color=" + colorBack + + ",arrowtail=none,arrowhead=none," + headOrTail + "=<" + labelLo + ">];"); + } else { + throw new IllegalStateException(type.toString() + " " + data.getUmlDiagramType()); + } + + } + + private void printEntity(StringBuilder sb, IEntity entity) throws IOException { + final EntityType type = entity.getType(); + final String label = getLabel(entity); + if (type == EntityType.GROUP) { + return; + } + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + if (type == EntityType.ABSTRACT_CLASS || type == EntityType.CLASS || type == EntityType.INTERFACE + || type == EntityType.ENUM) { + String dec = " [fontcolor=" + getFontColorString(FontParam.CLASS, stereo) + ",margin=0,fillcolor=" + + getColorString(ColorParam.background, stereo) + ",color=" + + getColorString(ColorParam.background, stereo) + ",style=filled,shape=box," + label; + if (this.data.hasUrl() && entity.getUrl() != null) { + dec += ",URL=\"" + entity.getUrl() + "\""; + } + dec += "];"; + sb.append(entity.getUid() + dec); + } else if (type == EntityType.OBJECT) { + sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.CLASS, stereo) + + ",margin=0,fillcolor=" + getColorString(ColorParam.classBackground, stereo) + ",color=" + + getColorString(ColorParam.classBorder, stereo) + ",style=filled,shape=record," + label + "];"); + } else if (type == EntityType.USECASE) { + sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.USECASE, stereo) + ",fillcolor=" + + getColorString(ColorParam.usecaseBackground, stereo) + ",color=" + + getColorString(ColorParam.usecaseBorder, stereo) + ",style=filled," + label + "];"); + } else if (type == EntityType.ACTOR) { + sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.USECASE_ACTOR, stereo) + + ",margin=0,shape=plaintext," + label + "];"); + } else if (type == EntityType.CIRCLE_INTERFACE) { + sb.append(entity.getUid() + " [margin=0,shape=plaintext," + label + "];"); + } else if (type == EntityType.COMPONENT) { + sb.append(entity.getUid() + " [margin=0.2,fontcolor=" + getFontColorString(FontParam.COMPONENT, stereo) + + ",fillcolor=" + getColorString(ColorParam.componentBackground, stereo) + ",color=" + + getColorString(ColorParam.componentBorder, stereo) + ",style=filled,shape=component," + label + + "];"); + } else if (type == EntityType.NOTE && data.getDpi() != 96) { + sb.append(entity.getUid() + " [margin=0,pad=0,shape=plaintext,label=" + getLabelForNoteDpi(entity) + "];"); + } else if (type == EntityType.NOTE) { + final DrawFile file = entity.getImageFile(); + if (file == null) { + throw new IllegalStateException("No file for NOTE"); + } + if (file.getPngOrEps(fileFormat == FileFormat.EPS).exists() == false) { + throw new IllegalStateException(); + } + final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file + .getPngOrEps(fileFormat == FileFormat.EPS)); + sb.append(entity.getUid() + " [margin=0,pad=0," + label + ",shape=none,image=\"" + absolutePath + "\"];"); + } else if (type == EntityType.ACTIVITY) { + String shape = "octagon"; + if (data.getSkinParam().useOctagonForActivity() == false || entity.getImageFile() != null) { + shape = "rect"; + } + sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.ACTIVITY, stereo) + ",fillcolor=" + + getBackColorOfEntity(entity) + ",color=" + getColorString(ColorParam.activityBorder, stereo) + + ",style=\"rounded,filled\",shape=" + shape + "," + label + "];"); + } else if (type == EntityType.BRANCH) { + sb.append(entity.getUid() + " [fillcolor=" + getBackColorOfEntity(entity) + ",color=" + + getColorString(ColorParam.activityBorder, stereo) + + ",style=\"filled\",shape=diamond,height=.25,width=.25,label=\"\"];"); + // if (StringUtils.isNotEmpty(entity.getDisplay())) { + // sb.append(entity.getUid() + "->" + entity.getUid() + + // "[taillabel=\"" + entity.getDisplay() + // + "\",arrowtail=none,arrowhead=none,color=\"white\"];"); + // } + } else if (type == EntityType.SYNCHRO_BAR) { + final String color = getColorString(ColorParam.activityBar, null); + sb.append(entity.getUid() + " [fillcolor=" + color + ",color=" + color + ",style=\"filled\"," + + "shape=rect,height=.08,width=1.30,label=\"\"];"); + } else if (type == EntityType.CIRCLE_START) { + final String color = getColorString(getStartColorParam(), null); + sb.append(entity.getUid() + " [fillcolor=" + color + ",color=" + color + ",style=\"filled\"," + + "shape=circle,width=.20,height=.20,label=\"\"];"); + } else if (type == EntityType.CIRCLE_END) { + final String color = getColorString(getEndColorParam(), null); + sb.append(entity.getUid() + " [fillcolor=" + color + ",color=" + color + ",style=\"filled\"," + + "shape=doublecircle,width=.13,height=.13,label=\"\"];"); + } else if (type == EntityType.POINT_FOR_ASSOCIATION) { + sb.append(entity.getUid() + " [width=.05,shape=point,color=" + getColorString(ColorParam.classBorder, null) + + "];"); + } else if (type == EntityType.STATE) { + sb.append(entity.getUid() + " [color=" + getColorString(ColorParam.stateBorder, stereo) + + ",shape=record,style=\"rounded,filled\",color=" + getColorString(ColorParam.stateBorder, stereo)); + if (entity.getImageFile() == null) { + sb.append(",fillcolor=" + getBackColorOfEntity(entity)); + } else { + sb.append(",fillcolor=" + getBackColorOfEntity(entity)); + // sb.append(",fillcolor=\"" + + // data.getSkinParam().getBackgroundColor().getAsHtml() + "\""); + } + sb.append("," + label + "];"); + } else if (type == EntityType.STATE_CONCURRENT) { + final DrawFile file = entity.getImageFile(); + if (file == null) { + throw new IllegalStateException(); + } + if (file.getPng().exists() == false) { + throw new IllegalStateException(); + } + final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file.getPng()); + sb.append(entity.getUid() + " [margin=1,pad=1," + label + ",style=dashed,shape=box,image=\"" + absolutePath + + "\"];"); + } else if (type == EntityType.ACTIVITY_CONCURRENT) { + final DrawFile file = entity.getImageFile(); + if (file == null) { + throw new IllegalStateException(); + } + if (file.getPng().exists() == false) { + throw new IllegalStateException(); + } + final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file.getPng()); + sb.append(entity.getUid() + " [margin=0,pad=0," + label + ",style=dashed,shape=box,image=\"" + absolutePath + + "\"];"); + } else if (type == EntityType.EMPTY_PACKAGE) { + sb.append(entity.getUid() + " [margin=0.2,fontcolor=" + getFontColorString(FontParam.PACKAGE, null) + + ",fillcolor=" + getColorString(ColorParam.packageBackground, null) + ",color=" + + getColorString(ColorParam.packageBorder, null) + ",style=filled,shape=tab," + label + "];"); + } else { + throw new IllegalStateException(type.toString() + " " + data.getUmlDiagramType()); + } + + if (entity.isTop()) { + rankMin.add(entity.getUid()); + } + + } + + private ColorParam getEndColorParam() { + if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + return ColorParam.activityEnd; + } + if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + return ColorParam.stateEnd; + } + throw new IllegalStateException(data.getUmlDiagramType().toString()); + } + + private ColorParam getStartColorParam() { + if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + return ColorParam.activityStart; + } + if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + return ColorParam.stateStart; + } + throw new IllegalStateException(data.getUmlDiagramType().toString()); + } + + private String getHeadOrTail(IEntity lollipop, Link link) { + assert lollipop.getType() == EntityType.LOLLIPOP; + if (link.getLength() > 1 && link.getEntity1() == lollipop) { + return "taillabel"; + } + return "headlabel"; + } + + private String getLabel(IEntity entity) throws IOException { + if (entity.getType() == EntityType.ABSTRACT_CLASS || entity.getType() == EntityType.CLASS + || entity.getType() == EntityType.INTERFACE || entity.getType() == EntityType.ENUM) { + return "label=" + getLabelForClassOrInterfaceOrEnum(entity); + } else if (entity.getType() == EntityType.LOLLIPOP) { + return "label=" + getLabelForLollipop(entity); + } else if (entity.getType() == EntityType.OBJECT) { + return "label=" + getLabelForObject(entity); + } else if (entity.getType() == EntityType.ACTOR) { + return "label=" + getLabelForActor(entity); + } else if (entity.getType() == EntityType.CIRCLE_INTERFACE) { + return "label=" + getLabelForCircleInterface(entity); + } else if (entity.getType() == EntityType.NOTE) { + return "label=\"\""; + } else if (entity.getType() == EntityType.STATE_CONCURRENT) { + return "label=\"\""; + } else if (entity.getType() == EntityType.ACTIVITY_CONCURRENT) { + return "label=\"\""; + } else if (entity.getType() == EntityType.COMPONENT) { + return "label=" + getLabelForComponent(entity); + } else if (entity.getType() == EntityType.ACTIVITY) { + final DrawFile drawFile = entity.getImageFile(); + if (drawFile != null) { + final String path = StringUtils.getPlateformDependentAbsolutePath(drawFile.getPng()); + final String bgcolor = "\"" + data.getSkinParam().getBackgroundColor().getAsHtml() + "\""; + final StringBuilder sb = new StringBuilder("label=<"); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("
"); + sb.append("
"); + sb.append(">"); + return sb.toString(); + } + final String stereotype = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + return "label=" + getSimpleLabelAsHtml(entity, FontParam.ACTIVITY, stereotype); + } else if (entity.getType() == EntityType.EMPTY_PACKAGE) { + return "label=" + getSimpleLabelAsHtml(entity, getFontParamForGroup(), null); + } else if (entity.getType() == EntityType.USECASE) { + return "label=" + getLabelForUsecase(entity); + } else if (entity.getType() == EntityType.STATE) { + return "label=" + getLabelForState(entity); + } + return "label=\"" + entity.getDisplay() + "\""; + } + + private String getSimpleLabelAsHtml(IEntity entity, FontParam param, String stereotype) { + return "<" + manageHtmlIB(entity.getDisplay(), param, stereotype) + ">"; + } + + private String getBackColorOfEntity(IEntity entity) { + if (entity.getSpecificBackColor() != null) { + return "\"" + entity.getSpecificBackColor().getAsHtml() + "\""; + } + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + if (entity.getType() == EntityType.STATE || entity.getType() == EntityType.STATE_CONCURRENT) { + return getColorString(ColorParam.stateBackground, stereo); + } + if (entity.getType() == EntityType.ACTIVITY || entity.getType() == EntityType.ACTIVITY_CONCURRENT + || entity.getType() == EntityType.BRANCH) { + return getColorString(ColorParam.activityBackground, stereo); + } + throw new IllegalArgumentException(entity.getType().toString()); + } + + private String getLabelForState(IEntity entity) throws IOException { + final DrawFile cFile = entity.getImageFile(); + final String stateBgcolor = getBackColorOfEntity(entity); + + final String stereotype = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + final StringBuilder sb = new StringBuilder("<{"); + sb.append(""); + sb.append("
" + manageHtmlIB(entity.getDisplay(), FontParam.STATE, stereotype) + "
"); + + if (entity.getFieldsToDisplay().size() > 0) { + sb.append("|"); + for (Member att : entity.getFieldsToDisplay()) { + sb.append(manageHtmlIB(att.getDisplayWithVisibilityChar(), FontParam.STATE_ATTRIBUTE, stereotype)); + sb.append("
"); + } + } + + if (cFile != null) { + sb.append("|"); + final String path = StringUtils.getPlateformDependentAbsolutePath(cFile.getPng()); + final String bgcolor; + if (OptionFlags.PBBACK) { + bgcolor = stateBgcolor; + } else { + bgcolor = "\"" + data.getSkinParam().getBackgroundColor().getAsHtml() + "\""; + } + // PBBACK + + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("
"); + sb.append("
"); + } + + if (data.isHideEmptyDescription() == false && entity.getFieldsToDisplay().size() == 0 && cFile == null) { + sb.append("|"); + } + + sb.append("}>"); + + return sb.toString(); + } + + private String getLabelForUsecase(IEntity entity) { + final Stereotype stereotype = getStereotype(entity); + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + if (stereotype == null) { + return getSimpleLabelAsHtml(entity, FontParam.USECASE, stereo); + } + final StringBuilder sb = new StringBuilder("<"); + if (isThereLabel(stereotype)) { + sb.append(""); + } + sb.append(""); + sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.USECASE_STEREOTYPE, stereo) + + "
" + manageHtmlIB(entity.getDisplay(), FontParam.USECASE, stereo) + "
>"); + return sb.toString(); + } + + private String getLabelForComponent(IEntity entity) { + final Stereotype stereotype = getStereotype(entity); + if (stereotype == null) { + return getSimpleLabelAsHtml(entity, FontParam.COMPONENT, null); + } + final String stereo = stereotype.getLabel(); + final StringBuilder sb = new StringBuilder("<"); + if (isThereLabel(stereotype)) { + sb.append(""); + } + sb.append(""); + sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.COMPONENT_STEREOTYPE, stereo) + + "
" + manageHtmlIB(entity.getDisplay(), FontParam.COMPONENT, stereo) + "
>"); + return sb.toString(); + } + + private String getLabelForNoteDpi(IEntity entity) throws IOException { + final DrawFile file = entity.getImageFile(); + if (file == null) { + throw new IllegalStateException("No file for NOTE"); + } + if (file.getPngOrEps(fileFormat == FileFormat.EPS).exists() == false) { + throw new IllegalStateException(); + } + final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file + .getPngOrEps(fileFormat == FileFormat.EPS)); + + final StringBuilder sb = new StringBuilder("<"); + sb.append(""); + addTdImageBugB1983(sb, absolutePath); + sb.append(""); + sb.append("
>"); + return sb.toString(); + } + + private void addTdImageBugB1983(final StringBuilder sb, final String absolutePath) throws IOException { + // http://www.graphviz.org/bugs/b1983.html + final BufferedImage im = ImageIO.read(new File(absolutePath)); + final int height = im.getHeight(); + final int width = im.getWidth(); + final double f = 1.0 / data.getDpiFactor(); + final int w = (int) (width * f); + final int h = (int) (height * f); + final int w2 = (int) (width * getMagicFactorForImageDpi()); + final int h2 = (int) (height * getMagicFactorForImageDpi()); + sb.append(getTdHeaderForDpi(w, h)); + sb.append(""); + sb.append(""); + sb.append(getTdHeaderForDpi(w2, h2)); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("
"); + sb.append(""); + } + + private double getMagicFactorForImageDpi() { + return 10500 / 100000.0; + } + + private String getLabelForActor(IEntity entity) throws IOException { + final String actorAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(entity.getImageFile() + .getPngOrEps(fileFormat == FileFormat.EPS)); + final Stereotype stereotype = getStereotype(entity); + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + final StringBuilder sb = new StringBuilder("<"); + if (isThereLabel(stereotype)) { + sb.append(""); + } + if (data.getDpi() == 96) { + sb.append(""); + } else { + sb.append(""); + addTdImageBugB1983(sb, actorAbsolutePath); + sb.append(""); + } + sb.append(""); + sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.USECASE_ACTOR_STEREOTYPE, stereo) + + "
" + manageHtmlIB(entity.getDisplay(), FontParam.USECASE_ACTOR, stereo) + "
>"); + return sb.toString(); + + } + + private String getLabelForCircleInterface(IEntity entity) throws IOException { + final String circleInterfaceAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(entity.getImageFile() + .getPngOrEps(fileFormat == FileFormat.EPS)); + final Stereotype stereotype = getStereotype(entity); + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + final StringBuilder sb = new StringBuilder("<"); + if (isThereLabel(stereotype)) { + sb.append(""); + } + sb.append(""); + if (data.getDpi() == 96) { + sb.append(""); + } else { + addTdImageBugB1983(sb, circleInterfaceAbsolutePath); + } + sb.append(""); + sb.append(""); + sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.COMPONENT_STEREOTYPE, stereo) + + "
" + manageHtmlIB(entity.getDisplay(), FontParam.COMPONENT, stereo) + "
>"); + return sb.toString(); + + } + + private String getLabelForLollipop(IEntity entity) throws IOException { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + final String circleInterfaceAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(data.getStaticImages( + EntityType.LOLLIPOP, stereo).getPngOrEps(fileFormat == FileFormat.EPS)); + final Stereotype stereotype = getStereotype(entity); + + final StringBuilder sb = new StringBuilder("<"); + if (isThereLabel(stereotype)) { + sb.append(""); + } + sb.append(""); + if (data.getDpi() == 96) { + sb.append(""); + } else { + addTdImageBugB1983(sb, circleInterfaceAbsolutePath); + } + sb.append(""); + sb.append(""); + sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.CLASS, null) + "
" + manageHtmlIB(entity.getDisplay(), FontParam.CLASS, null) + "
>"); + return sb.toString(); + + } + + private String getLabelForClassOrInterfaceOrEnum(IEntity entity) throws IOException { + if (isVisibilityModifierPresent) { + return getLabelForClassOrInterfaceOrEnumWithVisibilityImage(entity); + } + return getLabelForClassOrInterfaceOrEnumOld(entity); + + } + + private String getLabelForClassOrInterfaceOrEnumOld(IEntity entity) throws IOException { + + if (entity != null) { + final StringBuilder sb = new StringBuilder("<"); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("
B1
"); + sb.append(">"); + + return sb.toString(); + } + + DrawFile cFile = entity.getImageFile(); + if (cFile == null) { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + cFile = data.getStaticImages(entity.getType(), stereo); + } + if (cFile == null) { + throw new IllegalStateException(); + } + final String circleAbsolutePath; + if (data.showPortion(EntityPortion.CIRCLED_CHARACTER, entity)) { + circleAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(cFile + .getPngOrEps(fileFormat == FileFormat.EPS)); + } else { + circleAbsolutePath = null; + } + + final StringBuilder sb = new StringBuilder("<"); + + final boolean showFields = data.showPortion(EntityPortion.FIELD, entity); + final boolean showMethods = data.showPortion(EntityPortion.METHOD, entity); + + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + if (showFields == false && showMethods == false) { + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, 1, true, 1)); + } else { + sb.append(""); + sb.append(""); + + if (showFields) { + // if (fileFormat == FileFormat.EPS) { + // sb.append(addFieldsEps(entity.fields2(), true)); + // } else { + final boolean hasStatic = hasStatic(entity.getFieldsToDisplay()); + sb.append(""); + // } + } + if (showMethods) { + // if (fileFormat == FileFormat.EPS) { + // sb.append(addFieldsEps(entity.methods2(), true)); + // } else { + final boolean hasStatic = hasStatic(entity.getMethodsToDisplay()); + sb.append(""); + // } + } + sb.append("
"); + final int longuestFieldOrAttribute = getLongestFieldOrAttribute(entity); + final int longuestHeader = getLonguestHeader(entity); + final int spring = computeSpring(longuestHeader, longuestFieldOrAttribute, 30); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, spring, true, 0)); + + sb.append("
"); + for (Member att : entity.getFieldsToDisplay()) { + sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, getColorString( + ColorParam.classBackground, stereo), true)); + sb.append("
"); + } + sb.append("
"); + for (Member att : entity.getMethodsToDisplay()) { + sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, getColorString( + ColorParam.classBackground, stereo), true)); + sb.append("
"); + } + sb.append("
"); + } + sb.append(">"); + + return sb.toString(); + } + + final private List fileToClean = new ArrayList(); + + private String addFieldsEps(List members, boolean withVisibilityChar) throws IOException { + final List texts = new ArrayList(); + for (Member att : members) { + String s = att.getDisplay(withVisibilityChar); + if (att.isAbstract()) { + s = "" + s + ""; + } + if (att.isStatic()) { + s = "" + s + ""; + } + texts.add(s); + } + final Font font = data.getSkinParam().getFont(FontParam.CLASS_ATTRIBUTE, null); + final Color color = getFontHtmlColor(FontParam.CLASS_ATTRIBUTE, null).getColor(); + final TextBlock text = TextBlockUtils.create(texts, new FontConfiguration(font, color), + HorizontalAlignement.LEFT); + final File feps = FileUtils.createTempFile("member", ".eps"); + UGraphicEps.copyEpsToFile(new UDrawable() { + public void drawU(UGraphic ug) { + text.drawU(ug, 0, 0); + } + }, feps); + fileToClean.add(feps); + + final String path = StringUtils.getPlateformDependentAbsolutePath(feps); + + return "" + "" + + "" + "" + "
" + "
"; + } + + private boolean hasStatic(Collection attributes) { + for (Member att : attributes) { + if (att.isStatic()) { + return true; + } + } + return false; + } + + private String getLabelForClassOrInterfaceOrEnumWithVisibilityImage(IEntity entity) throws IOException { + DrawFile cFile = entity.getImageFile(); + if (cFile == null) { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + cFile = data.getStaticImages(entity.getType(), stereo); + } + if (cFile == null) { + throw new IllegalStateException(); + } + final String circleAbsolutePath; + if (data.showPortion(EntityPortion.CIRCLED_CHARACTER, entity)) { + circleAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(cFile + .getPngOrEps(fileFormat == FileFormat.EPS)); + } else { + circleAbsolutePath = null; + } + + final boolean showFields = data.showPortion(EntityPortion.FIELD, entity); + final boolean showMethods = data.showPortion(EntityPortion.METHOD, entity); + + final StringBuilder sb = new StringBuilder("<"); + if (showFields == false && showMethods == false) { + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, 1, true, 1)); + } else { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + final int longuestHeader = getLonguestHeader(entity); + final int spring = computeSpring(longuestHeader, getLongestFieldOrAttribute(entity), 30); + final int springField = computeSpring(getLongestField(entity), Math.max(longuestHeader, + getLongestMethods(entity)), 30); + final int springMethod = computeSpring(getLongestMethods(entity), Math.max(longuestHeader, + getLongestField(entity)), 30); + + sb.append(""); + sb.append(""); + + if (showFields) { + sb.append(""); + } + if (showMethods) { + sb.append(""); + } + sb.append("
"); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, spring, true, 0)); + sb.append("
"); + if (entity.getFieldsToDisplay().size() > 0) { + buildTableVisibility(entity, true, sb, springField); + } + sb.append("
"); + if (entity.getMethodsToDisplay().size() > 0) { + buildTableVisibility(entity, false, sb, springMethod); + } + sb.append("
"); + } + sb.append(">"); + + return sb.toString(); + + } + + private int computeSpring(final int current, final int maximum, int maxSpring) { + if (maximum <= current) { + return 0; + } + final int spring = maximum - current; + if (spring > maxSpring) { + return maxSpring; + } + return spring; + } + + private void buildTableVisibility(IEntity entity, boolean isField, final StringBuilder sb, int spring) + throws IOException { + sb.append(""); + + final boolean hasStatic = hasStatic(entity.getMethodsToDisplay()); + final boolean dpiNormal = data.getDpi() == 96; + for (Member att : isField ? entity.getFieldsToDisplay() : entity.getMethodsToDisplay()) { + sb.append(""); + if (dpiNormal) { + sb.append(""); + } + sb.append(""); + for (int i = 0; i < spring; i++) { + sb.append(""); + } + sb.append(""); + } + sb.append("
"); + } + String s = att.getDisplayWithVisibilityChar(); + final VisibilityModifier visibilityModifier = VisibilityModifier + .getVisibilityModifier(s.charAt(0), isField); + if (visibilityModifier != null) { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + final String modifierFile = StringUtils.getPlateformDependentAbsolutePath(data.getVisibilityImages( + visibilityModifier, stereo).getPngOrEps(fileFormat == FileFormat.EPS)); + if (dpiNormal) { + sb.append(""); + } else { + addTdImageBugB1983(sb, modifierFile); + } + s = s.substring(1); + } + if (dpiNormal) { + sb.append(""); + sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, getColorString( + ColorParam.classBackground, null), false)); + sb.append("
"); + } + + private int getLonguestHeader(IEntity entity) { + int result = entity.getDisplay().length(); + final Stereotype stereotype = getStereotype(entity); + if (isThereLabel(stereotype)) { + final int size = stereotype.getLabel().length(); + if (size > result) { + result = size; + } + } + return result; + } + + private int getLongestFieldOrAttribute(IEntity entity) { + return Math.max(getLongestField(entity), getLongestMethods(entity)); + } + + private int getLongestMethods(IEntity entity) { + int result = 0; + for (Member att : entity.getMethodsToDisplay()) { + final int size = att.getDisplayWithVisibilityChar().length(); + if (size > result) { + result = size; + } + } + return result; + + } + + private int getLongestField(IEntity entity) { + int result = 0; + for (Member att : entity.getFieldsToDisplay()) { + final int size = att.getDisplayWithVisibilityChar().length(); + if (size > result) { + result = size; + } + } + return result; + } + + private String getLabelForObject(IEntity entity) throws IOException { + if (isVisibilityModifierPresent) { + return getLabelForObjectWithVisibilityImage(entity); + } + return getLabelForObjectOld(entity); + } + + private String getLabelForObjectWithVisibilityImage(IEntity entity) throws IOException { + + final int longuestHeader = getLonguestHeader(entity); + final int spring = computeSpring(longuestHeader, getLongestFieldOrAttribute(entity), 30); + final int springField = computeSpring(getLongestField(entity), Math.max(longuestHeader, + getLongestMethods(entity)), 30); + + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + final StringBuilder sb = new StringBuilder("<"); + sb.append(""); + sb.append(""); + sb.append("
"); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, null, spring, false, 0)); + + sb.append("
"); + + if (entity.getFieldsToDisplay().size() == 0) { + sb.append(manageHtmlIB(" ", FontParam.OBJECT_ATTRIBUTE, stereo)); + } else { + buildTableVisibility(entity, true, sb, springField); + } + + sb.append("
>"); + + return sb.toString(); + + } + + private String getLabelForObjectOld(IEntity entity) throws IOException { + + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + final StringBuilder sb = new StringBuilder("<"); + sb.append(""); + sb.append(""); + sb.append("
"); + + final int longuestFieldOrAttribute = getLongestFieldOrAttribute(entity); + final int longuestHeader = getLonguestHeader(entity); + final int spring = computeSpring(longuestHeader, longuestFieldOrAttribute, 30); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, null, spring, false, 0)); + + sb.append("
"); + + if (entity.getFieldsToDisplay().size() == 0) { + sb.append(manageHtmlIB(" ", FontParam.OBJECT_ATTRIBUTE, stereo)); + } else { + for (Member att : entity.getFieldsToDisplay()) { + sb.append(manageHtmlIB(att.getDisplayWithVisibilityChar(), FontParam.OBJECT_ATTRIBUTE, stereo)); + sb.append("
"); + } + } + + sb.append("
>"); + + return sb.toString(); + } + + private String getWitdh55() { + if (data.getDpi() == 96) { + return "WIDTH=\"55\""; + } + return "WIDTH=\"55\""; + } + + private String manageHtmlIB(String s, FontParam param, String stereotype) { + s = unicode(s); + final int fontSize = data.getSkinParam().getFontSize(param, stereotype); + final int style = data.getSkinParam().getFontStyle(param, stereotype); + final String fontFamily = data.getSkinParam().getFontFamily(param, stereotype); + final DotExpression dotExpression = new DotExpression(s, fontSize, getFontHtmlColor(param, stereotype), + fontFamily, style, fileFormat); + final String result = dotExpression.getDotHtml(); + if (dotExpression.isUnderline()) { + underline = true; + } + return result; + } + + private String manageHtmlIBspecial(Member att, FontParam param, boolean hasStatic, String backColor, + boolean withVisibilityChar) { + String prefix = ""; + if (hasStatic) { + prefix = "_"; + } + if (att.isAbstract()) { + return prefix + manageHtmlIB("" + att.getDisplay(withVisibilityChar), param, null); + } + if (att.isStatic()) { + return manageHtmlIB("" + att.getDisplay(withVisibilityChar), param, null); + } + return prefix + manageHtmlIB(att.getDisplay(withVisibilityChar), param, null); + } + + private String manageSpace(int size) { + final DotExpression dotExpression = new DotExpression(" ", size, HtmlColor.getColorIfValid("white"), null, + Font.PLAIN, fileFormat); + final String result = dotExpression.getDotHtml(); + return result; + } + + static String unicode(String s) { + final StringBuilder result = new StringBuilder(); + for (char c : s.toCharArray()) { + if (c > 127 || c == '&') { + final int i = c; + result.append("&#" + i + ";"); + } else { + result.append(c); + } + } + return result.toString(); + } + + private String getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnumNoSpring(IEntity entity, + final String circleAbsolutePath, int cellSpacing, boolean classes) throws IOException { + final StringBuilder sb = new StringBuilder(); + sb.append(""); + sb.append(""); + if (circleAbsolutePath == null) { + sb.append(""); + } else { + addTdImageBugB1983(sb, circleAbsolutePath); + + } + sb.append("
"); + } else { + if (data.getDpi() == 96) { + sb.append(""); + sb.append(""); + } + + appendLabelAndStereotype(entity, sb, classes); + sb.append("
"); + return sb.toString(); + } + + private String getTdHeaderForDpi(final double w, final double h) { + // return ""; + return ""; + } + + private String getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(IEntity entity, final String circleAbsolutePath, + int spring, boolean classes, int border) throws IOException { + if (spring == 0) { + return getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnumNoSpring(entity, circleAbsolutePath, 0, classes); + } + final StringBuilder sb = new StringBuilder(); + + sb + .append(""); + sb.append(""); + + for (int i = 0; i < spring; i++) { + sb.append(""); + } + + if (circleAbsolutePath != null) { + if (circleAbsolutePath.endsWith(".png")) { + if (data.getDpi() == 96) { + final BufferedImage im = ImageIO.read(new File(circleAbsolutePath)); + final int height = im.getHeight(); + final int width = im.getWidth(); + sb.append(""); + } else { + addTdImageBugB1983(sb, circleAbsolutePath); + } + } else if (circleAbsolutePath.endsWith(".eps")) { + sb.append(""); + } + } + + sb.append(""); + + for (int i = 0; i < spring; i++) { + sb.append(""); + } + sb.append("
"); + appendLabelAndStereotype(entity, sb, classes); + sb.append("
"); + return sb.toString(); + } + + private void appendLabelAndStereotype(IEntity entity, final StringBuilder sb, boolean classes) { + final Stereotype stereotype = getStereotype(entity); + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + if (isThereLabel(stereotype)) { + sb.append("
"); + sb.append(manageHtmlIB(stereotype.getLabel(), classes ? FontParam.CLASS_STEREOTYPE + : FontParam.OBJECT_STEREOTYPE, stereo)); + sb.append("
"); + } + String display = entity.getDisplay(); + final boolean italic = entity.getType() == EntityType.ABSTRACT_CLASS + || entity.getType() == EntityType.INTERFACE; + if (italic) { + display = "" + display; + } + sb.append(manageHtmlIB(display, classes ? FontParam.CLASS : FontParam.OBJECT, stereo)); + } + + private String getHtmlHeaderTableForClassOrInterfaceOrEnumNew(Entity entity, final String circleAbsolutePath) { + final StringBuilder sb = new StringBuilder(); + sb.append(""); + sb.append("
"); + + appendLabelAndStereotype(entity, sb, true); + sb.append("
"); + return sb.toString(); + } + + private boolean isThereLabel(final Stereotype stereotype) { + return stereotype != null && stereotype.getLabel() != null; + } + + private Stereotype getStereotype(IEntity entity) { + if (data.showPortion(EntityPortion.STEREOTYPE, entity) == false) { + return null; + } + return entity.getStereotype(); + } + + public final boolean isUnderline() { + return underline; + } + + private boolean workAroundDotBug() { + for (Link link : data.getLinks()) { + if (link.getLength() != 1) { + return false; + } + } + if (data.getUmlDiagramType() == UmlDiagramType.CLASS && allEntitiesAreClasses(data.getEntities().values())) { + return true; + } + for (IEntity ent : data.getEntities().values()) { + if (data.getAllLinkedTo(ent).size() == 0) { + return true; + } + } + return false; + } + + private boolean allEntitiesAreClasses(Collection entities) { + for (IEntity ent : entities) { + if (ent.getType() != EntityType.CLASS && ent.getType() != EntityType.ABSTRACT_CLASS + && ent.getType() != EntityType.INTERFACE && ent.getType() != EntityType.ENUM) { + return false; + } + } + return true; + } + + private boolean isSpecialGroup(Group g) { + if (g.getType() == GroupType.STATE) { + return true; + } + if (g.getType() == GroupType.CONCURRENT_STATE) { + throw new IllegalStateException(); + } + if (data.isThereLink(g)) { + return true; + } + return false; + } + + public static final String getLastDotSignature() { + return lastDotSignature; + } + + public static final void reset() { + lastDotSignature = null; + } + + public void clean() { + if (OptionFlags.getInstance().isKeepTmpFiles()) { + return; + } + for (File f : fileToClean) { + Log.info("Deleting temporary file " + f); + final boolean ok = f.delete(); + if (ok == false) { + Log.error("Cannot delete: " + f); + } + } + } + +} diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/DotMakerOk.java b/src/net/sourceforge/plantuml/cucadiagram/dot/DotMakerOk.java new file mode 100644 index 000000000..c9d88ef67 --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/DotMakerOk.java @@ -0,0 +1,1813 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.awt.Color; +import java.awt.Font; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.imageio.ImageIO; + +import net.sourceforge.plantuml.ColorParam; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FileUtils; +import net.sourceforge.plantuml.FontParam; +import net.sourceforge.plantuml.Log; +import net.sourceforge.plantuml.OptionFlags; +import net.sourceforge.plantuml.SignatureUtils; +import net.sourceforge.plantuml.StringUtils; +import net.sourceforge.plantuml.UmlDiagramType; +import net.sourceforge.plantuml.cucadiagram.Entity; +import net.sourceforge.plantuml.cucadiagram.EntityPortion; +import net.sourceforge.plantuml.cucadiagram.EntityType; +import net.sourceforge.plantuml.cucadiagram.Group; +import net.sourceforge.plantuml.cucadiagram.GroupType; +import net.sourceforge.plantuml.cucadiagram.IEntity; +import net.sourceforge.plantuml.cucadiagram.Link; +import net.sourceforge.plantuml.cucadiagram.LinkType; +import net.sourceforge.plantuml.cucadiagram.Member; +import net.sourceforge.plantuml.cucadiagram.Rankdir; +import net.sourceforge.plantuml.cucadiagram.Stereotype; +import net.sourceforge.plantuml.graphic.FontConfiguration; +import net.sourceforge.plantuml.graphic.HorizontalAlignement; +import net.sourceforge.plantuml.graphic.HtmlColor; +import net.sourceforge.plantuml.graphic.TextBlock; +import net.sourceforge.plantuml.graphic.TextBlockUtils; +import net.sourceforge.plantuml.skin.UDrawable; +import net.sourceforge.plantuml.skin.VisibilityModifier; +import net.sourceforge.plantuml.skin.rose.Rose; +import net.sourceforge.plantuml.ugraphic.UGraphic; +import net.sourceforge.plantuml.ugraphic.eps.UGraphicEps; + +final public class DotMakerOk implements GraphvizMaker { + + private final DotData data; + + private static boolean isJunit = false; + + private final List dotStrings; + private boolean underline = false; + private final Rose rose = new Rose(); + + private static String lastDotSignature; + + private final FileFormat fileFormat; + + private final boolean isVisibilityModifierPresent; + + // http://www.graphviz.org/bugs/b2114.html + private static final boolean TURN_AROUND_B2114 = false; + + private final Set hasAlreadyOneIncommingArrowLenghtOne; + + final private Set rankMin = new HashSet(); + + public static void goJunit() { + isJunit = true; + } + + public DotMakerOk(DotData data, List dotStrings, FileFormat fileFormat) { + this.data = data; + this.dotStrings = dotStrings; + this.fileFormat = fileFormat; + if (data.getSkinParam().classAttributeIconSize() > 0) { + this.isVisibilityModifierPresent = data.isThereVisibilityImages(); + } else { + this.isVisibilityModifierPresent = false; + } + this.hasAlreadyOneIncommingArrowLenghtOne = TURN_AROUND_B2114 ? new HashSet() : null; + } + + public String createDotString() throws IOException { + + final StringBuilder sb = new StringBuilder(); + + initPrintWriter(sb); + printGroups(sb, null); + printEntities(sb, getUnpackagedEntities()); + printLinks(sb, data.getLinks()); + printRanking(sb); + sb.append("}"); + + // System.err.println(sb); + if (isJunit) { + lastDotSignature = SignatureUtils.getSignatureWithoutImgSrc(sb.toString()); + } + return sb.toString(); + } + + private void printRanking(StringBuilder sb) { + if (rankMin.size() == 0) { + return; + } + sb.append("{ rank = min;"); + for (String id : rankMin) { + sb.append(id); + sb.append(";"); + } + sb.append("}"); + + } + + private void initPrintWriter(StringBuilder sb) { + + Log.info("Entities = " + data.getEntities().size()); + final boolean huge = data.getEntities().size() > 800; + + sb.append("digraph unix {"); + // if (isJunit == false) { + for (String s : dotStrings) { + sb.append(s); + } + // } + sb.append("bgcolor=\"" + data.getSkinParam().getBackgroundColor().getAsHtml() + "\";"); + if (huge) { + sb.append("size=\"400,400;\""); + } else { + sb.append("ratio=auto;"); + // sb.append("concentrate=true;"); + } + // sb.append("ordering=out;"); + sb.append("compound=true;"); + final DotSplines dotSplines = data.getSkinParam().getDotSplines(); + final GraphvizLayoutStrategy strategy = data.getSkinParam().getStrategy(); + if (dotSplines == DotSplines.ORTHO) { + sb.append("splines=ortho;"); + } else if (dotSplines == DotSplines.POLYLINE) { + sb.append("splines=polyline;"); + } else if (strategy != GraphvizLayoutStrategy.DOT) { + sb.append("splines=true;"); + } + + // if (strategy == GraphvizLayoutStrategy.NEATO) { + // sb.append("overlap=false;"); + // } + if (strategy != GraphvizLayoutStrategy.DOT) { + sb.append("layout=" + strategy.name().toLowerCase() + ";"); + sb.append("overlap=false;"); + } + + sb.append("remincross=true;"); + sb.append("searchsize=500;"); + if (data.getRankdir() == Rankdir.LEFT_TO_RIGHT) { + sb.append("rankdir=LR;"); + } + + if (data.getDpi() != 96) { + sb.append("dpi=" + data.getDpi() + ";"); + sb.append("imagescale=both;"); + } + } + + private Collection getUnpackagedEntities() { + final List result = new ArrayList(); + for (IEntity ent : data.getEntities().values()) { + if (ent.getParent() == data.getTopParent()) { + result.add(ent); + } + } + return result; + } + + private void printGroups(StringBuilder sb, Group parent) throws IOException { + for (Group g : data.getGroupHierarchy().getChildrenGroups(parent)) { + if (data.isEmpty(g) && g.getType() == GroupType.PACKAGE) { + final IEntity folder = new Entity(g.getUid(), g.getCode(), g.getDisplay(), EntityType.EMPTY_PACKAGE, + null, null); + printEntity(sb, folder); + } else { + printGroup(sb, g); + } + } + } + + private void printGroup(StringBuilder sb, Group g) throws IOException { + if (g.getType() == GroupType.CONCURRENT_STATE) { + return; + } + + if (isSpecialGroup(g)) { + printGroupSpecial(sb, g); + } else { + printGroupNormal(sb, g); + } + } + + private void printGroupNormal(StringBuilder sb, Group g) throws IOException { + + final String stereo = g.getStereotype(); + + sb.append("subgraph " + g.getUid() + " {"); + // sb.append("margin=10;"); + + sb.append("fontsize=\"" + data.getSkinParam().getFontSize(getFontParamForGroup(), stereo) + "\";"); + final String fontFamily = data.getSkinParam().getFontFamily(getFontParamForGroup(), stereo); + if (fontFamily != null) { + sb.append("fontname=\"" + fontFamily + "\";"); + } + + if (g.getDisplay() != null) { + sb.append("label=<" + manageHtmlIB(g.getDisplay(), getFontParamForGroup(), stereo) + ">;"); + } + final String fontColor = data.getSkinParam().getFontHtmlColor(getFontParamForGroup(), stereo).getAsHtml(); + sb.append("fontcolor=\"" + fontColor + "\";"); + + if (getGroupBackColor(g) != null) { + sb.append("fillcolor=\"" + getGroupBackColor(g).getAsHtml() + "\";"); + } + + if (g.getType() == GroupType.STATE) { + sb.append("color=" + getColorString(ColorParam.stateBorder, stereo) + ";"); + } else { + sb.append("color=" + getColorString(ColorParam.packageBorder, stereo) + ";"); + } + sb.append("style=\"" + getStyle(g) + "\";"); + + printGroups(sb, g); + + this.printEntities(sb, g.entities().values()); + for (Link link : data.getLinks()) { + eventuallySameRank(sb, g, link); + } + sb.append("}"); + } + + private HtmlColor getGroupBackColor(Group g) { + HtmlColor value = g.getBackColor(); + if (value == null) { + value = data.getSkinParam().getHtmlColor(ColorParam.packageBackground, null); + // value = rose.getHtmlColor(this.data.getSkinParam(), + // ColorParam.packageBackground); + } + return value; + } + + private void printGroupSpecial(StringBuilder sb, Group g) throws IOException { + + sb.append("subgraph " + g.getUid() + "a {"); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("style=dotted;"); + sb.append("label=\"a\";"); + } else { + sb.append("style=invis;"); + sb.append("label=\"\";"); + } + + sb.append("subgraph " + g.getUid() + "v {"); + sb.append("style=solid;"); + // sb.append("margin=10;"); + + final List autolinks = data.getAutoLinks(g); + final List toEdgeLinks = data.getToEdgeLinks(g); + final List fromEdgeLinks = data.getFromEdgeLinks(g); + final boolean autoLabel = autolinks.size() == 1; + + final List nodesHiddenUidOut = getNodesHiddenUidOut(g); + final List nodesHiddenUidIn = getNodesHiddenUidIn(g); + final List nodesHiddenUid = new ArrayList(nodesHiddenUidOut); + nodesHiddenUid.addAll(nodesHiddenUidIn); + for (Link link : nodesHiddenUid) { + final String uid = getHiddenNodeUid(g, link); + // sb.append("subgraph " + g.getUid() + "k" + uid + " {"); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("style=dotted;"); + sb.append("label=\"k" + uid + "\";"); + } else { + sb.append("style=invis;"); + sb.append("label=\"\";"); + } + if (OptionFlags.getInstance().isDebugDot()) { + sb.append(uid + ";"); + } else { + sb.append(uid + " [shape=point,width=.01,style=invis,label=\"\"];"); + } + // sb.append("}"); // end of k + } + + for (int j = 1; j < nodesHiddenUidOut.size(); j++) { + for (int i = 0; i < j; i++) { + final Link linki = nodesHiddenUidOut.get(i); + final Link linkj = nodesHiddenUidOut.get(j); + if (linki.getEntity2() != linkj.getEntity2()) { + continue; + } + final String uidi = getHiddenNodeUid(g, linki); + final String uidj = getHiddenNodeUid(g, linkj); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append(uidi + "->" + uidj + ";"); + } else { + sb.append(uidi + "->" + uidj + " [style=invis,arrowtail=none,arrowhead=none];"); + } + + } + } + + if (autoLabel /* || toEdgeLinks.size() > 0 || fromEdgeLinks.size() > 0 */) { + if (OptionFlags.getInstance().isDebugDot()) { + sb.append(g.getUid() + "lmin;"); + sb.append(g.getUid() + "lmax;"); + sb.append(g.getUid() + "lmin->" + g.getUid() + "lmax [minlen=2]; "); + } else { + sb.append(g.getUid() + "lmin [shape=point,width=.01,style=invis,label=\"\"];"); + sb.append(g.getUid() + "lmax [shape=point,width=.01,style=invis,label=\"\"];"); + sb.append(g.getUid() + "lmin->" + g.getUid() + + "lmax [minlen=2,style=invis,arrowtail=none,arrowhead=none]; "); + } + } + // sb.append(g.getUid() + "min->" + g.getUid() + "max;"); + + sb.append("fontsize=\"" + data.getSkinParam().getFontSize(getFontParamForGroup(), null) + "\";"); + final String fontFamily = data.getSkinParam().getFontFamily(getFontParamForGroup(), null); + if (fontFamily != null) { + sb.append("fontname=\"" + fontFamily + "\";"); + } + + if (g.getDisplay() != null) { + final StringBuilder label = new StringBuilder(manageHtmlIB(g.getDisplay(), getFontParamForGroup(), null)); + if (g.getEntityCluster().getFieldsToDisplay().size() > 0) { + label.append("
"); + for (Member att : g.getEntityCluster().getFieldsToDisplay()) { + label.append(manageHtmlIB(" " + att.getDisplayWithVisibilityChar() + " ", + FontParam.STATE_ATTRIBUTE, null)); + label.append("
"); + } + } + sb.append("label=<" + label + ">;"); + } + + final String fontColor = data.getSkinParam().getFontHtmlColor(getFontParamForGroup(), null).getAsHtml(); + sb.append("fontcolor=\"" + fontColor + "\";"); + final HtmlColor groupBackColor = getGroupBackColor(g); + if (groupBackColor != null) { + sb.append("fillcolor=\"" + groupBackColor.getAsHtml() + "\";"); + } + if (g.getType() == GroupType.STATE) { + sb.append("color=" + getColorString(ColorParam.stateBorder, null) + ";"); + } else { + sb.append("color=" + getColorString(ColorParam.packageBorder, null) + ";"); + } + sb.append("style=\"" + getStyle(g) + "\";"); + + sb.append("subgraph " + g.getUid() + "i {"); + sb.append("label=\"i\";"); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("style=dotted;"); + sb.append("label=\"i\";"); + } else { + + if (groupBackColor == null) { + sb.append("style=invis;"); + } else { + final String colorBack = getColorString(ColorParam.background, null); + sb.append("fillcolor=" + colorBack + ";"); + sb.append("color=" + colorBack + ";"); + sb.append("style=\"filled,rounded\";"); + } + sb.append("label=\"\";"); + + } + + printGroups(sb, g); + + this.printEntities(sb, g.entities().values()); + for (Link link : data.getLinks()) { + eventuallySameRank(sb, g, link); + } + + for (int i = 0; i < fromEdgeLinks.size(); i++) { + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("eds" + i + ";"); + } else { + sb.append("eds" + i + " [shape=point,width=.01,style=invis,label=\"\"];"); + } + sb.append("eds" + i + " ->" + fromEdgeLinks.get(i).getEntity2().getUid() + + " [minlen=2,style=invis,arrowtail=none,arrowhead=none]; "); + + } + + sb.append("}"); // end of i + sb.append("}"); // end of v + + if (autoLabel) { + sb.append("subgraph " + g.getUid() + "l {"); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("style=dotted;"); + sb.append("label=\"l\";"); + } else { + sb.append("style=invis;"); + sb.append("label=\"\";"); + } + final String decorationColor = ",color=" + getColorString(getArrowColorParam(), null); + + sb.append(g.getUid() + "lab0 [shape=point,width=.01,label=\"\"" + decorationColor + "]"); + String autolabel = autolinks.get(0).getLabel(); + if (autolabel == null) { + autolabel = ""; + } + sb.append(g.getUid() + "lab1 [label=<" + manageHtmlIB(autolabel, getArrowFontParam(), null) + + ">,shape=plaintext,margin=0];"); + sb.append(g.getUid() + "lab0 -> " + g.getUid() + "lab1 [minlen=0,style=invis];"); + sb.append("}"); // end of l + + sb.append(g.getUid() + "lmin -> " + g.getUid() + "lab0 [ltail=" + g.getUid() + + "v,arrowtail=none,arrowhead=none" + decorationColor + "];"); + sb.append(g.getUid() + "lab0 -> " + g.getUid() + "lmax [lhead=" + g.getUid() + + "v,arrowtail=none,arrowhead=open" + decorationColor + "];"); + } + + for (int i = 0; i < fromEdgeLinks.size(); i++) { + sb.append("subgraph " + g.getUid() + "ed" + i + " {"); + if (OptionFlags.getInstance().isDebugDot()) { + sb.append("style=dotted;"); + sb.append("label=\"ed" + i + "\";"); + } else { + sb.append("style=invis;"); + sb.append("label=\"\";"); + } + final String decorationColor = ",color=" + getColorString(getArrowColorParam(), null); + String label = fromEdgeLinks.get(i).getLabel(); + if (label == null) { + label = ""; + } + + sb.append(g.getUid() + "fedge" + i + " [shape=point,width=.01,label=\"\"" + decorationColor + "]"); + sb.append("}"); // end of ed + sb.append("eds" + i + " -> " + g.getUid() + "fedge" + i + " [ltail=" + g.getUid() + + "v,arrowtail=none,arrowhead=none" + decorationColor + "];"); + sb.append(g.getUid() + "fedge" + i + " -> " + fromEdgeLinks.get(i).getEntity2().getUid() + + "[arrowtail=none,arrowhead=open" + decorationColor); + sb.append(",label=<" + manageHtmlIB(label, getArrowFontParam(), null) + ">];"); + + } + sb.append("}"); // end of a + } + + private FontParam getFontParamForGroup() { + if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + return FontParam.STATE; + } + return FontParam.PACKAGE; + } + + private String getStyle(Group g) { + final StringBuilder sb = new StringBuilder(); + if (g.isBold()) { + sb.append("bold"); + } else if (g.isDashed()) { + sb.append("dashed"); + } else { + sb.append("solid"); + + } + if (getGroupBackColor(g) != null) { + sb.append(",filled"); + } + if (g.isRounded()) { + sb.append(",rounded"); + } + return sb.toString(); + } + + private void printLinks(StringBuilder sb, List links) throws IOException { + for (Link link : appendPhantomLink(links)) { + final IEntity entity1 = link.getEntity1(); + final IEntity entity2 = link.getEntity2(); + if (entity1 == entity2 && entity1.getType() == EntityType.GROUP) { + continue; + } + if (entity1.getType() == EntityType.GROUP && entity2.getParent() == entity1.getParent()) { + continue; + } + if (entity2.getType() == EntityType.GROUP && entity1.getParent() == entity2.getParent()) { + continue; + } + if (entity1.getType() == EntityType.LOLLIPOP || entity2.getType() == EntityType.LOLLIPOP) { + continue; + } + // System.err.println("outing " + link); + printLink(sb, link); + } + } + + private void printLink(StringBuilder sb, Link link) throws IOException { + +// if (link.isConstraint()==false) { +// return; +// } + + final StringBuilder decoration = getLinkDecoration(link); + + if (link.getWeight() > 1) { + decoration.append("weight=" + link.getWeight() + ","); + } + if (link.getLabeldistance() != null) { + decoration.append("labeldistance=" + link.getLabeldistance() + ","); + } + if (link.getLabelangle() != null) { + decoration.append("labelangle=" + link.getLabelangle() + ","); + } + if (link.isConstraint() == false) { + decoration.append("constraint=false,"); + } + + final DrawFile noteLink = link.getImageFile(); + + boolean hasLabel = false; + + if (link.getLabel() != null) { + decoration.append("label=<" + manageHtmlIB(link.getLabel(), getArrowFontParam(), null) + ">,"); + hasLabel = true; + } else if (noteLink != null) { + decoration + .append("label=<" + getHtmlForLinkNote(noteLink.getPngOrEps(fileFormat == FileFormat.EPS)) + ">,"); + hasLabel = true; + } + + if (link.getQualifier1() != null) { + decoration.append("taillabel=<" + manageHtmlIB(link.getQualifier1(), getArrowFontParam(), null) + ">,"); + } + if (link.getQualifier2() != null) { + decoration.append("headlabel=<" + manageHtmlIB(link.getQualifier2(), getArrowFontParam(), null) + ">,"); + } + final int len = link.getLength(); + String uid1 = link.getEntity1().getUid(); + String uid2 = link.getEntity2().getUid(); + LinkType typeToDraw = link.getType(); + if (TURN_AROUND_B2114 && len == 1 && hasAlreadyOneIncommingArrowLenghtOne.contains(uid2) && hasLabel) { + typeToDraw = typeToDraw.getInv(); + } + if (TURN_AROUND_B2114 && len == 1) { + hasAlreadyOneIncommingArrowLenghtOne.add(uid2); + } + decoration.append(typeToDraw.getSpecificDecoration()); + if (link.isInvis()) { + decoration.append(",style=invis"); + } + + // if (len == 1) { + // decoration.append(",constraint=false"); + // } + final String lenString = len >= 3 ? ",minlen=" + (len - 1) : ""; + + if (link.getEntity1().getType() == EntityType.GROUP) { + uid1 = getHiddenNodeUid(link.getEntity1().getParent(), link); + decoration.append(",ltail=" + link.getEntity1().getParent().getUid() + "v"); + } + if (link.getEntity2().getType() == EntityType.GROUP) { + uid2 = getHiddenNodeUid(link.getEntity2().getParent(), link); + decoration.append(",lhead=" + link.getEntity2().getParent().getUid() + "v"); + } + + sb.append(uid1 + " -> " + uid2); + sb.append(decoration); + sb.append(lenString + "];"); + eventuallySameRank(sb, data.getTopParent(), link); + } + + private List getNodesHiddenUidOut(Group g) { + final List result = new ArrayList(); + for (Link link : data.getLinks()) { + if (link.getEntity1().getParent() == link.getEntity2().getParent()) { + continue; + } + if (link.getEntity1().getType() == EntityType.GROUP && link.getEntity1().getParent() == g) { + result.add(link); + } + } + return Collections.unmodifiableList(result); + } + + private List getNodesHiddenUidIn(Group g) { + final List result = new ArrayList(); + for (Link link : data.getLinks()) { + if (link.getEntity1().getParent() == link.getEntity2().getParent()) { + continue; + } + if (link.getEntity2().getType() == EntityType.GROUP && link.getEntity2().getParent() == g) { + result.add(link); + } + } + return Collections.unmodifiableList(result); + } + + private String getHiddenNodeUid(Group g, Link link) { + if (data.isEmpty(g) && g.getType() == GroupType.PACKAGE) { + return g.getUid(); + } + return g.getUid() + "_" + link.getUid(); + } + + private StringBuilder getLinkDecoration(Link link) { + final StringBuilder decoration = new StringBuilder("[color="); + if (link.getSpecificColor() == null) { + decoration.append(getColorString(getArrowColorParam(), null)); + } else { + decoration.append("\"" + link.getSpecificColor().getAsHtml() + "\""); + } + decoration.append(","); + + decoration.append("fontcolor=" + getFontColorString(getArrowFontParam(), null) + ","); + decoration.append("fontsize=\"" + data.getSkinParam().getFontSize(getArrowFontParam(), null) + "\","); + + final String fontName = data.getSkinParam().getFontFamily(getArrowFontParam(), null); + if (fontName != null) { + decoration.append("fontname=\"" + fontName + "\","); + } + return decoration; + } + + private List appendPhantomLink(List links) { + final List result = new ArrayList(links); + for (Link link : links) { + if (link.getLength() != 1) { + continue; + } + final DrawFile noteLink = link.getImageFile(); + if (noteLink == null) { + continue; + } + final Link phantom = new Link(link.getEntity1(), link.getEntity2(), link.getType(), null, link.getLength()); + phantom.setInvis(true); + result.add(phantom); + } + return result; + } + + private String getHtmlForLinkNote(File image) { + final String circleInterfaceAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(image); + final StringBuilder sb = new StringBuilder(""); + sb.append(""); + sb.append("
"); + return sb.toString(); + + } + + private FontParam getArrowFontParam() { + if (data.getUmlDiagramType() == UmlDiagramType.CLASS) { + return FontParam.CLASS_ARROW; + } else if (data.getUmlDiagramType() == UmlDiagramType.OBJECT) { + return FontParam.OBJECT_ARROW; + } else if (data.getUmlDiagramType() == UmlDiagramType.USECASE) { + return FontParam.USECASE_ARROW; + } else if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + return FontParam.ACTIVITY_ARROW; + } else if (data.getUmlDiagramType() == UmlDiagramType.COMPONENT) { + return FontParam.COMPONENT_ARROW; + } else if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + return FontParam.STATE_ARROW; + } + throw new IllegalStateException(); + } + + private ColorParam getArrowColorParam() { + if (data.getUmlDiagramType() == UmlDiagramType.CLASS) { + return ColorParam.classArrow; + } else if (data.getUmlDiagramType() == UmlDiagramType.OBJECT) { + return ColorParam.objectArrow; + } else if (data.getUmlDiagramType() == UmlDiagramType.USECASE) { + return ColorParam.usecaseArrow; + } else if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + return ColorParam.activityArrow; + } else if (data.getUmlDiagramType() == UmlDiagramType.COMPONENT) { + return ColorParam.componentArrow; + } else if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + return ColorParam.stateArrow; + } + throw new IllegalStateException(); + } + + private String getColorString(ColorParam colorParam, String stereotype) { + return "\"" + rose.getHtmlColor(data.getSkinParam(), colorParam, stereotype).getAsHtml() + "\""; + } + + private String getFontColorString(FontParam fontParam, String stereotype) { + return "\"" + getFontHtmlColor(fontParam, stereotype).getAsHtml() + "\""; + } + + private HtmlColor getFontHtmlColor(FontParam fontParam, String stereotype) { + return data.getSkinParam().getFontHtmlColor(fontParam, stereotype); + } + + private void eventuallySameRank(StringBuilder sb, Group entityPackage, Link link) { + final int len = link.getLength(); + if (len == 1 && link.getEntity1().getParent() == entityPackage + && link.getEntity2().getParent() == entityPackage) { + if (link.getEntity1().getType() == EntityType.GROUP) { + throw new IllegalArgumentException(); + } + if (link.getEntity2().getType() == EntityType.GROUP) { + throw new IllegalArgumentException(); + } + sb.append("{rank=same; " + link.getEntity1().getUid() + "; " + link.getEntity2().getUid() + "}"); + } + } + + private void printEntities(StringBuilder sb, Collection entities) throws IOException { + final Set lollipops = new HashSet(); + final Set lollipopsFriends = new HashSet(); + for (IEntity entity : entities) { + if (entity.getType() == EntityType.LOLLIPOP) { + lollipops.add(entity); + lollipopsFriends.add(getConnectedToLollipop(entity)); + } + } + for (IEntity entity : entities) { + if (lollipops.contains(entity) || lollipopsFriends.contains(entity)) { + continue; + } + printEntity(sb, entity); + } + + for (IEntity ent : lollipopsFriends) { + sb.append("subgraph cluster" + ent.getUid() + "lol {"); + sb.append("style=invis;"); + sb.append("label=\"\";"); + printEntity(sb, ent); + for (IEntity lollipop : getAllLollipop(ent)) { + final Link link = getLinkLollipop(lollipop, ent); + final String headOrTail = getHeadOrTail(lollipop, link); + printEntity(sb, lollipop, headOrTail); + printLink(sb, link); + } + sb.append("}"); + } + + } + + private Collection getAllLollipop(IEntity entity) { + final Collection result = new ArrayList(); + for (IEntity lollipop : data.getAllLinkedDirectedTo(entity)) { + if (lollipop.getType() == EntityType.LOLLIPOP) { + result.add(lollipop); + } + } + return result; + } + + private IEntity getConnectedToLollipop(IEntity lollipop) { + assert lollipop.getType() == EntityType.LOLLIPOP; + final Collection linked = data.getAllLinkedDirectedTo(lollipop); + if (linked.size() != 1) { + throw new IllegalStateException("size=" + linked.size()); + } + return linked.iterator().next(); + } + + private Link getLinkLollipop(IEntity lollipop, IEntity ent) { + assert lollipop.getType() == EntityType.LOLLIPOP; + for (Link link : data.getLinks()) { + if (link.isBetween(lollipop, ent)) { + return link; + } + } + throw new IllegalArgumentException(); + } + + private void printEntity(StringBuilder sb, IEntity entity, String headOrTail) throws IOException { + final EntityType type = entity.getType(); + if (type == EntityType.LOLLIPOP) { + final String color1 = getColorString(ColorParam.classBackground, null); + final String color2 = getColorString(ColorParam.classBorder, null); + final String colorBack = getColorString(ColorParam.background, null); + final String labelLo = manageHtmlIB(entity.getDisplay(), FontParam.CLASS_ATTRIBUTE, null); + sb.append(entity.getUid() + " [fillcolor=" + color1 + ",color=" + color2 + ",style=\"filled\"," + + "shape=circle,width=0.12,height=0.12,label=\"\"];"); + sb.append(entity.getUid() + " -> " + entity.getUid() + "[color=" + colorBack + + ",arrowtail=none,arrowhead=none," + headOrTail + "=<" + labelLo + ">];"); + } else { + throw new IllegalStateException(type.toString() + " " + data.getUmlDiagramType()); + } + + } + + private void printEntity(StringBuilder sb, IEntity entity) throws IOException { + final EntityType type = entity.getType(); + final String label = getLabel(entity); + if (type == EntityType.GROUP) { + return; + } + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + if (type == EntityType.ABSTRACT_CLASS || type == EntityType.CLASS || type == EntityType.INTERFACE + || type == EntityType.ENUM) { + String dec = " [fontcolor=" + getFontColorString(FontParam.CLASS, stereo) + ",margin=0,fillcolor=" + + getColorString(ColorParam.classBackground, stereo) + ",color=" + + getColorString(ColorParam.classBorder, stereo) + ",style=filled,shape=box," + label; + if (this.data.hasUrl() && entity.getUrl() != null) { + dec += ",URL=\"" + entity.getUrl() + "\""; + } + dec += "];"; + sb.append(entity.getUid() + dec); + } else if (type == EntityType.OBJECT) { + sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.CLASS, stereo) + + ",margin=0,fillcolor=" + getColorString(ColorParam.classBackground, stereo) + ",color=" + + getColorString(ColorParam.classBorder, stereo) + ",style=filled,shape=record," + label + "];"); + } else if (type == EntityType.USECASE) { + sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.USECASE, stereo) + ",fillcolor=" + + getColorString(ColorParam.usecaseBackground, stereo) + ",color=" + + getColorString(ColorParam.usecaseBorder, stereo) + ",style=filled," + label + "];"); + } else if (type == EntityType.ACTOR) { + sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.USECASE_ACTOR, stereo) + + ",margin=0,shape=plaintext," + label + "];"); + } else if (type == EntityType.CIRCLE_INTERFACE) { + sb.append(entity.getUid() + " [margin=0,shape=plaintext," + label + "];"); + } else if (type == EntityType.COMPONENT) { + sb.append(entity.getUid() + " [margin=0.2,fontcolor=" + getFontColorString(FontParam.COMPONENT, stereo) + + ",fillcolor=" + getColorString(ColorParam.componentBackground, stereo) + ",color=" + + getColorString(ColorParam.componentBorder, stereo) + ",style=filled,shape=component," + label + + "];"); + } else if (type == EntityType.NOTE && data.getDpi() != 96) { + sb.append(entity.getUid() + " [margin=0,pad=0,shape=plaintext,label=" + getLabelForNoteDpi(entity) + "];"); + } else if (type == EntityType.NOTE) { + final DrawFile file = entity.getImageFile(); + if (file == null) { + throw new IllegalStateException("No file for NOTE"); + } + if (file.getPngOrEps(fileFormat == FileFormat.EPS).exists() == false) { + throw new IllegalStateException(); + } + final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file + .getPngOrEps(fileFormat == FileFormat.EPS)); + sb.append(entity.getUid() + " [margin=0,pad=0," + label + ",shape=none,image=\"" + absolutePath + "\"];"); + } else if (type == EntityType.ACTIVITY) { + String shape = "octagon"; + if (data.getSkinParam().useOctagonForActivity() == false || entity.getImageFile() != null) { + shape = "rect"; + } + sb.append(entity.getUid() + " [fontcolor=" + getFontColorString(FontParam.ACTIVITY, stereo) + ",fillcolor=" + + getBackColorOfEntity(entity) + ",color=" + getColorString(ColorParam.activityBorder, stereo) + + ",style=\"rounded,filled\",shape=" + shape + "," + label + "];"); + } else if (type == EntityType.BRANCH) { + sb.append(entity.getUid() + " [fillcolor=" + getBackColorOfEntity(entity) + ",color=" + + getColorString(ColorParam.activityBorder, stereo) + + ",style=\"filled\",shape=diamond,height=.25,width=.25,label=\"\"];"); + // if (StringUtils.isNotEmpty(entity.getDisplay())) { + // sb.append(entity.getUid() + "->" + entity.getUid() + + // "[taillabel=\"" + entity.getDisplay() + // + "\",arrowtail=none,arrowhead=none,color=\"white\"];"); + // } + } else if (type == EntityType.SYNCHRO_BAR) { + final String color = getColorString(ColorParam.activityBar, null); + sb.append(entity.getUid() + " [fillcolor=" + color + ",color=" + color + ",style=\"filled\"," + + "shape=rect,height=.08,width=1.30,label=\"\"];"); + } else if (type == EntityType.CIRCLE_START) { + final String color = getColorString(getStartColorParam(), null); + sb.append(entity.getUid() + " [fillcolor=" + color + ",color=" + color + ",style=\"filled\"," + + "shape=circle,width=.20,height=.20,label=\"\"];"); + } else if (type == EntityType.CIRCLE_END) { + final String color = getColorString(getEndColorParam(), null); + sb.append(entity.getUid() + " [fillcolor=" + color + ",color=" + color + ",style=\"filled\"," + + "shape=doublecircle,width=.13,height=.13,label=\"\"];"); + } else if (type == EntityType.POINT_FOR_ASSOCIATION) { + sb.append(entity.getUid() + " [width=.05,shape=point,color=" + getColorString(ColorParam.classBorder, null) + + "];"); + } else if (type == EntityType.STATE) { + sb.append(entity.getUid() + " [color=" + getColorString(ColorParam.stateBorder, stereo) + + ",shape=record,style=\"rounded,filled\",color=" + getColorString(ColorParam.stateBorder, stereo)); + if (entity.getImageFile() == null) { + sb.append(",fillcolor=" + getBackColorOfEntity(entity)); + } else { + sb.append(",fillcolor=" + getBackColorOfEntity(entity)); + // sb.append(",fillcolor=\"" + + // data.getSkinParam().getBackgroundColor().getAsHtml() + "\""); + } + sb.append("," + label + "];"); + } else if (type == EntityType.STATE_CONCURRENT) { + final DrawFile file = entity.getImageFile(); + if (file == null) { + throw new IllegalStateException(); + } + if (file.getPng().exists() == false) { + throw new IllegalStateException(); + } + final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file.getPng()); + sb.append(entity.getUid() + " [margin=1,pad=1," + label + ",style=dashed,shape=box,image=\"" + absolutePath + + "\"];"); + } else if (type == EntityType.ACTIVITY_CONCURRENT) { + final DrawFile file = entity.getImageFile(); + if (file == null) { + throw new IllegalStateException(); + } + if (file.getPng().exists() == false) { + throw new IllegalStateException(); + } + final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file.getPng()); + sb.append(entity.getUid() + " [margin=0,pad=0," + label + ",style=dashed,shape=box,image=\"" + absolutePath + + "\"];"); + } else if (type == EntityType.EMPTY_PACKAGE) { + sb.append(entity.getUid() + " [margin=0.2,fontcolor=" + getFontColorString(FontParam.PACKAGE, null) + + ",fillcolor=" + getColorString(ColorParam.packageBackground, null) + ",color=" + + getColorString(ColorParam.packageBorder, null) + ",style=filled,shape=tab," + label + "];"); + } else { + throw new IllegalStateException(type.toString() + " " + data.getUmlDiagramType()); + } + + if (entity.isTop()) { + rankMin.add(entity.getUid()); + } + + } + + private ColorParam getEndColorParam() { + if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + return ColorParam.activityEnd; + } + if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + return ColorParam.stateEnd; + } + throw new IllegalStateException(data.getUmlDiagramType().toString()); + } + + private ColorParam getStartColorParam() { + if (data.getUmlDiagramType() == UmlDiagramType.ACTIVITY) { + return ColorParam.activityStart; + } + if (data.getUmlDiagramType() == UmlDiagramType.STATE) { + return ColorParam.stateStart; + } + throw new IllegalStateException(data.getUmlDiagramType().toString()); + } + + private String getHeadOrTail(IEntity lollipop, Link link) { + assert lollipop.getType() == EntityType.LOLLIPOP; + if (link.getLength() > 1 && link.getEntity1() == lollipop) { + return "taillabel"; + } + return "headlabel"; + } + + private String getLabel(IEntity entity) throws IOException { + if (entity.getType() == EntityType.ABSTRACT_CLASS || entity.getType() == EntityType.CLASS + || entity.getType() == EntityType.INTERFACE || entity.getType() == EntityType.ENUM) { + return "label=" + getLabelForClassOrInterfaceOrEnum(entity); + } else if (entity.getType() == EntityType.LOLLIPOP) { + return "label=" + getLabelForLollipop(entity); + } else if (entity.getType() == EntityType.OBJECT) { + return "label=" + getLabelForObject(entity); + } else if (entity.getType() == EntityType.ACTOR) { + return "label=" + getLabelForActor(entity); + } else if (entity.getType() == EntityType.CIRCLE_INTERFACE) { + return "label=" + getLabelForCircleInterface(entity); + } else if (entity.getType() == EntityType.NOTE) { + return "label=\"\""; + } else if (entity.getType() == EntityType.STATE_CONCURRENT) { + return "label=\"\""; + } else if (entity.getType() == EntityType.ACTIVITY_CONCURRENT) { + return "label=\"\""; + } else if (entity.getType() == EntityType.COMPONENT) { + return "label=" + getLabelForComponent(entity); + } else if (entity.getType() == EntityType.ACTIVITY) { + final DrawFile drawFile = entity.getImageFile(); + if (drawFile != null) { + final String path = StringUtils.getPlateformDependentAbsolutePath(drawFile.getPng()); + final String bgcolor = "\"" + data.getSkinParam().getBackgroundColor().getAsHtml() + "\""; + final StringBuilder sb = new StringBuilder("label=<"); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("
"); + sb.append("
"); + sb.append(">"); + return sb.toString(); + } + final String stereotype = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + return "label=" + getSimpleLabelAsHtml(entity, FontParam.ACTIVITY, stereotype); + } else if (entity.getType() == EntityType.EMPTY_PACKAGE) { + return "label=" + getSimpleLabelAsHtml(entity, getFontParamForGroup(), null); + } else if (entity.getType() == EntityType.USECASE) { + return "label=" + getLabelForUsecase(entity); + } else if (entity.getType() == EntityType.STATE) { + return "label=" + getLabelForState(entity); + } + return "label=\"" + entity.getDisplay() + "\""; + } + + private String getSimpleLabelAsHtml(IEntity entity, FontParam param, String stereotype) { + return "<" + manageHtmlIB(entity.getDisplay(), param, stereotype) + ">"; + } + + private String getBackColorOfEntity(IEntity entity) { + if (entity.getSpecificBackColor() != null) { + return "\"" + entity.getSpecificBackColor().getAsHtml() + "\""; + } + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + if (entity.getType() == EntityType.STATE || entity.getType() == EntityType.STATE_CONCURRENT) { + return getColorString(ColorParam.stateBackground, stereo); + } + if (entity.getType() == EntityType.ACTIVITY || entity.getType() == EntityType.ACTIVITY_CONCURRENT + || entity.getType() == EntityType.BRANCH) { + return getColorString(ColorParam.activityBackground, stereo); + } + throw new IllegalArgumentException(entity.getType().toString()); + } + + private String getLabelForState(IEntity entity) throws IOException { + final DrawFile cFile = entity.getImageFile(); + final String stateBgcolor = getBackColorOfEntity(entity); + + final String stereotype = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + final StringBuilder sb = new StringBuilder("<{"); + sb.append(""); + sb.append("
" + manageHtmlIB(entity.getDisplay(), FontParam.STATE, stereotype) + "
"); + + if (entity.getFieldsToDisplay().size() > 0) { + sb.append("|"); + for (Member att : entity.getFieldsToDisplay()) { + sb.append(manageHtmlIB(att.getDisplayWithVisibilityChar(), FontParam.STATE_ATTRIBUTE, stereotype)); + sb.append("
"); + } + } + + if (cFile != null) { + sb.append("|"); + final String path = StringUtils.getPlateformDependentAbsolutePath(cFile.getPng()); + final String bgcolor; + if (OptionFlags.PBBACK) { + bgcolor = stateBgcolor; + } else { + bgcolor = "\"" + data.getSkinParam().getBackgroundColor().getAsHtml() + "\""; + } + // PBBACK + + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("
"); + sb.append("
"); + } + + if (data.isHideEmptyDescription() == false && entity.getFieldsToDisplay().size() == 0 && cFile == null) { + sb.append("|"); + } + + sb.append("}>"); + + return sb.toString(); + } + + private String getLabelForUsecase(IEntity entity) { + final Stereotype stereotype = getStereotype(entity); + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + if (stereotype == null) { + return getSimpleLabelAsHtml(entity, FontParam.USECASE, stereo); + } + final StringBuilder sb = new StringBuilder("<"); + if (isThereLabel(stereotype)) { + sb.append(""); + } + sb.append(""); + sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.USECASE_STEREOTYPE, stereo) + + "
" + manageHtmlIB(entity.getDisplay(), FontParam.USECASE, stereo) + "
>"); + return sb.toString(); + } + + private String getLabelForComponent(IEntity entity) { + final Stereotype stereotype = getStereotype(entity); + if (stereotype == null) { + return getSimpleLabelAsHtml(entity, FontParam.COMPONENT, null); + } + final String stereo = stereotype.getLabel(); + final StringBuilder sb = new StringBuilder("<"); + if (isThereLabel(stereotype)) { + sb.append(""); + } + sb.append(""); + sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.COMPONENT_STEREOTYPE, stereo) + + "
" + manageHtmlIB(entity.getDisplay(), FontParam.COMPONENT, stereo) + "
>"); + return sb.toString(); + } + + private String getLabelForNoteDpi(IEntity entity) throws IOException { + final DrawFile file = entity.getImageFile(); + if (file == null) { + throw new IllegalStateException("No file for NOTE"); + } + if (file.getPngOrEps(fileFormat == FileFormat.EPS).exists() == false) { + throw new IllegalStateException(); + } + final String absolutePath = StringUtils.getPlateformDependentAbsolutePath(file + .getPngOrEps(fileFormat == FileFormat.EPS)); + + final StringBuilder sb = new StringBuilder("<"); + sb.append(""); + addTdImageBugB1983(sb, absolutePath); + sb.append(""); + sb.append("
>"); + return sb.toString(); + } + + private void addTdImageBugB1983(final StringBuilder sb, final String absolutePath) throws IOException { + // http://www.graphviz.org/bugs/b1983.html + final BufferedImage im = ImageIO.read(new File(absolutePath)); + final int height = im.getHeight(); + final int width = im.getWidth(); + final double f = 1.0 / data.getDpiFactor(); + final int w = (int) (width * f); + final int h = (int) (height * f); + final int w2 = (int) (width * getMagicFactorForImageDpi()); + final int h2 = (int) (height * getMagicFactorForImageDpi()); + sb.append(getTdHeaderForDpi(w, h)); + sb.append(""); + sb.append(""); + sb.append(getTdHeaderForDpi(w2, h2)); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("
"); + sb.append(""); + } + + private double getMagicFactorForImageDpi() { + return 10500 / 100000.0; + } + + private String getLabelForActor(IEntity entity) throws IOException { + final String actorAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(entity.getImageFile() + .getPngOrEps(fileFormat == FileFormat.EPS)); + final Stereotype stereotype = getStereotype(entity); + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + final StringBuilder sb = new StringBuilder("<"); + if (isThereLabel(stereotype)) { + sb.append(""); + } + if (data.getDpi() == 96) { + sb.append(""); + } else { + sb.append(""); + addTdImageBugB1983(sb, actorAbsolutePath); + sb.append(""); + } + sb.append(""); + sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.USECASE_ACTOR_STEREOTYPE, stereo) + + "
" + manageHtmlIB(entity.getDisplay(), FontParam.USECASE_ACTOR, stereo) + "
>"); + return sb.toString(); + + } + + private String getLabelForCircleInterface(IEntity entity) throws IOException { + final String circleInterfaceAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(entity.getImageFile() + .getPngOrEps(fileFormat == FileFormat.EPS)); + final Stereotype stereotype = getStereotype(entity); + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + final StringBuilder sb = new StringBuilder("<"); + if (isThereLabel(stereotype)) { + sb.append(""); + } + sb.append(""); + if (data.getDpi() == 96) { + sb.append(""); + } else { + addTdImageBugB1983(sb, circleInterfaceAbsolutePath); + } + sb.append(""); + sb.append(""); + sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.COMPONENT_STEREOTYPE, stereo) + + "
" + manageHtmlIB(entity.getDisplay(), FontParam.COMPONENT, stereo) + "
>"); + return sb.toString(); + + } + + private String getLabelForLollipop(IEntity entity) throws IOException { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + final String circleInterfaceAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(data.getStaticImages( + EntityType.LOLLIPOP, stereo).getPngOrEps(fileFormat == FileFormat.EPS)); + final Stereotype stereotype = getStereotype(entity); + + final StringBuilder sb = new StringBuilder("<"); + if (isThereLabel(stereotype)) { + sb.append(""); + } + sb.append(""); + if (data.getDpi() == 96) { + sb.append(""); + } else { + addTdImageBugB1983(sb, circleInterfaceAbsolutePath); + } + sb.append(""); + sb.append(""); + sb.append("
" + manageHtmlIB(stereotype.getLabel(), FontParam.CLASS, null) + "
" + manageHtmlIB(entity.getDisplay(), FontParam.CLASS, null) + "
>"); + return sb.toString(); + + } + + private String getLabelForClassOrInterfaceOrEnum(IEntity entity) throws IOException { + if (isVisibilityModifierPresent) { + return getLabelForClassOrInterfaceOrEnumWithVisibilityImage(entity); + } + return getLabelForClassOrInterfaceOrEnumOld(entity); + + } + + private String getLabelForClassOrInterfaceOrEnumOld(IEntity entity) throws IOException { + DrawFile cFile = entity.getImageFile(); + if (cFile == null) { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + cFile = data.getStaticImages(entity.getType(), stereo); + } + if (cFile == null) { + throw new IllegalStateException(); + } + final String circleAbsolutePath; + if (data.showPortion(EntityPortion.CIRCLED_CHARACTER, entity)) { + circleAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(cFile + .getPngOrEps(fileFormat == FileFormat.EPS)); + } else { + circleAbsolutePath = null; + } + + final StringBuilder sb = new StringBuilder("<"); + + final boolean showFields = data.showPortion(EntityPortion.FIELD, entity); + final boolean showMethods = data.showPortion(EntityPortion.METHOD, entity); + + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + if (showFields == false && showMethods == false) { + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, 1, true, 1)); + } else { + sb.append(""); + sb.append(""); + + if (showFields) { + // if (fileFormat == FileFormat.EPS) { + // sb.append(addFieldsEps(entity.fields2(), true)); + // } else { + final boolean hasStatic = hasStatic(entity.getFieldsToDisplay()); + sb.append(""); + // } + } + if (showMethods) { + // if (fileFormat == FileFormat.EPS) { + // sb.append(addFieldsEps(entity.methods2(), true)); + // } else { + final boolean hasStatic = hasStatic(entity.getMethodsToDisplay()); + sb.append(""); + // } + } + sb.append("
"); + final int longuestFieldOrAttribute = getLongestFieldOrAttribute(entity); + final int longuestHeader = getLonguestHeader(entity); + final int spring = computeSpring(longuestHeader, longuestFieldOrAttribute, 30); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, spring, true, 0)); + + sb.append("
"); + for (Member att : entity.getFieldsToDisplay()) { + sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, getColorString( + ColorParam.classBackground, stereo), true)); + sb.append("
"); + } + sb.append("
"); + for (Member att : entity.getMethodsToDisplay()) { + sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, getColorString( + ColorParam.classBackground, stereo), true)); + sb.append("
"); + } + sb.append("
"); + } + sb.append(">"); + + return sb.toString(); + } + + final private List fileToClean = new ArrayList(); + + private String addFieldsEps(List members, boolean withVisibilityChar) throws IOException { + final List texts = new ArrayList(); + for (Member att : members) { + String s = att.getDisplay(withVisibilityChar); + if (att.isAbstract()) { + s = "" + s + ""; + } + if (att.isStatic()) { + s = "" + s + ""; + } + texts.add(s); + } + final Font font = data.getSkinParam().getFont(FontParam.CLASS_ATTRIBUTE, null); + final Color color = getFontHtmlColor(FontParam.CLASS_ATTRIBUTE, null).getColor(); + final TextBlock text = TextBlockUtils.create(texts, new FontConfiguration(font, color), + HorizontalAlignement.LEFT); + final File feps = FileUtils.createTempFile("member", ".eps"); + UGraphicEps.copyEpsToFile(new UDrawable() { + public void drawU(UGraphic ug) { + text.drawU(ug, 0, 0); + } + }, feps); + fileToClean.add(feps); + + final String path = StringUtils.getPlateformDependentAbsolutePath(feps); + + return "" + "" + + "" + "" + "
" + "
"; + } + + private boolean hasStatic(Collection attributes) { + for (Member att : attributes) { + if (att.isStatic()) { + return true; + } + } + return false; + } + + private String getLabelForClassOrInterfaceOrEnumWithVisibilityImage(IEntity entity) throws IOException { + DrawFile cFile = entity.getImageFile(); + if (cFile == null) { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + cFile = data.getStaticImages(entity.getType(), stereo); + } + if (cFile == null) { + throw new IllegalStateException(); + } + final String circleAbsolutePath; + if (data.showPortion(EntityPortion.CIRCLED_CHARACTER, entity)) { + circleAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(cFile + .getPngOrEps(fileFormat == FileFormat.EPS)); + } else { + circleAbsolutePath = null; + } + + final boolean showFields = data.showPortion(EntityPortion.FIELD, entity); + final boolean showMethods = data.showPortion(EntityPortion.METHOD, entity); + + final StringBuilder sb = new StringBuilder("<"); + if (showFields == false && showMethods == false) { + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, 1, true, 1)); + } else { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + final int longuestHeader = getLonguestHeader(entity); + final int spring = computeSpring(longuestHeader, getLongestFieldOrAttribute(entity), 30); + final int springField = computeSpring(getLongestField(entity), Math.max(longuestHeader, + getLongestMethods(entity)), 30); + final int springMethod = computeSpring(getLongestMethods(entity), Math.max(longuestHeader, + getLongestField(entity)), 30); + + sb.append(""); + sb.append(""); + + if (showFields) { + sb.append(""); + } + if (showMethods) { + sb.append(""); + } + sb.append("
"); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, circleAbsolutePath, spring, true, 0)); + sb.append("
"); + if (entity.getFieldsToDisplay().size() > 0) { + buildTableVisibility(entity, true, sb, springField); + } + sb.append("
"); + if (entity.getMethodsToDisplay().size() > 0) { + buildTableVisibility(entity, false, sb, springMethod); + } + sb.append("
"); + } + sb.append(">"); + + return sb.toString(); + + } + + private int computeSpring(final int current, final int maximum, int maxSpring) { + if (maximum <= current) { + return 0; + } + final int spring = maximum - current; + if (spring > maxSpring) { + return maxSpring; + } + return spring; + } + + private void buildTableVisibility(IEntity entity, boolean isField, final StringBuilder sb, int spring) + throws IOException { + sb.append(""); + + final boolean hasStatic = hasStatic(entity.getMethodsToDisplay()); + final boolean dpiNormal = data.getDpi() == 96; + for (Member att : isField ? entity.getFieldsToDisplay() : entity.getMethodsToDisplay()) { + sb.append(""); + if (dpiNormal) { + sb.append(""); + } + sb.append(""); + for (int i = 0; i < spring; i++) { + sb.append(""); + } + sb.append(""); + } + sb.append("
"); + } + String s = att.getDisplayWithVisibilityChar(); + final VisibilityModifier visibilityModifier = VisibilityModifier + .getVisibilityModifier(s.charAt(0), isField); + if (visibilityModifier != null) { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + final String modifierFile = StringUtils.getPlateformDependentAbsolutePath(data.getVisibilityImages( + visibilityModifier, stereo).getPngOrEps(fileFormat == FileFormat.EPS)); + if (dpiNormal) { + sb.append(""); + } else { + addTdImageBugB1983(sb, modifierFile); + } + s = s.substring(1); + } + if (dpiNormal) { + sb.append(""); + sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, getColorString( + ColorParam.classBackground, null), false)); + sb.append("
"); + } + + private int getLonguestHeader(IEntity entity) { + int result = entity.getDisplay().length(); + final Stereotype stereotype = getStereotype(entity); + if (isThereLabel(stereotype)) { + final int size = stereotype.getLabel().length(); + if (size > result) { + result = size; + } + } + return result; + } + + private int getLongestFieldOrAttribute(IEntity entity) { + return Math.max(getLongestField(entity), getLongestMethods(entity)); + } + + private int getLongestMethods(IEntity entity) { + int result = 0; + for (Member att : entity.getMethodsToDisplay()) { + final int size = att.getDisplayWithVisibilityChar().length(); + if (size > result) { + result = size; + } + } + return result; + + } + + private int getLongestField(IEntity entity) { + int result = 0; + for (Member att : entity.getFieldsToDisplay()) { + final int size = att.getDisplayWithVisibilityChar().length(); + if (size > result) { + result = size; + } + } + return result; + } + + private String getLabelForObject(IEntity entity) throws IOException { + if (isVisibilityModifierPresent) { + return getLabelForObjectWithVisibilityImage(entity); + } + return getLabelForObjectOld(entity); + } + + private String getLabelForObjectWithVisibilityImage(IEntity entity) throws IOException { + + final int longuestHeader = getLonguestHeader(entity); + final int spring = computeSpring(longuestHeader, getLongestFieldOrAttribute(entity), 30); + final int springField = computeSpring(getLongestField(entity), Math.max(longuestHeader, + getLongestMethods(entity)), 30); + + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + final StringBuilder sb = new StringBuilder("<"); + sb.append(""); + sb.append(""); + sb.append("
"); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, null, spring, false, 0)); + + sb.append("
"); + + if (entity.getFieldsToDisplay().size() == 0) { + sb.append(manageHtmlIB(" ", FontParam.OBJECT_ATTRIBUTE, stereo)); + } else { + buildTableVisibility(entity, true, sb, springField); + } + + sb.append("
>"); + + return sb.toString(); + + } + + private String getLabelForObjectOld(IEntity entity) throws IOException { + + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + + final StringBuilder sb = new StringBuilder("<"); + sb.append(""); + sb.append(""); + sb.append("
"); + + final int longuestFieldOrAttribute = getLongestFieldOrAttribute(entity); + final int longuestHeader = getLonguestHeader(entity); + final int spring = computeSpring(longuestHeader, longuestFieldOrAttribute, 30); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(entity, null, spring, false, 0)); + + sb.append("
"); + + if (entity.getFieldsToDisplay().size() == 0) { + sb.append(manageHtmlIB(" ", FontParam.OBJECT_ATTRIBUTE, stereo)); + } else { + for (Member att : entity.getFieldsToDisplay()) { + sb.append(manageHtmlIB(att.getDisplayWithVisibilityChar(), FontParam.OBJECT_ATTRIBUTE, stereo)); + sb.append("
"); + } + } + + sb.append("
>"); + + return sb.toString(); + } + + private String getWitdh55() { + if (data.getDpi() == 96) { + return "WIDTH=\"55\""; + } + return "WIDTH=\"55\""; + } + + private String manageHtmlIB(String s, FontParam param, String stereotype) { + s = unicode(s); + final int fontSize = data.getSkinParam().getFontSize(param, stereotype); + final int style = data.getSkinParam().getFontStyle(param, stereotype); + final String fontFamily = data.getSkinParam().getFontFamily(param, stereotype); + final DotExpression dotExpression = new DotExpression(s, fontSize, getFontHtmlColor(param, stereotype), + fontFamily, style, fileFormat); + final String result = dotExpression.getDotHtml(); + if (dotExpression.isUnderline()) { + underline = true; + } + return result; + } + + private String manageHtmlIBspecial(Member att, FontParam param, boolean hasStatic, String backColor, + boolean withVisibilityChar) { + String prefix = ""; + if (hasStatic) { + prefix = "_"; + } + if (att.isAbstract()) { + return prefix + manageHtmlIB("" + att.getDisplay(withVisibilityChar), param, null); + } + if (att.isStatic()) { + return manageHtmlIB("" + att.getDisplay(withVisibilityChar), param, null); + } + return prefix + manageHtmlIB(att.getDisplay(withVisibilityChar), param, null); + } + + private String manageSpace(int size) { + final DotExpression dotExpression = new DotExpression(" ", size, HtmlColor.getColorIfValid("white"), null, + Font.PLAIN, fileFormat); + final String result = dotExpression.getDotHtml(); + return result; + } + + static String unicode(String s) { + final StringBuilder result = new StringBuilder(); + for (char c : s.toCharArray()) { + if (c > 127 || c == '&') { + final int i = c; + result.append("&#" + i + ";"); + } else { + result.append(c); + } + } + return result.toString(); + } + + private String getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnumNoSpring(IEntity entity, + final String circleAbsolutePath, int cellSpacing, boolean classes) throws IOException { + final StringBuilder sb = new StringBuilder(); + sb.append(""); + sb.append(""); + if (circleAbsolutePath == null) { + sb.append(""); + } else { + addTdImageBugB1983(sb, circleAbsolutePath); + + } + sb.append("
"); + } else { + if (data.getDpi() == 96) { + sb.append(""); + sb.append(""); + } + + appendLabelAndStereotype(entity, sb, classes); + sb.append("
"); + return sb.toString(); + } + + private String getTdHeaderForDpi(final double w, final double h) { + // return ""; + return ""; + } + + private String getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(IEntity entity, final String circleAbsolutePath, + int spring, boolean classes, int border) throws IOException { + if (spring == 0) { + return getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnumNoSpring(entity, circleAbsolutePath, 0, classes); + } + final StringBuilder sb = new StringBuilder(); + + sb + .append(""); + sb.append(""); + + for (int i = 0; i < spring; i++) { + sb.append(""); + } + + if (circleAbsolutePath != null) { + if (circleAbsolutePath.endsWith(".png")) { + if (data.getDpi() == 96) { + final BufferedImage im = ImageIO.read(new File(circleAbsolutePath)); + final int height = im.getHeight(); + final int width = im.getWidth(); + sb.append(""); + } else { + addTdImageBugB1983(sb, circleAbsolutePath); + } + } else if (circleAbsolutePath.endsWith(".eps")) { + sb.append(""); + } + } + + sb.append(""); + + for (int i = 0; i < spring; i++) { + sb.append(""); + } + sb.append("
"); + appendLabelAndStereotype(entity, sb, classes); + sb.append("
"); + return sb.toString(); + } + + private void appendLabelAndStereotype(IEntity entity, final StringBuilder sb, boolean classes) { + final Stereotype stereotype = getStereotype(entity); + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + if (isThereLabel(stereotype)) { + sb.append("
"); + sb.append(manageHtmlIB(stereotype.getLabel(), classes ? FontParam.CLASS_STEREOTYPE + : FontParam.OBJECT_STEREOTYPE, stereo)); + sb.append("
"); + } + String display = entity.getDisplay(); + final boolean italic = entity.getType() == EntityType.ABSTRACT_CLASS + || entity.getType() == EntityType.INTERFACE; + if (italic) { + display = "" + display; + } + sb.append(manageHtmlIB(display, classes ? FontParam.CLASS : FontParam.OBJECT, stereo)); + } + + private String getHtmlHeaderTableForClassOrInterfaceOrEnumNew(Entity entity, final String circleAbsolutePath) { + final StringBuilder sb = new StringBuilder(); + sb.append(""); + sb.append("
"); + + appendLabelAndStereotype(entity, sb, true); + sb.append("
"); + return sb.toString(); + } + + private boolean isThereLabel(final Stereotype stereotype) { + return stereotype != null && stereotype.getLabel() != null; + } + + private Stereotype getStereotype(IEntity entity) { + if (data.showPortion(EntityPortion.STEREOTYPE, entity) == false) { + return null; + } + return entity.getStereotype(); + } + + public final boolean isUnderline() { + return underline; + } + + private boolean workAroundDotBug() { + for (Link link : data.getLinks()) { + if (link.getLength() != 1) { + return false; + } + } + if (data.getUmlDiagramType() == UmlDiagramType.CLASS && allEntitiesAreClasses(data.getEntities().values())) { + return true; + } + for (IEntity ent : data.getEntities().values()) { + if (data.getAllLinkedTo(ent).size() == 0) { + return true; + } + } + return false; + } + + private boolean allEntitiesAreClasses(Collection entities) { + for (IEntity ent : entities) { + if (ent.getType() != EntityType.CLASS && ent.getType() != EntityType.ABSTRACT_CLASS + && ent.getType() != EntityType.INTERFACE && ent.getType() != EntityType.ENUM) { + return false; + } + } + return true; + } + + private boolean isSpecialGroup(Group g) { + if (g.getType() == GroupType.STATE) { + return true; + } + if (g.getType() == GroupType.CONCURRENT_STATE) { + throw new IllegalStateException(); + } + if (data.isThereLink(g)) { + return true; + } + return false; + } + + public static final String getLastDotSignature() { + return lastDotSignature; + } + + public static final void reset() { + lastDotSignature = null; + } + + public void clean() { + if (OptionFlags.getInstance().isKeepTmpFiles()) { + return; + } + for (File f : fileToClean) { + Log.info("Deleting temporary file " + f); + final boolean ok = f.delete(); + if (ok == false) { + Log.error("Cannot delete: " + f); + } + } + } + +} diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/DrawFileFactory.java b/src/net/sourceforge/plantuml/cucadiagram/dot/DrawFileFactory.java new file mode 100644 index 000000000..d42f6a474 --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/DrawFileFactory.java @@ -0,0 +1,83 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 3977 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import net.sourceforge.plantuml.EmptyImageBuilder; +import net.sourceforge.plantuml.FileUtils; +import net.sourceforge.plantuml.skin.UDrawable; +import net.sourceforge.plantuml.ugraphic.eps.UGraphicEps; +import net.sourceforge.plantuml.ugraphic.g2d.UGraphicG2d; + +public class DrawFileFactory { + + public static DrawFile create(final UDrawable drawable, final double width, final double height, + final double dpiFactor, final Color backgground, Object signature) { + + final Lazy lpng = new Lazy() { + public File getNow() throws IOException { + final File png = FileUtils.createTempFile("visi", ".png"); + final EmptyImageBuilder builder = new EmptyImageBuilder(width * dpiFactor, height * dpiFactor, + backgground); + final BufferedImage im = builder.getBufferedImage(); + drawable.drawU(new UGraphicG2d(builder.getGraphics2D(), im, dpiFactor)); + ImageIO.write(im, "png", png); + return png; + } + }; + + final Lazy leps = new Lazy() { + public File getNow() throws IOException { + final File eps = FileUtils.createTempFile("visi", ".eps"); + UGraphicEps.copyEpsToFile(drawable, eps); + return eps; + } + }; + + final Lazy lsvg = new Lazy() { + public String getNow() throws IOException { + return UGraphicG2d.getSvgString(drawable); + } + }; + + return DrawFile.create(lpng, lsvg, leps, signature); + } + +} diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/GroupPngMaker.java b/src/net/sourceforge/plantuml/cucadiagram/dot/GroupPngMaker.java index f576bcc3d..01b11e333 100644 --- a/src/net/sourceforge/plantuml/cucadiagram/dot/GroupPngMaker.java +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/GroupPngMaker.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6228 $ + * Revision $Revision: 6453 $ * */ package net.sourceforge.plantuml.cucadiagram.dot; @@ -47,8 +47,6 @@ import java.util.Map; import javax.imageio.ImageIO; -import com.sun.org.apache.bcel.internal.generic.GETSTATIC; - import net.sourceforge.plantuml.FileFormat; import net.sourceforge.plantuml.FileUtils; import net.sourceforge.plantuml.ISkinParam; diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilder.java b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilder.java new file mode 100644 index 000000000..a64575da0 --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilder.java @@ -0,0 +1,44 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.io.IOException; + +interface LabelBuilder { + + void appendLabel(StringBuilder sb) throws IOException; + boolean isUnderline(); + + +} diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderClassOld.java b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderClassOld.java new file mode 100644 index 000000000..5481a43ea --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderClassOld.java @@ -0,0 +1,125 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.io.IOException; + +import net.sourceforge.plantuml.ColorParam; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FontParam; +import net.sourceforge.plantuml.StringUtils; +import net.sourceforge.plantuml.cucadiagram.EntityPortion; +import net.sourceforge.plantuml.cucadiagram.IEntity; +import net.sourceforge.plantuml.cucadiagram.Member; + +class LabelBuilderClassOld extends LabelBuilderObjectOrClass implements LabelBuilder { + + LabelBuilderClassOld(FileFormat fileFormat, DotData data, IEntity entity) { + super(fileFormat, data, entity); + } + + public void appendLabel(StringBuilder sb) throws IOException { + DrawFile cFile = getEntity().getImageFile(); + if (cFile == null) { + final String stereo = getEntity().getStereotype() == null ? null : getEntity().getStereotype().getLabel(); + cFile = getData().getStaticImages(getEntity().getType(), stereo); + } + if (cFile == null) { + throw new IllegalStateException(); + } + final String circleAbsolutePath; + if (getData().showPortion(EntityPortion.CIRCLED_CHARACTER, getEntity())) { + circleAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(cFile + .getPngOrEps(getFileFormat() == FileFormat.EPS)); + } else { + circleAbsolutePath = null; + } + + // sb.append("<"); + + final boolean showFields = getData().showPortion(EntityPortion.FIELD, getEntity()); + final boolean showMethods = getData().showPortion(EntityPortion.METHOD, getEntity()); + + final String stereo = getEntity().getStereotype() == null ? null : getEntity().getStereotype().getLabel(); + + if (showFields == false && showMethods == false) { + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(getEntity(), circleAbsolutePath, 1, true, BorderMode.NO_BORDER_CELLSPACING)); + } else { + sb.append(""); + sb.append(""); + + if (showFields) { + // if (fileFormat == FileFormat.EPS) { + // sb.append(addFieldsEps(getEntity().fields2(), true)); + // } else { + final boolean hasStatic = hasStatic(getEntity().getFieldsToDisplay()); + sb.append(""); + // } + } + if (showMethods) { + // if (fileFormat == FileFormat.EPS) { + // sb.append(addFieldsEps(getEntity().methods2(), true)); + // } else { + final boolean hasStatic = hasStatic(getEntity().getMethodsToDisplay()); + sb.append(""); + // } + } + sb.append("
"); + final int longuestFieldOrAttribute = getLongestFieldOrAttribute(getEntity()); + final int longuestHeader = getLonguestHeader(getEntity()); + final int spring = computeSpring(longuestHeader, longuestFieldOrAttribute, 30); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(getEntity(), circleAbsolutePath, spring, + true, BorderMode.NO_BORDER)); + + sb.append("
"); + for (Member att : getEntity().getFieldsToDisplay()) { + sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, + getColorString(ColorParam.classBackground, stereo), true)); + sb.append("
"); + } + sb.append("
"); + for (Member att : getEntity().getMethodsToDisplay()) { + sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, + getColorString(ColorParam.classBackground, stereo), true)); + sb.append("
"); + } + sb.append("
"); + } + // sb.append(">"); + } + +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderClassWithVisibilityImage.java b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderClassWithVisibilityImage.java new file mode 100644 index 000000000..8c8398501 --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderClassWithVisibilityImage.java @@ -0,0 +1,110 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.io.IOException; + +import net.sourceforge.plantuml.ColorParam; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.StringUtils; +import net.sourceforge.plantuml.cucadiagram.EntityPortion; +import net.sourceforge.plantuml.cucadiagram.IEntity; + +class LabelBuilderClassWithVisibilityImage extends LabelBuilderObjectOrClass implements LabelBuilder { + + LabelBuilderClassWithVisibilityImage(FileFormat fileFormat, DotData data, IEntity entity) { + super(fileFormat, data, entity); + } + + public void appendLabel(StringBuilder sb) throws IOException { + DrawFile cFile = getEntity().getImageFile(); + if (cFile == null) { + final String stereo = getEntity().getStereotype() == null ? null : getEntity().getStereotype().getLabel(); + cFile = getData().getStaticImages(getEntity().getType(), stereo); + } + if (cFile == null) { + throw new IllegalStateException(); + } + final String circleAbsolutePath; + if (getData().showPortion(EntityPortion.CIRCLED_CHARACTER, getEntity())) { + circleAbsolutePath = StringUtils.getPlateformDependentAbsolutePath(cFile + .getPngOrEps(getFileFormat() == FileFormat.EPS)); + } else { + circleAbsolutePath = null; + } + + final boolean showFields = getData().showPortion(EntityPortion.FIELD, getEntity()); + final boolean showMethods = getData().showPortion(EntityPortion.METHOD, getEntity()); + + // sb.append("<"); + if (showFields == false && showMethods == false) { + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(getEntity(), circleAbsolutePath, 1, true, BorderMode.NO_BORDER_CELLSPACING)); + } else { + final String stereo = getEntity().getStereotype() == null ? null : getEntity().getStereotype().getLabel(); + final int longuestHeader = getLonguestHeader(getEntity()); + final int spring = computeSpring(longuestHeader, getLongestFieldOrAttribute(getEntity()), 30); + final int springField = computeSpring(getLongestField(getEntity()), + Math.max(longuestHeader, getLongestMethods(getEntity())), 30); + final int springMethod = computeSpring(getLongestMethods(getEntity()), + Math.max(longuestHeader, getLongestField(getEntity())), 30); + + sb.append(""); + sb.append(""); + + if (showFields) { + sb.append(""); + } + if (showMethods) { + sb.append(""); + } + sb.append("
"); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(getEntity(), circleAbsolutePath, spring, + true, BorderMode.NO_BORDER)); + sb.append("
"); + if (getEntity().getFieldsToDisplay().size() > 0) { + buildTableVisibility(getEntity(), true, sb, springField); + } + sb.append("
"); + if (getEntity().getMethodsToDisplay().size() > 0) { + buildTableVisibility(getEntity(), false, sb, springMethod); + } + sb.append("
"); + } + // sb.append(">"); + } + +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderHtmlHeaderTableForObjectOrClass.java b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderHtmlHeaderTableForObjectOrClass.java new file mode 100644 index 000000000..6c4de6560 --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderHtmlHeaderTableForObjectOrClass.java @@ -0,0 +1,130 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +import net.sourceforge.plantuml.ColorParam; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.cucadiagram.IEntity; + +class LabelBuilderHtmlHeaderTableForObjectOrClass extends DotCommon implements LabelBuilder { + + private final IEntity entity; + private final String circleAbsolutePath; + private final int spring; + private final BorderMode borderMode; + private final boolean classes; + + LabelBuilderHtmlHeaderTableForObjectOrClass(FileFormat fileFormat, DotData data, IEntity entity, + final String circleAbsolutePath, int spring, boolean classes, BorderMode borderMode) { + super(fileFormat, data); + this.entity = entity; + this.circleAbsolutePath = circleAbsolutePath; + this.classes = classes; + this.borderMode = borderMode; + this.spring = spring; + + } + + public void appendLabel(StringBuilder sb) throws IOException { + if (spring == 0) { + htmlHeaderTableForObjectOrClassOrInterfaceOrEnumNoSpring(sb, 0); + return; + } + + if (borderMode == BorderMode.NO_BORDER) { + sb.append(""); + } else if (borderMode == BorderMode.NO_BORDER_CELLSPACING) { + sb.append("
"); + } else if (borderMode == BorderMode.BORDER_1_WITHOUT_COLOR) { + sb.append("
"); + } else if (borderMode == BorderMode.BORDER_1_WITH_COLOR) { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + sb.append("
"); + } else { + throw new IllegalStateException(); + } + sb.append(""); + + for (int i = 0; i < spring; i++) { + sb.append(""); + } + + if (circleAbsolutePath != null) { + appendImageAsTD(sb, circleAbsolutePath); + } + + sb.append(""); + + for (int i = 0; i < spring; i++) { + sb.append(""); + } + sb.append("
"); + appendLabelAndStereotype(entity, sb, classes); + sb.append("
"); + + } + + private void htmlHeaderTableForObjectOrClassOrInterfaceOrEnumNoSpring(StringBuilder sb, int cellSpacing) + throws IOException { + sb.append(""); + sb.append(""); + if (circleAbsolutePath == null) { + sb.append(""); + } else { + addTdImageBugB1983(sb, circleAbsolutePath); + } + sb.append("
"); + } else { + if (getData().getDpi() == 96) { + sb.append(""); + sb.append(""); + } + + appendLabelAndStereotype(entity, sb, classes); + sb.append("
"); + + } + +} diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderObjectOld.java b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderObjectOld.java new file mode 100644 index 000000000..b0c059e13 --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderObjectOld.java @@ -0,0 +1,80 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.io.IOException; + +import net.sourceforge.plantuml.ColorParam; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FontParam; +import net.sourceforge.plantuml.cucadiagram.IEntity; +import net.sourceforge.plantuml.cucadiagram.Member; + +class LabelBuilderObjectOld extends LabelBuilderObjectOrClass implements LabelBuilder { + + LabelBuilderObjectOld(FileFormat fileFormat, DotData data, IEntity entity) { + super(fileFormat, data, entity); + } + + public void appendLabel(StringBuilder sb) throws IOException { + final String stereo = getEntity().getStereotype() == null ? null : getEntity().getStereotype().getLabel(); + + sb.append("<"); + sb.append(""); + sb.append(""); + sb.append("
"); + + final int longuestFieldOrAttribute = getLongestFieldOrAttribute(getEntity()); + final int longuestHeader = getLonguestHeader(getEntity()); + final int spring = computeSpring(longuestHeader, longuestFieldOrAttribute, 30); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(getEntity(), null, spring, false, BorderMode.NO_BORDER)); + + sb.append("
"); + + if (getEntity().getFieldsToDisplay().size() == 0) { + sb.append(manageHtmlIB(" ", FontParam.OBJECT_ATTRIBUTE, stereo)); + } else { + for (Member att : getEntity().getFieldsToDisplay()) { + sb.append(manageHtmlIB(att.getDisplayWithVisibilityChar(), FontParam.OBJECT_ATTRIBUTE, stereo)); + sb.append("
"); + } + } + + sb.append("
>"); + + } + +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderObjectOrClass.java b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderObjectOrClass.java new file mode 100644 index 000000000..b18c62478 --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderObjectOrClass.java @@ -0,0 +1,78 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.io.IOException; + +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.cucadiagram.IEntity; + +abstract class LabelBuilderObjectOrClass extends DotCommon implements LabelBuilder { + + private final IEntity entity; + + LabelBuilderObjectOrClass(FileFormat fileFormat, DotData data, IEntity entity) { + super(fileFormat, data); + this.entity = entity; + } + + + protected final void buildTableVisibility(IEntity entity, boolean isField, final StringBuilder sb, int spring) + throws IOException { + final LabelBuilder builder = new LabelBuilderTableVisibility(getFileFormat(), getData(), entity, isField, + spring); + builder.appendLabel(sb); + if (builder.isUnderline()) { + setUnderline(true); + } + } + + protected final String getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(IEntity entity, final String circleAbsolutePath, + int spring, boolean classes, BorderMode border) throws IOException { + final LabelBuilder builder = new LabelBuilderHtmlHeaderTableForObjectOrClass(getFileFormat(), getData(), + entity, circleAbsolutePath, spring, classes, border); + final StringBuilder sb = new StringBuilder(); + builder.appendLabel(sb); + if (builder.isUnderline()) { + setUnderline(true); + } + return sb.toString(); + } + + + protected final IEntity getEntity() { + return entity; + } + +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderObjectWithVisibilityImage.java b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderObjectWithVisibilityImage.java new file mode 100644 index 000000000..b0ff5efee --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderObjectWithVisibilityImage.java @@ -0,0 +1,75 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.io.IOException; + +import net.sourceforge.plantuml.ColorParam; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FontParam; +import net.sourceforge.plantuml.cucadiagram.IEntity; + +class LabelBuilderObjectWithVisibilityImage extends LabelBuilderObjectOrClass implements LabelBuilder { + + LabelBuilderObjectWithVisibilityImage(FileFormat fileFormat, DotData data, IEntity entity) { + super(fileFormat, data, entity); + } + + public void appendLabel(StringBuilder sb) throws IOException { + final int longuestHeader = getLonguestHeader(getEntity()); + final int spring = computeSpring(longuestHeader, getLongestFieldOrAttribute(getEntity()), 30); + final int springField = computeSpring(getLongestField(getEntity()), Math.max(longuestHeader, + getLongestMethods(getEntity())), 30); + + final String stereo = getEntity().getStereotype() == null ? null : getEntity().getStereotype().getLabel(); + + sb.append("<"); + sb.append(""); + sb.append(""); + sb.append("
"); + + sb.append(getHtmlHeaderTableForObjectOrClassOrInterfaceOrEnum(getEntity(), null, spring, false, BorderMode.NO_BORDER)); + + sb.append("
"); + + if (getEntity().getFieldsToDisplay().size() == 0) { + sb.append(manageHtmlIB(" ", FontParam.OBJECT_ATTRIBUTE, stereo)); + } else { + buildTableVisibility(getEntity(), true, sb, springField); + } + + sb.append("
>"); + } +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderTableLollipopDecorator.java b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderTableLollipopDecorator.java new file mode 100644 index 000000000..b6592bc43 --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderTableLollipopDecorator.java @@ -0,0 +1,127 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import javax.imageio.ImageIO; + +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.cucadiagram.IEntity; + +class LabelBuilderTableLollipopDecorator extends LabelBuilderObjectOrClass implements LabelBuilder { + + private final LabelBuilder builder; + + private final String northPath; + private final String southPath; + private final String eastPath; + private final String westPath; + + LabelBuilderTableLollipopDecorator(FileFormat fileFormat, DotData data, IEntity entity, LabelBuilder builder, + String northPath, String southPath, String eastPath, String westPath, Collection collection) { + super(fileFormat, data, entity); + this.builder = builder; + this.northPath = northPath; + this.southPath = southPath; + this.eastPath = eastPath; + this.westPath = westPath; + + } + + public void appendLabel(StringBuilder sb) throws IOException { + final String backColor = getBackColorAroundEntity(getEntity()); + // backColor="\"#EE0000\""; + sb.append(""); + sb.append(""); + sb.append(""); + appendBlankOrImage(sb, northPath, Orientation.NORTH); + sb.append(""); + sb.append(""); + sb.append(""); + appendBlankOrImage(sb, westPath, Orientation.WEST); + sb.append(""); + appendBlankOrImage(sb, eastPath, Orientation.EAST); + sb.append(""); + sb.append(""); + sb.append(""); + appendBlankOrImage(sb, southPath, Orientation.SOUTH); + sb.append(""); + sb.append(""); + sb.append("
"); + sb.append(" "); + sb.append(""); + sb.append(" "); + sb.append("
"); + + builder.appendLabel(sb); + if (builder.isUnderline()) { + setUnderline(true); + } + + sb.append("
"); + sb.append(" "); + sb.append(""); + sb.append(" "); + sb.append("
"); + } + + private void appendBlankOrImage(StringBuilder sb, String path, Orientation orientation) throws IOException { + if (path == null) { + sb.append(""); + sb.append(" "); + sb.append(""); + } else if (path.endsWith(".png")) { + if (getData().getDpi() == 96) { + final BufferedImage im = ImageIO.read(new File(path)); + final int height = im.getHeight(); + final int width = im.getWidth(); + // sb.append(""); + sb.append(""); + } else { + addTdImageBugB1983(sb, path); + } + } else if (path.endsWith(".eps")) { + sb.append(""); + } + + } + +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderTableNineDecorator.java b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderTableNineDecorator.java new file mode 100644 index 000000000..1efd6e42f --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderTableNineDecorator.java @@ -0,0 +1,98 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.io.IOException; + +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.cucadiagram.IEntity; + +class LabelBuilderTableNineDecorator extends LabelBuilderObjectOrClass implements LabelBuilder { + + private final LabelBuilder builder; + + LabelBuilderTableNineDecorator(FileFormat fileFormat, DotData data, IEntity entity, LabelBuilder builder) { + super(fileFormat, data, entity); + this.builder = builder; + + } + + public void appendLabel(StringBuilder sb) throws IOException { + final String backColor = getBackColorAroundEntity(getEntity()); + // backColor="\"#EE0000\""; + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("
"); + sb.append(" "); + sb.append(""); + sb.append(" "); + sb.append(""); + sb.append(" "); + sb.append("
"); + sb.append(" "); + sb.append(""); + + builder.appendLabel(sb); + if (builder.isUnderline()) { + setUnderline(true); + } + + sb.append(""); + sb.append(" "); + sb.append("
"); + sb.append(" "); + sb.append(""); + sb.append(" "); + sb.append(""); + sb.append(" "); + sb.append("
"); + } + + +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderTableVisibility.java b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderTableVisibility.java new file mode 100644 index 000000000..d7989528f --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/LabelBuilderTableVisibility.java @@ -0,0 +1,99 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +import java.io.IOException; + +import net.sourceforge.plantuml.ColorParam; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FontParam; +import net.sourceforge.plantuml.StringUtils; +import net.sourceforge.plantuml.cucadiagram.IEntity; +import net.sourceforge.plantuml.cucadiagram.Member; +import net.sourceforge.plantuml.skin.VisibilityModifier; + +class LabelBuilderTableVisibility extends DotCommon implements LabelBuilder { + + private final IEntity entity; + private final boolean isField; + private final int spring; + + LabelBuilderTableVisibility(FileFormat fileFormat, DotData data, IEntity entity, boolean isField, int spring) { + super(fileFormat, data); + this.entity = entity; + this.isField = isField; + this.spring = spring; + } + + public void appendLabel(StringBuilder sb) throws IOException { + sb.append(""); + + final boolean hasStatic = hasStatic(entity.getMethodsToDisplay()); + final boolean dpiNormal = getData().getDpi() == 96; + for (Member att : isField ? entity.getFieldsToDisplay() : entity.getMethodsToDisplay()) { + sb.append(""); + if (dpiNormal) { + sb.append(""); + } + sb.append(""); + for (int i = 0; i < spring; i++) { + sb.append(""); + } + sb.append(""); + } + sb.append("
"); + } + String s = att.getDisplayWithVisibilityChar(); + final VisibilityModifier visibilityModifier = VisibilityModifier + .getVisibilityModifier(s.charAt(0), isField); + if (visibilityModifier != null) { + final String stereo = entity.getStereotype() == null ? null : entity.getStereotype().getLabel(); + final String modifierFile = StringUtils + .getPlateformDependentAbsolutePath(getData().getVisibilityImages(visibilityModifier, stereo) + .getPngOrEps(getFileFormat() == FileFormat.EPS)); + if (dpiNormal) { + sb.append(""); + } else { + addTdImageBugB1983(sb, modifierFile); + } + s = s.substring(1); + } + if (dpiNormal) { + sb.append(""); + sb.append(manageHtmlIBspecial(att, FontParam.CLASS_ATTRIBUTE, hasStatic, getColorString( + ColorParam.classBackground, null), false)); + sb.append("
"); + } + +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/cucadiagram/dot/Orientation.java b/src/net/sourceforge/plantuml/cucadiagram/dot/Orientation.java new file mode 100644 index 000000000..d922e0327 --- /dev/null +++ b/src/net/sourceforge/plantuml/cucadiagram/dot/Orientation.java @@ -0,0 +1,38 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6295 $ + * + */ +package net.sourceforge.plantuml.cucadiagram.dot; + +enum Orientation { + NORTH, SOUTH, EAST, WEST +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/directdot/PSystemDot.java b/src/net/sourceforge/plantuml/directdot/PSystemDot.java new file mode 100644 index 000000000..874c0ac76 --- /dev/null +++ b/src/net/sourceforge/plantuml/directdot/PSystemDot.java @@ -0,0 +1,67 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.directdot; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import net.sourceforge.plantuml.AbstractPSystem; +import net.sourceforge.plantuml.FileFormatOption; +import net.sourceforge.plantuml.cucadiagram.dot.Graphviz; +import net.sourceforge.plantuml.cucadiagram.dot.GraphvizUtils; + +public class PSystemDot extends AbstractPSystem { + + private final String data; + + public PSystemDot(String data) throws UnsupportedEncodingException { + this.data = data; + } + + public String getDescription() { + return "(Dot)"; + } + + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormatOption) + throws IOException { + final Graphviz graphviz = GraphvizUtils.create(data, fileFormatOption.getFileFormat().name().toLowerCase()); + try { + graphviz.createPng(os); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + throw new IOException(e.toString()); + } + + } +} diff --git a/src/net/sourceforge/plantuml/directdot/PSystemDotFactory.java b/src/net/sourceforge/plantuml/directdot/PSystemDotFactory.java new file mode 100644 index 000000000..c7673aa5a --- /dev/null +++ b/src/net/sourceforge/plantuml/directdot/PSystemDotFactory.java @@ -0,0 +1,83 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.directdot; + +import java.io.UnsupportedEncodingException; + +import net.sourceforge.plantuml.DiagramType; +import net.sourceforge.plantuml.PSystemBasicFactory; + +public class PSystemDotFactory implements PSystemBasicFactory { + + private StringBuilder data; + private boolean first; + private final DiagramType diagramType; + + public PSystemDotFactory(DiagramType diagramType) { + this.diagramType = diagramType; + reset(); + } + + public void reset() { + data = null; + first = true; + } + + public boolean executeLine(String line) { + if (first && line.matches("digraph\\s+\\w+\\s+\\{")) { + data = new StringBuilder(line); + data.append("\n"); + return true; + } + first = false; + if (data == null) { + return false; + } + data.append(line); + data.append("\n"); + return true; + } + + public PSystemDot getSystem() { + try { + return new PSystemDot(data.toString()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return null; + } + } + + public DiagramType getDiagramType() { + return diagramType; + } + +} diff --git a/src/net/sourceforge/plantuml/ditaa/PSystemDitaa.java b/src/net/sourceforge/plantuml/ditaa/PSystemDitaa.java new file mode 100644 index 000000000..c370196c6 --- /dev/null +++ b/src/net/sourceforge/plantuml/ditaa/PSystemDitaa.java @@ -0,0 +1,72 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.ditaa; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import javax.imageio.ImageIO; + +import net.sourceforge.plantuml.AbstractPSystem; +import net.sourceforge.plantuml.FileFormatOption; + +import org.stathissideris.ascii2image.core.ConversionOptions; +import org.stathissideris.ascii2image.graphics.BitmapRenderer; +import org.stathissideris.ascii2image.graphics.Diagram; +import org.stathissideris.ascii2image.text.TextGrid; + +public class PSystemDitaa extends AbstractPSystem { + + private final TextGrid grid = new TextGrid(); + + public PSystemDitaa(String data) throws UnsupportedEncodingException { + grid.initialiseWithText(data, null); + } + + public String getDescription() { + return "(Ditaa)"; + } + + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormatOption) + throws IOException { + + final ConversionOptions options = new ConversionOptions(); + final Diagram diagram = new Diagram(grid, options); + final BufferedImage image = (BufferedImage) new BitmapRenderer().renderToImage(diagram, + options.renderingOptions); + ImageIO.write(image, "png", os); + + } + +} diff --git a/src/net/sourceforge/plantuml/ditaa/PSystemDitaaFactory.java b/src/net/sourceforge/plantuml/ditaa/PSystemDitaaFactory.java new file mode 100644 index 000000000..71d18a0a4 --- /dev/null +++ b/src/net/sourceforge/plantuml/ditaa/PSystemDitaaFactory.java @@ -0,0 +1,89 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + */ +package net.sourceforge.plantuml.ditaa; + +import java.io.UnsupportedEncodingException; + +import net.sourceforge.plantuml.DiagramType; +import net.sourceforge.plantuml.PSystemBasicFactory; + +public class PSystemDitaaFactory implements PSystemBasicFactory { + + private StringBuilder data; + private boolean first; + private final DiagramType diagramType; + + public PSystemDitaaFactory(DiagramType diagramType) { + this.diagramType = diagramType; + reset(); + } + + public void reset() { + data = null; + if (diagramType == DiagramType.UML) { + first = true; + } else if (diagramType == DiagramType.DITAA) { + first = false; + data = new StringBuilder(); + } else { + throw new IllegalStateException(diagramType.name()); + } + } + + public boolean executeLine(String line) { + if (first && line.equals("ditaa")) { + data = new StringBuilder(); + return true; + } + first = false; + if (data == null) { + return false; + } + data.append(line); + data.append("\n"); + return true; + } + + public PSystemDitaa getSystem() { + try { + return new PSystemDitaa(data.toString()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return null; + } + } + + public DiagramType getDiagramType() { + return diagramType; + } + +} diff --git a/src/net/sourceforge/plantuml/eggs/PSystemEgg.java b/src/net/sourceforge/plantuml/eggs/PSystemEgg.java index 0b5bd38da..3e66acfd3 100644 --- a/src/net/sourceforge/plantuml/eggs/PSystemEgg.java +++ b/src/net/sourceforge/plantuml/eggs/PSystemEgg.java @@ -28,19 +28,16 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5794 $ + * Revision $Revision: 6453 $ * */ package net.sourceforge.plantuml.eggs; import java.awt.Color; import java.awt.Font; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.StringTokenizer; @@ -59,20 +56,20 @@ public class PSystemEgg extends AbstractPSystem { } } - public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { - OutputStream os = null; - try { - os = new FileOutputStream(suggestedFile); - getGraphicStrings().writeImage(os, fileFormat); - } finally { - if (os != null) { - os.close(); - } - } - return Arrays.asList(suggestedFile); - } +// public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { +// OutputStream os = null; +// try { +// os = new FileOutputStream(suggestedFile); +// getGraphicStrings().writeImage(os, fileFormat); +// } finally { +// if (os != null) { +// os.close(); +// } +// } +// return Arrays.asList(suggestedFile); +// } - public void createFile(OutputStream os, int index, FileFormatOption fileFormat) throws IOException { + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormat) throws IOException { getGraphicStrings().writeImage(os, fileFormat); } diff --git a/src/net/sourceforge/plantuml/eggs/PSystemEggFactory.java b/src/net/sourceforge/plantuml/eggs/PSystemEggFactory.java index d77ba64fd..86a73b560 100644 --- a/src/net/sourceforge/plantuml/eggs/PSystemEggFactory.java +++ b/src/net/sourceforge/plantuml/eggs/PSystemEggFactory.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 3830 $ + * Revision $Revision: 6341 $ * */ package net.sourceforge.plantuml.eggs; @@ -37,6 +37,7 @@ import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.List; +import net.sourceforge.plantuml.DiagramType; import net.sourceforge.plantuml.PSystemBasicFactory; public class PSystemEggFactory implements PSystemBasicFactory { @@ -75,5 +76,10 @@ public class PSystemEggFactory implements PSystemBasicFactory { public PSystemEgg getSystem() { return system; } + + public DiagramType getDiagramType() { + return DiagramType.UML; + } + } diff --git a/src/net/sourceforge/plantuml/eggs/PSystemLost.java b/src/net/sourceforge/plantuml/eggs/PSystemLost.java index 81432656e..77f247c1b 100644 --- a/src/net/sourceforge/plantuml/eggs/PSystemLost.java +++ b/src/net/sourceforge/plantuml/eggs/PSystemLost.java @@ -35,12 +35,9 @@ package net.sourceforge.plantuml.eggs; import java.awt.Color; import java.awt.Font; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import net.sourceforge.plantuml.AbstractPSystem; @@ -55,20 +52,20 @@ public class PSystemLost extends AbstractPSystem { strings.add("Thank you for choosing Oceanic Airlines."); } - public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { - OutputStream os = null; - try { - os = new FileOutputStream(suggestedFile); - getGraphicStrings().writeImage(os, fileFormat); - } finally { - if (os != null) { - os.close(); - } - } - return Arrays.asList(suggestedFile); - } +// public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { +// OutputStream os = null; +// try { +// os = new FileOutputStream(suggestedFile); +// getGraphicStrings().writeImage(os, fileFormat); +// } finally { +// if (os != null) { +// os.close(); +// } +// } +// return Arrays.asList(suggestedFile); +// } - public void createFile(OutputStream os, int index, FileFormatOption fileFormat) throws IOException { + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormat) throws IOException { getGraphicStrings().writeImage(os, fileFormat); } diff --git a/src/net/sourceforge/plantuml/eggs/PSystemLostFactory.java b/src/net/sourceforge/plantuml/eggs/PSystemLostFactory.java index fa6cc502a..741f117e2 100644 --- a/src/net/sourceforge/plantuml/eggs/PSystemLostFactory.java +++ b/src/net/sourceforge/plantuml/eggs/PSystemLostFactory.java @@ -33,6 +33,7 @@ */ package net.sourceforge.plantuml.eggs; +import net.sourceforge.plantuml.DiagramType; import net.sourceforge.plantuml.PSystemBasicFactory; public class PSystemLostFactory implements PSystemBasicFactory { @@ -57,5 +58,10 @@ public class PSystemLostFactory implements PSystemBasicFactory { public PSystemLost getSystem() { return system; } + + public DiagramType getDiagramType() { + return DiagramType.UML; + } + } diff --git a/src/net/sourceforge/plantuml/eggs/PSystemPath.java b/src/net/sourceforge/plantuml/eggs/PSystemPath.java index 14ae68ecd..d7e0ad090 100644 --- a/src/net/sourceforge/plantuml/eggs/PSystemPath.java +++ b/src/net/sourceforge/plantuml/eggs/PSystemPath.java @@ -33,12 +33,8 @@ */ package net.sourceforge.plantuml.eggs; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; import net.sourceforge.plantuml.AbstractPSystem; import net.sourceforge.plantuml.FileFormatOption; @@ -52,20 +48,20 @@ public class PSystemPath extends AbstractPSystem { } - public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { - OutputStream os = null; - try { - os = new FileOutputStream(suggestedFile); - path.writeImage(os); - } finally { - if (os != null) { - os.close(); - } - } - return Arrays.asList(suggestedFile); - } +// public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { +// OutputStream os = null; +// try { +// os = new FileOutputStream(suggestedFile); +// path.writeImage(os); +// } finally { +// if (os != null) { +// os.close(); +// } +// } +// return Arrays.asList(suggestedFile); +// } - public void createFile(OutputStream os, int index, FileFormatOption fileFormat) throws IOException { + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormat) throws IOException { path.writeImage(os); } diff --git a/src/net/sourceforge/plantuml/eggs/PSystemPathFactory.java b/src/net/sourceforge/plantuml/eggs/PSystemPathFactory.java index fe3c6d3a2..3d501b12d 100644 --- a/src/net/sourceforge/plantuml/eggs/PSystemPathFactory.java +++ b/src/net/sourceforge/plantuml/eggs/PSystemPathFactory.java @@ -36,6 +36,7 @@ package net.sourceforge.plantuml.eggs; import java.util.regex.Matcher; import java.util.regex.Pattern; +import net.sourceforge.plantuml.DiagramType; import net.sourceforge.plantuml.PSystemBasicFactory; public class PSystemPathFactory implements PSystemBasicFactory { @@ -64,5 +65,10 @@ public class PSystemPathFactory implements PSystemBasicFactory { public PSystemPath getSystem() { return system; } + + public DiagramType getDiagramType() { + return DiagramType.UML; + } + } diff --git a/src/net/sourceforge/plantuml/eggs/PSystemRIP.java b/src/net/sourceforge/plantuml/eggs/PSystemRIP.java index 8f29ac0e4..c013e490a 100644 --- a/src/net/sourceforge/plantuml/eggs/PSystemRIP.java +++ b/src/net/sourceforge/plantuml/eggs/PSystemRIP.java @@ -37,13 +37,10 @@ import java.awt.Color; import java.awt.Font; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import javax.imageio.ImageIO; @@ -75,20 +72,20 @@ public class PSystemRIP extends AbstractPSystem { is.close(); } - public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { - OutputStream os = null; - try { - os = new FileOutputStream(suggestedFile); - getGraphicStrings().writeImage(os, fileFormat); - } finally { - if (os != null) { - os.close(); - } - } - return Arrays.asList(suggestedFile); - } +// public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { +// OutputStream os = null; +// try { +// os = new FileOutputStream(suggestedFile); +// getGraphicStrings().writeImage(os, fileFormat); +// } finally { +// if (os != null) { +// os.close(); +// } +// } +// return Arrays.asList(suggestedFile); +// } - public void createFile(OutputStream os, int index, FileFormatOption fileFormat) throws IOException { + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormat) throws IOException { getGraphicStrings().writeImage(os, fileFormat); } diff --git a/src/net/sourceforge/plantuml/eggs/PSystemRIPFactory.java b/src/net/sourceforge/plantuml/eggs/PSystemRIPFactory.java index 70316c1f9..abc8982ce 100644 --- a/src/net/sourceforge/plantuml/eggs/PSystemRIPFactory.java +++ b/src/net/sourceforge/plantuml/eggs/PSystemRIPFactory.java @@ -35,6 +35,7 @@ package net.sourceforge.plantuml.eggs; import java.io.IOException; +import net.sourceforge.plantuml.DiagramType; import net.sourceforge.plantuml.Log; import net.sourceforge.plantuml.PSystemBasicFactory; @@ -66,5 +67,10 @@ public class PSystemRIPFactory implements PSystemBasicFactory { public PSystemRIP getSystem() { return system; } + + public DiagramType getDiagramType() { + return DiagramType.UML; + } + } diff --git a/src/net/sourceforge/plantuml/eps/EpsGraphics.java b/src/net/sourceforge/plantuml/eps/EpsGraphics.java index a289e47ae..5a8d77343 100644 --- a/src/net/sourceforge/plantuml/eps/EpsGraphics.java +++ b/src/net/sourceforge/plantuml/eps/EpsGraphics.java @@ -44,6 +44,8 @@ import net.sourceforge.plantuml.ugraphic.UGradient; public class EpsGraphics { + public static final String END_OF_FILE = "%plantuml done"; + // http://www.linuxfocus.org/Francais/May1998/article43.html // http://www.tailrecursive.org/postscript/text.html private final StringBuilder body = new StringBuilder(); @@ -53,7 +55,7 @@ public class EpsGraphics { private Color fillcolor = Color.BLACK; private String strokeWidth = "1"; - private String strokeDasharray = null; + // private String strokeDasharray = null; private final PostScriptCommandMacro setcolorgradient = new PostScriptCommandMacro("setcolorgradient"); private final PostScriptCommandMacro simplerect = new PostScriptCommandMacro("simplerect"); @@ -135,6 +137,7 @@ public class EpsGraphics { // writer.write("grestore\n"); append("showpage", true); + append(END_OF_FILE, true); append("%%EOF", true); closeDone = true; } @@ -166,28 +169,60 @@ public class EpsGraphics { this.fillcolor = c; } - public final void setStrokeWidth(String strokeWidth, String strokeDasharray) { + public final void setStrokeWidth(String strokeWidth, double dashVisible, double dashSpace) { checkCloseDone(); this.strokeWidth = strokeWidth; - this.strokeDasharray = strokeDasharray; + this.dashVisible = dashVisible; + this.dashSpace = dashSpace; } + private double dashVisible = 0; + private double dashSpace = 0; + public void epsLine(double x1, double y1, double x2, double y2) { ensureVisible(x1, y1); ensureVisible(x2, y2); - if (strokeDasharray != null) { - append("[" + strokeDasharray + "] 0 setdash", true); - } checkCloseDone(); append(strokeWidth + " setlinewidth", true); appendColor(color); append("newpath", true); - append(format(x1) + " " + format(y1) + " moveto", true); - append(format(x2 - x1) + " " + format(y2 - y1) + " rlineto", true); + if (dashVisible == 0 || dashSpace == 0) { + append(format(x1) + " " + format(y1) + " moveto", true); + append(format(x2 - x1) + " " + format(y2 - y1) + " rlineto", true); + } else if (x1 == x2) { + epsHLine(x1, Math.min(y1, y2), Math.max(y1, y2)); + } else if (y1 == y2) { + epsVLine(y1, Math.min(x1, x2), Math.max(x1, x2)); + } append("closepath stroke", true); ensureVisible(Math.max(x1, x2), Math.max(y1, y2)); - if (strokeDasharray != null) { - append("[] 0 setdash", true); + } + + protected void epsHLine(double x, double ymin, double ymax) { + append(format(x) + " " + format(ymin) + " moveto", true); + for (double y = ymin; y < ymax; y += dashVisible + dashSpace) { + final double v; + if (y + dashVisible > ymax) { + v = y - ymax; + } else { + v = dashSpace; + } + append("0 " + format(v) + " rlineto", true); + append("0 " + format(dashSpace) + " rmoveto", true); + } + } + + protected void epsVLine(double y, double xmin, double xmax) { + append(format(xmin) + " " + format(y) + " moveto", true); + for (double x = xmin; x < xmax; x += dashVisible + dashSpace) { + final double v; + if (x + dashVisible > xmax) { + v = x - xmax; + } else { + v = dashSpace; + } + append(format(v) + " 0 rlineto", true); + append(format(dashSpace) + " 0 rmoveto", true); } } @@ -238,14 +273,14 @@ public class EpsGraphics { ensureVisible(x + width, y + height); if (fillcolor != null) { appendColor(fillcolor); - epsRectangleInternal(x, y, width, height, rx, ry); + epsRectangleInternal(x, y, width, height, rx, ry, true); append("closepath eofill", true); } if (color != null) { append(strokeWidth + " setlinewidth", true); appendColor(color); - epsRectangleInternal(x, y, width, height, rx, ry); + epsRectangleInternal(x, y, width, height, rx, ry, false); append("closepath stroke", true); } } @@ -306,9 +341,10 @@ public class EpsGraphics { } } - private void epsRectangleInternal(double x, double y, double width, double height, double rx, double ry) { + private void epsRectangleInternal(double x, double y, double width, double height, double rx, double ry, + boolean fill) { if (rx == 0 && ry == 0) { - simpleRectangle(x, y, width, height); + simpleRectangle(x, y, width, height, fill); } else { roundRectangle(x, y, width, height, rx, ry); } @@ -320,9 +356,16 @@ public class EpsGraphics { roundrectUsed = true; } - private void simpleRectangle(double x, double y, double width, double height) { - append(format(width) + " " + format(height) + " " + format(x) + " " + format(y) + " simplerect", true); - simplerectUsed = true; + private void simpleRectangle(double x, double y, double width, double height, boolean fill) { + if ((dashSpace == 0 && dashVisible == 0) || fill) { + append(format(width) + " " + format(height) + " " + format(x) + " " + format(y) + " simplerect", true); + simplerectUsed = true; + } else { + epsVLine(y, x, x + width); + epsVLine(y + height, x, x + width); + epsHLine(x, y, y + height); + epsHLine(x + width, y, y + height); + } } public void epsEllipse(double x, double y, double xRadius, double yRadius) { @@ -408,7 +451,6 @@ public class EpsGraphics { public void newpath() { append("0 setlinewidth", true); - append("[] 0 setdash", true); appendColor(color); append("newpath", true); } @@ -479,4 +521,12 @@ public class EpsGraphics { append("grestore", true); } + protected final double getDashVisible() { + return dashVisible; + } + + protected final double getDashSpace() { + return dashSpace; + } + } diff --git a/src/net/sourceforge/plantuml/eps/EpsGraphicsMacro.java b/src/net/sourceforge/plantuml/eps/EpsGraphicsMacro.java index f373ca2fb..22ea6b97b 100644 --- a/src/net/sourceforge/plantuml/eps/EpsGraphicsMacro.java +++ b/src/net/sourceforge/plantuml/eps/EpsGraphicsMacro.java @@ -163,4 +163,44 @@ public class EpsGraphicsMacro extends EpsGraphics { macroInProgress = null; } + @Override + protected void epsHLine(double x, double ymin, double ymax) { + append(format(x) + " " + format(ymin) + " moveto", true); + int nb = (int) ((ymax - ymin) / (getDashVisible() + getDashSpace())); + final double lastY = ymin + nb * (getDashVisible() + getDashSpace()); + double v = ymax - lastY; + if (v > getDashVisible()) { + v = getDashVisible(); + nb++; + v = 0; + } + append(nb + "{", true); + append("0 " + format(getDashVisible()) + " rlineto", true); + append("0 " + format(getDashSpace()) + " rmoveto", true); + append("} repeat", true); + if (v > 0) { + append("0 " + format(v) + " rlineto", true); + } + } + + @Override + protected void epsVLine(double y, double xmin, double xmax) { + append(format(xmin) + " " + format(y) + " moveto", true); + int nb = (int) ((xmax - xmin) / (getDashVisible() + getDashSpace())); + final double lastX = xmin + nb * (getDashVisible() + getDashSpace()); + double v = xmax - lastX; + if (v > getDashVisible()) { + v = getDashVisible(); + nb++; + v = 0; + } + append(nb + "{", true); + append(format(getDashVisible()) + " 0 rlineto", true); + append(format(getDashSpace()) + " 0 rmoveto", true); + append("} repeat", true); + if (v > 0) { + append(format(v) + " 0 rlineto", true); + } + } + } diff --git a/src/net/sourceforge/plantuml/graphic/EmbededSystemLine.java b/src/net/sourceforge/plantuml/graphic/EmbededSystemLine.java new file mode 100644 index 000000000..4e4599a78 --- /dev/null +++ b/src/net/sourceforge/plantuml/graphic/EmbededSystemLine.java @@ -0,0 +1,114 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6009 $ + * + */ +package net.sourceforge.plantuml.graphic; + +import java.awt.Graphics2D; +import java.awt.geom.Dimension2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import javax.imageio.ImageIO; + +import net.sourceforge.plantuml.BlockUml; +import net.sourceforge.plantuml.Dimension2DDouble; +import net.sourceforge.plantuml.EmbededDiagram; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FileFormatOption; +import net.sourceforge.plantuml.PSystem; +import net.sourceforge.plantuml.ugraphic.UGraphic; +import net.sourceforge.plantuml.ugraphic.UImage; +import net.sourceforge.plantuml.ugraphic.UShape; + +class EmbededSystemLine implements Line { + + final List lines; + + public EmbededSystemLine(EmbededDiagram sys) { + this.lines = sys.getLines(); + } + + public Dimension2D calculateDimension(StringBounder stringBounder) { + try { + final BufferedImage im = getImage(); + return new Dimension2DDouble(im.getWidth(), im.getHeight()); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return new Dimension2DDouble(42, 42); + } + + public void draw(Graphics2D g2d, double x, double y) { + throw new UnsupportedOperationException(); + } + + public void drawU(UGraphic ug, double x, double y) { + try { + final BufferedImage im = getImage(); + final UShape image = new UImage(im); + ug.draw(x, y, image); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + + private BufferedImage getImage() throws IOException, InterruptedException { + final PSystem system = getSystem(); + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + system.exportDiagram(os, null, 0, new FileFormatOption(FileFormat.PNG)); + os.close(); + final ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); + final BufferedImage im = ImageIO.read(is); + is.close(); + return im; + } + + public HorizontalAlignement getHorizontalAlignement() { + return HorizontalAlignement.LEFT; + } + + private PSystem getSystem() throws IOException, InterruptedException { + final BlockUml blockUml = new BlockUml(lines); + return blockUml.getSystem(); + + } + +} diff --git a/src/net/sourceforge/plantuml/graphic/SingleLine.java b/src/net/sourceforge/plantuml/graphic/SingleLine.java index 5bceeb6da..f71ce0dc2 100644 --- a/src/net/sourceforge/plantuml/graphic/SingleLine.java +++ b/src/net/sourceforge/plantuml/graphic/SingleLine.java @@ -28,13 +28,11 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6009 $ + * Revision $Revision: 6353 $ * */ package net.sourceforge.plantuml.graphic; -import java.awt.Color; -import java.awt.Font; import java.awt.Graphics2D; import java.awt.geom.Dimension2D; import java.util.ArrayList; diff --git a/src/net/sourceforge/plantuml/graphic/TextBlockSimple.java b/src/net/sourceforge/plantuml/graphic/TextBlockSimple.java index 08899358b..a9ffeabef 100644 --- a/src/net/sourceforge/plantuml/graphic/TextBlockSimple.java +++ b/src/net/sourceforge/plantuml/graphic/TextBlockSimple.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6009 $ + * Revision $Revision: 6353 $ * */ package net.sourceforge.plantuml.graphic; @@ -39,6 +39,7 @@ import java.util.ArrayList; import java.util.List; import net.sourceforge.plantuml.Dimension2DDouble; +import net.sourceforge.plantuml.EmbededDiagram; import net.sourceforge.plantuml.cucadiagram.Stereotype; import net.sourceforge.plantuml.ugraphic.UGraphic; @@ -51,6 +52,8 @@ class TextBlockSimple implements TextBlock { for (CharSequence s : texts) { if (s instanceof Stereotype) { lines.add(createLineForStereotype(fontConfiguration, (Stereotype) s, horizontalAlignement)); + } else if (s instanceof EmbededDiagram) { + lines.add(new EmbededSystemLine((EmbededDiagram) s)); } else { lines.add(new SingleLine(s.toString(), fontConfiguration, horizontalAlignement)); } diff --git a/src/net/sourceforge/plantuml/objectdiagram/ObjectDiagramFactory.java b/src/net/sourceforge/plantuml/objectdiagram/ObjectDiagramFactory.java index cd4344df6..58b84ad55 100644 --- a/src/net/sourceforge/plantuml/objectdiagram/ObjectDiagramFactory.java +++ b/src/net/sourceforge/plantuml/objectdiagram/ObjectDiagramFactory.java @@ -35,6 +35,7 @@ package net.sourceforge.plantuml.objectdiagram; import net.sourceforge.plantuml.classdiagram.command.CommandLinkClass2; import net.sourceforge.plantuml.classdiagram.command.CommandMultilinesClassNote; +import net.sourceforge.plantuml.classdiagram.command.CommandUrl; import net.sourceforge.plantuml.command.AbstractUmlSystemCommandFactory; import net.sourceforge.plantuml.command.CommandCreateNote; import net.sourceforge.plantuml.command.CommandEndPackage; @@ -74,6 +75,7 @@ public class ObjectDiagramFactory extends AbstractUmlSystemCommandFactory { // // addCommand(new CommandImport(system)); addCommand(new CommandNoteEntity(system)); + addCommand(new CommandUrl(system)); addCommand(new CommandMultilinesClassNote(system)); addCommand(new CommandMultilinesStandaloneNote(system)); diff --git a/src/net/sourceforge/plantuml/oregon/PSystemOregon.java b/src/net/sourceforge/plantuml/oregon/PSystemOregon.java index bc57c2b8f..86b637505 100644 --- a/src/net/sourceforge/plantuml/oregon/PSystemOregon.java +++ b/src/net/sourceforge/plantuml/oregon/PSystemOregon.java @@ -35,12 +35,8 @@ package net.sourceforge.plantuml.oregon; import java.awt.Color; import java.awt.Font; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; import net.sourceforge.plantuml.AbstractPSystem; import net.sourceforge.plantuml.FileFormatOption; @@ -62,20 +58,20 @@ public class PSystemOregon extends AbstractPSystem { } } - public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { - OutputStream os = null; - try { - os = new FileOutputStream(suggestedFile); - getGraphicStrings().writeImage(os, fileFormat); - } finally { - if (os != null) { - os.close(); - } - } - return Arrays.asList(suggestedFile); - } +// public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { +// OutputStream os = null; +// try { +// os = new FileOutputStream(suggestedFile); +// getGraphicStrings().writeImage(os, fileFormat); +// } finally { +// if (os != null) { +// os.close(); +// } +// } +// return Arrays.asList(suggestedFile); +// } - public void createFile(OutputStream os, int index, FileFormatOption fileFormat) throws IOException { + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormat) throws IOException { getGraphicStrings().writeImage(os, fileFormat); } diff --git a/src/net/sourceforge/plantuml/oregon/PSystemOregonFactory.java b/src/net/sourceforge/plantuml/oregon/PSystemOregonFactory.java index 4ebb503b1..65538adca 100644 --- a/src/net/sourceforge/plantuml/oregon/PSystemOregonFactory.java +++ b/src/net/sourceforge/plantuml/oregon/PSystemOregonFactory.java @@ -36,6 +36,7 @@ package net.sourceforge.plantuml.oregon; import java.util.ArrayList; import java.util.List; +import net.sourceforge.plantuml.DiagramType; import net.sourceforge.plantuml.PSystemBasicFactory; public class PSystemOregonFactory implements PSystemBasicFactory { @@ -73,4 +74,9 @@ public class PSystemOregonFactory implements PSystemBasicFactory { inputs.add(line); return true; } + + public DiagramType getDiagramType() { + return DiagramType.UML; + } + } diff --git a/src/net/sourceforge/plantuml/png/PngFlashcoder.java b/src/net/sourceforge/plantuml/png/PngFlashcoder.java new file mode 100644 index 000000000..4b40b9529 --- /dev/null +++ b/src/net/sourceforge/plantuml/png/PngFlashcoder.java @@ -0,0 +1,95 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6009 $ + * + */ +package net.sourceforge.plantuml.png; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.util.List; + +public class PngFlashcoder { + + private final List flashcodes; + + public PngFlashcoder(List flashcodes) { + this.flashcodes = flashcodes; + } + + public BufferedImage processImage(BufferedImage im, Color background) { + if (flashcodes != null) { + im = addImage(im, background); + } + return im; + + } + + private BufferedImage addImage(BufferedImage im, Color background) { + + final double width = Math.max(im.getWidth(), getWidth(flashcodes)); + final double height = im.getHeight() + getHeight(flashcodes); + + final BufferedImage newIm = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_RGB); + final Graphics2D g2d = newIm.createGraphics(); + + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + g2d.setColor(background); + g2d.fillRect(0, 0, newIm.getWidth(), newIm.getHeight()); + g2d.drawImage(im, null, 0, 0); + int x = 0; + for (BufferedImage f : flashcodes) { + g2d.drawImage(f, null, x, (int) im.getHeight()); + x += f.getWidth(); + } + g2d.dispose(); + return newIm; + + } + + public static int getHeight(List flashcodes) { + int result = 0; + for (BufferedImage im : flashcodes) { + result = Math.max(result, im.getWidth()); + } + return result; + } + + public static int getWidth(List flashcodes) { + int result = 0; + for (BufferedImage im : flashcodes) { + result += im.getWidth(); + } + return result; + } +} diff --git a/src/net/sourceforge/plantuml/png/PngTitler.java b/src/net/sourceforge/plantuml/png/PngTitler.java index 5bb95d320..17ae8774a 100644 --- a/src/net/sourceforge/plantuml/png/PngTitler.java +++ b/src/net/sourceforge/plantuml/png/PngTitler.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6009 $ + * Revision $Revision: 6471 $ * */ package net.sourceforge.plantuml.png; @@ -149,4 +149,27 @@ public class PngTitler { return newIm; } + + public double getOffsetX(double imWidth, StringBounder stringBounder) { + final TextBlock textBloc = getTextBlock(); + if (textBloc == null) { + return 0; + } + final Dimension2D dimText = textBloc.calculateDimension(stringBounder); + + if (imWidth >= dimText.getWidth()) { + return 0; + } + return (dimText.getWidth() - imWidth) / 2; + } + + public double getOffsetY(StringBounder stringBounder) { + final TextBlock textBloc = getTextBlock(); + if (textBloc == null) { + return 0; + } + final Dimension2D dimText = textBloc.calculateDimension(stringBounder); + final double height = dimText.getHeight(); + return height; + } } diff --git a/src/net/sourceforge/plantuml/postit/Area.java b/src/net/sourceforge/plantuml/postit/Area.java index ac8c29913..2c29a0389 100644 --- a/src/net/sourceforge/plantuml/postit/Area.java +++ b/src/net/sourceforge/plantuml/postit/Area.java @@ -77,7 +77,7 @@ public class Area implements Elastic { } public double heightWhenWidthIs(double width, StringBounder stringBounder) { - final AreaLayoutFixedWidth layout = new AreaLayoutFixedWidth(); + final AreaLayoutFixedWidth layout = new AreaLayoutFixedWidth(width); final Map pos = layout.getPositions(postIts, stringBounder); double max = 10; for (Map.Entry ent : pos.entrySet()) { @@ -96,8 +96,8 @@ public class Area implements Elastic { postIts.add(postIt); } - public void drawU(UGraphic ug) { - final AreaLayout layout = new AreaLayoutFixedWidth(); + public void drawU(UGraphic ug, double width) { + final AreaLayout layout = new AreaLayoutFixedWidth(width); final Map pos = layout.getPositions(postIts, ug.getStringBounder()); final double tx = ug.getTranslateX(); final double ty = ug.getTranslateY(); diff --git a/src/net/sourceforge/plantuml/postit/AreaLayoutFixedWidth.java b/src/net/sourceforge/plantuml/postit/AreaLayoutFixedWidth.java index 3272b9507..436942964 100644 --- a/src/net/sourceforge/plantuml/postit/AreaLayoutFixedWidth.java +++ b/src/net/sourceforge/plantuml/postit/AreaLayoutFixedWidth.java @@ -44,15 +44,27 @@ import net.sourceforge.plantuml.graphic.StringBounder; public class AreaLayoutFixedWidth implements AreaLayout { + private final double width; + + public AreaLayoutFixedWidth(double width) { + this.width = width; + } + public Map getPositions(Collection all, StringBounder stringBounder) { double x = 0; double y = 0; + double maxY = 0; final Map result = new LinkedHashMap(); for (PostIt p : all) { - result.put(p, new Point2D.Double(x, y)); final Dimension2D dim = p.getDimension(stringBounder); - x += dim.getWidth() + 10; + if (x + dim.getWidth() > width) { + x = 0; + y = maxY; + } + result.put(p, new Point2D.Double(x, y)); + x += dim.getWidth(); + maxY = Math.max(maxY, y + dim.getHeight()); } return Collections.unmodifiableMap(result); diff --git a/src/net/sourceforge/plantuml/postit/PostItDiagram.java b/src/net/sourceforge/plantuml/postit/PostItDiagram.java index 2e8518eff..dfbb8f29d 100644 --- a/src/net/sourceforge/plantuml/postit/PostItDiagram.java +++ b/src/net/sourceforge/plantuml/postit/PostItDiagram.java @@ -36,11 +36,8 @@ package net.sourceforge.plantuml.postit; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -68,7 +65,8 @@ public class PostItDiagram extends UmlDiagram { throw new UnsupportedOperationException(); } - public void createFile(OutputStream os, int index, FileFormatOption fileFormatOption) throws IOException { + @Override + final protected void exportDiagramInternal(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormatOption, List flashcodes) throws IOException { final UGraphic ug = createImage(fileFormatOption); drawU(ug); if (ug instanceof UGraphicG2d) { @@ -83,19 +81,19 @@ public class PostItDiagram extends UmlDiagram { } } - public List createFiles(File suggestedFile, FileFormatOption fileFormatOption) throws IOException, - InterruptedException { - OutputStream os = null; - try { - os = new FileOutputStream(suggestedFile); - createFile(os, 0, fileFormatOption); - } finally { - if (os != null) { - os.close(); - } - } - return Arrays.asList(suggestedFile); - } +// public List createFiles(File suggestedFile, FileFormatOption fileFormatOption) throws IOException, +// InterruptedException { +// OutputStream os = null; +// try { +// os = new FileOutputStream(suggestedFile); +// createFile(os, 0, fileFormatOption); +// } finally { +// if (os != null) { +// os.close(); +// } +// } +// return Arrays.asList(suggestedFile); +// } public String getDescription() { return "Board of post-it"; @@ -120,7 +118,7 @@ public class PostItDiagram extends UmlDiagram { } void drawU(UGraphic ug) { - getDefaultArea().drawU(ug); + getDefaultArea().drawU(ug, width); } private UGraphic createImage(FileFormatOption fileFormatOption) { diff --git a/src/net/sourceforge/plantuml/preproc/PreprocessorInclude.java b/src/net/sourceforge/plantuml/preproc/PreprocessorInclude.java index d608d6212..4bcafb16c 100644 --- a/src/net/sourceforge/plantuml/preproc/PreprocessorInclude.java +++ b/src/net/sourceforge/plantuml/preproc/PreprocessorInclude.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6219 $ + * Revision $Revision: 6338 $ * */ package net.sourceforge.plantuml.preproc; @@ -102,12 +102,12 @@ class PreprocessorInclude implements ReadLine { } private ReadLine getReaderInclude(final File f, String suf) throws IOException { - if (StartumlExtractReader.containsStartuml(f)) { + if (StartDiagramExtractReader.containsStartDiagram(f)) { int bloc = 0; if (suf != null && suf.matches("\\d+")) { bloc = Integer.parseInt(suf); } - return new StartumlExtractReader(f, bloc); + return new StartDiagramExtractReader(f, bloc); } return new ReadLineReader(new FileReader(f)); } diff --git a/src/net/sourceforge/plantuml/preproc/StartumlExtractReader.java b/src/net/sourceforge/plantuml/preproc/StartDiagramExtractReader.java similarity index 85% rename from src/net/sourceforge/plantuml/preproc/StartumlExtractReader.java rename to src/net/sourceforge/plantuml/preproc/StartDiagramExtractReader.java index cbbfb1a34..678f9ef8a 100644 --- a/src/net/sourceforge/plantuml/preproc/StartumlExtractReader.java +++ b/src/net/sourceforge/plantuml/preproc/StartDiagramExtractReader.java @@ -38,21 +38,21 @@ import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import net.sourceforge.plantuml.BlockUmlBuilder; +import net.sourceforge.plantuml.StartUtils; -public class StartumlExtractReader implements ReadLine { +public class StartDiagramExtractReader implements ReadLine { private final ReadLine raw; private boolean finished = false; - public StartumlExtractReader(File f, int num) throws IOException { + public StartDiagramExtractReader(File f, int num) throws IOException { if (num < 0) { throw new IllegalArgumentException(); } raw = getReadLine(f); String s = null; while ((s = raw.readLine()) != null) { - if (BlockUmlBuilder.isArobaseStartuml(s)) { + if (StartUtils.isArobaseStartDiagram(s)) { if (num == 0) { return; } @@ -66,13 +66,13 @@ public class StartumlExtractReader implements ReadLine { return new UncommentReadLine(new ReadLineReader(new FileReader(f))); } - static public boolean containsStartuml(File f) throws IOException { + static public boolean containsStartDiagram(File f) throws IOException { ReadLine r = null; try { r = getReadLine(f); String s = null; while ((s = r.readLine()) != null) { - if (BlockUmlBuilder.isArobaseStartuml(s)) { + if (StartUtils.isArobaseStartDiagram(s)) { return true; } } @@ -89,7 +89,7 @@ public class StartumlExtractReader implements ReadLine { return null; } final String result = raw.readLine(); - if (result != null && BlockUmlBuilder.isArobaseEnduml(result)) { + if (result != null && StartUtils.isArobaseEndDiagram(result)) { finished = true; return null; } diff --git a/src/net/sourceforge/plantuml/preproc/UncommentReadLine.java b/src/net/sourceforge/plantuml/preproc/UncommentReadLine.java index 9578ecee4..c984c6774 100644 --- a/src/net/sourceforge/plantuml/preproc/UncommentReadLine.java +++ b/src/net/sourceforge/plantuml/preproc/UncommentReadLine.java @@ -45,7 +45,7 @@ public class UncommentReadLine implements ReadLine { public UncommentReadLine(ReadLine source) { this.raw = source; - this.start = Pattern.compile("(?i)(\\W*)@startuml"); + this.start = Pattern.compile("(?i)((?:\\W|\\<[^<>]*\\>)*)@start"); } public String readLine() throws IOException { diff --git a/src/net/sourceforge/plantuml/printskin/PrintSkin.java b/src/net/sourceforge/plantuml/printskin/PrintSkin.java index 3aa30fd14..237722650 100644 --- a/src/net/sourceforge/plantuml/printskin/PrintSkin.java +++ b/src/net/sourceforge/plantuml/printskin/PrintSkin.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6009 $ + * Revision $Revision: 6453 $ * */ package net.sourceforge.plantuml.printskin; @@ -37,7 +37,6 @@ import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.image.BufferedImage; -import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; @@ -73,17 +72,17 @@ class PrintSkin extends AbstractPSystem { private float ypos = 0; private float maxYpos = 0; - public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, - InterruptedException { - final List result = Arrays.asList(suggestedFile); - final BufferedImage im = createImage(); +// public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, +// InterruptedException { +// final List result = Arrays.asList(suggestedFile); +// final BufferedImage im = createImage(); +// +// PngIO.write(im.getSubimage(0, 0, im.getWidth(), (int) maxYpos), suggestedFile, 96); +// return result; +// +// } - PngIO.write(im.getSubimage(0, 0, im.getWidth(), (int) maxYpos), suggestedFile, 96); - return result; - - } - - public void createFile(OutputStream os, int index, FileFormatOption fileFormat) throws IOException { + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormat) throws IOException { final BufferedImage im = createImage(); PngIO.write(im.getSubimage(0, 0, im.getWidth(), (int) maxYpos), os, 96); } diff --git a/src/net/sourceforge/plantuml/printskin/PrintSkinFactory.java b/src/net/sourceforge/plantuml/printskin/PrintSkinFactory.java index 3fc8c387c..d68a0ef23 100644 --- a/src/net/sourceforge/plantuml/printskin/PrintSkinFactory.java +++ b/src/net/sourceforge/plantuml/printskin/PrintSkinFactory.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 3835 $ + * Revision $Revision: 6341 $ * */ package net.sourceforge.plantuml.printskin; @@ -37,6 +37,7 @@ import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; +import net.sourceforge.plantuml.DiagramType; import net.sourceforge.plantuml.PSystemBasicFactory; public class PrintSkinFactory implements PSystemBasicFactory { @@ -66,5 +67,10 @@ public class PrintSkinFactory implements PSystemBasicFactory { system = new PrintSkin(m.group(1), Arrays.asList(m.group(2))); return true; } + + public DiagramType getDiagramType() { + return DiagramType.UML; + } + } diff --git a/src/net/sourceforge/plantuml/project/BasicInstantArithmetic.java b/src/net/sourceforge/plantuml/project/BasicInstantArithmetic.java new file mode 100644 index 000000000..b3ba65fb1 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/BasicInstantArithmetic.java @@ -0,0 +1,82 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +class BasicInstantArithmetic implements InstantArithmetic { + + private final DayClose dayClose; + + BasicInstantArithmetic(DayClose dayClose) { + if (dayClose == null) { + throw new IllegalArgumentException(); + } + this.dayClose = dayClose; + } + + public Instant add(Instant i1, Duration duration) { + Instant result = i1; + final long min = duration.getMinutes(); + if (min < 0) { + throw new IllegalArgumentException(); + } + for (long i = 0; i < min; i += 24 * 60 * 60) { + result = result.next(dayClose); + } + return result; + } + + public Instant sub(Instant i1, Duration duration) { + Instant result = i1; + final long min = duration.getMinutes(); + if (min < 0) { + throw new IllegalArgumentException(); + } + for (long i = 0; i < min; i += 24 * 60 * 60) { + result = result.prev(dayClose); + } + return result; + } + + public Duration diff(Instant i1, Instant i2) { + if (i2.compareTo(i1) < 0) { + throw new IllegalArgumentException(); + } + long minutes = 0; + while (i2.compareTo(i1) > 0) { + minutes += 24 * 60 * 60; + i1 = i1.next(null); + } + return new Duration(minutes); + } +} diff --git a/src/net/sourceforge/plantuml/project/Constant.java b/src/net/sourceforge/plantuml/project/Constant.java new file mode 100644 index 000000000..9b452048d --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Constant.java @@ -0,0 +1,56 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +class Constant implements Expression { + + private final Numeric value; + + public Constant(Numeric value) { + this.value = value; + } + + public String getDescription() { + return "Constant:" + value; + } + + public NumericType getNumericType() { + return value.getNumericType(); + } + + public Numeric getValue() { + return value; + } + +} diff --git a/src/net/sourceforge/plantuml/project/Day.java b/src/net/sourceforge/plantuml/project/Day.java new file mode 100644 index 000000000..a6cadb552 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Day.java @@ -0,0 +1,175 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +public class Day implements Comparable { + + private final int numDay; + private final Month month; + private final int year; + private final WeekDay weekDay; + + private Day(int year, Month month, int numDay, WeekDay weekDay) { + this.year = year; + this.month = month; + this.numDay = numDay; + this.weekDay = weekDay; + } + + public static boolean isValidDesc(String desc) { + if (desc.matches("^\\d{4}/\\d{2}/\\d{2}$")) { + return true; + } + if (desc.matches("^\\d{2}-[A-Za-z]{3}-\\d{4}$")) { + return true; + } + return false; + } + + public Day(String desc) { + if (desc.matches("^\\d{4}/\\d{2}/\\d{2}$")) { + this.year = Integer.parseInt(desc.substring(0, 4)); + this.month = Month.fromNum(Integer.parseInt(desc.substring(5, 7))); + this.numDay = Integer.parseInt(desc.substring(8, 10)); + } else if (desc.matches("^\\d{2}-[A-Za-z]{3}-\\d{4}$")) { + this.year = Integer.parseInt(desc.substring(7, 11)); + this.month = Month.valueOf(desc.substring(3, 6)); + this.numDay = Integer.parseInt(desc.substring(0, 2)); + } else { + throw new IllegalArgumentException(desc); + } + final int wd = new GregorianCalendar(year, month.getNum() - 1, numDay).get(Calendar.DAY_OF_WEEK); + this.weekDay = WeekDay.values()[wd - 1]; + } + + public Day next(DayClose dayClose) { + if (dayClose == null) { + return nextInternal(); + } + if (dayClose.isClose(this)) { + throw new IllegalArgumentException(); + } + Day result = nextInternal(); + while (dayClose.isClose(result)) { + result = result.nextInternal(); + } + return result; + } + + public Day prev(DayClose dayClose) { + if (dayClose == null) { + return prevInternal(); + } + if (dayClose.isClose(this)) { + throw new IllegalArgumentException(); + } + Day result = prevInternal(); + while (dayClose.isClose(result)) { + result = result.prevInternal(); + } + return result; + } + + private Day nextInternal() { + if (numDay < month.getNbDays(year)) { + return new Day(year, month, numDay + 1, weekDay.next()); + } + final Month next = month.next(); + if (next == null) { + return new Day(year + 1, Month.JAN, 1, weekDay.next()); + } + return new Day(year, next, 1, weekDay.next()); + } + + private Day prevInternal() { + if (numDay > 1) { + return new Day(year, month, numDay - 1, weekDay.prev()); + } + final Month prev = month.prev(); + if (prev == null) { + return new Day(year - 1, Month.DEC, 31, weekDay.prev()); + } + return new Day(year, prev, prev.getNbDays(year), weekDay.prev()); + } + + @Override + public String toString() { + return "" + weekDay + " " + year + "-" + month + "-" + String.format("%02d", numDay); + } + + public final int getNumDay() { + return numDay; + } + + public final Month getMonth() { + return month; + } + + public final int getYear() { + return year; + } + + public int compareTo(Day other) { + if (year > other.year) { + return 1; + } + if (year < other.year) { + return -1; + } + final int cmpMonth = month.compareTo(other.month); + if (cmpMonth != 0) { + return cmpMonth; + } + return numDay - other.numDay; + } + + @Override + public boolean equals(Object obj) { + final Day this2 = (Day) obj; + return this.numDay == this2.numDay && this.month == this2.month && this.year == this2.year; + } + + @Override + public int hashCode() { + return numDay * 420 + year + month.hashCode(); + } + + public final WeekDay getWeekDay() { + return weekDay; + } + +} diff --git a/src/net/sourceforge/plantuml/project/DayClose.java b/src/net/sourceforge/plantuml/project/DayClose.java new file mode 100644 index 000000000..e77026cc6 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/DayClose.java @@ -0,0 +1,40 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +interface DayClose { + + boolean isClose(Day day); + +} diff --git a/src/net/sourceforge/plantuml/project/DayCloseNone.java b/src/net/sourceforge/plantuml/project/DayCloseNone.java new file mode 100644 index 000000000..f7b6212f6 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/DayCloseNone.java @@ -0,0 +1,42 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +class DayCloseNone implements DayClose { + + public boolean isClose(Day day) { + return false; + } + +} diff --git a/src/net/sourceforge/plantuml/project/DayCloseOr.java b/src/net/sourceforge/plantuml/project/DayCloseOr.java new file mode 100644 index 000000000..fb8dd38e9 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/DayCloseOr.java @@ -0,0 +1,56 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.util.ArrayList; +import java.util.List; + +class DayCloseOr implements DayClose { + + private final List all = new ArrayList(); + + public boolean isClose(Day day) { + for (DayClose dc : all) { + if (dc.isClose(day)) { + return true; + } + } + return false; + } + + public void add(DayClose dayClose) { + all.add(dayClose); + } + +} diff --git a/src/net/sourceforge/plantuml/project/DayCloseWeekDay.java b/src/net/sourceforge/plantuml/project/DayCloseWeekDay.java new file mode 100644 index 000000000..4b8d87070 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/DayCloseWeekDay.java @@ -0,0 +1,51 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +class DayCloseWeekDay implements DayClose { + + private final WeekDay weekDay; + + public DayCloseWeekDay(WeekDay weekDay) { + this.weekDay = weekDay; + } + + public boolean isClose(Day day) { + if (day.getWeekDay() == weekDay) { + return true; + } + return false; + } + +} diff --git a/src/net/sourceforge/plantuml/project/Duration.java b/src/net/sourceforge/plantuml/project/Duration.java new file mode 100644 index 000000000..c37bb432a --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Duration.java @@ -0,0 +1,80 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +class Duration implements Numeric { + + private final long minutes; + + public Duration(long minutes) { + this.minutes = minutes; + } + + public Duration(NumericNumber value) { + this(value.getIntValue() * 24L * 60 * 60); + } + + public Numeric add(Numeric other) { + return new Duration(((Duration) other).minutes + minutes); + } + + public static Duration of(long days) { + return new Duration(days * 24 * 60 * 60); + } + + public NumericType getNumericType() { + return NumericType.DURATION; + } + + public long getMinutes() { + return minutes; + } + + @Override + public String toString() { + return "DURATION:" + minutes / (24 * 60 * 60); + } + + public int compareTo(Numeric other) { + final Duration this2 = (Duration) other; + if (this2.minutes > minutes) { + return -1; + } + if (this2.minutes < minutes) { + return 1; + } + return 0; + } + +} diff --git a/src/net/sourceforge/plantuml/project/Expression.java b/src/net/sourceforge/plantuml/project/Expression.java new file mode 100644 index 000000000..d07115f9f --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Expression.java @@ -0,0 +1,44 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +public interface Expression { + + Numeric getValue(); + + String getDescription(); + + NumericType getNumericType(); + +} diff --git a/src/net/sourceforge/plantuml/project/Formal.java b/src/net/sourceforge/plantuml/project/Formal.java new file mode 100644 index 000000000..315f56007 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Formal.java @@ -0,0 +1,38 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +interface Formal extends Expression { + +} diff --git a/src/net/sourceforge/plantuml/project/FormalAddition.java b/src/net/sourceforge/plantuml/project/FormalAddition.java new file mode 100644 index 000000000..41f08ba08 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/FormalAddition.java @@ -0,0 +1,58 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +class FormalAddition implements Formal { + + private final Expression exp1; + private final Expression exp2; + + public FormalAddition(Expression exp1, Expression exp2) { + this.exp1 = exp1; + this.exp2 = exp2; + } + + public String getDescription() { + return "add " + exp1 + " " + exp2; + } + + public NumericType getNumericType() { + return exp1.getNumericType(); + } + + public Numeric getValue() { + return exp1.getValue().add(exp2.getValue()); + } + +} diff --git a/src/net/sourceforge/plantuml/project/FormalAdditionInstantDuration.java b/src/net/sourceforge/plantuml/project/FormalAdditionInstantDuration.java new file mode 100644 index 000000000..0dad01422 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/FormalAdditionInstantDuration.java @@ -0,0 +1,65 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +class FormalAdditionInstantDuration implements Formal { + + private final Expression exp1; + private final Expression exp2; + private final InstantArithmetic math; + + public FormalAdditionInstantDuration(Expression exp1, Expression exp2, InstantArithmetic math) { + this.exp1 = exp1; + this.exp2 = exp2; + this.math = math; + } + + public String getDescription() { + return "addID " + exp1 + " " + exp2; + } + + public NumericType getNumericType() { + return exp1.getNumericType(); + } + + public Numeric getValue() { + if (exp2.getNumericType() == NumericType.NUMBER) { + final Duration d = new Duration((NumericNumber) exp2.getValue()); + return math.add((Instant) exp1.getValue(), d); + } + + return math.add((Instant) exp1.getValue(), (Duration) exp2.getValue()); + } + +} diff --git a/src/net/sourceforge/plantuml/project/FreeVariable.java b/src/net/sourceforge/plantuml/project/FreeVariable.java new file mode 100644 index 000000000..b583c9082 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/FreeVariable.java @@ -0,0 +1,69 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +class FreeVariable implements Expression { + + private Expression value; + private final String name; + private final NumericType type; + + public FreeVariable(String name, NumericType type) { + this.name = name; + this.type = type; + } + + public String getDescription() { + return "$" + name + "=" + (value == null ? "null" : value.getDescription()); + } + + public NumericType getNumericType() { + return type; + } + + public Numeric getValue() { + if (value == null) { + return null; + } + return value.getValue(); + } + + public void setValue(Expression expression) { + if (expression.getNumericType() != type) { + throw new IllegalArgumentException("Bad type"); + } + this.value = expression; + } + +} diff --git a/src/net/sourceforge/plantuml/project/IncompleteItem.java b/src/net/sourceforge/plantuml/project/IncompleteItem.java new file mode 100644 index 000000000..a9601ab90 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/IncompleteItem.java @@ -0,0 +1,190 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +class IncompleteItem implements Item { + + private Map data = new EnumMap(ItemCaract.class); + + private final InstantArithmetic math; + + private final Item parent; + + private final String code; + + public IncompleteItem(String code, Item parent, InstantArithmetic math) { + this.math = math; + this.code = code; + this.parent = parent; + } + + public void setData(ItemCaract caract, Numeric value) { + if (caract.getNumericType() != value.getNumericType()) { + throw new IllegalArgumentException(); + } + if (data.containsKey(caract.getNumericType())) { + throw new IllegalStateException(); + } + data.put(caract, value); + boolean change = false; + do { + change = false; + change = eventuallyUseBeginComplete() || change; + change = eventuallyUseBeginDuration() || change; + change = eventuallyUseCompleteDuration() || change; + change = eventuallyUseDurationWork() || change; + change = eventuallyUseDurationLoad() || change; + change = eventuallyUseLoadWork() || change; + } while (change); + } + + private boolean eventuallyUseDurationWork() { + if (data.containsKey(ItemCaract.DURATION) && data.containsKey(ItemCaract.WORK) + && data.containsKey(ItemCaract.LOAD) == false) { + final Duration d = (Duration) data.get(ItemCaract.DURATION); + final NumericNumber w = (NumericNumber) data.get(ItemCaract.WORK); + data.put(ItemCaract.LOAD, new Load(d.getMinutes() * w.getIntValue())); + return true; + } + return false; + } + + private boolean eventuallyUseLoadWork() { + if (data.containsKey(ItemCaract.LOAD) && data.containsKey(ItemCaract.WORK) + && data.containsKey(ItemCaract.DURATION) == false) { + final Load l = (Load) data.get(ItemCaract.LOAD); + final NumericNumber w = (NumericNumber) data.get(ItemCaract.WORK); + data.put(ItemCaract.DURATION, new Duration(l.getMinuteMen() / w.getIntValue())); + return true; + } + return false; + } + + private boolean eventuallyUseDurationLoad() { + if (data.containsKey(ItemCaract.DURATION) && data.containsKey(ItemCaract.LOAD) + && data.containsKey(ItemCaract.WORK) == false) { + final Duration d = (Duration) data.get(ItemCaract.DURATION); + final Load l = (Load) data.get(ItemCaract.LOAD); + data.put(ItemCaract.WORK, new NumericNumber((int) (l.getMinuteMen() / d.getMinutes()))); + return true; + } + return false; + } + + private boolean eventuallyUseBeginDuration() { + if (data.containsKey(ItemCaract.BEGIN) && data.containsKey(ItemCaract.DURATION) + && data.containsKey(ItemCaract.COMPLETED) == false) { + final Instant i1 = (Instant) data.get(ItemCaract.BEGIN); + final Duration d = (Duration) data.get(ItemCaract.DURATION); + data.put(ItemCaract.COMPLETED, math.add(i1, d)); + return true; + } + return false; + } + + private boolean eventuallyUseCompleteDuration() { + if (data.containsKey(ItemCaract.COMPLETED) && data.containsKey(ItemCaract.DURATION) + && data.containsKey(ItemCaract.BEGIN) == false) { + final Instant i2 = (Instant) data.get(ItemCaract.COMPLETED); + final Duration d = (Duration) data.get(ItemCaract.DURATION); + data.put(ItemCaract.BEGIN, math.sub(i2, d)); + return true; + } + return false; + } + + private boolean eventuallyUseBeginComplete() { + if (data.containsKey(ItemCaract.BEGIN) && data.containsKey(ItemCaract.COMPLETED) + && data.containsKey(ItemCaract.DURATION) == false) { + final Instant i1 = (Instant) data.get(ItemCaract.BEGIN); + final Instant i2 = (Instant) data.get(ItemCaract.COMPLETED); + if (i2.compareTo(i1) <= 0) { + throw new IllegalArgumentException(); + } + data.put(ItemCaract.DURATION, math.diff(i1, i2)); + return true; + } + return false; + } + + public boolean isValid() { + return data.size() == EnumSet.allOf(ItemCaract.class).size(); + } + + public Instant getBegin() { + return (Instant) data.get(ItemCaract.BEGIN); + } + + public Instant getCompleted() { + return (Instant) data.get(ItemCaract.COMPLETED); + } + + public Duration getDuration() { + return (Duration) data.get(ItemCaract.DURATION); + } + + public Load getLoad() { + return (Load) data.get(ItemCaract.LOAD); + } + + public NumericNumber getWork() { + return (NumericNumber) data.get(ItemCaract.WORK); + } + + public boolean isLeaf() { + return true; + } + + public Item getParent() { + return parent; + } + + public List getChildren() { + return null; + } + + public String getCode() { + return code; + } + + @Override + public String toString() { + return code + " " + data.toString(); + } +} diff --git a/src/net/sourceforge/plantuml/project/Instant.java b/src/net/sourceforge/plantuml/project/Instant.java new file mode 100644 index 000000000..ef13d9e32 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Instant.java @@ -0,0 +1,85 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +public class Instant implements Numeric { + + private final Day value; + + public Instant(Day d) { + this.value = d; + } + + public Numeric add(Numeric other) { + throw new UnsupportedOperationException(); + } + + public NumericType getNumericType() { + return NumericType.INSTANT; + } + + public Day getDay() { + return value; + } + + public Instant next(DayClose dayClose) { + return new Instant(value.next(dayClose)); + } + + public Instant prev(DayClose dayClose) { + return new Instant(value.prev(dayClose)); + } + + @Override + public String toString() { + return "Instant:" + value; + } + + public int compareTo(Numeric other) { + final Instant this2 = (Instant) other; + return value.compareTo(this2.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + final Instant other = (Instant) obj; + return value.equals(other.value); + } + +} diff --git a/src/net/sourceforge/plantuml/project/InstantArithmetic.java b/src/net/sourceforge/plantuml/project/InstantArithmetic.java new file mode 100644 index 000000000..47d8f2987 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/InstantArithmetic.java @@ -0,0 +1,43 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +interface InstantArithmetic { + + public Instant add(Instant i1, Duration duration); + + public Instant sub(Instant i1, Duration duration); + + public Duration diff(Instant i1, Instant i2); +} diff --git a/src/net/sourceforge/plantuml/project/Item.java b/src/net/sourceforge/plantuml/project/Item.java new file mode 100644 index 000000000..fe58f901a --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Item.java @@ -0,0 +1,60 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.util.List; + +public interface Item { + + Instant getBegin(); + + Instant getCompleted(); + + Duration getDuration(); + + Load getLoad(); + + NumericNumber getWork(); + + boolean isLeaf(); + + Item getParent(); + + List getChildren(); + + public String getCode(); + + public boolean isValid(); + +} diff --git a/src/net/sourceforge/plantuml/project/ItemCaract.java b/src/net/sourceforge/plantuml/project/ItemCaract.java new file mode 100644 index 000000000..8130a553d --- /dev/null +++ b/src/net/sourceforge/plantuml/project/ItemCaract.java @@ -0,0 +1,71 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +enum ItemCaract { + BEGIN(NumericType.INSTANT), // + COMPLETED(NumericType.INSTANT), // + DURATION(NumericType.DURATION), // + LOAD(NumericType.LOAD), // + WORK(NumericType.NUMBER); + + private final NumericType type; + + private ItemCaract(NumericType type) { + this.type = type; + } + + public NumericType getNumericType() { + return type; + } + + public Numeric getData(Item item) { + if (this == BEGIN) { + return item.getBegin(); + } + if (this == COMPLETED) { + return item.getCompleted(); + } + if (this == DURATION) { + return item.getDuration(); + } + if (this == LOAD) { + return item.getLoad(); + } + if (this == WORK) { + return item.getWork(); + } + throw new UnsupportedOperationException(); + } +} diff --git a/src/net/sourceforge/plantuml/project/ItemComparator.java b/src/net/sourceforge/plantuml/project/ItemComparator.java new file mode 100644 index 000000000..687f7af05 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/ItemComparator.java @@ -0,0 +1,58 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.util.Comparator; + +public class ItemComparator implements Comparator { + + public int compare(Item it1, Item it2) { + final int cmp1 = it1.getBegin().compareTo(it2.getBegin()); + if (cmp1 != 0) { + return cmp1; + } + if (it1 instanceof Jalon && it2 instanceof Jalon == false) { + return -1; + } + if (it2 instanceof Jalon && it1 instanceof Jalon == false) { + return 1; + } + final int cmp2 = it2.getCompleted().compareTo(it1.getCompleted()); + if (cmp2 != 0) { + return cmp2; + } + return it1.getCode().compareTo(it2.getCode()); + } + +} diff --git a/src/net/sourceforge/plantuml/project/Jalon.java b/src/net/sourceforge/plantuml/project/Jalon.java new file mode 100644 index 000000000..3246330ea --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Jalon.java @@ -0,0 +1,93 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.util.List; + +public class Jalon implements Item { + + private Instant begin; + private final String code; + private final Item parent; + + public Jalon(String code, Item parent) { + this.code = code; + this.parent = parent; + } + + public Instant getBegin() { + return begin; + } + + public Instant getCompleted() { + return begin; + } + + public Duration getDuration() { + return new Duration(0); + } + + public Load getLoad() { + return new Load(0); + } + + public NumericNumber getWork() { + return new NumericNumber(1); + } + + public boolean isLeaf() { + return true; + } + + public Item getParent() { + return parent; + } + + public List getChildren() { + return null; + } + + public String getCode() { + return code; + } + + public boolean isValid() { + return begin != null; + } + + public void setInstant(Numeric value) { + this.begin = (Instant) value; + } + +} diff --git a/src/net/sourceforge/plantuml/project/Load.java b/src/net/sourceforge/plantuml/project/Load.java new file mode 100644 index 000000000..ae7bdb4d1 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Load.java @@ -0,0 +1,78 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +class Load implements Numeric { + + private final long minuteMen; + + public Load(long minuteMen) { + this.minuteMen = minuteMen; + } + + public Load(NumericNumber value) { + this(value.getIntValue() * 24L * 60 * 60); + } + + public Numeric add(Numeric other) { + return new Load(((Load) other).minuteMen + minuteMen); + } + + public NumericType getNumericType() { + return NumericType.LOAD; + } + + public int compareTo(Numeric other) { + final Load this2 = (Load) other; + if (this2.minuteMen > minuteMen) { + return -1; + } + if (this2.minuteMen < minuteMen) { + return 1; + } + return 0; + } + + public final long getMinuteMen() { + return minuteMen; + } + + @Override + public String toString() { + return "LOAD:" + minuteMen / (24 * 60 * 60); + } + + + +} diff --git a/src/net/sourceforge/plantuml/project/Month.java b/src/net/sourceforge/plantuml/project/Month.java new file mode 100644 index 000000000..ab1ec57a0 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Month.java @@ -0,0 +1,84 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +public enum Month { + + JAN(31), FEB(28), MAR(31), APR(30), MAY(31), JUN(30), JUL(31), AUG(31), SEP(30), OCT(31), NOV(30), DEC(31); + + final private int nbDays; + + private Month(int nbDays) { + this.nbDays = nbDays; + } + + public final int getNbDays(int year) { + if (this == FEB && year % 4 == 0) { + return 29; + } + return nbDays; + } + + public final int getNum() { + return ordinal() + 1; + } + + public Month next() { + if (this == DEC) { + return null; + } + final List all = new ArrayList(EnumSet.allOf(Month.class)); + return all.get(getNum()); + } + + public Month prev() { + if (this == JAN) { + return null; + } + final List all = new ArrayList(EnumSet.allOf(Month.class)); + return all.get(getNum() - 2); + } + + public static Month fromNum(int num) { + if (num < 1 || num > 12) { + throw new IllegalArgumentException(); + } + final List all = new ArrayList(EnumSet.allOf(Month.class)); + return all.get(num - 1); + } +} diff --git a/src/net/sourceforge/plantuml/project/Numeric.java b/src/net/sourceforge/plantuml/project/Numeric.java new file mode 100644 index 000000000..d03c60a23 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Numeric.java @@ -0,0 +1,41 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +public interface Numeric extends Comparable { + Numeric add(Numeric other); + + NumericType getNumericType(); + +} diff --git a/src/net/sourceforge/plantuml/project/NumericNumber.java b/src/net/sourceforge/plantuml/project/NumericNumber.java new file mode 100644 index 000000000..60cef46f9 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/NumericNumber.java @@ -0,0 +1,75 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +class NumericNumber implements Numeric { + + private final int value; + + public NumericNumber(int v) { + this.value = v; + } + + public Numeric add(Numeric other) { + if (other.getNumericType() != getNumericType()) { + throw new IllegalArgumentException(); + } + return new NumericNumber(value + ((NumericNumber) other).value); + } + + public NumericType getNumericType() { + return NumericType.NUMBER; + } + + public int getIntValue() { + return value; + } + + @Override + public String toString() { + return "Number:" + value; + } + + public int compareTo(Numeric other) { + final NumericNumber this2 = (NumericNumber) other; + if (this2.value > value) { + return -1; + } + if (this2.value < value) { + return 1; + } + return 0; + } + +} diff --git a/src/net/sourceforge/plantuml/project/NumericType.java b/src/net/sourceforge/plantuml/project/NumericType.java new file mode 100644 index 000000000..e62f96b19 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/NumericType.java @@ -0,0 +1,78 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +enum NumericType { + + NUMBER, INSTANT, LOAD, DURATION; + + public NumericType add(NumericType other) { + if (this == NUMBER) { + return addNumber(other); + } + if (this == INSTANT) { + return null; + } + if (this == LOAD) { + return addLoad(other); + } + if (this == DURATION) { + return addDuration(other); + } + throw new UnsupportedOperationException(); + + } + + private NumericType addDuration(NumericType other) { + if (other == DURATION) { + return DURATION; + } + return null; + } + + private NumericType addLoad(NumericType other) { + if (other == LOAD) { + return LOAD; + } + return null; + } + + private NumericType addNumber(NumericType other) { + if (other == NUMBER) { + return NUMBER; + } + return null; + } + +} diff --git a/src/net/sourceforge/plantuml/project/PSystemProject.java b/src/net/sourceforge/plantuml/project/PSystemProject.java new file mode 100644 index 000000000..9952ccac2 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/PSystemProject.java @@ -0,0 +1,111 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; + +import net.sourceforge.plantuml.AbstractPSystem; +import net.sourceforge.plantuml.EmptyImageBuilder; +import net.sourceforge.plantuml.FileFormat; +import net.sourceforge.plantuml.FileFormatOption; +import net.sourceforge.plantuml.eps.EpsStrategy; +import net.sourceforge.plantuml.graphic.HtmlColor; +import net.sourceforge.plantuml.png.PngIO; +import net.sourceforge.plantuml.project.graphic.GanttDiagram; +import net.sourceforge.plantuml.ugraphic.eps.UGraphicEps; +import net.sourceforge.plantuml.ugraphic.g2d.UGraphicG2d; +import net.sourceforge.plantuml.ugraphic.svg.UGraphicSvg; + +public class PSystemProject extends AbstractPSystem { + + private final Project project = new Project(); + private final Color background = Color.WHITE; + + public int getNbImages() { + return 1; + } + + public String getDescription() { + return "(Project)"; + } + + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormatOption) + throws IOException { + final GanttDiagram diagram = new GanttDiagram(project); + final FileFormat fileFormat = fileFormatOption.getFileFormat(); + if (fileFormat == FileFormat.PNG) { + final BufferedImage im = createImage(diagram); + PngIO.write(im, os, getMetadata(), 96); + } else if (fileFormat == FileFormat.SVG) { + final UGraphicSvg svg = new UGraphicSvg(HtmlColor.getAsHtml(background), false); + diagram.draw(svg, 0, 0); + svg.createXml(os); + } else if (fileFormat == FileFormat.EPS) { + final UGraphicEps eps = new UGraphicEps(EpsStrategy.getDefault()); + diagram.draw(eps, 0, 0); + os.write(eps.getEPSCode().getBytes()); + } else { + throw new UnsupportedOperationException(); + } + } + + private BufferedImage createImage(GanttDiagram diagram) { + EmptyImageBuilder builder = new EmptyImageBuilder(10, 10, background); + Graphics2D g2d = builder.getGraphics2D(); + UGraphicG2d ug = new UGraphicG2d(g2d, null, 1.0); + + final double height = diagram.getHeight(ug.getStringBounder()); + final double width = diagram.getWidth(ug.getStringBounder()); + + g2d.dispose(); + + builder = new EmptyImageBuilder(width, height, background); + final BufferedImage im = builder.getBufferedImage(); + g2d = builder.getGraphics2D(); + + ug = new UGraphicG2d(g2d, im, 1.0); + diagram.draw(ug, 0, 0); + g2d.dispose(); + return im; + } + + public final Project getProject() { + return project; + } + +} diff --git a/src/net/sourceforge/plantuml/project/PSystemProjectFactory.java b/src/net/sourceforge/plantuml/project/PSystemProjectFactory.java new file mode 100644 index 000000000..54d471120 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/PSystemProjectFactory.java @@ -0,0 +1,69 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6002 $ + * + */ +package net.sourceforge.plantuml.project; + +import net.sourceforge.plantuml.DiagramType; +import net.sourceforge.plantuml.PSystem; +import net.sourceforge.plantuml.command.AbstractUmlSystemCommandFactory; +import net.sourceforge.plantuml.command.CommandComment; +import net.sourceforge.plantuml.command.CommandNope; +import net.sourceforge.plantuml.project.command.CommandAffectation; +import net.sourceforge.plantuml.project.command.CommandCloseWeekDay; + +public class PSystemProjectFactory extends AbstractUmlSystemCommandFactory { + + private PSystemProject systemProject; + + public PSystemProjectFactory() { + super(DiagramType.PROJECT); + } + + public PSystem getSystem() { + return systemProject; + } + + Project getProject() { + return systemProject.getProject(); + } + + @Override + protected void initCommands() { + systemProject = new PSystemProject(); + addCommand(new CommandNope(systemProject)); + addCommand(new CommandComment(systemProject)); + addCommand(new CommandAffectation(systemProject)); + addCommand(new CommandCloseWeekDay(systemProject)); + } + +} diff --git a/src/net/sourceforge/plantuml/project/ParentItem.java b/src/net/sourceforge/plantuml/project/ParentItem.java new file mode 100644 index 000000000..9f93f4b57 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/ParentItem.java @@ -0,0 +1,131 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public class ParentItem implements Item { + + private final String code; + private final Item parent; + + private final List children = new ArrayList(); + + public ParentItem(String code, Item parent) { + this.code = code; + this.parent = parent; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(code + " {"); + for (Iterator it = children.iterator(); it.hasNext();) { + final Item child = it.next(); + sb.append(child.getCode()); + if (it.hasNext()) { + sb.append(", "); + } + } + sb.append("}"); + return sb.toString(); + } + + public Instant getBegin() { + Instant result = null; + for (Item it : children) { + if (result == null || result.compareTo(it.getBegin()) > 0) { + result = it.getBegin(); + } + } + return result; + } + + public Instant getCompleted() { + Instant result = null; + for (Item it : children) { + if (result == null || result.compareTo(it.getCompleted()) < 0) { + result = it.getCompleted(); + } + } + return result; + } + + public Duration getDuration() { + throw new UnsupportedOperationException(); + } + + public Load getLoad() { + throw new UnsupportedOperationException(); + } + + public NumericNumber getWork() { + throw new UnsupportedOperationException(); + } + + public boolean isLeaf() { + return false; + } + + public Item getParent() { + return parent; + } + + public List getChildren() { + return Collections.unmodifiableList(children); + } + + public String getCode() { + return code; + } + + public void addChild(Item child) { + this.children.add(child); + } + + public boolean isValid() { + if (children.size() == 0) { + return false; + } + for (Item it : children) { + if (it.isValid() == false) { + return false; + } + } + return true; + } + +} diff --git a/src/net/sourceforge/plantuml/project/Project.java b/src/net/sourceforge/plantuml/project/Project.java new file mode 100644 index 000000000..0b7da4811 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Project.java @@ -0,0 +1,279 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class Project { + + private final Map variables = new TreeMap(); + + private final Map items = new TreeMap(); + + private final Map ressources = new TreeMap(); + + private final DayCloseOr dayClose = new DayCloseOr(); + + public Project() { + } + + public final Instant getStart() { + Instant result = null; + for (Item it : getValidItems()) { + if (result == null || result.compareTo(it.getBegin()) > 0) { + result = it.getBegin(); + } + } + return result; + } + + public final Instant getEnd() { + Instant result = null; + for (Item it : getValidItems()) { + if (result == null || result.compareTo(it.getCompleted()) < 0) { + result = it.getCompleted(); + } + } + return result; + } + + public FreeVariable createVariable(String name, NumericType type) { + if (variables.containsKey(name)) { + throw new IllegalArgumentException("Already exist: " + name); + } + final FreeVariable variable = new FreeVariable(name, type); + variables.put(name, variable); + return variable; + } + + public Expression getExpression(String desc) { + desc = desc.trim(); + final int plus = desc.indexOf('+'); + + if (plus != -1) { + final Expression exp1 = getExpression(desc.substring(0, plus)); + final Expression exp2 = getExpression(desc.substring(plus + 1)); + if (exp1.getNumericType() == NumericType.INSTANT + && (exp2.getNumericType() == NumericType.DURATION || exp2.getNumericType() == NumericType.NUMBER)) { + return new FormalAdditionInstantDuration(exp1, exp2, new BasicInstantArithmetic(dayClose)); + } + if (exp2.getNumericType() == NumericType.INSTANT + && (exp1.getNumericType() == NumericType.DURATION || exp1.getNumericType() == NumericType.NUMBER)) { + return new FormalAdditionInstantDuration(exp2, exp1, new BasicInstantArithmetic(dayClose)); + } + return new FormalAddition(exp1, exp2); + } + + if (desc.matches("^\\d+$")) { + return new Constant(new NumericNumber(Integer.parseInt(desc))); + } + if (desc.matches("^\\$\\w+$")) { + final String varName = desc.substring(1); + final FreeVariable v = variables.get(varName); + if (v != null) { + return v; + } + throw new IllegalArgumentException("No such variable: " + desc); + } + if (Day.isValidDesc(desc)) { + final Day d = new Day(desc); + return new Constant(new Instant(d)); + } + if (desc.matches("^[\\w/]+\\$(begin|completed|work|load|duration)$")) { + final int idx = desc.indexOf('$'); + final String varName = desc.substring(0, idx); + final Item item = items.get(varName); + if (item == null) { + throw new IllegalArgumentException("No such variable: " + desc); + } + return new Constant(ItemCaract.valueOf(desc.substring(idx + 1).toUpperCase()).getData(item)); + } + if (desc.startsWith("^")) { + final Item item = items.get(desc.substring(1)); + if (item == null) { + throw new IllegalArgumentException("No such variable: " + desc); + } + return new Constant(item.getBegin()); + } + throw new IllegalArgumentException("cannot parse"); + } + + public boolean affectation(String destination, Expression expression) { + if (destination.startsWith("^")) { + return affectationJalon(destination, expression); + } + if (destination.startsWith("~")) { + return affectationRessource(destination, expression); + } + final int idx = destination.indexOf('$'); + if (idx == -1) { + return affectationVariable(destination, expression); + } + final String itemName = destination.substring(0, idx); + final Item item = getItem(itemName); + if (item instanceof IncompleteItem == false) { + return false; + } + final IncompleteItem incompleteItem = (IncompleteItem) item; + final String suf = destination.substring(idx + 1); + if (suf.equalsIgnoreCase("begin")) { + incompleteItem.setData(ItemCaract.BEGIN, expression.getValue()); + } else if (suf.equalsIgnoreCase("completed")) { + incompleteItem.setData(ItemCaract.COMPLETED, expression.getValue()); + } else if (suf.equalsIgnoreCase("work")) { + incompleteItem.setData(ItemCaract.WORK, expression.getValue()); + } else if (suf.equalsIgnoreCase("duration")) { + if (expression.getNumericType() == NumericType.NUMBER) { + expression = new Constant(new Duration((NumericNumber) expression.getValue())); + } + incompleteItem.setData(ItemCaract.DURATION, expression.getValue()); + } else if (suf.equalsIgnoreCase("LOAD")) { + if (expression.getNumericType() == NumericType.NUMBER) { + expression = new Constant(new Load((NumericNumber) expression.getValue())); + } + incompleteItem.setData(ItemCaract.LOAD, expression.getValue()); + } else { + return false; + } + return true; + } + + private boolean affectationRessource(String res, Expression expression) { + res = res.substring(1); + final int idx = res.indexOf('$'); + final String suf = res.substring(idx + 1); + if (suf.equals("capacity")) { + final Ressource ressource = getRessource(res.substring(0, idx)); + ressource.setCapacity(((NumericNumber) expression.getValue()).getIntValue()); + return true; + } + return false; + } + + private Ressource getRessource(String code) { + Ressource result = ressources.get(code); + if (result == null) { + result = new Ressource(code); + ressources.put(code, result); + } + return result; + } + + private boolean affectationJalon(String jalon, Expression expression) { + final Jalon it = getItemJalon(jalon.substring(1)); + it.setInstant(expression.getValue()); + return true; + } + + private Jalon getItemJalon(String jalon) { + Jalon result = (Jalon) items.get(jalon); + if (result == null) { + result = new Jalon(jalon, null); + items.put(jalon, result); + + } + return result; + } + + private Item getItem(String code) { + Item result = items.get(code); + if (result == null) { + final int idx = code.indexOf('/'); + if (idx == -1) { + result = new IncompleteItem(code, null, new BasicInstantArithmetic(dayClose)); + } else { + final ParentItem parent = getItemParent(code.substring(0, idx)); + result = new IncompleteItem(code, parent, new BasicInstantArithmetic(dayClose)); + parent.addChild(result); + } + items.put(code, result); + } + return result; + } + + private ParentItem getItemParent(String code) { + Item result = items.get(code); + if (result == null) { + final int idx = code.indexOf('/'); + if (idx == -1) { + result = new ParentItem(code, null); + items.put(code, result); + } else { + throw new UnsupportedOperationException(); + } + } + return (ParentItem) result; + } + + private boolean affectationVariable(String destination, Expression expression) { + if (variables.containsKey(destination) == false) { + return false; + } + variables.get(destination).setValue(expression); + return true; + } + + public List getValidItems() { + final List result = new ArrayList(); + for (Item item : items.values()) { + if (item.isValid()) { + result.add(item); + } + } + Collections.sort(result, new ItemComparator()); + return Collections.unmodifiableList(result); + } + + public final DayClose getDayClose() { + return dayClose; + } + + public void closeWeekDay(WeekDay weekDay) { + dayClose.add(new DayCloseWeekDay(weekDay)); + } + + // public Item getItem(String code) { + // BasicItem result = items.get(code); + // if (result == null) { + // result = new BasicItem(code); + // items.put(code, result); + // } + // return result; + // } + +} diff --git a/src/net/sourceforge/plantuml/project/Ressource.java b/src/net/sourceforge/plantuml/project/Ressource.java new file mode 100644 index 000000000..72a2fced5 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/Ressource.java @@ -0,0 +1,58 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +public class Ressource { + + private final String code; + + private int capacity = 1; + + public Ressource(String code) { + this.code = code; + } + + public DayClose getDayClose() { + return new DayCloseNone(); + } + + public final int getCapacity() { + return capacity; + } + + public final void setCapacity(int capacity) { + this.capacity = capacity; + } + +} diff --git a/src/net/sourceforge/plantuml/project/WeekDay.java b/src/net/sourceforge/plantuml/project/WeekDay.java new file mode 100644 index 000000000..94d4a0362 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/WeekDay.java @@ -0,0 +1,60 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; + +public enum WeekDay { + + SUN, MON, TUE, WED, THU, FRI, SAT; + + public WeekDay next() { + if (this.ordinal() == 6) { + return SUN; + } + final List all = new ArrayList(EnumSet.allOf(WeekDay.class)); + return all.get(this.ordinal() + 1); + } + + public WeekDay prev() { + if (this.ordinal() == 0) { + return SAT; + } + final List all = new ArrayList(EnumSet.allOf(WeekDay.class)); + return all.get(this.ordinal() - 1); + } + +} diff --git a/src/net/sourceforge/plantuml/project/command/CommandAffectation.java b/src/net/sourceforge/plantuml/project/command/CommandAffectation.java new file mode 100644 index 000000000..6b2ebb870 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/command/CommandAffectation.java @@ -0,0 +1,58 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 5884 $ + * + */ +package net.sourceforge.plantuml.project.command; + +import java.util.List; + +import net.sourceforge.plantuml.command.CommandExecutionResult; +import net.sourceforge.plantuml.command.SingleLineCommand; +import net.sourceforge.plantuml.project.Expression; +import net.sourceforge.plantuml.project.PSystemProject; + +public class CommandAffectation extends SingleLineCommand { + + public CommandAffectation(PSystemProject diagram) { + super(diagram, "(?i)^\\s*([~\\^]?[\\w$/]+)\\s*:=\\s*(.+)$"); + } + + @Override + protected CommandExecutionResult executeArg(List arg) { + final Expression exp = getSystem().getProject().getExpression(arg.get(1).trim()); + final boolean ok = getSystem().getProject().affectation(arg.get(0).trim(), exp); + if (ok) { + return CommandExecutionResult.ok(); + } + return CommandExecutionResult.error("Cannot execute"); + } +} diff --git a/src/net/sourceforge/plantuml/project/command/CommandCloseWeekDay.java b/src/net/sourceforge/plantuml/project/command/CommandCloseWeekDay.java new file mode 100644 index 000000000..f8aa301cd --- /dev/null +++ b/src/net/sourceforge/plantuml/project/command/CommandCloseWeekDay.java @@ -0,0 +1,55 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 5884 $ + * + */ +package net.sourceforge.plantuml.project.command; + +import java.util.List; + +import net.sourceforge.plantuml.command.CommandExecutionResult; +import net.sourceforge.plantuml.command.SingleLineCommand; +import net.sourceforge.plantuml.project.PSystemProject; +import net.sourceforge.plantuml.project.WeekDay; + +public class CommandCloseWeekDay extends SingleLineCommand { + + public CommandCloseWeekDay(PSystemProject diagram) { + super(diagram, "(?i)^\\s*close\\s+(\\w{3,}day)\\s*$"); + } + + @Override + protected CommandExecutionResult executeArg(List arg) { + final WeekDay weekDay = WeekDay.valueOf(arg.get(0).substring(0, 3).toUpperCase()); + getSystem().getProject().closeWeekDay(weekDay); + return CommandExecutionResult.ok(); + } +} diff --git a/src/net/sourceforge/plantuml/project/graphic/GanttDiagram.java b/src/net/sourceforge/plantuml/project/graphic/GanttDiagram.java new file mode 100644 index 000000000..f06868959 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/graphic/GanttDiagram.java @@ -0,0 +1,132 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project.graphic; + +import java.awt.Color; +import java.util.Map; +import java.util.SortedMap; + +import net.sourceforge.plantuml.graphic.StringBounder; +import net.sourceforge.plantuml.project.Instant; +import net.sourceforge.plantuml.project.Item; +import net.sourceforge.plantuml.project.Jalon; +import net.sourceforge.plantuml.project.Project; +import net.sourceforge.plantuml.ugraphic.UGraphic; +import net.sourceforge.plantuml.ugraphic.ULine; +import net.sourceforge.plantuml.ugraphic.UPolygon; +import net.sourceforge.plantuml.ugraphic.URectangle; +import net.sourceforge.plantuml.ugraphic.UShape; + +public class GanttDiagram { + + private final Project project; + private final TimeScale timeScale; + private final ItemHeader itemHeader; + + public GanttDiagram(Project project) { + this.project = project; + this.timeScale = new TimeScale(project); + this.itemHeader = new ItemHeader(project); + } + + public void draw(UGraphic ug, double x, double y) { + final StringBounder stringBounder = ug.getStringBounder(); + final double x0start = itemHeader.getWidth(stringBounder); + + final double timeScaleHeight = timeScale.getHeight(stringBounder); + + final SortedMap pos = timeScale.getAbscisse(stringBounder); + for (Item it : project.getValidItems()) { + final Instant start = it.getBegin(); + final Instant completed = it.getCompleted(); + if (pos.get(start) == null || pos.get(completed) == null) { + System.err.println("PB " + it); + continue; + } + final double x1 = pos.get(start) + 3; + final double x2 = pos.get(completed) - 3; + + final double yitem = timeScaleHeight + itemHeader.getPosition(stringBounder, it) + 3; + + final UShape rect; + if (it instanceof Jalon) { + rect = new UPolygon(); + ((UPolygon) rect).addPoint(0, 3); + ((UPolygon) rect).addPoint(3, 0); + ((UPolygon) rect).addPoint(6, 3); + ((UPolygon) rect).addPoint(3, 6); + } else { + rect = new URectangle(x2 - x1, 3); + } + ug.getParam().setColor(Color.GREEN); + ug.getParam().setBackcolor(Color.GRAY); + ug.draw(x0start + x1, yitem, rect); + + } + + drawGrid(ug, x + x0start, y + timeScaleHeight, pos); + + ug.getParam().setColor(Color.BLACK); + ug.getParam().setBackcolor(null); + timeScale.draw(ug, x + x0start, y); + itemHeader.draw(ug, x, y + timeScaleHeight); + + } + + private final Color lightGray = new Color(200, 200, 200); + + private void drawGrid(UGraphic ug, double x, double y, SortedMap pos) { + final ULine line = new ULine(0, itemHeader.getHeight(ug.getStringBounder())); + Instant last = null; + for (Map.Entry ent : pos.entrySet()) { + final double xcur = ent.getValue(); + if (last == null || last.next(null).equals(ent.getKey())) { + ug.getParam().setColor(lightGray); + } else { + ug.getParam().setColor(Color.BLACK); + } + ug.draw(x + xcur, y, line); + last = ent.getKey(); + } + } + + public double getWidth(StringBounder stringBounder) { + return itemHeader.getWidth(stringBounder) + timeScale.getWidth(stringBounder) + 3; + } + + public double getHeight(StringBounder stringBounder) { + return itemHeader.getHeight(stringBounder) + timeScale.getHeight(stringBounder) + 3; + } + +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/project/graphic/ItemHeader.java b/src/net/sourceforge/plantuml/project/graphic/ItemHeader.java new file mode 100644 index 000000000..b9b85f0c4 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/graphic/ItemHeader.java @@ -0,0 +1,111 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project.graphic; + +import java.awt.Color; +import java.awt.Font; +import java.awt.geom.Dimension2D; +import java.util.Arrays; + +import net.sourceforge.plantuml.graphic.FontConfiguration; +import net.sourceforge.plantuml.graphic.HorizontalAlignement; +import net.sourceforge.plantuml.graphic.StringBounder; +import net.sourceforge.plantuml.graphic.TextBlock; +import net.sourceforge.plantuml.graphic.TextBlockUtils; +import net.sourceforge.plantuml.project.Item; +import net.sourceforge.plantuml.project.Project; +import net.sourceforge.plantuml.ugraphic.UGraphic; +import net.sourceforge.plantuml.ugraphic.ULine; +import net.sourceforge.plantuml.ugraphic.URectangle; + +class ItemHeader { + + private final Font font = new Font("Serif", Font.PLAIN, 9); + private final Project project; + private final FontConfiguration fontConfig = new FontConfiguration(font, Color.BLACK); + + public ItemHeader(Project project) { + this.project = project; + } + + public void draw(UGraphic ug, double x, double y) { + + final StringBounder stringBounder = ug.getStringBounder(); + + ug.getParam().setColor(Color.BLACK); + ug.draw(x, y, new URectangle(getWidth(stringBounder), getHeight(stringBounder))); + + for (Item it : project.getValidItems()) { + final TextBlock b = TextBlockUtils.create(Arrays.asList("" + it.getCode()), fontConfig, + HorizontalAlignement.LEFT); + final Dimension2D dim = b.calculateDimension(stringBounder); + b.drawU(ug, x, y); + y += dim.getHeight(); + ug.draw(x, y, new ULine(getWidth(stringBounder), 0)); + } + } + + public double getWidth(StringBounder stringBounder) { + double width = 0; + for (Item it : project.getValidItems()) { + final Dimension2D dim = stringBounder.calculateDimension(font, it.getCode()); + width = Math.max(width, dim.getWidth()); + } + return width; + } + + public double getHeight(StringBounder stringBounder) { + double height = 0; + for (Item it : project.getValidItems()) { + final Dimension2D dim = stringBounder.calculateDimension(font, it.getCode()); + height += dim.getHeight(); + + } + return height; + } + + public double getPosition(StringBounder stringBounder, Item item) { + double pos = 0; + for (Item it : project.getValidItems()) { + if (it == item) { + return pos; + } + final Dimension2D dim = stringBounder.calculateDimension(font, it.getCode()); + pos += dim.getHeight(); + + } + throw new IllegalArgumentException(); + } + +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/project/graphic/TimeScale.java b/src/net/sourceforge/plantuml/project/graphic/TimeScale.java new file mode 100644 index 000000000..0c5c5a451 --- /dev/null +++ b/src/net/sourceforge/plantuml/project/graphic/TimeScale.java @@ -0,0 +1,153 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6104 $ + * + */ +package net.sourceforge.plantuml.project.graphic; + +import java.awt.Color; +import java.awt.Font; +import java.awt.geom.Dimension2D; +import java.util.Arrays; +import java.util.Collections; +import java.util.SortedMap; +import java.util.TreeMap; + +import net.sourceforge.plantuml.graphic.FontConfiguration; +import net.sourceforge.plantuml.graphic.HorizontalAlignement; +import net.sourceforge.plantuml.graphic.StringBounder; +import net.sourceforge.plantuml.graphic.TextBlock; +import net.sourceforge.plantuml.graphic.TextBlockUtils; +import net.sourceforge.plantuml.project.Day; +import net.sourceforge.plantuml.project.Instant; +import net.sourceforge.plantuml.project.Month; +import net.sourceforge.plantuml.project.Project; +import net.sourceforge.plantuml.ugraphic.UGraphic; +import net.sourceforge.plantuml.ugraphic.ULine; +import net.sourceforge.plantuml.ugraphic.URectangle; + +class TimeScale { + + private final Font font = new Font("Serif", Font.PLAIN, 9); + private final Project project; + private final FontConfiguration fontConfig = new FontConfiguration(font, Color.BLACK); + + public TimeScale(Project project) { + this.project = project; + } + + public void draw(UGraphic ug, final double x, double y) { + final StringBounder stringBounder = ug.getStringBounder(); + final double monthHeight = getMonthHeight(stringBounder); + final double caseWidth = getCaseWidth(stringBounder); + final double caseHeight = getCaseHeight(stringBounder); + final int nb = getNbCase(); + + ug.getParam().setColor(Color.BLACK); + ug.draw(x, y, new URectangle(nb * caseWidth, monthHeight)); + final Instant end = project.getEnd(); + + Month printed = null; + + double curx = x; + for (Instant cur = project.getStart(); cur.compareTo(end) <= 0; cur = cur.next(project.getDayClose())) { + final Day d = cur.getDay(); + if (printed == null || d.getMonth() != printed) { + ug.draw(curx, y, new ULine(0, monthHeight)); + printed = d.getMonth(); + final TextBlock b = TextBlockUtils.create(Arrays.asList(printed.name()), fontConfig, + HorizontalAlignement.LEFT); + final Dimension2D dim = b.calculateDimension(stringBounder); + b.drawU(ug, curx, y + (monthHeight - dim.getHeight()) / 2); + } + curx += caseWidth; + } + + curx = x; + y += monthHeight; + ug.draw(x, y, new URectangle(nb * caseWidth, caseHeight)); + + for (Instant cur = project.getStart(); cur.compareTo(end) <= 0; cur = cur.next(project.getDayClose())) { + final Day d = cur.getDay(); + final TextBlock b = TextBlockUtils.create(Arrays.asList("" + d.getNumDay()), fontConfig, + HorizontalAlignement.LEFT); + final Dimension2D dim = b.calculateDimension(stringBounder); + b.drawU(ug, curx + (caseWidth - dim.getWidth()) / 2, y + (caseHeight - dim.getHeight()) / 2); + curx += caseWidth; + ug.draw(curx, y, new ULine(0, caseHeight)); + } + } + + public SortedMap getAbscisse(StringBounder stringBounder) { + final SortedMap pos = new TreeMap(); + final double caseWidth = getCaseWidth(stringBounder); + final Instant end = project.getEnd(); + double x = 0; + for (Instant cur = project.getStart(); cur.compareTo(end) <= 0; cur = cur.next(project.getDayClose())) { + pos.put(cur, x); + x += caseWidth; + } + return Collections.unmodifiableSortedMap(pos); + } + + private int getNbCase() { + int result = 0; + final Instant end = project.getEnd(); + for (Instant cur = project.getStart(); cur.compareTo(end) <= 0; cur = cur.next(project.getDayClose())) { + result++; + } + return result; + } + + private double getCaseWidth(StringBounder stringBounder) { + final Dimension2D dim00 = stringBounder.calculateDimension(font, "00"); + return dim00.getWidth() + 3; + } + + private double getCaseHeight(StringBounder stringBounder) { + final Dimension2D dim00 = stringBounder.calculateDimension(font, "00"); + return dim00.getHeight() + 3; + } + + private double getMonthHeight(StringBounder stringBounder) { + final Dimension2D dimZZ = stringBounder.calculateDimension(font, "ZZ"); + return dimZZ.getHeight() + 3; + } + + public double getWidth(StringBounder stringBounder) { + return getCaseWidth(stringBounder) * getNbCase(); + } + + public double getHeight(StringBounder stringBounder) { + return getCaseHeight(stringBounder) + getMonthHeight(stringBounder); + } + +} \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/sequencediagram/AbstractMessage.java b/src/net/sourceforge/plantuml/sequencediagram/AbstractMessage.java index 0f9361e74..6b941ed29 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/AbstractMessage.java +++ b/src/net/sourceforge/plantuml/sequencediagram/AbstractMessage.java @@ -37,20 +37,22 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.graphic.HtmlColor; import net.sourceforge.plantuml.skin.ArrowConfiguration; public abstract class AbstractMessage implements Event { final private List label; -// final private boolean dotted; -// final private boolean full; + // final private boolean dotted; + // final private boolean full; final private ArrowConfiguration arrowConfiguration; final private List lifeEvents = new ArrayList(); - private List notes; + private List notes; private NotePosition notePosition; private HtmlColor noteBackColor; + private Url urlNote; private final String messageNumber; public AbstractMessage(List label, ArrowConfiguration arrowConfiguration, String messageNumber) { @@ -92,15 +94,20 @@ public abstract class AbstractMessage implements Event { return arrowConfiguration; } - public final List getNote() { + public final List getNote() { return notes == null ? notes : Collections.unmodifiableList(notes); } - public final void setNote(List strings, NotePosition notePosition, String backcolor) { + public final Url getUrlNote() { + return urlNote; + } + + public final void setNote(List strings, NotePosition notePosition, String backcolor, Url url) { if (notePosition != NotePosition.LEFT && notePosition != NotePosition.RIGHT) { throw new IllegalArgumentException(); } this.notes = strings; + this.urlNote = url; this.notePosition = notePosition; this.noteBackColor = HtmlColor.getColorIfValid(backcolor); } diff --git a/src/net/sourceforge/plantuml/sequencediagram/Delay.java b/src/net/sourceforge/plantuml/sequencediagram/Delay.java index 470af5e87..34d77014f 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/Delay.java +++ b/src/net/sourceforge/plantuml/sequencediagram/Delay.java @@ -35,6 +35,8 @@ package net.sourceforge.plantuml.sequencediagram; import java.util.List; +import net.sourceforge.plantuml.Url; + public class Delay implements Event { private final List text; @@ -51,4 +53,8 @@ public class Delay implements Event { return false; } + public Url getUrl() { + return null; + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/Divider.java b/src/net/sourceforge/plantuml/sequencediagram/Divider.java index bcf35858c..cd03bc620 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/Divider.java +++ b/src/net/sourceforge/plantuml/sequencediagram/Divider.java @@ -35,6 +35,8 @@ package net.sourceforge.plantuml.sequencediagram; import java.util.List; +import net.sourceforge.plantuml.Url; + public class Divider implements Event { private final List text; @@ -46,10 +48,12 @@ public class Divider implements Event { public final List getText() { return text; } - + public boolean dealWith(Participant someone) { return false; } - + public Url getUrl() { + return null; + } } diff --git a/src/net/sourceforge/plantuml/sequencediagram/Event.java b/src/net/sourceforge/plantuml/sequencediagram/Event.java index 15b55abe1..d59590e8f 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/Event.java +++ b/src/net/sourceforge/plantuml/sequencediagram/Event.java @@ -28,13 +28,17 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6097 $ + * Revision $Revision: 6483 $ * */ package net.sourceforge.plantuml.sequencediagram; +import net.sourceforge.plantuml.Url; + public interface Event { boolean dealWith(Participant someone); + Url getUrl(); + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/GroupingLeaf.java b/src/net/sourceforge/plantuml/sequencediagram/GroupingLeaf.java index 50d160357..00db3ebb5 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/GroupingLeaf.java +++ b/src/net/sourceforge/plantuml/sequencediagram/GroupingLeaf.java @@ -28,11 +28,12 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6097 $ + * Revision $Revision: 6483 $ * */ package net.sourceforge.plantuml.sequencediagram; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.graphic.HtmlColor; public class GroupingLeaf extends Grouping { @@ -74,9 +75,13 @@ public class GroupingLeaf extends Grouping { } return backColorGeneral; } - + public boolean dealWith(Participant someone) { return false; } + public Url getUrl() { + return null; + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/GroupingStart.java b/src/net/sourceforge/plantuml/sequencediagram/GroupingStart.java index f9978c525..a79241a44 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/GroupingStart.java +++ b/src/net/sourceforge/plantuml/sequencediagram/GroupingStart.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.graphic.HtmlColor; public class GroupingStart extends Grouping { @@ -72,10 +73,13 @@ public class GroupingStart extends Grouping { public HtmlColor getBackColorGeneral() { return backColorGeneral; } - + public boolean dealWith(Participant someone) { return false; } + public Url getUrl() { + return null; + } } diff --git a/src/net/sourceforge/plantuml/sequencediagram/LifeEvent.java b/src/net/sourceforge/plantuml/sequencediagram/LifeEvent.java index 865c24240..4182951e1 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/LifeEvent.java +++ b/src/net/sourceforge/plantuml/sequencediagram/LifeEvent.java @@ -28,11 +28,12 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6097 $ + * Revision $Revision: 6483 $ * */ package net.sourceforge.plantuml.sequencediagram; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.graphic.HtmlColor; public class LifeEvent implements Event { @@ -63,4 +64,8 @@ public class LifeEvent implements Event { return this.p == someone; } + public Url getUrl() { + return null; + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/Message.java b/src/net/sourceforge/plantuml/sequencediagram/Message.java index 279010c08..d5009117b 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/Message.java +++ b/src/net/sourceforge/plantuml/sequencediagram/Message.java @@ -28,13 +28,14 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6097 $ + * Revision $Revision: 6483 $ * */ package net.sourceforge.plantuml.sequencediagram; import java.util.List; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.skin.ArrowConfiguration; public class Message extends AbstractMessage { @@ -61,4 +62,8 @@ public class Message extends AbstractMessage { return someone == p1 || someone == p2; } + public Url getUrl() { + return null; + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/MessageExo.java b/src/net/sourceforge/plantuml/sequencediagram/MessageExo.java index d8a2a9ae3..4a2fb8849 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/MessageExo.java +++ b/src/net/sourceforge/plantuml/sequencediagram/MessageExo.java @@ -35,6 +35,7 @@ package net.sourceforge.plantuml.sequencediagram; import java.util.List; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.skin.ArrowConfiguration; public class MessageExo extends AbstractMessage { @@ -61,4 +62,8 @@ public class MessageExo extends AbstractMessage { return participant == someone; } + public Url getUrl() { + return null; + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/Newpage.java b/src/net/sourceforge/plantuml/sequencediagram/Newpage.java index 7ff9f1ea4..25fb6d586 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/Newpage.java +++ b/src/net/sourceforge/plantuml/sequencediagram/Newpage.java @@ -28,13 +28,15 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6097 $ + * Revision $Revision: 6483 $ * */ package net.sourceforge.plantuml.sequencediagram; import java.util.List; +import net.sourceforge.plantuml.Url; + public class Newpage implements Event { private final List title; @@ -46,10 +48,13 @@ public class Newpage implements Event { public final List getTitle() { return title; } - + public boolean dealWith(Participant someone) { return false; } + public Url getUrl() { + return null; + } } diff --git a/src/net/sourceforge/plantuml/sequencediagram/Note.java b/src/net/sourceforge/plantuml/sequencediagram/Note.java index 012bae833..ed253611d 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/Note.java +++ b/src/net/sourceforge/plantuml/sequencediagram/Note.java @@ -28,14 +28,17 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6097 $ + * Revision $Revision: 6486 $ * */ package net.sourceforge.plantuml.sequencediagram; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import net.sourceforge.plantuml.SpecificBackcolorable; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.graphic.HtmlColor; public class Note implements Event, SpecificBackcolorable { @@ -47,18 +50,40 @@ public class Note implements Event, SpecificBackcolorable { private final NotePosition position; + private final Url url; + public Note(Participant p, NotePosition position, List strings) { - this.p = p; - this.p2 = null; - this.position = position; - this.strings = strings; + this(p, null, position, strings); } public Note(Participant p, Participant p2, List strings) { + this(p, p2, NotePosition.OVER_SEVERAL, strings); + } + + private Note(Participant p, Participant p2, NotePosition position, List strings) { this.p = p; this.p2 = p2; - this.position = NotePosition.OVER_SEVERAL; - this.strings = strings; + this.position = position; + if (strings != null && strings.size() > 0) { + this.url = extractUrl(strings.get(0)); + } else { + this.url = null; + } + + if (this.url == null) { + this.strings = strings; + } else { + this.strings = strings.subList(1, strings.size()); + } + } + + public static Url extractUrl(String s) { + final Pattern p = Pattern.compile("(?i)^\\[\\[([^|]*)(?:\\|([^|]*))?\\]\\]$"); + final Matcher m = p.matcher(s.trim()); + if (m.matches() == false) { + return null; + } + return new Url(m.group(1), m.group(2)); } public Participant getParticipant() { @@ -90,4 +115,9 @@ public class Note implements Event, SpecificBackcolorable { public boolean dealWith(Participant someone) { return p == someone || p2 == someone; } + + public Url getUrl() { + return url; + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/Participant.java b/src/net/sourceforge/plantuml/sequencediagram/Participant.java index 2cbe665b7..4fc454a05 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/Participant.java +++ b/src/net/sourceforge/plantuml/sequencediagram/Participant.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6099 $ + * Revision $Revision: 6482 $ * */ package net.sourceforge.plantuml.sequencediagram; @@ -38,6 +38,7 @@ import java.util.Collections; import java.util.List; import net.sourceforge.plantuml.SpecificBackcolorable; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.cucadiagram.Stereotype; import net.sourceforge.plantuml.graphic.HtmlColor; @@ -69,7 +70,7 @@ public class Participant implements SpecificBackcolorable { public String getCode() { return code; } - + @Override public String toString() { return getCode(); @@ -100,20 +101,20 @@ public class Participant implements SpecificBackcolorable { public final int getInitialLife() { return initialLife; } - + private HtmlColor liveBackcolor; public final void incInitialLife(HtmlColor backcolor) { initialLife++; this.liveBackcolor = backcolor; } - + public HtmlColor getLiveSpecificBackColor() { return liveBackcolor; } - + private HtmlColor specificBackcolor; - + public HtmlColor getSpecificBackColor() { return specificBackcolor; } @@ -122,4 +123,14 @@ public class Participant implements SpecificBackcolorable { this.specificBackcolor = HtmlColor.getColorIfValid(s); } + private Url url; + + public final Url getUrl() { + return url; + } + + public final void setUrl(Url url) { + this.url = url; + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/Reference.java b/src/net/sourceforge/plantuml/sequencediagram/Reference.java new file mode 100644 index 000000000..4d88f566a --- /dev/null +++ b/src/net/sourceforge/plantuml/sequencediagram/Reference.java @@ -0,0 +1,85 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 6097 $ + * + */ +package net.sourceforge.plantuml.sequencediagram; + +import java.util.List; + +import net.sourceforge.plantuml.Url; + +public class Reference implements Event { + + private final Participant p; + private final Participant p2; + private final Url url; + + private final List strings; + + // public Reference(Participant p, List strings) { + // this.p = p; + // this.p2 = null; + // this.strings = strings; + // } + // + public Reference(Participant p, Participant p2, Url url, List strings) { + this.p = p; + this.p2 = p2; + this.url = url; + this.strings = strings; + } + + public Participant getParticipant() { + return p; + } + + public Participant getParticipant2() { + return p2; + } + + public List getStrings() { + return strings; + } + + public boolean dealWith(Participant someone) { + return p == someone || p2 == someone; + } + + public final Url getUrl() { + return url; + } + + @Override + public String toString() { + return p.getCode() + "-" + p2.getCode(); + } +} diff --git a/src/net/sourceforge/plantuml/sequencediagram/SequenceDiagram.java b/src/net/sourceforge/plantuml/sequencediagram/SequenceDiagram.java index ed950d0af..b0b3623cf 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/SequenceDiagram.java +++ b/src/net/sourceforge/plantuml/sequencediagram/SequenceDiagram.java @@ -31,7 +31,9 @@ */ package net.sourceforge.plantuml.sequencediagram; +import java.awt.image.BufferedImage; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.text.DecimalFormat; @@ -45,6 +47,7 @@ import java.util.Map; import net.sourceforge.plantuml.FileFormat; import net.sourceforge.plantuml.FileFormatOption; +import net.sourceforge.plantuml.Log; import net.sourceforge.plantuml.StringUtils; import net.sourceforge.plantuml.UmlDiagram; import net.sourceforge.plantuml.UmlDiagramType; @@ -153,7 +156,7 @@ public class SequenceDiagram extends UmlDiagram { return Collections.unmodifiableList(events); } - private FileMaker getSequenceDiagramPngMaker(FileFormatOption fileFormatOption) { + private FileMaker getSequenceDiagramPngMaker(FileFormatOption fileFormatOption, List flashcodes) { final FileFormat fileFormat = fileFormatOption.getFileFormat(); @@ -161,15 +164,47 @@ public class SequenceDiagram extends UmlDiagram { return new SequenceDiagramTxtMaker(this, fileFormat); } - return new SequenceDiagramFileMaker(this, skin, fileFormatOption); + return new SequenceDiagramFileMaker(this, skin, fileFormatOption, flashcodes); } - public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException { - return getSequenceDiagramPngMaker(fileFormat).createMany(suggestedFile); + // public List exportDiagrams(File suggestedFile, FileFormatOption + // fileFormat) throws IOException { + // return + // getSequenceDiagramPngMaker(fileFormat).createManyRRMV(suggestedFile); + // } + + public List exportDiagrams(File suggestedFile, FileFormatOption fileFormat) throws IOException { + + final List result = new ArrayList(); + final int nbImages = getNbImages(); + for (int i = 0; i < nbImages; i++) { + + final File f = SequenceDiagramFileMaker.computeFilename(suggestedFile, i, fileFormat.getFileFormat()); + Log.info("Creating file: " + f); + final FileOutputStream fos = new FileOutputStream(f); + final StringBuilder cmap = new StringBuilder(); + try { + exportDiagram(fos, cmap, i, fileFormat); + } finally { + fos.close(); + } + if (this.hasUrl() && cmap.length() > 0) { + exportCmap(suggestedFile, cmap); + } + Log.info("File size : " + f.length()); + result.add(f); + } + return result; } - public void createFile(OutputStream os, int index, FileFormatOption fileFormat) throws IOException { - getSequenceDiagramPngMaker(fileFormat).createOne(os, index); + @Override + protected void exportDiagramInternal(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormat, + List flashcodes) throws IOException { + final FileMaker sequenceDiagramPngMaker = getSequenceDiagramPngMaker(fileFormat, flashcodes); + sequenceDiagramPngMaker.createOne(os, index); + if (this.hasUrl() && fileFormat.getFileFormat() == FileFormat.PNG) { + sequenceDiagramPngMaker.appendCmap(cmap); + } } private LifeEvent pendingCreate = null; @@ -268,11 +303,11 @@ public class SequenceDiagram extends UmlDiagram { return UmlDiagramType.SEQUENCE; } -// private Participant boxStart; -// private List boxStartComment; -// private HtmlColor boxColor; -// private boolean boxPending = false; - + // private Participant boxStart; + // private List boxStartComment; + // private HtmlColor boxColor; + // private boolean boxPending = false; + private ParticipantEnglober participantEnglober; public void boxStart(List comment, HtmlColor color) { @@ -293,34 +328,35 @@ public class SequenceDiagram extends UmlDiagram { return participantEnglober != null; } -// private Participant next(Participant p) { -// if (p == null) { -// return participants.values().iterator().next(); -// } -// for (final Iterator it = participants.values().iterator(); it.hasNext();) { -// final Participant current = it.next(); -// if (current == p && it.hasNext()) { -// return it.next(); -// } -// } -// throw new IllegalArgumentException("p=" + p.getCode()); -// } -// -// private Participant getLastParticipant() { -// Participant result = null; -// for (Participant p : participants.values()) { -// result = p; -// } -// return result; -// } -// -// public final List getParticipantEnglobers() { -// return Collections.unmodifiableList(participantEnglobers); -// } + // private Participant next(Participant p) { + // if (p == null) { + // return participants.values().iterator().next(); + // } + // for (final Iterator it = participants.values().iterator(); + // it.hasNext();) { + // final Participant current = it.next(); + // if (current == p && it.hasNext()) { + // return it.next(); + // } + // } + // throw new IllegalArgumentException("p=" + p.getCode()); + // } + // + // private Participant getLastParticipant() { + // Participant result = null; + // for (Participant p : participants.values()) { + // result = p; + // } + // return result; + // } + // + // public final List getParticipantEnglobers() { + // return Collections.unmodifiableList(participantEnglobers); + // } @Override public int getNbImages() { - return getSequenceDiagramPngMaker(new FileFormatOption(FileFormat.PNG)).getNbPages(); + return getSequenceDiagramPngMaker(new FileFormatOption(FileFormat.PNG), null).getNbPages(); } public void removeHiddenParticipants() { @@ -333,7 +369,7 @@ public class SequenceDiagram extends UmlDiagram { private void remove(Participant p) { final boolean ok = participants.values().remove(p); - if (ok==false) { + if (ok == false) { throw new IllegalArgumentException(); } participantEnglobers2.remove(p); @@ -362,4 +398,32 @@ public class SequenceDiagram extends UmlDiagram { return participantEnglobers2.get(p); } + private boolean autoactivate; + + public final void setAutoactivate(boolean autoactivate) { + this.autoactivate = autoactivate; + } + + public final boolean isAutoactivate() { + return autoactivate; + } + + public boolean hasUrl() { + for (Participant p : participants.values()) { + if (p.getUrl() != null) { + return true; + } + } + for (Event ev : events) { + if (ev.getUrl() != null) { + return true; + } + } + return false; + } + + public void addReference(Reference ref) { + events.add(ref); + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/SequenceDiagramFactory.java b/src/net/sourceforge/plantuml/sequencediagram/SequenceDiagramFactory.java index 7391a95fe..4f2091146 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/SequenceDiagramFactory.java +++ b/src/net/sourceforge/plantuml/sequencediagram/SequenceDiagramFactory.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6137 $ + * Revision $Revision: 6475 $ * */ package net.sourceforge.plantuml.sequencediagram; @@ -38,6 +38,7 @@ import net.sourceforge.plantuml.sequencediagram.command.CommandActivate; import net.sourceforge.plantuml.sequencediagram.command.CommandActivate2; import net.sourceforge.plantuml.sequencediagram.command.CommandArrow; import net.sourceforge.plantuml.sequencediagram.command.CommandAutoNewpage; +import net.sourceforge.plantuml.sequencediagram.command.CommandAutoactivate; import net.sourceforge.plantuml.sequencediagram.command.CommandAutonumber; import net.sourceforge.plantuml.sequencediagram.command.CommandBoxEnd; import net.sourceforge.plantuml.sequencediagram.command.CommandBoxStart; @@ -59,7 +60,9 @@ import net.sourceforge.plantuml.sequencediagram.command.CommandNoteSequence; import net.sourceforge.plantuml.sequencediagram.command.CommandParticipantA; import net.sourceforge.plantuml.sequencediagram.command.CommandParticipantA2; import net.sourceforge.plantuml.sequencediagram.command.CommandParticipantA3; +import net.sourceforge.plantuml.sequencediagram.command.CommandReferenceOverSeveral; import net.sourceforge.plantuml.sequencediagram.command.CommandSkin; +import net.sourceforge.plantuml.sequencediagram.command.CommandUrl; public class SequenceDiagramFactory extends AbstractUmlSystemCommandFactory { @@ -83,7 +86,7 @@ public class SequenceDiagramFactory extends AbstractUmlSystemCommandFactory { addCommand(new CommandBoxStart(system)); addCommand(new CommandBoxEnd(system)); addCommand(new CommandGrouping(system)); - + addCommand(new CommandActivate(system)); addCommand(new CommandActivate2(system)); @@ -97,19 +100,21 @@ public class SequenceDiagramFactory extends AbstractUmlSystemCommandFactory { addCommand(new CommandIgnoreNewpage(system)); addCommand(new CommandAutoNewpage(system)); addCommand(new CommandDivider(system)); + addCommand(new CommandReferenceOverSeveral(system)); addCommand(new CommandSkin(system)); addCommand(new CommandAutonumber(system)); + addCommand(new CommandAutoactivate(system)); addCommand(new CommandFootbox(system)); addCommand(new CommandDelay(system)); addCommand(new CommandFootboxOld(system)); - + addCommand(new CommandUrl(system)); + } public SequenceDiagram getSystem() { return system; } - - + @Override public String checkFinalError() { if (system.isHideUnlinkedData()) { diff --git a/src/net/sourceforge/plantuml/sequencediagram/command/CommandArrow.java b/src/net/sourceforge/plantuml/sequencediagram/command/CommandArrow.java index d70e530f1..19ef68ed2 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/command/CommandArrow.java +++ b/src/net/sourceforge/plantuml/sequencediagram/command/CommandArrow.java @@ -44,6 +44,7 @@ import net.sourceforge.plantuml.command.regex.RegexConcat; import net.sourceforge.plantuml.command.regex.RegexLeaf; import net.sourceforge.plantuml.command.regex.RegexOr; import net.sourceforge.plantuml.command.regex.RegexPartialMatch; +import net.sourceforge.plantuml.sequencediagram.LifeEventType; import net.sourceforge.plantuml.sequencediagram.Message; import net.sourceforge.plantuml.sequencediagram.Participant; import net.sourceforge.plantuml.sequencediagram.SequenceDiagram; @@ -66,7 +67,7 @@ public class CommandArrow extends SingleLineCommand2 { new RegexLeaf("PART1LONGCODE", "\"([^\"]+)\"\\s*as\\s+([\\p{L}0-9_.]+)"), // new RegexLeaf("PART1CODELONG", "([\\p{L}0-9_.]+)\\s+as\\s*\"([^\"]+)\"")), new RegexLeaf("\\s*"), // - new RegexLeaf("ARROW", "([=-]+(?:>>?|//?|\\\\\\\\?)|(?:<>?|//?|\\\\\\\\?)|(< { @Override protected CommandExecutionResult executeArg(Map arg2) { + + String arrow = StringUtils.manageArrowForSequence(arg2.get("ARROW").get(0)); + boolean startDollar = arrow.startsWith("$"); + boolean endDollar = arrow.endsWith("$"); + arrow = arrow.replaceAll("\\$", ""); + Participant p1; Participant p2; - - final String arrow = StringUtils.manageArrowForSequence(arg2.get("ARROW").get(0)); + final boolean activatep2; + final boolean deactivatep1; if (arrow.endsWith(">") || arrow.endsWith("\\") || arrow.endsWith("/")) { p1 = getOrCreateParticipant(arg2, "PART1"); p2 = getOrCreateParticipant(arg2, "PART2"); + activatep2 = endDollar; + deactivatep1 = startDollar; } else if (arrow.startsWith("<") || arrow.startsWith("\\") || arrow.startsWith("/")) { p2 = getOrCreateParticipant(arg2, "PART1"); p1 = getOrCreateParticipant(arg2, "PART2"); + activatep2 = startDollar; + deactivatep1 = endDollar; } else { throw new IllegalStateException(arg2.toString()); } @@ -143,6 +154,23 @@ public class CommandArrow extends SingleLineCommand2 { } getSystem().addMessage(new Message(p1, p2, labels, config, getSystem().getNextMessageNumber())); + + if (getSystem().isAutoactivate()) { + if (p1 != p2 && config.isASync() == false) { + if (config.isDotted()) { + getSystem().activate(p1, LifeEventType.DEACTIVATE, null); + } else { + getSystem().activate(p2, LifeEventType.ACTIVATE, null); + } + } + } else { + if (deactivatep1) { + getSystem().activate(p1, LifeEventType.DEACTIVATE, null); + } + if (activatep2) { + getSystem().activate(p2, LifeEventType.ACTIVATE, null); + } + } return CommandExecutionResult.ok(); } diff --git a/src/net/sourceforge/plantuml/sequencediagram/command/CommandAutoactivate.java b/src/net/sourceforge/plantuml/sequencediagram/command/CommandAutoactivate.java new file mode 100644 index 000000000..7384b5e2c --- /dev/null +++ b/src/net/sourceforge/plantuml/sequencediagram/command/CommandAutoactivate.java @@ -0,0 +1,53 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 4762 $ + * + */ +package net.sourceforge.plantuml.sequencediagram.command; + +import java.util.List; + +import net.sourceforge.plantuml.command.CommandExecutionResult; +import net.sourceforge.plantuml.command.SingleLineCommand; +import net.sourceforge.plantuml.sequencediagram.SequenceDiagram; + +public class CommandAutoactivate extends SingleLineCommand { + + public CommandAutoactivate(SequenceDiagram sequenceDiagram) { + super(sequenceDiagram, "(?i)^autoactivate\\s+(off|on)*$"); + } + + @Override + protected CommandExecutionResult executeArg(List arg) { + getSystem().setAutoactivate("on".equalsIgnoreCase(arg.get(0))); + return CommandExecutionResult.ok(); + } +} diff --git a/src/net/sourceforge/plantuml/sequencediagram/command/CommandMultilinesNoteOnArrow.java b/src/net/sourceforge/plantuml/sequencediagram/command/CommandMultilinesNoteOnArrow.java index 6aec417db..9ec21d567 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/command/CommandMultilinesNoteOnArrow.java +++ b/src/net/sourceforge/plantuml/sequencediagram/command/CommandMultilinesNoteOnArrow.java @@ -28,13 +28,16 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5751 $ + * Revision $Revision: 6485 $ * */ package net.sourceforge.plantuml.sequencediagram.command; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import net.sourceforge.plantuml.EmbededDiagram; import net.sourceforge.plantuml.StringUtils; import net.sourceforge.plantuml.command.CommandExecutionResult; import net.sourceforge.plantuml.command.CommandMultilines; @@ -54,8 +57,26 @@ public class CommandMultilinesNoteOnArrow extends CommandMultilines strings = StringUtils.removeEmptyColumns(lines.subList(1, lines.size() - 1)); - m.setNote(strings, position, line0.get(1)); + final List strings = new ArrayList(); + final Iterator it = StringUtils.removeEmptyColumns(lines.subList(1, lines.size() - 1)).iterator(); + while (it.hasNext()) { + CharSequence s = it.next(); + if (s.equals("{{")) { + final List other = new ArrayList(); + other.add("@startuml"); + while (it.hasNext()) { + String s2 = it.next(); + if (s2.equals("}}")) { + break; + } + other.add(s2); + } + other.add("@enduml"); + s = new EmbededDiagram(other); + } + strings.add(s); + } + m.setNote(strings, position, line0.get(1), null); } return CommandExecutionResult.ok(); diff --git a/src/net/sourceforge/plantuml/sequencediagram/command/CommandNoteOnArrow.java b/src/net/sourceforge/plantuml/sequencediagram/command/CommandNoteOnArrow.java index af8314d7b..205529cc4 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/command/CommandNoteOnArrow.java +++ b/src/net/sourceforge/plantuml/sequencediagram/command/CommandNoteOnArrow.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 4762 $ + * Revision $Revision: 6485 $ * */ package net.sourceforge.plantuml.sequencediagram.command; @@ -55,7 +55,7 @@ public class CommandNoteOnArrow extends SingleLineCommand { if (m != null) { final NotePosition position = NotePosition.valueOf(arg.get(0).toUpperCase()); final List strings = StringUtils.getWithNewlines(arg.get(2)); - m.setNote(strings, position, arg.get(1)); + m.setNote(strings, position, arg.get(1), null); } return CommandExecutionResult.ok(); diff --git a/src/net/sourceforge/plantuml/sequencediagram/command/CommandReferenceOverSeveral.java b/src/net/sourceforge/plantuml/sequencediagram/command/CommandReferenceOverSeveral.java new file mode 100644 index 000000000..0a923f166 --- /dev/null +++ b/src/net/sourceforge/plantuml/sequencediagram/command/CommandReferenceOverSeveral.java @@ -0,0 +1,91 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 3835 $ + * + */ +package net.sourceforge.plantuml.sequencediagram.command; + +import java.util.List; +import java.util.Map; + +import net.sourceforge.plantuml.StringUtils; +import net.sourceforge.plantuml.Url; +import net.sourceforge.plantuml.command.CommandExecutionResult; +import net.sourceforge.plantuml.command.SingleLineCommand2; +import net.sourceforge.plantuml.command.regex.RegexConcat; +import net.sourceforge.plantuml.command.regex.RegexLeaf; +import net.sourceforge.plantuml.command.regex.RegexPartialMatch; +import net.sourceforge.plantuml.sequencediagram.Participant; +import net.sourceforge.plantuml.sequencediagram.Reference; +import net.sourceforge.plantuml.sequencediagram.SequenceDiagram; + +public class CommandReferenceOverSeveral extends SingleLineCommand2 { + + public CommandReferenceOverSeveral(SequenceDiagram sequenceDiagram) { + super(sequenceDiagram, getConcat()); + } + + private static RegexConcat getConcat() { + return new RegexConcat(new RegexLeaf("^"), // + new RegexLeaf("ref\\s+over\\s+"), // + new RegexLeaf("P1", "([\\p{L}0-9_.]+|\"[^\"]+\")"), // + new RegexLeaf("\\s*,\\s*"), // + new RegexLeaf("P2", "([\\p{L}0-9_.]+|\"[^\"]+\")"), // + new RegexLeaf("\\s*:\\s*"), // + new RegexLeaf("URL", "(?:\\[\\[([^|]*)(?:\\|([^|]*))?\\]\\])?"), // + new RegexLeaf("TEXT", "(.*)"), // + new RegexLeaf("$")); + } + + @Override + protected CommandExecutionResult executeArg(Map arg) { + final String s1 = arg.get("P1").get(0); + final String s2 = arg.get("P2").get(0); + final String url = arg.get("URL").get(0); + final String title = arg.get("URL").get(1); + final String text = arg.get("TEXT").get(0); + final Participant p1 = getSystem().getOrCreateParticipant( + StringUtils.eventuallyRemoveStartingAndEndingDoubleQuote(s1)); + final Participant p2 = getSystem().getOrCreateParticipant( + StringUtils.eventuallyRemoveStartingAndEndingDoubleQuote(s2)); + final List strings = StringUtils.getWithNewlines(text); + + Url u = null; + if (url != null) { + u = new Url(url, title); + } + + final Reference ref = new Reference(p1, p2, u, strings); + getSystem().addReference(ref); + return CommandExecutionResult.ok(); + } + +} diff --git a/src/net/sourceforge/plantuml/sequencediagram/command/CommandUrl.java b/src/net/sourceforge/plantuml/sequencediagram/command/CommandUrl.java new file mode 100644 index 000000000..7421b74c9 --- /dev/null +++ b/src/net/sourceforge/plantuml/sequencediagram/command/CommandUrl.java @@ -0,0 +1,67 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 4161 $ + * + */ +package net.sourceforge.plantuml.sequencediagram.command; + +import java.util.List; + +import net.sourceforge.plantuml.Url; +import net.sourceforge.plantuml.command.CommandExecutionResult; +import net.sourceforge.plantuml.command.SingleLineCommand; +import net.sourceforge.plantuml.sequencediagram.Participant; +import net.sourceforge.plantuml.sequencediagram.SequenceDiagram; + +public class CommandUrl extends SingleLineCommand { + + public CommandUrl(SequenceDiagram diagram) { + super(diagram, + "(?i)^url\\s*(?:of|for)?\\s+([\\p{L}0-9_.]+|\"[^\"]+\")\\s+(?:is)?\\s*\\[\\[([^|]*)(?:\\|([^|]*))?\\]\\]$"); + } + + @Override + protected CommandExecutionResult executeArg(List arg) { + final String code = arg.get(0); + String url = arg.get(1); + final String title = arg.get(2); + final Participant p = getSystem().getOrCreateParticipant(code); + if (url.startsWith("http:") == false && url.startsWith("https:") == false) { + final String top = getSystem().getSkinParam().getValue("topurl"); + if (top != null) { + url = top + url; + } + } + p.setUrl(new Url(url, title)); + return CommandExecutionResult.ok(); + } + +} diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/ArrowAndNoteBox.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/ArrowAndNoteBox.java index c46567bb4..ded1776dd 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/ArrowAndNoteBox.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/ArrowAndNoteBox.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5870 $ + * Revision $Revision: 6323 $ * */ package net.sourceforge.plantuml.sequencediagram.graphic; @@ -67,7 +67,7 @@ class ArrowAndNoteBox extends Arrow implements InGroupable { final public double getArrowOnlyWidth(StringBounder stringBounder) { return arrow.getPreferredWidth(stringBounder); } - + @Override public void setMaxX(double m) { super.setMaxX(m); @@ -76,8 +76,18 @@ class ArrowAndNoteBox extends Arrow implements InGroupable { @Override protected void drawInternalU(UGraphic ug, double maxX, Context2D context) { + final double atX = ug.getTranslateX(); + final double atY = ug.getTranslateY(); arrow.drawU(ug, maxX, context); - noteBox.drawU(ug, maxX, context); +// if (arrow instanceof ArrowAndParticipant) { +// final double diff = ((ArrowAndParticipant) arrow).getDiff(ug); +// ug.setTranslate(atX, atY + diff); +// // noteBox.drawU(ug, maxX, context); +// } else { + ug.setTranslate(atX, atY); + noteBox.drawU(ug, maxX, context); + // } + ug.setTranslate(atX, atY); } @Override @@ -88,10 +98,10 @@ class ArrowAndNoteBox extends Arrow implements InGroupable { @Override public double getPreferredWidth(StringBounder stringBounder) { double w = arrow.getPreferredWidth(stringBounder); - w = Math.max(w, arrow.getActualWidth(stringBounder)); + w = Math.max(w, arrow.getActualWidth(stringBounder)); return w + noteBox.getPreferredWidth(stringBounder); } - + @Override public double getActualWidth(StringBounder stringBounder) { return getPreferredWidth(stringBounder); @@ -133,5 +143,5 @@ class ArrowAndNoteBox extends Arrow implements InGroupable { public LivingParticipantBox getParticipantAt(StringBounder stringBounder, NotePosition position) { return arrow.getParticipantAt(stringBounder, position); } - + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/ArrowAndParticipant.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/ArrowAndParticipant.java index fbcd36000..52618d968 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/ArrowAndParticipant.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/ArrowAndParticipant.java @@ -48,10 +48,7 @@ class ArrowAndParticipant extends Arrow implements InGroupable { super(arrow.getStartingY(), arrow.getSkin(), arrow.getArrowComponent()); this.arrow = arrow; this.participantBox = participantBox; - //participantBox.setDeltaHead(arrow.getStartingY()); arrow.setPaddingArrowHead(participantBox.getPreferredWidth(stringBounder) / 2); - // participantBox.setTopStartingY(arrow.getStartingY()); - } @Override @@ -92,12 +89,26 @@ class ArrowAndParticipant extends Arrow implements InGroupable { final double atY = ug.getTranslateY(); arrow.drawInternalU(ug, maxX, context); ug.setTranslate(atX, atY); - //ug.translate(getStartingX(ug.getStringBounder()), getStartingY()); - ug.translate(participantBox.getStartingX(), getStartingY()); + final double arrowHeight = arrow.getPreferredHeight(ug.getStringBounder()); + final double boxHeight = participantBox.getHeadHeight(ug.getStringBounder()); + // final double diff = getDiff(ug); + double diff = 0; + if (arrowHeight > boxHeight) { + diff = arrowHeight - boxHeight; + } + ug.translate(participantBox.getStartingX(), getStartingY() + diff); participantBox.drawParticipantHead(ug); + ug.setTranslate(atX, atY); } + private double getDiff(UGraphic ug) { + final double y1 = arrow.getPreferredHeight(ug.getStringBounder()); + final double y2 = participantBox.getHeadHeight(ug.getStringBounder()); + final double diff = y1 - y2; + return diff; + } + @Override public double getPreferredHeight(StringBounder stringBounder) { return Math.max(arrow.getPreferredHeight(stringBounder), participantBox.getHeadHeight(stringBounder)); diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/DrawableSet.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/DrawableSet.java index e11898f6c..039f2f17f 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/DrawableSet.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/DrawableSet.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6198 $ + * Revision $Revision: 6484 $ * */ package net.sourceforge.plantuml.sequencediagram.graphic; @@ -46,8 +46,10 @@ import java.util.Set; import net.sourceforge.plantuml.Dimension2DDouble; import net.sourceforge.plantuml.ISkinParam; import net.sourceforge.plantuml.SkinParamBackcolored; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.graphic.StringBounder; import net.sourceforge.plantuml.sequencediagram.Event; +import net.sourceforge.plantuml.sequencediagram.InGroupable; import net.sourceforge.plantuml.sequencediagram.Newpage; import net.sourceforge.plantuml.sequencediagram.Participant; import net.sourceforge.plantuml.sequencediagram.ParticipantEnglober; @@ -281,7 +283,9 @@ class DrawableSet { } private void drawHeadTailU(UGraphic ug, Page page, double positionTail) { - for (LivingParticipantBox box : getAllLivingParticipantBox()) { + for (Map.Entry ent : participants.entrySet()) { + final Participant p = ent.getKey(); + final LivingParticipantBox box = ent.getValue(); final double create = box.getCreate(); boolean showHead = true; if (create > 0) { @@ -292,7 +296,14 @@ class DrawableSet { showHead = false; } } + final Url url = p.getUrl(); + if (url != null) { + ug.setUrl(url.getUrl(), url.getTooltip()); + } box.getParticipantBox().drawHeadTailU(ug, topStartingY, showHead, positionTail); + if (url != null) { + ug.setUrl(null, null); + } } } @@ -322,12 +333,12 @@ class DrawableSet { final Component comp = getEngloberComponent(englober.getParticipantEnglober()); final double width = x2 - x1; - final double preferedWidth = getEngloberPreferedWidth(ug.getStringBounder(), - englober.getParticipantEnglober()); + final double preferedWidth = getEngloberPreferedWidth(ug.getStringBounder(), englober + .getParticipantEnglober()); if (preferedWidth > width) { - //if (englober.getFirst2() == englober.getLast2()) { - x1 -= (preferedWidth - width) / 2; - //} + // if (englober.getFirst2() == englober.getLast2()) { + x1 -= (preferedWidth - width) / 2; + // } final Dimension2DDouble dim = new Dimension2DDouble(preferedWidth, height); ug.translate(x1, 1); comp.drawU(ug, dim, context); @@ -346,8 +357,8 @@ class DrawableSet { } private Component getEngloberComponent(ParticipantEnglober englober) { - final ISkinParam s = englober.getBoxColor() == null ? skinParam : new SkinParamBackcolored(skinParam, - englober.getBoxColor()); + final ISkinParam s = englober.getBoxColor() == null ? skinParam : new SkinParamBackcolored(skinParam, englober + .getBoxColor()); return skin.createComponent(ComponentType.ENGLOBER, s, englober.getTitle()); } @@ -370,29 +381,6 @@ class DrawableSet { line.drawU(ug, getSkin(), skinParam); } - // public void addParticipantEnglober(ParticipantEnglober englober) { - // participantEnglobers.add(englober); - // } - - // private boolean contains(ParticipantEngloberContexted englober, Participant toTest) { - // if (toTest == englober.getFirst2() || toTest == englober.getLast2()) { - // return true; - // } - // boolean inside = false; - // for (Participant p : participants.keySet()) { - // if (p == englober.getFirst2()) { - // inside = true; - // } - // if (p == toTest) { - // return inside; - // } - // if (p == englober.getLast2()) { - // inside = false; - // } - // } - // throw new IllegalArgumentException(); - // } - private ParticipantEngloberContexted getParticipantEnglober(Participant p) { for (ParticipantEngloberContexted pe : getExistingParticipantEnglober()) { if (pe.contains(p)) { @@ -406,8 +394,64 @@ class DrawableSet { this.topStartingY = topStartingY; } - // public final List getParticipantEnglobers() { - // return Collections.unmodifiableList(participantEnglobers); - // } + public void appendCmap(StringBuilder cmap, int offsetX, int offsetY, StringBounder stringBounder) { + cmap.append("\n"); + for (Map.Entry entry : participants.entrySet()) { + final Participant p = entry.getKey(); + final Url url = p.getUrl(); + if (url != null) { + final ParticipantBox box = entry.getValue().getParticipantBox(); + + final double width = box.getPreferredWidth(stringBounder); + final double height = box.getHeadHeightOnly(stringBounder); + + final double x1 = offsetX + box.getMinX(); + final double x2 = x1 + width; + + // final double y1 = sequenceAreaY; + // final double y2 = y1 + height; + final double y2 = offsetY + topStartingY - box.magicMargin(stringBounder); + final double y1 = y2 - height; + final String id = p.getCode(); + appendArea(cmap, id, url, x1, x2, y2, y1); + } + } + + for (Map.Entry ent : events.entrySet()) { + final Event ev = ent.getKey(); + final Url url = ev.getUrl(); + if (url == null) { + continue; + } + final GraphicalElement gra = ent.getValue(); + final double x1 = ((InGroupable) gra).getMinX(stringBounder); + final double x2 = ((InGroupable) gra).getMaxX(stringBounder); + final double y1 = gra.getStartingY() + offsetY; + final double y2 = y1 + gra.getPreferredHeight(stringBounder); + appendArea(cmap, ev.toString(), url, x1, x2, y2, y1); + + } + + cmap.append("\n"); + } + + private void appendArea(StringBuilder cmap, final String id, final Url url, final double x1, final double x2, + final double y2, final double y1) { + cmap.append("\n"); + } } diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/DrawableSetInitializer.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/DrawableSetInitializer.java index ba60db01c..ec696961c 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/DrawableSetInitializer.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/DrawableSetInitializer.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6214 $ + * Revision $Revision: 6485 $ * */ package net.sourceforge.plantuml.sequencediagram.graphic; @@ -59,6 +59,7 @@ import net.sourceforge.plantuml.sequencediagram.Participant; import net.sourceforge.plantuml.sequencediagram.ParticipantEnglober; import net.sourceforge.plantuml.sequencediagram.ParticipantEngloberContexted; import net.sourceforge.plantuml.sequencediagram.ParticipantType; +import net.sourceforge.plantuml.sequencediagram.Reference; import net.sourceforge.plantuml.skin.Component; import net.sourceforge.plantuml.skin.ComponentType; import net.sourceforge.plantuml.skin.Skin; @@ -149,14 +150,16 @@ class DrawableSetInitializer { prepareDivider(stringBounder, (Divider) ev); } else if (ev instanceof Delay) { prepareDelay(stringBounder, (Delay) ev, col); + } else if (ev instanceof Reference) { + prepareReference(stringBounder, (Reference) ev); } else { throw new IllegalStateException(); } } - //takeParticipantEngloberTitleWidth(stringBounder); + // takeParticipantEngloberTitleWidth(stringBounder); constraintSet.takeConstraintIntoAccount(stringBounder); - //takeParticipantEngloberTitleWidth2(stringBounder); + // takeParticipantEngloberTitleWidth2(stringBounder); takeParticipantEngloberTitleWidth3(stringBounder); prepareMissingSpace(stringBounder); @@ -167,18 +170,16 @@ class DrawableSetInitializer { private void takeParticipantEngloberTitleWidth3(StringBounder stringBounder) { for (ParticipantEngloberContexted pe : drawableSet.getExistingParticipantEnglober()) { - final double preferredWidth = drawableSet.getEngloberPreferedWidth(stringBounder, - pe.getParticipantEnglober()); + final double preferredWidth = drawableSet.getEngloberPreferedWidth(stringBounder, pe + .getParticipantEnglober()); final ParticipantBox first = drawableSet.getLivingParticipantBox(pe.getFirst2()).getParticipantBox(); final ParticipantBox last = drawableSet.getLivingParticipantBox(pe.getLast2()).getParticipantBox(); final double x1 = drawableSet.getX1(pe); final double x2 = drawableSet.getX2(stringBounder, pe); final double missing = preferredWidth - (x2 - x1); - if (missing>0) { - constraintSet - .pushToLeftParticipantBox(missing / 2, first, true); - constraintSet - .pushToLeftParticipantBox(missing / 2, last, false); + if (missing > 0) { + constraintSet.pushToLeftParticipantBox(missing / 2, first, true); + constraintSet.pushToLeftParticipantBox(missing / 2, last, false); } } } @@ -186,8 +187,8 @@ class DrawableSetInitializer { private void takeParticipantEngloberTitleWidth2(StringBounder stringBounder) { double lastX2; for (ParticipantEngloberContexted pe : drawableSet.getExistingParticipantEnglober()) { - final double preferredWidth = drawableSet.getEngloberPreferedWidth(stringBounder, - pe.getParticipantEnglober()); + final double preferredWidth = drawableSet.getEngloberPreferedWidth(stringBounder, pe + .getParticipantEnglober()); final ParticipantBox first = drawableSet.getLivingParticipantBox(pe.getFirst2()).getParticipantBox(); final ParticipantBox last = drawableSet.getLivingParticipantBox(pe.getLast2()).getParticipantBox(); final double x1 = drawableSet.getX1(pe); @@ -201,11 +202,12 @@ class DrawableSetInitializer { lastX2 = x2; } } + private void takeParticipantEngloberTitleWidth(StringBounder stringBounder) { ParticipantBox previousLast = null; for (ParticipantEngloberContexted pe : drawableSet.getExistingParticipantEnglober()) { - final double preferredWidth = drawableSet.getEngloberPreferedWidth(stringBounder, - pe.getParticipantEnglober()); + final double preferredWidth = drawableSet.getEngloberPreferedWidth(stringBounder, pe + .getParticipantEnglober()); final ParticipantBox first = drawableSet.getLivingParticipantBox(pe.getFirst2()).getParticipantBox(); final ParticipantBox last = drawableSet.getLivingParticipantBox(pe.getLast2()).getParticipantBox(); final double margin = 5; @@ -322,11 +324,11 @@ class DrawableSetInitializer { if (m.getType() != GroupingType.START) { throw new IllegalStateException(); } - final ISkinParam skinParam = new SkinParamBackcolored(drawableSet.getSkinParam(), m.getBackColorElement(), - m.getBackColorGeneral()); + final ISkinParam skinParam = new SkinParamBackcolored(drawableSet.getSkinParam(), m.getBackColorElement(), m + .getBackColorGeneral()); // this.maxGrouping++; - final List strings = m.getTitle().equals("group") ? Arrays.asList(m.getComment()) : Arrays.asList( - m.getTitle(), m.getComment()); + final List strings = m.getTitle().equals("group") ? Arrays.asList(m.getComment()) : Arrays.asList(m + .getTitle(), m.getComment()); final Component header = drawableSet.getSkin().createComponent(ComponentType.GROUPING_HEADER, skinParam, strings); final InGroupableList inGroupableList = new InGroupableList(m, freeY); @@ -396,7 +398,7 @@ class DrawableSetInitializer { } final ISkinParam skinParam = new SkinParamBackcolored(drawableSet.getSkinParam(), n.getSpecificBackColor()); final NoteBox noteBox = new NoteBox(freeY, drawableSet.getSkin().createComponent(ComponentType.NOTE, skinParam, - n.getStrings()), p1, p2, n.getPosition()); + n.getStrings()), p1, p2, n.getPosition(), n.getUrl()); for (InGroupableList groupingStructure : inGroupableLists) { groupingStructure.addInGroupable(noteBox); @@ -417,6 +419,23 @@ class DrawableSetInitializer { freeY = step1Message.prepareMessage(constraintSet, inGroupableLists); } + private void prepareReference(StringBounder stringBounder, Reference reference) { + final LivingParticipantBox p1 = drawableSet.getLivingParticipantBox(reference.getParticipant()); + final LivingParticipantBox p2 = drawableSet.getLivingParticipantBox(reference.getParticipant2()); + final GraphicalReference graphicalReference = new GraphicalReference(freeY, drawableSet.getSkin() + .createComponent(ComponentType.REFERENCE, drawableSet.getSkinParam(), reference.getStrings()), p1, p2, + reference.getUrl()); + + final double width = graphicalReference.getPreferredWidth(stringBounder) + - p1.getParticipantBox().getPreferredWidth(stringBounder) / 2 + - p2.getParticipantBox().getPreferredWidth(stringBounder) / 2; + + constraintSet.getConstraint(p1.getParticipantBox(), p2.getParticipantBox()).ensureValue(width); + + freeY += graphicalReference.getPreferredHeight(stringBounder); + drawableSet.addEvent(reference, graphicalReference); + } + private void prepareMessageExo(StringBounder stringBounder, MessageExo m) { final Step1MessageExo step1Message = new Step1MessageExo(stringBounder, m, drawableSet, freeY); freeY = step1Message.prepareMessage(constraintSet, inGroupableLists); diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/FileMaker.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/FileMaker.java index 915d91569..5a08cd7a5 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/FileMaker.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/FileMaker.java @@ -28,23 +28,23 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5520 $ + * Revision $Revision: 6383 $ * */ package net.sourceforge.plantuml.sequencediagram.graphic; -import java.io.File; import java.io.IOException; import java.io.OutputStream; -import java.util.List; public interface FileMaker { - List createMany(final File suggestedFile) throws IOException; + // List createManyRRMV(final File suggestedFile) throws IOException; void createOne(OutputStream os, int index) throws IOException; public int getNbPages(); + void appendCmap(StringBuilder cmap); + } \ No newline at end of file diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/GraphicalReference.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/GraphicalReference.java new file mode 100644 index 000000000..6cf3a4bfa --- /dev/null +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/GraphicalReference.java @@ -0,0 +1,114 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 3916 $ + * + */ +package net.sourceforge.plantuml.sequencediagram.graphic; + +import java.awt.geom.Dimension2D; + +import net.sourceforge.plantuml.Dimension2DDouble; +import net.sourceforge.plantuml.Url; +import net.sourceforge.plantuml.graphic.StringBounder; +import net.sourceforge.plantuml.sequencediagram.InGroupable; +import net.sourceforge.plantuml.skin.Component; +import net.sourceforge.plantuml.skin.Context2D; +import net.sourceforge.plantuml.ugraphic.UGraphic; + +class GraphicalReference extends GraphicalElement implements InGroupable { + + private final Component comp; + private final LivingParticipantBox livingParticipantBox1; + private final LivingParticipantBox livingParticipantBox2; + private final Url url; + + public GraphicalReference(double startingY, Component comp, LivingParticipantBox livingParticipantBox1, + LivingParticipantBox livingParticipantBox2, Url url) { + super(startingY); + if (livingParticipantBox1 == null || livingParticipantBox2 == null) { + throw new IllegalArgumentException(); + } + this.url = url; + this.comp = comp; + this.livingParticipantBox1 = livingParticipantBox1; + this.livingParticipantBox2 = livingParticipantBox2; + } + + @Override + protected void drawInternalU(UGraphic ug, double maxX, Context2D context) { + + final StringBounder stringBounder = ug.getStringBounder(); + final double posX = getMinX(stringBounder); + + ug.translate(posX, getStartingY()); + final double preferredWidth = comp.getPreferredWidth(stringBounder); + final double w = getMaxX(stringBounder) - getMinX(stringBounder); + + final double width = Math.max(preferredWidth, w); + + final Dimension2D dim = new Dimension2DDouble(width, comp.getPreferredHeight(stringBounder)); + if (url != null) { + ug.setUrl(url.getUrl(), url.getTooltip()); + } + comp.drawU(ug, dim, context); + if (url != null) { + ug.setUrl(null, null); + } + } + + @Override + public double getPreferredHeight(StringBounder stringBounder) { + return comp.getPreferredHeight(stringBounder); + } + + @Override + public double getPreferredWidth(StringBounder stringBounder) { + return comp.getPreferredWidth(stringBounder); + } + + @Override + public double getStartingX(StringBounder stringBounder) { + return getMinX(stringBounder); + } + + public double getMaxX(StringBounder stringBounder) { + return Math.max(livingParticipantBox1.getMaxX(stringBounder), livingParticipantBox2.getMaxX(stringBounder)); + } + + public double getMinX(StringBounder stringBounder) { + return Math.min(livingParticipantBox1.getMinX(stringBounder), livingParticipantBox2.getMinX(stringBounder)); + } + + public String toString(StringBounder stringBounder) { + return toString(); + } + +} diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/MessageArrow.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/MessageArrow.java index 1a477ea10..cf59b2664 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/MessageArrow.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/MessageArrow.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6026 $ + * Revision $Revision: 6330 $ * */ package net.sourceforge.plantuml.sequencediagram.graphic; @@ -125,6 +125,10 @@ class MessageArrow extends Arrow { protected void drawInternalU(UGraphic ug, double maxX, Context2D context) { final StringBounder stringBounder = ug.getStringBounder(); ug.translate(getStartingX(stringBounder), getStartingY()); +// ug.getParam().setColor(Color.GREEN); +// ug.getParam().setBackcolor(Color.LIGHT_GRAY); +// ug.draw(0, 0, new URectangle(getActualDimension(stringBounder).getWidth(), getActualDimension(stringBounder) +// .getHeight())); getArrowComponent().drawU(ug, getActualDimension(stringBounder), context); } @@ -166,5 +170,5 @@ class MessageArrow extends Arrow { public String toString(StringBounder stringBounder) { return getMinX(stringBounder) + "-" + getMaxX(stringBounder); } - + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/NoteBox.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/NoteBox.java index 664445379..6dc439db8 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/NoteBox.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/NoteBox.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6026 $ + * Revision $Revision: 6485 $ * */ package net.sourceforge.plantuml.sequencediagram.graphic; @@ -36,6 +36,7 @@ package net.sourceforge.plantuml.sequencediagram.graphic; import java.awt.geom.Dimension2D; import net.sourceforge.plantuml.Dimension2DDouble; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.graphic.StringBounder; import net.sourceforge.plantuml.sequencediagram.InGroupable; import net.sourceforge.plantuml.sequencediagram.NotePosition; @@ -46,6 +47,7 @@ import net.sourceforge.plantuml.ugraphic.UGraphic; class NoteBox extends GraphicalElement implements InGroupable { private final NotePosition position; + private final Url url; private final LivingParticipantBox p1; private final LivingParticipantBox p2; @@ -55,7 +57,7 @@ class NoteBox extends GraphicalElement implements InGroupable { private double delta = 0; public NoteBox(double startingY, Component comp, LivingParticipantBox p1, LivingParticipantBox p2, - NotePosition position) { + NotePosition position, Url url) { super(startingY); if (p1 == null) { throw new IllegalArgumentException(); @@ -66,6 +68,7 @@ class NoteBox extends GraphicalElement implements InGroupable { this.p1 = p1; this.p2 = p2; this.position = position; + this.url = url; this.comp = comp; } @@ -86,7 +89,13 @@ class NoteBox extends GraphicalElement implements InGroupable { ug.translate(xStart, getStartingY()); final Dimension2D dimensionToUse = new Dimension2DDouble(comp.getPreferredWidth(stringBounder), comp .getPreferredHeight(stringBounder)); + if (url != null) { + ug.setUrl(url.getUrl(), url.getTooltip()); + } comp.drawU(ug, dimensionToUse, context); + if (url != null) { + ug.setUrl(null, null); + } } @Override @@ -136,4 +145,8 @@ class NoteBox extends GraphicalElement implements InGroupable { return toString(); } + public final Url getUrl() { + return url; + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/ParticipantBox.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/ParticipantBox.java index 7c1766bd5..8819e55cb 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/ParticipantBox.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/ParticipantBox.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6049 $ + * Revision $Revision: 6463 $ * */ package net.sourceforge.plantuml.sequencediagram.graphic; @@ -72,6 +72,10 @@ public class ParticipantBox implements Pushable { return "PB" + cpt; } + public double getMinX() { + return startingX + outMargin; + } + public double getMaxX(StringBounder stringBounder) { return startingX + head.getPreferredWidth(stringBounder) + 2 * outMargin; } @@ -84,6 +88,10 @@ public class ParticipantBox implements Pushable { return head.getPreferredHeight(stringBounder) + line.getPreferredHeight(stringBounder) / 2.0; } + public double getHeadHeightOnly(StringBounder stringBounder) { + return head.getPreferredHeight(stringBounder); + } + public double getPreferredWidth(StringBounder stringBounder) { return head.getPreferredWidth(stringBounder); } @@ -108,7 +116,7 @@ public class ParticipantBox implements Pushable { if (showHead) { final double y1 = topStartingY - head.getPreferredHeight(stringBounder) - line.getPreferredHeight(stringBounder) / 2; - ug.translate(startingX + outMargin, y1); + ug.translate(getMinX(), y1); head.drawU(ug, new Dimension2DDouble(head.getPreferredWidth(stringBounder), head .getPreferredHeight(stringBounder)), new SimpleContext2D(false)); ug.setTranslate(atX, atY); @@ -121,7 +129,7 @@ public class ParticipantBox implements Pushable { // if (y2 != y22) { // throw new IllegalStateException(); // } - ug.translate(startingX + outMargin, positionTail); + ug.translate(getMinX(), positionTail); tail.drawU(ug, new Dimension2DDouble(tail.getPreferredWidth(stringBounder), tail .getPreferredHeight(stringBounder)), new SimpleContext2D(false)); ug.setTranslate(atX, atY); diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/Segment.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/Segment.java index 4a10c608b..a6cc05bb5 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/Segment.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/Segment.java @@ -34,7 +34,6 @@ package net.sourceforge.plantuml.sequencediagram.graphic; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/SequenceDiagramFileMaker.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/SequenceDiagramFileMaker.java index 5a65bd800..15fdb51a4 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/SequenceDiagramFileMaker.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/SequenceDiagramFileMaker.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6208 $ + * Revision $Revision: 6466 $ * */ package net.sourceforge.plantuml.sequencediagram.graphic; @@ -39,8 +39,6 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Dimension2D; import java.awt.image.BufferedImage; import java.io.File; -import java.io.FileOutputStream; -import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; @@ -54,7 +52,6 @@ import net.sourceforge.plantuml.EmptyImageBuilder; import net.sourceforge.plantuml.FileFormat; import net.sourceforge.plantuml.FileFormatOption; import net.sourceforge.plantuml.FontParam; -import net.sourceforge.plantuml.Log; import net.sourceforge.plantuml.eps.EpsStrategy; import net.sourceforge.plantuml.graphic.HtmlColor; import net.sourceforge.plantuml.graphic.StringBounder; @@ -69,7 +66,6 @@ import net.sourceforge.plantuml.sequencediagram.LifeEventType; import net.sourceforge.plantuml.sequencediagram.Message; import net.sourceforge.plantuml.sequencediagram.Newpage; import net.sourceforge.plantuml.sequencediagram.Participant; -import net.sourceforge.plantuml.sequencediagram.ParticipantEnglober; import net.sourceforge.plantuml.sequencediagram.SequenceDiagram; import net.sourceforge.plantuml.skin.Component; import net.sourceforge.plantuml.skin.ComponentType; @@ -94,9 +90,17 @@ public class SequenceDiagramFileMaker implements FileMaker { private final Dimension2D fullDimension; private final List pages; private final FileFormatOption fileFormatOption; + private final List flashcodes; + + private int offsetX; + private int offsetY; - public SequenceDiagramFileMaker(SequenceDiagram sequenceDiagram, Skin skin, FileFormatOption fileFormatOption) { + + + public SequenceDiagramFileMaker(SequenceDiagram sequenceDiagram, Skin skin, FileFormatOption fileFormatOption, + List flashcodes) { HtmlColor.setForceMonochrome(sequenceDiagram.getSkinParam().isMonochrome()); + this.flashcodes = flashcodes; this.diagram = sequenceDiagram; this.fileFormatOption = fileFormatOption; final DrawableSetInitializer initializer = new DrawableSetInitializer(skin, sequenceDiagram.getSkinParam(), @@ -105,9 +109,6 @@ public class SequenceDiagramFileMaker implements FileMaker { for (Participant p : sequenceDiagram.participants().values()) { initializer.addParticipant(p, sequenceDiagram.getEnglober(p)); } -// for (ParticipantEnglober englober : sequenceDiagram.getParticipantEnglobers()) { -// initializer.addParticipantEnglober(englober); -// } for (Event ev : sequenceDiagram.events()) { initializer.addEvent(ev); @@ -156,62 +157,6 @@ public class SequenceDiagramFileMaker implements FileMaker { newpageHeight, title); } - public List createMany(final File suggestedFile) throws IOException { - final List result = new ArrayList(); - final FileFormat fileFormat = fileFormatOption.getFileFormat(); - if (fileFormat == FileFormat.ATXT) { - throw new UnsupportedOperationException(); - } - for (int i = 0; i < pages.size(); i++) { - final UGraphic createImage = createImage((int) fullDimension.getWidth(), pages.get(i), i); - final File f = computeFilename(suggestedFile, i, fileFormat); - Log.info("Creating file: " + f); - if (createImage instanceof UGraphicG2d) { - final BufferedImage im = ((UGraphicG2d) createImage).getBufferedImage(); - Log.info("Image size " + im.getWidth() + " x " + im.getHeight()); - PngIO.write(im, f, diagram.getMetadata(), diagram.getDpi(fileFormatOption)); - } else if (createImage instanceof UGraphicSvg && fileFormat == FileFormat.SVG) { - final UGraphicSvg svg = (UGraphicSvg) createImage; - final FileOutputStream fos = new FileOutputStream(f); - try { - svg.createXml(fos); - } finally { - fos.close(); - } - // } else if (createImage instanceof UGraphicSvg && fileFormat - // == FileFormat.EPS_VIA_SVG) { - // final File svgFile = - // CucaDiagramFileMaker.createTempFile("seq", ".svg"); - // final UGraphicSvg svg = (UGraphicSvg) createImage; - // final FileOutputStream fos = new FileOutputStream(svgFile); - // try { - // svg.createXml(fos); - // } finally { - // fos.close(); - // } - // try { - // InkscapeUtils.create().createEps(svgFile, f); - // } catch (InterruptedException e) { - // e.printStackTrace(); - // Log.error("Error "+e); - // throw new IOException(e.toString()); - // } - } else if (createImage instanceof UGraphicEps) { - final UGraphicEps eps = (UGraphicEps) createImage; - final FileWriter fw = new FileWriter(f); - try { - fw.write(eps.getEPSCode()); - } finally { - fw.close(); - } - } else { - throw new IllegalStateException(); - } - Log.info("File size : " + f.length()); - result.add(f); - } - return result; - } public void createOne(OutputStream os, int index) throws IOException { final UGraphic createImage = createImage((int) fullDimension.getWidth(), pages.get(index), index); @@ -262,7 +207,7 @@ public class SequenceDiagramFileMaker implements FileMaker { } return area.getHeight() * getScale(area.getWidth(), area.getHeight()) * dpiFactor; } - + private UGraphic createImage(final int diagramWidth, final Page page, final int indice) { double delta = 0; if (indice > 0) { @@ -284,6 +229,9 @@ public class SequenceDiagramFileMaker implements FileMaker { } addFooter2(area); addHeader2(area); + + offsetX = (int) Math.round(area.getSequenceAreaX()); + offsetY = (int) Math.round(area.getSequenceAreaY()); final Color backColor = diagram.getSkinParam().getBackgroundColor().getColor(); final UGraphic ug; @@ -295,9 +243,17 @@ public class SequenceDiagramFileMaker implements FileMaker { if (imageHeight == 0) { imageHeight = 1; } - final EmptyImageBuilder builder = new EmptyImageBuilder(imageWidth, imageHeight, backColor); + double flashCodeHeight = 0; + if (flashcodes != null) { + flashCodeHeight = flashcodes.get(0).getHeight(); + } + final EmptyImageBuilder builder = new EmptyImageBuilder(imageWidth, imageHeight + flashCodeHeight, + backColor); final Graphics2D graphics2D = builder.getGraphics2D(); + if (flashcodes != null) { + graphics2D.drawImage(flashcodes.get(0), null, 0, (int) imageHeight); + } if (diagram.isRotation()) { final AffineTransform at = new AffineTransform(0, 1, 1, 0, 0, 0); at.concatenate(new AffineTransform(-1, 0, 0, 1, imageHeight, 0)); @@ -412,4 +368,8 @@ public class SequenceDiagramFileMaker implements FileMaker { return dummyStringBounder; } + public void appendCmap(StringBuilder cmap) { + drawableSet.appendCmap(cmap, offsetX, offsetY, dummyStringBounder); + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/SequenceDiagramTxtMaker.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/SequenceDiagramTxtMaker.java index 99dbab785..4ecc0b149 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/SequenceDiagramTxtMaker.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/SequenceDiagramTxtMaker.java @@ -34,12 +34,9 @@ package net.sourceforge.plantuml.sequencediagram.graphic; import java.awt.geom.Dimension2D; -import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; -import java.util.Collections; -import java.util.List; import net.sourceforge.plantuml.FileFormat; import net.sourceforge.plantuml.asciiart.TextSkin; @@ -105,14 +102,14 @@ public class SequenceDiagramTxtMaker implements FileMaker { drawableSet.drawU(ug, 0, fullDimension.getWidth(), page, diagram.isShowFootbox()); } - public List createMany(File suggestedFile) throws IOException { - if (fileFormat == FileFormat.UTXT) { - ug.getCharArea().print(new PrintStream(suggestedFile, "UTF-8")); - } else { - ug.getCharArea().print(new PrintStream(suggestedFile)); - } - return Collections.singletonList(suggestedFile); - } +// public List createManyRRMV(File suggestedFile) throws IOException { +// if (fileFormat == FileFormat.UTXT) { +// ug.getCharArea().print(new PrintStream(suggestedFile, "UTF-8")); +// } else { +// ug.getCharArea().print(new PrintStream(suggestedFile)); +// } +// return Collections.singletonList(suggestedFile); +// } public void createOne(OutputStream os, int index) throws IOException { final PrintStream ps = new PrintStream(os); @@ -127,4 +124,7 @@ public class SequenceDiagramTxtMaker implements FileMaker { return 1; } + public void appendCmap(StringBuilder cmap) { + } + } diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1Abstract.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1Abstract.java index 68283710c..7fa7141c3 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1Abstract.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1Abstract.java @@ -37,6 +37,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import net.sourceforge.plantuml.Url; import net.sourceforge.plantuml.graphic.StringBounder; import net.sourceforge.plantuml.sequencediagram.AbstractMessage; import net.sourceforge.plantuml.sequencediagram.InGroupableList; @@ -156,9 +157,9 @@ abstract class Step1Abstract { } protected final NoteBox createNoteBox(StringBounder stringBounder, Arrow arrow, Component noteComp, - NotePosition notePosition) { + NotePosition notePosition, Url url) { final LivingParticipantBox p = arrow.getParticipantAt(stringBounder, notePosition); - final NoteBox noteBox = new NoteBox(arrow.getStartingY(), noteComp, p, null, notePosition); + final NoteBox noteBox = new NoteBox(arrow.getStartingY(), noteComp, p, null, notePosition, url); if (arrow instanceof MessageSelfArrow && notePosition == NotePosition.RIGHT) { noteBox.pushToRight(arrow.getPreferredWidth(stringBounder)); diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1Message.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1Message.java index d8b148d99..5bdc3ac2f 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1Message.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1Message.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6026 $ + * Revision $Revision: 6485 $ * */ package net.sourceforge.plantuml.sequencediagram.graphic; @@ -90,8 +90,8 @@ class Step1Message extends Step1Abstract { final double length; if (isSelfMessage()) { length = graphic.getArrowOnlyWidth(getStringBounder()) - + getLivingParticipantBox1().getLiveThicknessAt(getStringBounder(), arrowYStartLevel) - .getSegment().getLength(); + + getLivingParticipantBox1().getLiveThicknessAt(getStringBounder(), arrowYStartLevel).getSegment() + .getLength(); } else { length = graphic.getArrowOnlyWidth(getStringBounder()) + getLivingParticipantBox(NotePosition.LEFT).getLifeLine().getRightShift(arrowYStartLevel) @@ -165,11 +165,11 @@ class Step1Message extends Step1Abstract { getLabelOfMessage(getMessage())), getLivingParticipantBox1()); if (getMessage().getNote() != null && isSelfMessage()) { final NoteBox noteBox = createNoteBox(getStringBounder(), messageSelfArrow, getNote(), getMessage() - .getNotePosition()); + .getNotePosition(), getMessage().getUrlNote()); return new ArrowAndNoteBox(getStringBounder(), messageSelfArrow, noteBox); } else if (getMessage().getNote() != null) { final NoteBox noteBox = createNoteBox(getStringBounder(), messageArrow, getNote(), getMessage() - .getNotePosition()); + .getNotePosition(), getMessage().getUrlNote()); return new ArrowAndNoteBox(getStringBounder(), messageArrow, noteBox); } else if (isSelfMessage()) { return messageSelfArrow; @@ -179,18 +179,17 @@ class Step1Message extends Step1Abstract { } private Arrow createArrowCreate() { - getLivingParticipantBox2().create(getFreeY()); + Arrow result = new ArrowAndParticipant(getStringBounder(), messageArrow, getParticipantBox2()); if (getMessage().getNote() != null) { - final ArrowAndParticipant arrowAndParticipant = new ArrowAndParticipant(getStringBounder(), messageArrow, - getParticipantBox2()); - final NoteBox noteBox = createNoteBox(getStringBounder(), arrowAndParticipant, getNote(), getMessage() - .getNotePosition()); + final NoteBox noteBox = createNoteBox(getStringBounder(), result, getNote(), + getMessage().getNotePosition(), getMessage().getUrlNote()); if (getMessage().getNotePosition() == NotePosition.RIGHT) { noteBox.pushToRight(getParticipantBox2().getPreferredWidth(getStringBounder()) / 2); } - return new ArrowAndNoteBox(getStringBounder(), arrowAndParticipant, noteBox); + result = new ArrowAndNoteBox(getStringBounder(), result, noteBox); } - return new ArrowAndParticipant(getStringBounder(), messageArrow, getParticipantBox2()); + getLivingParticipantBox2().create(getFreeY() + result.getPreferredHeight(getStringBounder()) / 2); + return result; } private ComponentType getSelfArrowType(Message m) { diff --git a/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1MessageExo.java b/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1MessageExo.java index 3da3185c5..6fbcd2fe7 100644 --- a/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1MessageExo.java +++ b/src/net/sourceforge/plantuml/sequencediagram/graphic/Step1MessageExo.java @@ -46,9 +46,7 @@ import net.sourceforge.plantuml.sequencediagram.LifeEvent; import net.sourceforge.plantuml.sequencediagram.MessageExo; import net.sourceforge.plantuml.sequencediagram.MessageExoType; import net.sourceforge.plantuml.sequencediagram.MessageNumber; -import net.sourceforge.plantuml.skin.ArrowBody; import net.sourceforge.plantuml.skin.ArrowDirection; -import net.sourceforge.plantuml.skin.ArrowHead; import net.sourceforge.plantuml.skin.ComponentType; class Step1MessageExo extends Step1Abstract { @@ -134,7 +132,8 @@ class Step1MessageExo extends Step1Abstract { if (getMessage().getNote() == null) { return messageArrow; } - final NoteBox toto = createNoteBox(getStringBounder(), messageArrow, getNote(), getMessage().getNotePosition()); + final NoteBox toto = createNoteBox(getStringBounder(), messageArrow, getNote(), getMessage().getNotePosition(), + getMessage().getUrlNote()); return new ArrowAndNoteBox(getStringBounder(), messageArrow, toto); } diff --git a/src/net/sourceforge/plantuml/skin/ComponentType.java b/src/net/sourceforge/plantuml/skin/ComponentType.java index 02eba46b9..38ed6a4ef 100644 --- a/src/net/sourceforge/plantuml/skin/ComponentType.java +++ b/src/net/sourceforge/plantuml/skin/ComponentType.java @@ -70,6 +70,7 @@ public class ComponentType { static public final ComponentType NEWPAGE = new ComponentType("NEWPAGE"); static public final ComponentType NOTE = new ComponentType("NOTE"); static public final ComponentType DIVIDER = new ComponentType("DIVIDER"); + static public final ComponentType REFERENCE = new ComponentType("REFERENCE"); static public final ComponentType ENGLOBER = new ComponentType("ENGLOBER"); // diff --git a/src/net/sourceforge/plantuml/skin/bluemodern/BlueModern.java b/src/net/sourceforge/plantuml/skin/bluemodern/BlueModern.java index 4a18f73e7..e955d2728 100644 --- a/src/net/sourceforge/plantuml/skin/bluemodern/BlueModern.java +++ b/src/net/sourceforge/plantuml/skin/bluemodern/BlueModern.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6052 $ + * Revision $Revision: 6448 $ * */ package net.sourceforge.plantuml.skin.bluemodern; @@ -67,11 +67,11 @@ public class BlueModern implements Skin { if (type.isArrow()) { if (type.getArrowConfiguration().isSelfArrow()) { - return new ComponentBlueModernSelfArrow(Color.BLACK, Color.BLACK, normalFont, stringsToDisplay, type - .getArrowConfiguration()); + return new ComponentBlueModernSelfArrow(Color.BLACK, Color.BLACK, normalFont, stringsToDisplay, + type.getArrowConfiguration()); } - return new ComponentBlueModernArrow(Color.BLACK, Color.BLACK, normalFont, stringsToDisplay, type - .getArrowConfiguration()); + return new ComponentBlueModernArrow(Color.BLACK, Color.BLACK, normalFont, stringsToDisplay, + type.getArrowConfiguration()); } if (type == ComponentType.PARTICIPANT_HEAD) { return new ComponentBlueModernParticipant(blue1, blue2, Color.WHITE, participantFont, stringsToDisplay); @@ -127,7 +127,7 @@ public class BlueModern implements Skin { return new ComponentBlueModernGroupingTail(borderGroupColor); } if (type == ComponentType.GROUPING_ELSE) { - return new ComponentRoseGroupingElse(Color.BLACK, smallFont, stringsToDisplay.get(0)); + return new ComponentRoseGroupingElse(Color.BLACK, Color.BLACK, smallFont, stringsToDisplay.get(0)); } if (type == ComponentType.TITLE) { return new ComponentRoseTitle(Color.BLACK, bigFont, stringsToDisplay); @@ -144,7 +144,7 @@ public class BlueModern implements Skin { } if (type == ComponentType.ENGLOBER) { return new ComponentBlueModernEnglober(blue1, blue3, stringsToDisplay, Color.BLACK, param.getFont( - FontParam.SEQUENCE_ENGLOBER, null)); + FontParam.SEQUENCE_BOX, null)); } return null; diff --git a/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingElse.java b/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingElse.java index bbc028a83..d1d983b6f 100644 --- a/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingElse.java +++ b/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingElse.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5937 $ + * Revision $Revision: 6404 $ * */ package net.sourceforge.plantuml.skin.rose; @@ -46,15 +46,17 @@ import net.sourceforge.plantuml.ugraphic.UStroke; public class ComponentRoseGroupingElse extends AbstractTextualComponent { - public ComponentRoseGroupingElse(Color fontColor, Font smallFont, CharSequence comment) { + private final Color groupBorder; + + public ComponentRoseGroupingElse(Color fontColor, Color groupBorder, Font smallFont, CharSequence comment) { super(comment == null ? null : "[" + comment + "]", fontColor, smallFont, HorizontalAlignement.LEFT, 5, 5, 1); + this.groupBorder = groupBorder; } - @Override protected void drawInternalU(UGraphic ug, Dimension2D dimensionToUse) { stroke(ug, 2, 2); - ug.getParam().setColor(getFontColor()); + ug.getParam().setColor(groupBorder); ug.draw(0, 1, new ULine(dimensionToUse.getWidth(), 0)); ug.getParam().setStroke(new UStroke()); getTextBlock().drawU(ug, getMarginX1(), getMarginY()); diff --git a/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingHeader.java b/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingHeader.java index b8e501447..f28a05a41 100644 --- a/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingHeader.java +++ b/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingHeader.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6009 $ + * Revision $Revision: 6403 $ * */ package net.sourceforge.plantuml.skin.rose; @@ -59,12 +59,14 @@ public class ComponentRoseGroupingHeader extends AbstractTextualComponent { private final TextBlock commentTextBlock; private final Color groupBackground; + private final Color groupBorder; private final Color background; - public ComponentRoseGroupingHeader(Color fontColor, Color background, Color groupBackground, Font bigFont, + public ComponentRoseGroupingHeader(Color fontColor, Color background, Color groupBackground, Color groupBorder, Font bigFont, Font smallFont, List strings) { super(strings.get(0), fontColor, bigFont, HorizontalAlignement.LEFT, 15, 30, 1); this.groupBackground = groupBackground; + this.groupBorder = groupBorder; this.background = background; if (strings.size() == 1 || strings.get(1) == null) { this.commentTextBlock = null; @@ -124,7 +126,7 @@ public class ComponentRoseGroupingHeader extends AbstractTextualComponent { polygon.addPoint(0, 0); ug.getParam().setStroke(new UStroke(2)); - ug.getParam().setColor(getFontColor()); + ug.getParam().setColor(groupBorder); ug.getParam().setBackcolor(this.groupBackground); ug.draw(0, 0, polygon); diff --git a/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingTail.java b/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingTail.java index 009be0013..2c198905b 100644 --- a/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingTail.java +++ b/src/net/sourceforge/plantuml/skin/rose/ComponentRoseGroupingTail.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5528 $ + * Revision $Revision: 6404 $ * */ package net.sourceforge.plantuml.skin.rose; @@ -45,16 +45,18 @@ import net.sourceforge.plantuml.ugraphic.UStroke; public class ComponentRoseGroupingTail extends AbstractComponent { private final Color foregroundColor; + private final Color groupBorder; - public ComponentRoseGroupingTail(Color foregroundColor) { + public ComponentRoseGroupingTail(Color foregroundColor, Color groupBorder) { this.foregroundColor = foregroundColor; + this.groupBorder = groupBorder; } @Override protected void drawInternalU(UGraphic ug, Dimension2D dimensionToUse) { ug.getParam().setStroke(new UStroke(2)); - ug.getParam().setColor(foregroundColor); + ug.getParam().setColor(groupBorder); ug.draw(0, dimensionToUse.getHeight(), new ULine(dimensionToUse.getWidth(), 0)); ug.getParam().setStroke(new UStroke()); } diff --git a/src/net/sourceforge/plantuml/skin/rose/ComponentRoseReference.java b/src/net/sourceforge/plantuml/skin/rose/ComponentRoseReference.java new file mode 100644 index 000000000..0e1225cc5 --- /dev/null +++ b/src/net/sourceforge/plantuml/skin/rose/ComponentRoseReference.java @@ -0,0 +1,96 @@ +/* ======================================================================== + * PlantUML : a free UML diagram generator + * ======================================================================== + * + * (C) Copyright 2009, Arnaud Roques + * + * Project Info: http://plantuml.sourceforge.net + * + * This file is part of PlantUML. + * + * PlantUML is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlantUML 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 General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * [Java is a trademark or registered trademark of Sun Microsystems, Inc. + * in the United States and other countries.] + * + * Original Author: Arnaud Roques + * + * Revision $Revision: 3837 $ + * + */ +package net.sourceforge.plantuml.skin.rose; + +import java.awt.Color; +import java.awt.Font; +import java.awt.geom.Dimension2D; +import java.util.List; + +import net.sourceforge.plantuml.graphic.HorizontalAlignement; +import net.sourceforge.plantuml.graphic.StringBounder; +import net.sourceforge.plantuml.graphic.TextBlock; +import net.sourceforge.plantuml.skin.AbstractTextualComponent; +import net.sourceforge.plantuml.ugraphic.UGraphic; +import net.sourceforge.plantuml.ugraphic.ULine; +import net.sourceforge.plantuml.ugraphic.URectangle; +import net.sourceforge.plantuml.ugraphic.UShape; +import net.sourceforge.plantuml.ugraphic.UStroke; + +public class ComponentRoseReference extends AbstractTextualComponent { + + // private final int outMargin = 5; + private final Color background; + + public ComponentRoseReference(Color fontColor, Font font, Color background, + List stringsToDisplay) { + super(stringsToDisplay, fontColor, font, HorizontalAlignement.CENTER, 4, 4, 4); + this.background = background; + } + + @Override + protected void drawInternalU(UGraphic ug, Dimension2D dimensionToUse) { + final TextBlock textBlock = getTextBlock(); + final StringBounder stringBounder = ug.getStringBounder(); + final double textWidth = getTextWidth(stringBounder); + final double textHeight = getTextHeight(stringBounder); + + ug.getParam().setColor(getFontColor()); + ug.getParam().setBackcolor(background); + + ug.getParam().setStroke(new UStroke(5, 5, 1)); + ug.draw(1, 1, new URectangle(dimensionToUse.getWidth() - 2, dimensionToUse.getHeight() - 2)); + ug.getParam().setStroke(new UStroke()); + + final UShape box = new URectangle(8, 8); + ug.draw(5, 5, box); + final ULine lineH = new ULine(4, 0); + final ULine lineV = new ULine(0, 4); + ug.draw(5 + 2, 5 + 4, lineH); + ug.draw(5 + 4, 5 + 2, lineV); + + textBlock.drawU(ug, 20, getMarginY()); + } + + @Override + public double getPreferredHeight(StringBounder stringBounder) { + return getTextHeight(stringBounder) + 0; + } + + @Override + public double getPreferredWidth(StringBounder stringBounder) { + return getTextWidth(stringBounder) + 26; + } + +} diff --git a/src/net/sourceforge/plantuml/skin/rose/Rose.java b/src/net/sourceforge/plantuml/skin/rose/Rose.java index c34091198..d873518c6 100644 --- a/src/net/sourceforge/plantuml/skin/rose/Rose.java +++ b/src/net/sourceforge/plantuml/skin/rose/Rose.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6046 $ + * Revision $Revision: 6476 $ * */ package net.sourceforge.plantuml.skin.rose; @@ -66,9 +66,11 @@ public class Rose implements Skin { defaultsColor.put(ColorParam.sequenceLifeLineBackground, HtmlColor.getColorIfValid("white")); defaultsColor.put(ColorParam.sequenceLifeLineBorder, HtmlColor.getColorIfValid("#A80036")); defaultsColor.put(ColorParam.sequenceGroupBackground, HtmlColor.getColorIfValid("#EEEEEE")); + defaultsColor.put(ColorParam.sequenceGroupBorder, HtmlColor.getColorIfValid("black")); defaultsColor.put(ColorParam.sequenceDividerBackground, HtmlColor.getColorIfValid("#EEEEEE")); - defaultsColor.put(ColorParam.sequenceEngloberLine, HtmlColor.getColorIfValid("#A80036")); - defaultsColor.put(ColorParam.sequenceEngloberBackground, HtmlColor.getColorIfValid("#DDDDDD")); + defaultsColor.put(ColorParam.sequenceReferenceBackground, HtmlColor.getColorIfValid("#EEEEEE")); + defaultsColor.put(ColorParam.sequenceBoxBorder, HtmlColor.getColorIfValid("#A80036")); + defaultsColor.put(ColorParam.sequenceBoxBackground, HtmlColor.getColorIfValid("#DDDDDD")); defaultsColor.put(ColorParam.noteBackground, HtmlColor.getColorIfValid("#FBFB77")); defaultsColor.put(ColorParam.noteBorder, HtmlColor.getColorIfValid("#A80036")); @@ -142,8 +144,11 @@ public class Rose implements Skin { public Component createComponent(ComponentType type, ISkinParam param, List stringsToDisplay) { final Color background = param.getBackgroundColor().getColor(); - final Color groundBackgroundColor = getHtmlColor(param, ColorParam.sequenceGroupBackground).getColor(); + final Color groupBorder = getHtmlColor(param, ColorParam.sequenceGroupBorder).getColor(); + final Color groupBackground = getHtmlColor(param, ColorParam.sequenceGroupBackground).getColor(); final Color sequenceDividerBackground = getHtmlColor(param, ColorParam.sequenceDividerBackground).getColor(); + final Color sequenceReferenceBackground = getHtmlColor(param, ColorParam.sequenceReferenceBackground) + .getColor(); final Color lifeLineBackgroundColor = getHtmlColor(param, ColorParam.sequenceLifeLineBackground).getColor(); final Color sequenceArrow = getHtmlColor(param, ColorParam.sequenceArrow).getColor(); final Color sequenceActorBackground = getHtmlColor(param, ColorParam.sequenceActorBackground).getColor(); @@ -153,7 +158,7 @@ public class Rose implements Skin { // ColorParam.border).getColor(); final Font fontArrow = param.getFont(FontParam.SEQUENCE_ARROW, null); - final Font fontGrouping = param.getFont(FontParam.SEQUENCE_GROUPING, null); + final Font fontGrouping = param.getFont(FontParam.SEQUENCE_GROUP, null); final Font fontParticipant = param.getFont(FontParam.SEQUENCE_PARTICIPANT, null); final Font fontActor = param.getFont(FontParam.SEQUENCE_ACTOR, null); @@ -202,19 +207,19 @@ public class Rose implements Skin { fontNote, stringsToDisplay); } if (type == ComponentType.GROUPING_HEADER) { - final Font fontGroupingHeader = param.getFont(FontParam.SEQUENCE_GROUPING_HEADER, null); - return new ComponentRoseGroupingHeader(getFontColor(param, FontParam.SEQUENCE_GROUPING_HEADER), background, - groundBackgroundColor, fontGroupingHeader, fontGrouping, stringsToDisplay); + final Font fontGroupingHeader = param.getFont(FontParam.SEQUENCE_GROUP_HEADER, null); + return new ComponentRoseGroupingHeader(getFontColor(param, FontParam.SEQUENCE_GROUP_HEADER), background, + groupBackground, groupBorder, fontGroupingHeader, fontGrouping, stringsToDisplay); } if (type == ComponentType.GROUPING_BODY) { - return new ComponentRoseGroupingBody(background, getFontColor(param, FontParam.SEQUENCE_GROUPING)); + return new ComponentRoseGroupingBody(background, groupBorder); } if (type == ComponentType.GROUPING_TAIL) { - return new ComponentRoseGroupingTail(getFontColor(param, FontParam.SEQUENCE_GROUPING)); + return new ComponentRoseGroupingTail(getFontColor(param, FontParam.SEQUENCE_GROUP), groupBorder); } if (type == ComponentType.GROUPING_ELSE) { - return new ComponentRoseGroupingElse(getFontColor(param, FontParam.SEQUENCE_GROUPING), fontGrouping, - stringsToDisplay.get(0)); + return new ComponentRoseGroupingElse(getFontColor(param, FontParam.SEQUENCE_GROUP), groupBorder, + fontGrouping, stringsToDisplay.get(0)); } if (type == ComponentType.ALIVE_BOX_CLOSE_CLOSE) { final Color borderColor = getHtmlColor(param, ColorParam.sequenceLifeLineBorder).getColor(); @@ -234,7 +239,8 @@ public class Rose implements Skin { } if (type == ComponentType.DELAY_LINE) { final Color borderColor = getHtmlColor(param, ColorParam.sequenceLifeLineBorder).getColor(); - // final Color borderColor = getFontColor(param, FontParam.SEQUENCE_DELAY); + // final Color borderColor = getFontColor(param, + // FontParam.SEQUENCE_DELAY); return new ComponentRoseDelayLine(borderColor); } if (type == ComponentType.DELAY_TEXT) { @@ -246,12 +252,16 @@ public class Rose implements Skin { return new ComponentRoseDestroy(borderColor); } if (type == ComponentType.NEWPAGE) { - return new ComponentRoseNewpage(getFontColor(param, FontParam.SEQUENCE_GROUPING)); + return new ComponentRoseNewpage(getFontColor(param, FontParam.SEQUENCE_GROUP)); } if (type == ComponentType.DIVIDER) { return new ComponentRoseDivider(getFontColor(param, FontParam.SEQUENCE_DIVIDER), param.getFont( FontParam.SEQUENCE_DIVIDER, null), sequenceDividerBackground, stringsToDisplay); } + if (type == ComponentType.REFERENCE) { + return new ComponentRoseReference(getFontColor(param, FontParam.SEQUENCE_REFERENCE), param.getFont( + FontParam.SEQUENCE_REFERENCE, null), sequenceReferenceBackground, stringsToDisplay); + } if (type == ComponentType.TITLE) { return new ComponentRoseTitle(getFontColor(param, FontParam.SEQUENCE_TITLE), param.getFont( FontParam.SEQUENCE_TITLE, null), stringsToDisplay); @@ -261,10 +271,10 @@ public class Rose implements Skin { "in April 2009.")); } if (type == ComponentType.ENGLOBER) { - final Color borderColor = getHtmlColor(param, ColorParam.sequenceEngloberLine).getColor(); - final Color backColor = getHtmlColor(param, ColorParam.sequenceEngloberBackground).getColor(); + final Color borderColor = getHtmlColor(param, ColorParam.sequenceBoxBorder).getColor(); + final Color backColor = getHtmlColor(param, ColorParam.sequenceBoxBackground).getColor(); return new ComponentRoseEnglober(borderColor, backColor, stringsToDisplay, getFontColor(param, - FontParam.SEQUENCE_ENGLOBER), param.getFont(FontParam.SEQUENCE_ENGLOBER, null)); + FontParam.SEQUENCE_BOX), param.getFont(FontParam.SEQUENCE_BOX, null)); } return null; } diff --git a/src/net/sourceforge/plantuml/statediagram/StateDiagramFactory.java b/src/net/sourceforge/plantuml/statediagram/StateDiagramFactory.java index bc07691e6..6e853d12a 100644 --- a/src/net/sourceforge/plantuml/statediagram/StateDiagramFactory.java +++ b/src/net/sourceforge/plantuml/statediagram/StateDiagramFactory.java @@ -28,12 +28,13 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6221 $ + * Revision $Revision: 6396 $ * */ package net.sourceforge.plantuml.statediagram; import net.sourceforge.plantuml.classdiagram.command.CommandMultilinesClassNote; +import net.sourceforge.plantuml.classdiagram.command.CommandUrl; import net.sourceforge.plantuml.command.AbstractUmlSystemCommandFactory; import net.sourceforge.plantuml.command.CommandNoteEntity; import net.sourceforge.plantuml.statediagram.command.CommandAddField; @@ -73,6 +74,7 @@ public class StateDiagramFactory extends AbstractUmlSystemCommandFactory { addCommand(new CommandHideEmptyDescription(system)); addCommand(new CommandNoteEntity(system)); + addCommand(new CommandUrl(system)); addCommonCommands(system); } } diff --git a/src/net/sourceforge/plantuml/sudoku/PSystemSudoku.java b/src/net/sourceforge/plantuml/sudoku/PSystemSudoku.java index 64a0ac2de..c4a0e4e82 100644 --- a/src/net/sourceforge/plantuml/sudoku/PSystemSudoku.java +++ b/src/net/sourceforge/plantuml/sudoku/PSystemSudoku.java @@ -28,17 +28,13 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5794 $ + * Revision $Revision: 6453 $ * */ package net.sourceforge.plantuml.sudoku; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; import net.sourceforge.plantuml.AbstractPSystem; import net.sourceforge.plantuml.FileFormatOption; @@ -47,20 +43,20 @@ public class PSystemSudoku extends AbstractPSystem { final private ISudoku sudoku; - public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { - OutputStream os = null; - try { - os = new FileOutputStream(suggestedFile); - new GraphicsSudoku(sudoku).writeImage(os); - } finally { - if (os != null) { - os.close(); - } - } - return Arrays.asList(suggestedFile); - } +// public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, InterruptedException { +// OutputStream os = null; +// try { +// os = new FileOutputStream(suggestedFile); +// new GraphicsSudoku(sudoku).writeImage(os); +// } finally { +// if (os != null) { +// os.close(); +// } +// } +// return Arrays.asList(suggestedFile); +// } - public void createFile(OutputStream os, int index, FileFormatOption fileFormat) throws IOException { + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormat) throws IOException { new GraphicsSudoku(sudoku).writeImage(os); } diff --git a/src/net/sourceforge/plantuml/sudoku/PSystemSudokuFactory.java b/src/net/sourceforge/plantuml/sudoku/PSystemSudokuFactory.java index e9c55368c..23b26e4e8 100644 --- a/src/net/sourceforge/plantuml/sudoku/PSystemSudokuFactory.java +++ b/src/net/sourceforge/plantuml/sudoku/PSystemSudokuFactory.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 3837 $ + * Revision $Revision: 6341 $ * */ package net.sourceforge.plantuml.sudoku; @@ -36,6 +36,7 @@ package net.sourceforge.plantuml.sudoku; import java.util.regex.Matcher; import java.util.regex.Pattern; +import net.sourceforge.plantuml.DiagramType; import net.sourceforge.plantuml.PSystemBasicFactory; public class PSystemSudokuFactory implements PSystemBasicFactory { @@ -69,4 +70,9 @@ public class PSystemSudokuFactory implements PSystemBasicFactory { return true; } + + public DiagramType getDiagramType() { + return DiagramType.UML; + } + } diff --git a/src/net/sourceforge/plantuml/suggest/SuggestEngine.java b/src/net/sourceforge/plantuml/suggest/SuggestEngine.java index 321749046..c99a1334e 100644 --- a/src/net/sourceforge/plantuml/suggest/SuggestEngine.java +++ b/src/net/sourceforge/plantuml/suggest/SuggestEngine.java @@ -40,7 +40,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; -import net.sourceforge.plantuml.BlockUmlBuilder; +import net.sourceforge.plantuml.StartUtils; import net.sourceforge.plantuml.UmlSource; import net.sourceforge.plantuml.command.Command; import net.sourceforge.plantuml.command.CommandControl; @@ -59,7 +59,7 @@ final public class SuggestEngine { public SuggestEngine(UmlSource source, PSystemCommandFactory systemFactory) { this.systemFactory = systemFactory; this.it = source.iterator(); - if (BlockUmlBuilder.isArobaseStartuml(next()) == false) { + if (StartUtils.isArobaseStartDiagram(next()) == false) { throw new UnsupportedOperationException(); } } @@ -83,7 +83,7 @@ final public class SuggestEngine { systemFactory.reset(); while (hasNext()) { final String s = next(); - if (BlockUmlBuilder.isArobaseEnduml(s)) { + if (StartUtils.isArobaseEndDiagram(s)) { return SuggestEngineResult.SYNTAX_OK; } final SuggestEngineResult check = checkAndCorrect(s); @@ -114,7 +114,7 @@ final public class SuggestEngine { lines.add(init); while (hasNext()) { final String s = next(); - if (BlockUmlBuilder.isArobaseEnduml(s)) { + if (StartUtils.isArobaseEndDiagram(s)) { return false; } lines.add(s); diff --git a/src/net/sourceforge/plantuml/svg/SvgGraphics.java b/src/net/sourceforge/plantuml/svg/SvgGraphics.java index b502e5814..cffd6c30b 100644 --- a/src/net/sourceforge/plantuml/svg/SvgGraphics.java +++ b/src/net/sourceforge/plantuml/svg/SvgGraphics.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5361 $ + * Revision $Revision: 6461 $ * */ package net.sourceforge.plantuml.svg; @@ -53,8 +53,8 @@ import javax.xml.transform.stream.StreamResult; import net.sourceforge.plantuml.eps.EpsGraphics; import net.sourceforge.plantuml.ugraphic.UPath; -import net.sourceforge.plantuml.ugraphic.USegmentType; import net.sourceforge.plantuml.ugraphic.USegment; +import net.sourceforge.plantuml.ugraphic.USegmentType; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -68,14 +68,27 @@ public class SvgGraphics { // http://en.wikipedia.org/wiki/Scalable_Vector_Graphics final private Document document; - final private Element svg; + final private Element root; final private Element defs; - final private Element g; + final private Element gRoot; private String fill = "black"; private String stroke = "black"; private String strokeWidth = "1"; private String strokeDasharray = null; + private final String backcolor; + + private int maxX = 10; + private int maxY = 10; + + final protected void ensureVisible(double x, double y) { + if (x > maxX) { + maxX = (int) (x + 1); + } + if (y > maxY) { + maxY = (int) (y + 1); + } + } public SvgGraphics() { this(null); @@ -83,14 +96,15 @@ public class SvgGraphics { public SvgGraphics(String backcolor) { try { - document = getDocument(); + this.document = getDocument(); + this.backcolor = backcolor; - svg = getRootNode("100%", "100%", "absolute", "0", "0", backcolor); + this.root = getRootNode(); // Create a node named defs, which will be the parent // for a pair of linear gradient definitions. defs = simpleElement("defs"); - g = simpleElement("g"); + gRoot = simpleElement("g"); } catch (ParserConfigurationException e) { e.printStackTrace(); throw new IllegalStateException(e); @@ -102,7 +116,7 @@ public class SvgGraphics { // element node that has no attributes. private Element simpleElement(String type) { final Element theElement = (Element) document.createElement(type); - svg.appendChild(theElement); + root.appendChild(theElement); return theElement; } @@ -117,7 +131,7 @@ public class SvgGraphics { // This method returns a reference to a root node that // has already been appended to the document. - private Element getRootNode(String width, String height, String position, String top, String left, String backcolor) { + private Element getRootNode() { // Create the root node named svg and append it to // the document. final Element svg = (Element) document.createElement("svg"); @@ -130,17 +144,10 @@ public class SvgGraphics { // particularly with regard to the style. svg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); svg.setAttribute("version", "1.1"); - String style = "width:" + width + ";height:" + height + ";position:" + position + ";top:" + top + ";left:" - + left + ";"; - if (backcolor != null) { - style += "background:" + backcolor + ";"; - } - svg.setAttribute("style", style); return svg; } - public void svgEllipse(double x, double y, double xRadius, double yRadius) { final Element elt = (Element) document.createElement("ellipse"); elt.setAttribute("cx", "" + x); @@ -150,6 +157,7 @@ public class SvgGraphics { elt.setAttribute("fill", fill); elt.setAttribute("style", getStyle()); getG().appendChild(elt); + ensureVisible(x + xRadius, y + yRadius); } private Map, String> gradients = new HashMap, String>(); @@ -194,7 +202,41 @@ public class SvgGraphics { this.strokeDasharray = strokeDasharray; } + private boolean xlinkXmlns = false; + + public void closeLink() { + gRoot.appendChild(pendingLink); + pendingLink = null; + } + + private Element pendingLink; + + public void openLink(String url, String title) { + if (url == null) { + throw new IllegalArgumentException(); + } + if (xlinkXmlns == false) { + root.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); + xlinkXmlns = true; + } + pendingLink = (Element) document.createElement("a"); + pendingLink.setAttribute("xlink:href", url); + if (title == null) { + pendingLink.setAttribute("xlink:title", url); + } else { + pendingLink.setAttribute("xlink:title", title); + } + } + + public final Element getG() { + if (pendingLink == null) { + return gRoot; + } + return pendingLink; + } + public void svgRectangle(double x, double y, double width, double height, double rx, double ry) { + final Element elt = (Element) document.createElement("rect"); elt.setAttribute("x", "" + x); elt.setAttribute("y", "" + y); @@ -206,7 +248,10 @@ public class SvgGraphics { elt.setAttribute("rx", "" + rx); elt.setAttribute("ry", "" + ry); } + getG().appendChild(elt); + + ensureVisible(x + width, y + height); } public void svgLine(double x1, double y1, double x2, double y2) { @@ -217,6 +262,8 @@ public class SvgGraphics { elt.setAttribute("y2", "" + y2); elt.setAttribute("style", getStyle()); getG().appendChild(elt); + ensureVisible(x1, y1); + ensureVisible(x2, y2); } private String getStyle() { @@ -240,6 +287,11 @@ public class SvgGraphics { elt.setAttribute("fill", fill); elt.setAttribute("style", getStyle()); getG().appendChild(elt); + + for (int i = 0; i < points.length; i += 2) { + ensureVisible(points[i], points[i + 1]); + } + } public void text(String text, double x, double y, String fontFamily, int fontSize, String fontWeight, @@ -263,16 +315,14 @@ public class SvgGraphics { } elt.setTextContent(text); getG().appendChild(elt); + ensureVisible(x, y); + ensureVisible(x + textLength, y); } public final Element getDefs() { return defs; } - public final Element getG() { - return g; - } - private Transformer getTransformer() throws TransformerException { // Get a TransformerFactory object. final TransformerFactory xformFactory = TransformerFactory.newInstance(); @@ -283,11 +333,11 @@ public class SvgGraphics { // // Sets the standalone property in the first line of // // the output file. transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); - // + // // Properties proprietes = new Properties(); // proprietes.put("standalone", "yes"); // transformer.setOutputProperties(proprietes); - // + // // transformer.setParameter(OutputKeys.STANDALONE, "yes"); return transformer; @@ -299,6 +349,12 @@ public class SvgGraphics { // Document object final DOMSource source = new DOMSource(document); + String style = "width:" + maxX + ";height:" + maxY + ";"; + if (backcolor != null) { + style += "background:" + backcolor + ";"; + } + root.setAttribute("style", style); + // Get a StreamResult object that points to the // screen. Then transform the DOM sending XML to // the screen. @@ -309,7 +365,7 @@ public class SvgGraphics { public String getGElement() throws TransformerException, IOException { final ByteArrayOutputStream os = new ByteArrayOutputStream(); final StreamResult sr = new StreamResult(os); - getTransformer().transform(new DOMSource(g), sr); + getTransformer().transform(new DOMSource(gRoot), sr); os.close(); final String s = new String(os.toByteArray(), "UTF-8"); return s.replaceFirst("^\\<\\?xml.*?\\?\\>", ""); @@ -338,6 +394,7 @@ public class SvgGraphics { elt.setAttribute("d", sb.toString()); elt.setAttribute("style", getStyle()); getG().appendChild(elt); + ensureVisible(x, y); } private StringBuilder currentPath = null; @@ -349,10 +406,12 @@ public class SvgGraphics { public void moveto(double x, double y) { currentPath.append("M" + format(x) + "," + format(y) + " "); + ensureVisible(x, y); } public void lineto(double x, double y) { currentPath.append("L" + format(x) + "," + format(y) + " "); + ensureVisible(x, y); } public void closepath() { @@ -361,14 +420,20 @@ public class SvgGraphics { } public void curveto(double x1, double y1, double x2, double y2, double x3, double y3) { - currentPath.append("C" + format(x1) + "," + format(y1) + " " + format(x2) + "," + format(y2) + " " + format(x3) + "," + format(y3) + " "); + currentPath.append("C" + format(x1) + "," + format(y1) + " " + format(x2) + "," + format(y2) + " " + format(x3) + + "," + format(y3) + " "); + ensureVisible(x1, y1); + ensureVisible(x2, y2); + ensureVisible(x3, y3); } public void quadto(double x1, double y1, double x2, double y2) { currentPath.append("Q" + format(x1) + "," + format(y1) + " " + format(x2) + "," + format(y2) + " "); + ensureVisible(x1, y1); + ensureVisible(x2, y2); } - + private static String format(double x) { return EpsGraphics.format(x); } @@ -376,7 +441,7 @@ public class SvgGraphics { public void fill(int windingRule) { final Element elt = (Element) document.createElement("path"); elt.setAttribute("d", currentPath.toString()); - //elt elt.setAttribute("style", getStyle()); + // elt elt.setAttribute("style", getStyle()); getG().appendChild(elt); currentPath = null; diff --git a/src/net/sourceforge/plantuml/swing/MainWindow.java b/src/net/sourceforge/plantuml/swing/MainWindow.java index 941fb6968..a707b38b3 100644 --- a/src/net/sourceforge/plantuml/swing/MainWindow.java +++ b/src/net/sourceforge/plantuml/swing/MainWindow.java @@ -28,14 +28,13 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6075 $ + * Revision $Revision: 6453 $ * */ package net.sourceforge.plantuml.swing; import java.awt.BorderLayout; import java.awt.Frame; -import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; diff --git a/src/net/sourceforge/plantuml/ugraphic/UGraphic.java b/src/net/sourceforge/plantuml/ugraphic/UGraphic.java index 25a0c5289..24dbcc51e 100644 --- a/src/net/sourceforge/plantuml/ugraphic/UGraphic.java +++ b/src/net/sourceforge/plantuml/ugraphic/UGraphic.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5988 $ + * Revision $Revision: 6398 $ * */ package net.sourceforge.plantuml.ugraphic; @@ -59,4 +59,6 @@ public interface UGraphic { public void setAntiAliasing(boolean trueForOn); + public void setUrl(String url, String tooltip); + } diff --git a/src/net/sourceforge/plantuml/ugraphic/UGraphicUtils.java b/src/net/sourceforge/plantuml/ugraphic/UGraphicUtils.java index ca62cfd7b..e12451b36 100644 --- a/src/net/sourceforge/plantuml/ugraphic/UGraphicUtils.java +++ b/src/net/sourceforge/plantuml/ugraphic/UGraphicUtils.java @@ -81,6 +81,10 @@ public abstract class UGraphicUtils { public void setAntiAliasing(boolean trueForOn) { g.setAntiAliasing(trueForOn); } + + public void setUrl(String url, String tooltip) { + g.setUrl(url, tooltip); + } }; } diff --git a/src/net/sourceforge/plantuml/ugraphic/UParam.java b/src/net/sourceforge/plantuml/ugraphic/UParam.java index 3f92c3983..e2b247615 100644 --- a/src/net/sourceforge/plantuml/ugraphic/UParam.java +++ b/src/net/sourceforge/plantuml/ugraphic/UParam.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 4918 $ + * Revision $Revision: 6398 $ * */ package net.sourceforge.plantuml.ugraphic; diff --git a/src/net/sourceforge/plantuml/ugraphic/URectangle.java b/src/net/sourceforge/plantuml/ugraphic/URectangle.java index 3ba19549b..08f439690 100644 --- a/src/net/sourceforge/plantuml/ugraphic/URectangle.java +++ b/src/net/sourceforge/plantuml/ugraphic/URectangle.java @@ -43,9 +43,9 @@ public class URectangle implements UShape { } public URectangle(double width, double height, double rx, double ry) { -// if (height == 0) { -// throw new IllegalArgumentException(); -// } + // if (height == 0) { + // throw new IllegalArgumentException(); + // } if (width == 0) { throw new IllegalArgumentException(); } diff --git a/src/net/sourceforge/plantuml/ugraphic/UStroke.java b/src/net/sourceforge/plantuml/ugraphic/UStroke.java index cdc1791ed..9350adf33 100644 --- a/src/net/sourceforge/plantuml/ugraphic/UStroke.java +++ b/src/net/sourceforge/plantuml/ugraphic/UStroke.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5939 $ + * Revision $Revision: 6470 $ * */ package net.sourceforge.plantuml.ugraphic; @@ -72,11 +72,11 @@ public class UStroke { return "" + dashVisible + "," + dashSpace; } - public String getDasharrayEps() { - if (dashVisible == 0) { - return null; - } - return "" + dashVisible + " " + dashSpace; - } +// public String getDasharrayEps() { +// if (dashVisible == 0) { +// return null; +// } +// return "" + dashVisible + " " + dashSpace; +// } } diff --git a/src/net/sourceforge/plantuml/ugraphic/eps/DriverEllipseEps.java b/src/net/sourceforge/plantuml/ugraphic/eps/DriverEllipseEps.java index 53fa2ee9d..6beb1c77e 100644 --- a/src/net/sourceforge/plantuml/ugraphic/eps/DriverEllipseEps.java +++ b/src/net/sourceforge/plantuml/ugraphic/eps/DriverEllipseEps.java @@ -46,7 +46,8 @@ public class DriverEllipseEps implements UDriver { eps.setFillColor(param.getBackcolor()); eps.setStrokeColor(param.getColor()); - eps.setStrokeWidth(""+param.getStroke().getThickness(), param.getStroke().getDasharraySvg()); + eps.setStrokeWidth("" + param.getStroke().getThickness(), param.getStroke().getDashVisible(), param.getStroke() + .getDashSpace()); eps.epsEllipse(x + width / 2, y + height / 2, width / 2, height / 2); } diff --git a/src/net/sourceforge/plantuml/ugraphic/eps/DriverLineEps.java b/src/net/sourceforge/plantuml/ugraphic/eps/DriverLineEps.java index 3dd70e78e..2d7bb2c44 100644 --- a/src/net/sourceforge/plantuml/ugraphic/eps/DriverLineEps.java +++ b/src/net/sourceforge/plantuml/ugraphic/eps/DriverLineEps.java @@ -54,7 +54,7 @@ public class DriverLineEps implements UDriver { double x2 = x + shape.getDX(); double y2 = y + shape.getDY(); - + final UClip clip = clipContainer.getClip(); if (clip != null) { final Line2D.Double line = clip.getClippedLine(new Line2D.Double(x, y, x2, y2)); @@ -68,7 +68,8 @@ public class DriverLineEps implements UDriver { } eps.setStrokeColor(param.getColor()); - eps.setStrokeWidth("" + param.getStroke().getThickness(), param.getStroke().getDasharrayEps()); + eps.setStrokeWidth("" + param.getStroke().getThickness(), param.getStroke().getDashVisible(), param.getStroke() + .getDashSpace()); eps.epsLine(x, y, x2, y2); } } diff --git a/src/net/sourceforge/plantuml/ugraphic/eps/DriverRectangleEps.java b/src/net/sourceforge/plantuml/ugraphic/eps/DriverRectangleEps.java index 923bfa856..2148be9a2 100644 --- a/src/net/sourceforge/plantuml/ugraphic/eps/DriverRectangleEps.java +++ b/src/net/sourceforge/plantuml/ugraphic/eps/DriverRectangleEps.java @@ -55,7 +55,7 @@ public class DriverRectangleEps implements UDriver { double width = rect.getWidth(); double height = rect.getHeight(); - + final UClip clip = clipContainer.getClip(); if (clip != null) { final Rectangle2D.Double r = clip.getClippedRectangle(new Rectangle2D.Double(x, y, width, height)); @@ -72,7 +72,8 @@ public class DriverRectangleEps implements UDriver { if (gr == null) { eps.setStrokeColor(param.getColor()); eps.setFillColor(param.getBackcolor()); - eps.setStrokeWidth("" + param.getStroke().getThickness(), param.getStroke().getDasharrayEps()); + eps.setStrokeWidth("" + param.getStroke().getThickness(), param.getStroke().getDashVisible(), param + .getStroke().getDashSpace()); eps.epsRectangle(x, y, width, height, rx / 2, ry / 2); } else { eps.epsRectangle(x, y, width, height, rx / 2, ry / 2, gr); diff --git a/src/net/sourceforge/plantuml/ugraphic/eps/DriverTextEps.java b/src/net/sourceforge/plantuml/ugraphic/eps/DriverTextEps.java index c00c99983..b27ccff1d 100644 --- a/src/net/sourceforge/plantuml/ugraphic/eps/DriverTextEps.java +++ b/src/net/sourceforge/plantuml/ugraphic/eps/DriverTextEps.java @@ -88,9 +88,9 @@ public class DriverTextEps implements UDriver { eps.setStrokeColor(extended); } final Dimension2D dim = DriverTextG2d.calculateDimension(stringBounder, font, shape.getText()); - eps.setStrokeWidth("1.1", null); + eps.setStrokeWidth("1.1", 0, 0); eps.epsLine(x, y + 1.5, x + dim.getWidth(), y + 1.5); - eps.setStrokeWidth("1", null); + eps.setStrokeWidth("1", 0, 0); } if (fontConfiguration.containsStyle(FontStyle.WAVE)) { final Dimension2D dim = DriverTextG2d.calculateDimension(stringBounder, font, shape.getText()); @@ -99,12 +99,12 @@ public class DriverTextEps implements UDriver { if (extended != null) { eps.setStrokeColor(extended); } - eps.setStrokeWidth("1.1", null); + eps.setStrokeWidth("1.1", 0, 0); for (int i = (int) x; i < x + dim.getWidth() - 5; i += 6) { eps.epsLine(i, ypos - 0, i + 3, ypos + 1); eps.epsLine(i + 3, ypos + 1, i + 6, ypos - 0); } - eps.setStrokeWidth("1", null); + eps.setStrokeWidth("1", 0, 0); } if (fontConfiguration.containsStyle(FontStyle.STRIKE)) { final Color extended = fontConfiguration.getExtendedColor(); @@ -114,9 +114,9 @@ public class DriverTextEps implements UDriver { final Dimension2D dim = DriverTextG2d.calculateDimension(stringBounder, font, shape.getText()); final FontMetrics fm = g2dummy.getFontMetrics(font); final int ypos = (int) (y - fm.getDescent() - 0.5); - eps.setStrokeWidth("1.3", null); + eps.setStrokeWidth("1.3", 0, 0); eps.epsLine(x, ypos, x + dim.getWidth(), ypos); - eps.setStrokeWidth("1", null); + eps.setStrokeWidth("1", 0, 0); } } diff --git a/src/net/sourceforge/plantuml/ugraphic/eps/UGraphicEps.java b/src/net/sourceforge/plantuml/ugraphic/eps/UGraphicEps.java index e33579d31..b038f8eb3 100644 --- a/src/net/sourceforge/plantuml/ugraphic/eps/UGraphicEps.java +++ b/src/net/sourceforge/plantuml/ugraphic/eps/UGraphicEps.java @@ -134,4 +134,7 @@ public class UGraphicEps extends AbstractUGraphic implements ClipCo public void setAntiAliasing(boolean trueForOn) { } + public void setUrl(String url, String tooltip) { + } + } diff --git a/src/net/sourceforge/plantuml/ugraphic/g2d/DriverRectangleG2d.java b/src/net/sourceforge/plantuml/ugraphic/g2d/DriverRectangleG2d.java index 6bad0da04..e678145e1 100644 --- a/src/net/sourceforge/plantuml/ugraphic/g2d/DriverRectangleG2d.java +++ b/src/net/sourceforge/plantuml/ugraphic/g2d/DriverRectangleG2d.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 3914 $ + * Revision $Revision: 6475 $ * */ package net.sourceforge.plantuml.ugraphic.g2d; @@ -63,6 +63,7 @@ public class DriverRectangleG2d implements UDriver { if (gr == null) { if (param.getBackcolor() != null) { g2d.setColor(param.getBackcolor()); + DriverLineG2d.manageStroke(param, g2d); g2d.fill(rect); } if (param.getColor() != null) { diff --git a/src/net/sourceforge/plantuml/ugraphic/g2d/UGraphicG2d.java b/src/net/sourceforge/plantuml/ugraphic/g2d/UGraphicG2d.java index c1674f722..e99cba34d 100644 --- a/src/net/sourceforge/plantuml/ugraphic/g2d/UGraphicG2d.java +++ b/src/net/sourceforge/plantuml/ugraphic/g2d/UGraphicG2d.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 5988 $ + * Revision $Revision: 6398 $ * */ package net.sourceforge.plantuml.ugraphic.g2d; @@ -132,4 +132,7 @@ public class UGraphicG2d extends AbstractUGraphic { } + public void setUrl(String url, String tooltip) { + } + } diff --git a/src/net/sourceforge/plantuml/ugraphic/svg/UGraphicSvg.java b/src/net/sourceforge/plantuml/ugraphic/svg/UGraphicSvg.java index 158699aed..35c235a4c 100644 --- a/src/net/sourceforge/plantuml/ugraphic/svg/UGraphicSvg.java +++ b/src/net/sourceforge/plantuml/ugraphic/svg/UGraphicSvg.java @@ -130,9 +130,16 @@ public class UGraphicSvg extends AbstractUGraphic implements ClipCo DriverTextAsPathSvg.drawPathIterator(getGraphicObject(), xpos + getTranslateX(), ypos + getTranslateY(), t .getOutline(null).getPathIterator(null)); } - + public void setAntiAliasing(boolean trueForOn) { } + public void setUrl(String url, String tooltip) { + if (url == null) { + getGraphicObject().closeLink(); + } else { + getGraphicObject().openLink(url, tooltip); + } + } } diff --git a/src/net/sourceforge/plantuml/ugraphic/txt/UGraphicTxt.java b/src/net/sourceforge/plantuml/ugraphic/txt/UGraphicTxt.java index 40b5e3284..aeddf6d87 100644 --- a/src/net/sourceforge/plantuml/ugraphic/txt/UGraphicTxt.java +++ b/src/net/sourceforge/plantuml/ugraphic/txt/UGraphicTxt.java @@ -33,10 +33,10 @@ package net.sourceforge.plantuml.ugraphic.txt; import java.awt.Font; -import net.sourceforge.plantuml.asciiart.UmlCharAreaImpl; -import net.sourceforge.plantuml.asciiart.UmlCharArea; import net.sourceforge.plantuml.asciiart.TextStringBounder; import net.sourceforge.plantuml.asciiart.TranslatedCharArea; +import net.sourceforge.plantuml.asciiart.UmlCharArea; +import net.sourceforge.plantuml.asciiart.UmlCharAreaImpl; import net.sourceforge.plantuml.graphic.FontStyle; import net.sourceforge.plantuml.graphic.StringBounder; import net.sourceforge.plantuml.ugraphic.AbstractCommonUGraphic; @@ -82,4 +82,7 @@ public class UGraphicTxt extends AbstractCommonUGraphic { public void setAntiAliasing(boolean trueForOn) { } + public void setUrl(String url, String tooltip) { + } + } diff --git a/src/net/sourceforge/plantuml/usecasediagram/UsecaseDiagramFactory.java b/src/net/sourceforge/plantuml/usecasediagram/UsecaseDiagramFactory.java index d916c02f7..e3281949e 100644 --- a/src/net/sourceforge/plantuml/usecasediagram/UsecaseDiagramFactory.java +++ b/src/net/sourceforge/plantuml/usecasediagram/UsecaseDiagramFactory.java @@ -31,6 +31,7 @@ */ package net.sourceforge.plantuml.usecasediagram; +import net.sourceforge.plantuml.classdiagram.command.CommandUrl; import net.sourceforge.plantuml.command.AbstractUmlSystemCommandFactory; import net.sourceforge.plantuml.command.CommandCreateNote; import net.sourceforge.plantuml.command.CommandEndPackage; @@ -68,6 +69,7 @@ public class UsecaseDiagramFactory extends AbstractUmlSystemCommandFactory { addCommand(new CommandPackage(system)); addCommand(new CommandEndPackage(system)); addCommand(new CommandNoteEntity(system)); + addCommand(new CommandUrl(system)); addCommand(new CommandCreateNote(system)); addCommand(new CommandCreateActor(system)); diff --git a/src/net/sourceforge/plantuml/version/PSystemVersion.java b/src/net/sourceforge/plantuml/version/PSystemVersion.java index 5a391cbe6..05b4be44b 100644 --- a/src/net/sourceforge/plantuml/version/PSystemVersion.java +++ b/src/net/sourceforge/plantuml/version/PSystemVersion.java @@ -34,13 +34,10 @@ package net.sourceforge.plantuml.version; import java.awt.Color; import java.awt.Font; import java.awt.image.BufferedImage; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Properties; @@ -69,21 +66,21 @@ public class PSystemVersion extends AbstractPSystem { } } - public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, - InterruptedException { - OutputStream os = null; - try { - os = new FileOutputStream(suggestedFile); - getGraphicStrings().writeImage(os, fileFormat); - } finally { - if (os != null) { - os.close(); - } - } - return Arrays.asList(suggestedFile); - } +// public List createFiles(File suggestedFile, FileFormatOption fileFormat) throws IOException, +// InterruptedException { +// OutputStream os = null; +// try { +// os = new FileOutputStream(suggestedFile); +// getGraphicStrings().writeImage(os, fileFormat); +// } finally { +// if (os != null) { +// os.close(); +// } +// } +// return Arrays.asList(suggestedFile); +// } - public void createFile(OutputStream os, int index, FileFormatOption fileFormat) throws IOException { + public void exportDiagram(OutputStream os, StringBuilder cmap, int index, FileFormatOption fileFormat) throws IOException { getGraphicStrings().writeImage(os, fileFormat); } diff --git a/src/net/sourceforge/plantuml/version/PSystemVersionFactory.java b/src/net/sourceforge/plantuml/version/PSystemVersionFactory.java index 9fac644b6..b92d3d86c 100644 --- a/src/net/sourceforge/plantuml/version/PSystemVersionFactory.java +++ b/src/net/sourceforge/plantuml/version/PSystemVersionFactory.java @@ -33,6 +33,7 @@ package net.sourceforge.plantuml.version; import java.io.IOException; +import net.sourceforge.plantuml.DiagramType; import net.sourceforge.plantuml.Log; import net.sourceforge.plantuml.PSystemBasicFactory; @@ -71,5 +72,10 @@ public class PSystemVersionFactory implements PSystemBasicFactory { public PSystemVersion getSystem() { return system; } + + public DiagramType getDiagramType() { + return DiagramType.UML; + } + } diff --git a/src/net/sourceforge/plantuml/version/Version.java b/src/net/sourceforge/plantuml/version/Version.java index 15e7779c8..d930ce73a 100644 --- a/src/net/sourceforge/plantuml/version/Version.java +++ b/src/net/sourceforge/plantuml/version/Version.java @@ -28,7 +28,7 @@ * * Original Author: Arnaud Roques * - * Revision $Revision: 6243 $ + * Revision $Revision: 6488 $ * */ package net.sourceforge.plantuml.version; @@ -36,11 +36,11 @@ package net.sourceforge.plantuml.version; public class Version { public static int version() { - return 6242; + return 6487; } public static long compileTime() { - return 1301142337781L; + return 1303229639640L; } } diff --git a/src/net/sourceforge/plantuml/xmi/CucaDiagramXmiMaker.java b/src/net/sourceforge/plantuml/xmi/CucaDiagramXmiMaker.java index 5c8db80a0..70c547b8c 100644 --- a/src/net/sourceforge/plantuml/xmi/CucaDiagramXmiMaker.java +++ b/src/net/sourceforge/plantuml/xmi/CucaDiagramXmiMaker.java @@ -33,11 +33,8 @@ */ package net.sourceforge.plantuml.xmi; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.util.Collections; -import java.util.List; +import java.io.OutputStream; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; @@ -57,13 +54,12 @@ public final class CucaDiagramXmiMaker { this.fileFormat = fileFormat; } - public List createFiles(File suggestedFile) throws IOException { + public void createFiles(OutputStream fos) throws IOException { try { final XmiClassDiagram xmi = new XmiClassDiagram((ClassDiagram) diagram, fileFormat); - final FileOutputStream fos = new FileOutputStream(suggestedFile); xmi.transformerXml(fos); - fos.close(); - return Collections.singletonList(suggestedFile); + // fos.close(); + // return Collections.singletonList(suggestedFile); } catch (ParserConfigurationException e) { Log.error(e.toString()); e.printStackTrace(); diff --git a/src/org/stathissideris/ascii2image/core/ConfigurationParser.java b/src/org/stathissideris/ascii2image/core/ConfigurationParser.java new file mode 100644 index 000000000..ee2788135 --- /dev/null +++ b/src/org/stathissideris/ascii2image/core/ConfigurationParser.java @@ -0,0 +1,190 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +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; + +public class ConfigurationParser { + + 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 String currentDir = ""; + private File configFile; + + private HashMap shapeDefinitions = + new HashMap(); + + public Collection getShapeDefinitions() { + return shapeDefinitions.values(); + } + + public HashMap getShapeDefinitionsHash() { + return shapeDefinitions; + } + + public void parseFile(File file) + throws ParserConfigurationException, SAXException, IOException + { + configFile = file; + + DefaultHandler handler = new XMLHandler(); + + // Use the default (non-validating) parser + SAXParserFactory factory = SAXParserFactory.newInstance(); + + 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(); + } + } + 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); + } + } + } + } + + private String createFilename(String baseDir, String filename){ + if(baseDir == null || baseDir.trim().equals("")){ + return filename; + } + 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/org/stathissideris/ascii2image/core/ConversionOptions.java b/src/org/stathissideris/ascii2image/core/ConversionOptions.java new file mode 100644 index 000000000..0a59ca641 --- /dev/null +++ b/src/org/stathissideris/ascii2image/core/ConversionOptions.java @@ -0,0 +1,149 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.core; + + +/** + * + * @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(){} + +// 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")); +// +// +// +// 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); +// } +// +// 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){ +// exportFormat = exportFormat.toLowerCase(); +// if(exportFormat == "jpeg" || exportFormat == "jpg"){ +// processingOptions.setExportFormat(ProcessingOptions.FORMAT_JPEG); +// } else if(exportFormat == "png"){ +// processingOptions.setExportFormat(ProcessingOptions.FORMAT_PNG); +// } else if(exportFormat == "gif"){ +// processingOptions.setExportFormat(ProcessingOptions.FORMAT_GIF); +// } +//} +// +//String colorCodeMode = (String) cmdLine.getOptionValue("color-codes"); +//if(colorCodeMode != null){ +// if(colorCodeMode.equals("use")) +// processingOptions.setColorCodesProcessingMode(ProcessingOptions.USE_COLOR_CODES); +// else if(colorCodeMode.equals("ignore")) +// processingOptions.setColorCodesProcessingMode(ProcessingOptions.IGNORE_COLOR_CODES); +// else if(colorCodeMode.equals("render")) +// processingOptions.setColorCodesProcessingMode(ProcessingOptions.RENDER_COLOR_CODES); +//} +// +//String tagsMode = (String) cmdLine.getOptionValue("tags"); +//if(tagsMode != null){ +// if(tagsMode.equals("use")) +// processingOptions.setTagProcessingMode(ProcessingOptions.USE_TAGS); +// else if(tagsMode.equals("ignore")) +// processingOptions.setTagProcessingMode(ProcessingOptions.IGNORE_TAGS); +// else if(tagsMode.equals("render")) +// processingOptions.setTagProcessingMode(ProcessingOptions.RENDER_TAGS); +//} +// +// +//String markupMode = (String) cmdLine.getOptionValue("markup"); +//if(markupMode != null){ +// if(markupMode.equals("use")){ +// processingOptions.setColorCodesProcessingMode(ProcessingOptions.USE_COLOR_CODES); +// processingOptions.setTagProcessingMode(ProcessingOptions.USE_TAGS); +// } else if(markupMode.equals("ignore")){ +// processingOptions.setColorCodesProcessingMode(ProcessingOptions.IGNORE_COLOR_CODES); +// processingOptions.setTagProcessingMode(ProcessingOptions.IGNORE_TAGS); +// } else if(markupMode.equals("render")){ +// processingOptions.setColorCodesProcessingMode(ProcessingOptions.RENDER_COLOR_CODES); +// processingOptions.setTagProcessingMode(ProcessingOptions.RENDER_TAGS); +// } +//} \ No newline at end of file diff --git a/src/org/stathissideris/ascii2image/core/DebugUtils.java b/src/org/stathissideris/ascii2image/core/DebugUtils.java new file mode 100644 index 000000000..f0e437640 --- /dev/null +++ b/src/org/stathissideris/ascii2image/core/DebugUtils.java @@ -0,0 +1,7 @@ +package org.stathissideris.ascii2image.core; + +public class DebugUtils { + public static int getLineNumber() { + return Thread.currentThread().getStackTrace()[2].getLineNumber(); + } +} diff --git a/src/org/stathissideris/ascii2image/core/DocBookConverter.java b/src/org/stathissideris/ascii2image/core/DocBookConverter.java new file mode 100644 index 000000000..dfbf79613 --- /dev/null +++ b/src/org/stathissideris/ascii2image/core/DocBookConverter.java @@ -0,0 +1,70 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.core; + +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; + + 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 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( ); + } +} diff --git a/src/org/stathissideris/ascii2image/core/FileUtils.java b/src/org/stathissideris/ascii2image/core/FileUtils.java new file mode 100644 index 000000000..976345347 --- /dev/null +++ b/src/org/stathissideris/ascii2image/core/FileUtils.java @@ -0,0 +1,128 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.core; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @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 { + InputStream is = new FileInputStream(file); + 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 + } + + // 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 "+file.getName()); + } + + // 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/org/stathissideris/ascii2image/core/Pair.java b/src/org/stathissideris/ascii2image/core/Pair.java new file mode 100644 index 000000000..3d5816381 --- /dev/null +++ b/src/org/stathissideris/ascii2image/core/Pair.java @@ -0,0 +1,11 @@ +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; + } +} diff --git a/src/org/stathissideris/ascii2image/core/ProcessingOptions.java b/src/org/stathissideris/ascii2image/core/ProcessingOptions.java new file mode 100644 index 000000000..c0f421940 --- /dev/null +++ b/src/org/stathissideris/ascii2image/core/ProcessingOptions.java @@ -0,0 +1,244 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.core; + +import java.util.HashMap; + +import org.stathissideris.ascii2image.graphics.CustomShapeDefinition; + +/** + * @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); + } + + + +} diff --git a/src/org/stathissideris/ascii2image/core/RenderingOptions.java b/src/org/stathissideris/ascii2image/core/RenderingOptions.java new file mode 100644 index 000000000..851514334 --- /dev/null +++ b/src/org/stathissideris/ascii2image/core/RenderingOptions.java @@ -0,0 +1,116 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.core; + +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 int cellWidth = 10; + private int cellHeight = 14; + + private float scale = 1; + + /** + * @return + */ + public int getCellHeight() { + return cellHeight; + } + + /** + * @return + */ + public int getCellWidth() { + return cellWidth; + } + + /** + * @return + */ + public boolean dropShadows() { + return dropShadows; + } + + /** + * @return + */ + public boolean renderDebugLines() { + return renderDebugLines; + } + + /** + * @return + */ + public float getScale() { + return scale; + } + + /** + * @param b + */ + public void setDropShadows(boolean b) { + dropShadows = b; + } + + /** + * @param b + */ + public void setRenderDebugLines(boolean b) { + renderDebugLines = b; + } + + /** + * @param f + */ + public void setScale(float f) { + scale = f; + cellWidth *= scale; + cellHeight *= scale; + } + + /** + * @return + */ + public boolean performAntialias() { + return antialias; + } + + /** + * @param b + */ + public void setAntialias(boolean b) { + antialias = b; + } + +} diff --git a/src/org/stathissideris/ascii2image/core/Shape3DOrderingComparator.java b/src/org/stathissideris/ascii2image/core/Shape3DOrderingComparator.java new file mode 100644 index 000000000..937ea0707 --- /dev/null +++ b/src/org/stathissideris/ascii2image/core/Shape3DOrderingComparator.java @@ -0,0 +1,54 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.core; + +import java.util.Comparator; + +import org.stathissideris.ascii2image.graphics.DiagramShape; + +/** + * + * @author Efstathios Sideris + */ +public class Shape3DOrderingComparator implements Comparator { + + /** + * Puts diagram shapes in pseudo-3d order starting from back to front + * + */ + public int compare(Object object1, Object object2) { + if(!(object1 instanceof DiagramShape) + || !(object2 instanceof DiagramShape)) + throw new RuntimeException("This comparator can only compare DiagramShapeS"); + + DiagramShape shape1 = (DiagramShape) object1; + DiagramShape shape2 = (DiagramShape) object2; + + 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/org/stathissideris/ascii2image/graphics/BitmapRenderer.java b/src/org/stathissideris/ascii2image/graphics/BitmapRenderer.java new file mode 100644 index 000000000..5fee87a03 --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/BitmapRenderer.java @@ -0,0 +1,486 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.graphics; + +import java.awt.BasicStroke; +import java.awt.Canvas; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.image.BufferedImage; +import java.awt.image.ConvolveOp; +import java.awt.image.Kernel; +import java.awt.image.RenderedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +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.text.TextGrid; + +/** + * + * @author Efstathios Sideris + */ +public class BitmapRenderer { + + private static final boolean DEBUG = 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 = "dak_orgstruktur_vs_be.ditaa.OutOfMemoryError.edit.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 = 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(Color.white); + //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 = (DiagramShape) shapesIt.next(); + + if(shape.getPoints().isEmpty()) continue; + + //GeneralPath path = shape.makeIntoPath(); + GeneralPath path; + path = shape.makeIntoRenderPath(diagram); + + 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); + + 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); + } + + + //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); + + //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); + + 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 = (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()); + } + + if(options.renderDebugLines() || DEBUG){ + 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 distintion 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); + throw new UnsupportedOperationException(); + } + } + +// 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/org/stathissideris/ascii2image/graphics/CompositeDiagramShape.java b/src/org/stathissideris/ascii2image/graphics/CompositeDiagramShape.java new file mode 100644 index 000000000..108f01e4a --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/CompositeDiagramShape.java @@ -0,0 +1,313 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.graphics; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.stathissideris.ascii2image.core.DebugUtils; +import org.stathissideris.ascii2image.text.CellSet; +import org.stathissideris.ascii2image.text.TextGrid; + +/** + * + * @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(); + } else if(nextCells.size() > 1) {//3- or 4- way intersection + finished = true; + if(DEBUG) System.out.println("finished shape"); + 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/org/stathissideris/ascii2image/graphics/CustomShapeDefinition.java b/src/org/stathissideris/ascii2image/graphics/CustomShapeDefinition.java new file mode 100644 index 000000000..c3bce8ab5 --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/CustomShapeDefinition.java @@ -0,0 +1,80 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +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" + ; + } + +} diff --git a/src/org/stathissideris/ascii2image/graphics/Diagram.java b/src/org/stathissideris/ascii2image/graphics/Diagram.java new file mode 100644 index 000000000..5db33abaf --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/Diagram.java @@ -0,0 +1,972 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.graphics; + +import java.awt.Color; +import java.awt.Font; +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; +import org.stathissideris.ascii2image.text.CellSet; +import org.stathissideris.ascii2image.text.TextGrid; + +/** + * + * @author Efstathios Sideris + */ +public class Diagram { + + private static final boolean DEBUG = false; + private static final boolean VERBOSE_DEBUG = 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. Seperate 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 seperate 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. Assing 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 = (CellSet) 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(); + Iterator boundarySetIt = boundarySetsStep1.iterator(); + while (boundarySetIt.hasNext()) { + CellSet set = (CellSet) boundarySetIt.next(); + + //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 = (CellSet) 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 = (CellSet) 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 + ArrayList closedShapes = new ArrayList(); + sets = closed.iterator(); + while(sets.hasNext()){ + CellSet set = (CellSet) sets.next(); + 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 + 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 + //TODO: each color tag should be assigned to the smallest containing shape (like shape tags) + + 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)); + Iterator shapes = getShapes().iterator(); + while(shapes.hasNext()){ + DiagramShape shape = (DiagramShape) shapes.next(); + if(shape.contains(point)) shape.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)); + + //find the smallest shape that contains the tag + DiagramShape containingShape = null; + Iterator shapes = getShapes().iterator(); + while(shapes.hasNext()){ + DiagramShape shape = (DiagramShape) shapes.next(); + if(shape.contains(point)){ + if(containingShape == null){ + containingShape = shape; + } else { + if(shape.isSmallerThan(containingShape)){ + containingShape = shape; + } + } + } + } + + //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 = (TextGrid.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 = (TextGrid.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 = (TextGrid.CellStringPair) 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 + Iterator shapes = this.getAllDiagramShapes().iterator(); + while(shapes.hasNext()){ + DiagramShape shape = (DiagramShape) shapes.next(); + Color fillColor = shape.getFillColor(); + if(shape.isClosed() + && shape.getType() != DiagramShape.TYPE_ARROWHEAD + && fillColor != null + && BitmapRenderer.isColorDark(fillColor)){ + Iterator textObjects = getTextObjects().iterator(); + while(textObjects.hasNext()){ + DiagramText textObject = (DiagramText) textObjects.next(); + if(shape.intersects(textObject.getBounds())){ + textObject.setColor(Color.white); + } + } + } + } + + //set outline to true for test within custom shapes + 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()); + + Iterator shapesIt = this.getCompositeShapes().iterator(); + while(shapesIt.hasNext()){ + CompositeDiagramShape compShape = (CompositeDiagramShape) shapesIt.next(); + 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(VERBOSE_DEBUG) { + 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(VERBOSE_DEBUG){ + 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(VERBOSE_DEBUG){ + 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(VERBOSE_DEBUG){ + System.out.println("One of smalls:"); + set2.printAsGrid(); + } + gridOfSmalls.fillCellsWith(set2, '*'); + } + if(VERBOSE_DEBUG){ + 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(VERBOSE_DEBUG) { + 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 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/org/stathissideris/ascii2image/graphics/DiagramComponent.java b/src/org/stathissideris/ascii2image/graphics/DiagramComponent.java new file mode 100644 index 000000000..2601889c6 --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/DiagramComponent.java @@ -0,0 +1,115 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.graphics; + +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); + } + + 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; + } + } + + return shape; + + } +} diff --git a/src/org/stathissideris/ascii2image/graphics/DiagramShape.java b/src/org/stathissideris/ascii2image/graphics/DiagramShape.java new file mode 100644 index 000000000..7a151335a --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/DiagramShape.java @@ -0,0 +1,951 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.graphics; + +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.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import org.stathissideris.ascii2image.text.TextGrid; + +/** + * + * @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; + + 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) { + 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); + } + + if(getType() == TYPE_DECISION && points.size() == 4){ + return makeDecisionPath(diagram); + } + + if(getType() == TYPE_MANUAL_OPERATION && points.size() == 4){ + return makeTrapezoidPath(diagram, true); + } + + if(getType() == TYPE_TRAPEZOID && points.size() == 4){ + return makeTrapezoidPath(diagram, 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 { //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 { + 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("Upexpected 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("Upexpected 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, boolean inverted) { + if(points.size() != 4) return null; + Rectangle bounds = makeIntoPath().getBounds(); + float offset = 0.7f * diagram.getCellWidth(); // fixed slope + 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) { + 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 = diagram.getCellWidth() / 2; + + 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; + } + +} diff --git a/src/org/stathissideris/ascii2image/graphics/DiagramText.java b/src/org/stathissideris/ascii2image/graphics/DiagramText.java new file mode 100644 index 000000000..58c984d50 --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/DiagramText.java @@ -0,0 +1,192 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.graphics; + +import java.awt.Color; +import java.awt.Font; +import java.awt.geom.Rectangle2D; + +/** + * + * @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; + } + + +} diff --git a/src/org/stathissideris/ascii2image/graphics/FontMeasurer.java b/src/org/stathissideris/ascii2image/graphics/FontMeasurer.java new file mode 100644 index 000000000..665c0f314 --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/FontMeasurer.java @@ -0,0 +1,198 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.graphics; + +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.font.FontRenderContext; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.util.Locale; + +/** + * + * @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); + 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/org/stathissideris/ascii2image/graphics/ImageHandler.java b/src/org/stathissideris/ascii2image/graphics/ImageHandler.java new file mode 100644 index 000000000..4c89799f2 --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/ImageHandler.java @@ -0,0 +1,78 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.graphics; + +import java.awt.Image; +import java.awt.MediaTracker; +import java.awt.Toolkit; +import java.net.URL; + +import javax.swing.JLabel; + +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 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); +// } + + +} diff --git a/src/org/stathissideris/ascii2image/graphics/ShapeEdge.java b/src/org/stathissideris/ascii2image/graphics/ShapeEdge.java new file mode 100644 index 000000000..c02b35395 --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/ShapeEdge.java @@ -0,0 +1,258 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +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 egde 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 verical 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 egde"); + } + } + + 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/org/stathissideris/ascii2image/graphics/ShapePoint.java b/src/org/stathissideris/ascii2image/graphics/ShapePoint.java new file mode 100644 index 000000000..8ea9c17be --- /dev/null +++ b/src/org/stathissideris/ascii2image/graphics/ShapePoint.java @@ -0,0 +1,140 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.graphics; + + +/** + * + * @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; + } + +} diff --git a/src/org/stathissideris/ascii2image/text/AbstractCell.java b/src/org/stathissideris/ascii2image/text/AbstractCell.java new file mode 100644 index 000000000..c3ae969f4 --- /dev/null +++ b/src/org/stathissideris/ascii2image/text/AbstractCell.java @@ -0,0 +1,124 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +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; + } + + +} diff --git a/src/org/stathissideris/ascii2image/text/AbstractionGrid.java b/src/org/stathissideris/ascii2image/text/AbstractionGrid.java new file mode 100644 index 000000000..c5bb56baf --- /dev/null +++ b/src/org/stathissideris/ascii2image/text/AbstractionGrid.java @@ -0,0 +1,183 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.text; + +import java.util.ArrayList; +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()); + /*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 = (CellSet) 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/org/stathissideris/ascii2image/text/CellSet.java b/src/org/stathissideris/ascii2image/text/CellSet.java new file mode 100644 index 000000000..fca9edf0f --- /dev/null +++ b/src/org/stathissideris/ascii2image/text/CellSet.java @@ -0,0 +1,690 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.text; + +import java.util.ArrayList; +import java.util.HashSet; +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; + } + + /*public BoundarySet(BoundarySet set) { + Iterator it = set.iterator(); + while(it.hasNext()){ + TextGrid.Cell cell = (TextGrid.Cell) it.next(); + add(new TextGrid.Cell(cell)); + } + }*/ + + 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(); + + TextGrid.Cell cell1 = null; + TextGrid.Cell cell2 = null; + + 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 = (TextGrid.Cell) 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 = (TextGrid.Cell) 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 = (TextGrid.Cell) 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 = (TextGrid.Cell) 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 = (TextGrid.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 = (TextGrid.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 = (TextGrid.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 = (TextGrid.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 = (TextGrid.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 = (TextGrid.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 = (CellSet) it.next(); + boolean isOriginal = true; + Iterator uniquesIt = uniqueSets.iterator(); + while(uniquesIt.hasNext()){ + CellSet uniqueSet = (CellSet) 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); + it = result.iterator(); + while (it.hasNext()) { + CellSet set = (CellSet) it.next(); + 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/org/stathissideris/ascii2image/text/GridPattern.java b/src/org/stathissideris/ascii2image/text/GridPattern.java new file mode 100644 index 000000000..0a53a39b0 --- /dev/null +++ b/src/org/stathissideris/ascii2image/text/GridPattern.java @@ -0,0 +1,334 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.text; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.regex.Pattern; + +/** + * 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 - = / \ + | :) + * ! means not boundary (none of - = / \ + | :) + * - means - or = + * | means | or : + * [ means not | nor : + * ~ means not - nor = + * ^ means a boundary but not - nor = + * ( 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
+ *  *--*--*
+ *  |     |
+ * 8*     *4
+ *  |     |
+ *  *--*--*
+ * 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){ + /*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"); + } + } +} diff --git a/src/org/stathissideris/ascii2image/text/GridPatternGroup.java b/src/org/stathissideris/ascii2image/text/GridPatternGroup.java new file mode 100644 index 000000000..96b861378 --- /dev/null +++ b/src/org/stathissideris/ascii2image/text/GridPatternGroup.java @@ -0,0 +1,435 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.text; + +import java.util.ArrayList; +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); + + } +} diff --git a/src/org/stathissideris/ascii2image/text/StringUtils.java b/src/org/stathissideris/ascii2image/text/StringUtils.java new file mode 100644 index 000000000..4a32be765 --- /dev/null +++ b/src/org/stathissideris/ascii2image/text/StringUtils.java @@ -0,0 +1,170 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.text; + +/** + * @author sideris + * + * To change the template for this generated type comment go to + * Window>Preferences>Java>Code Generation>Code and Comments + */ +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; + } + + /*public static String repeatString(String string, int repeats){ + if(repeats == 0) return ""; + StringBuffer buffer = new StringBuffer(""); + for(int i=0; i < repeats; i++){ + buffer.append(string); + } + 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; + + //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 ckecked 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)); + + + } +} diff --git a/src/org/stathissideris/ascii2image/text/TextGrid.java b/src/org/stathissideris/ascii2image/text/TextGrid.java new file mode 100644 index 000000000..40a635fe5 --- /dev/null +++ b/src/org/stathissideris/ascii2image/text/TextGrid.java @@ -0,0 +1,1798 @@ +/* + * DiTAA - Diagrams Through Ascii Art + * + * Copyright (C) 2004 Efstathios Sideris + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ +package org.stathissideris.ascii2image.text; + +import java.awt.Color; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +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.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.stathissideris.ascii2image.core.FileUtils; +import org.stathissideris.ascii2image.core.ProcessingOptions; + + +/** + * + * @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 StringBuffer(space)); + } + + public static TextGrid makeSameSizeAs(TextGrid grid){ + return new TextGrid(grid.getWidth(), grid.getHeight()); + } + + + public TextGrid(TextGrid otherGrid){ + rows = new ArrayList(); + for(StringBuffer row : otherGrid.getRows()) { + rows.add(new StringBuffer(row)); + } + } + + public void clear(){ + String blank = StringUtils.repeatString(" ", getWidth()); + int height = getHeight(); + rows.clear(); + for(int i = 0; i < height; i++) + rows.add(new StringBuffer(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 StringBuffer 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 StringBuffer(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; + StringBuffer 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 StringBuffer(row)); + } + + public void setRow(int y, StringBuffer 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(){ + StringBuffer buffer = new StringBuffer(); + 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 StringBuffer(row)); //TODO: this is not the most efficient way to do this + row = rows.get(y).toString(); + } + } + } + } + + + /** + * Replace all occurences 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 contants 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("Umbiguous 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; + } + +// @Override +// public int hashCode() { +// int h = 0; +// for(StringBuffer row : rows) { +// h += row.hashCode(); +// } +// return h; +// } + + /** + * 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(); + String[] linesArray = FileUtils.readFile(new File(filename), encoding).split("(\r)?\n"); + for(int i = 0; i < linesArray.length; i++) + lines.add(new StringBuffer(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 StringBuffer(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; !done; i--){ + StringBuffer 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 StringBuffer(row)); + index++; + } + + it = rows.iterator(); + ArrayList newRows = new ArrayList(); + //TODO: make the following depend on blankBorderSize + + StringBuffer topBottomRow = + new StringBuffer(StringUtils.repeatString(" ", maxLength + blankBorderSize * 2)); + + newRows.add(topBottomRow); + newRows.add(topBottomRow); + while(it.hasNext()){ + StringBuffer row = it.next(); + + if(row.length() < maxLength) { + String borderString = StringUtils.repeatString(" ", blankBorderSize); + StringBuffer newRow = new StringBuffer(); + + 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 StringBuffer(" ").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(); + StringBuffer newRow = new StringBuffer(); + + 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; + } + } +} +