diff --git a/.gitignore b/.gitignore index 36111bc1a41b6dd6012549b0af28a2848ee83581..db83415b8e0ecb2e09de775547add9d9751a52da 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,7 @@ out # Miscellaneous *.DS_Store -*.db \ No newline at end of file +*.db + +# Exported SVG files +*.svg diff --git a/build.gradle b/build.gradle index acf86f9c71d2b05cf613b6f2f7ee237d47df3d78..9276de11f01561d6acee5025d3c17bded657708a 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,9 @@ dependencies { // CSV parsing compile "com.opencsv:opencsv:4.6" + + // SVG + compile group: 'org.jfree', name: 'jfreesvg', version: '3.4' } tasks.withType(Test) { diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/App.java b/src/main/java/de/tudresden/inf/mci/brailleplot/App.java index 3ecd4c4bc3e958e0bb1f0e251ff04e333dcd4345..c703a1a3f49d8f9a0fe231cef7777e1fc7d9c0c7 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/App.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/App.java @@ -22,6 +22,8 @@ import de.tudresden.inf.mci.brailleplot.datacontainers.CategoricalPointListConta import de.tudresden.inf.mci.brailleplot.datacontainers.PointList; import de.tudresden.inf.mci.brailleplot.diagrams.BarChart; import de.tudresden.inf.mci.brailleplot.rendering.MasterRenderer; +import de.tudresden.inf.mci.brailleplot.svgexporter.BoolMatrixDataSvgExporter; +import de.tudresden.inf.mci.brailleplot.svgexporter.SvgExporter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,7 +38,7 @@ import java.util.concurrent.ConcurrentLinkedDeque; * Main class. * Set up the application and run it. * @author Georg Graßnick, Andrey Ruzhanskiy - * @version 06.06.19 + * @version 2019.08.16 */ public final class App { @@ -170,7 +172,10 @@ public final class App { SimpleMatrixDataImpl<Boolean> mat = (SimpleMatrixDataImpl<Boolean>) canvas.getCurrentPage(); mLogger.debug("Render preview:\n" + mat.toBoolString()); - // Config Parsing + // SVG exporting + SvgExporter<RasterCanvas> svgExporter = new BoolMatrixDataSvgExporter(canvas); + svgExporter.render(); + svgExporter.dump("boolMat"); // Check if some SpoolerService/Printservice exists if (!PrintDirector.isPrintServiceOn()) { diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/layout/AbstractCanvas.java b/src/main/java/de/tudresden/inf/mci/brailleplot/layout/AbstractCanvas.java index 0b4a84a9f7383f8de6f9c70456dd634fd86940c4..b28a3cc0525cc8734d8b7573bb0824c864aae0b2 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/layout/AbstractCanvas.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/layout/AbstractCanvas.java @@ -12,10 +12,11 @@ import java.util.ListIterator; /** * Representation of a target onto which can be drawn. It wraps a {@link PrintableData} instance and specifies the size of the drawing area (in mm). - * @author Leonard Kupper - * @version 2019.07.22 + * @param <T> The type of the managed {@link PrintableData} + * @author Leonard Kupper, Georg Graßnick + * @version 2019.08.16 */ -public abstract class AbstractCanvas { +public abstract class AbstractCanvas<T extends PrintableData> { private final Logger mLogger = LoggerFactory.getLogger(this.getClass()); @@ -24,7 +25,7 @@ public abstract class AbstractCanvas { Rectangle mPrintableArea; - List<PrintableData> mPageContainer; + List<T> mPageContainer; AbstractCanvas(final Printer printer, final Format format) throws InsufficientRenderingAreaException { @@ -117,6 +118,14 @@ public abstract class AbstractCanvas { return mPrintableArea.getHeight(); } + public final double getPageWidth() { + return mFormat.getProperty("page.width").toDouble(); + } + + public final double getPageHeight() { + return mFormat.getProperty("page.height").toDouble(); + } + /** * Get the number of pages in the canvas. * @return The number of pages. @@ -126,11 +135,10 @@ public abstract class AbstractCanvas { } /** - * Get an Iterator for the PrintableData instances representing the canvas pages. The single instances should be - * casted to the regarding concrete type depending on the canvas implementation. + * Get an Iterator for the PrintableData instances representing the canvas pages. * @return A {@link ListIterator}<{@link PrintableData}>. */ - public ListIterator<PrintableData> getPageIterator() { + public ListIterator<T> getPageIterator() { return mPageContainer.listIterator(); } diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/layout/RasterCanvas.java b/src/main/java/de/tudresden/inf/mci/brailleplot/layout/RasterCanvas.java index 285f5db31f5c111664f000714d0e2c37b0441812..9e88d921126e6552ffaf73ab746c9bd0ca08329c 100644 --- a/src/main/java/de/tudresden/inf/mci/brailleplot/layout/RasterCanvas.java +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/layout/RasterCanvas.java @@ -8,6 +8,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import static java.lang.Math.abs; import static java.lang.Math.ceil; @@ -16,10 +18,10 @@ import static java.lang.Math.floor; /** * Representation of a target onto which an image can be rasterized. * It wraps a {@link de.tudresden.inf.mci.brailleplot.printabledata.MatrixData} instance and describes the raster size and its (not necessarily equidistant) layout. - * @author Leonard Kupper - * @version 2019.07.22 + * @author Leonard Kupper, georg Graßnick + * @version 2019.08.16 */ -public class RasterCanvas extends AbstractCanvas { +public class RasterCanvas extends AbstractCanvas<MatrixData<Boolean>> { private final Logger mLogger = LoggerFactory.getLogger(this.getClass()); @@ -83,14 +85,11 @@ public class RasterCanvas extends AbstractCanvas { return getCurrentPage(); } - @SuppressWarnings("unchecked") - // This is allowed because the mPageContainer fields are always initialized with the correct type by the page getters, - // cannot be accessed from the outside and are never changed anywhere else. public final MatrixData<Boolean> getCurrentPage() { if (mPageContainer.size() < 1) { return getNewPage(); } - return (MatrixData<Boolean>) mPageContainer.get(mPageContainer.size() - 1); + return mPageContainer.get(mPageContainer.size() - 1); } private void readConfig() { @@ -259,6 +258,22 @@ public class RasterCanvas extends AbstractCanvas { return dotY / mCellHeight; } + public final int getFullConstraintLeft() { + return (int) Math.round(mPrinter.getProperty("raster.constraint.left").toInt() * mCellHorizontalMM + mPrinter.getProperty("constraint.left").toDouble()); + } + + public final int getFullConstraintTop() { + return (int) Math.round(mPrinter.getProperty("raster.constraint.top").toInt() * mCellVerticalMM + mPrinter.getProperty("constraint.top").toDouble()); + } + + public final List<Double> getXPositions() { + return Collections.unmodifiableList(mXPositions); + } + + public final List<Double> getYPositions() { + return Collections.unmodifiableList(mYPositions); + } + public final int quantifyX(final double unquantifiedMillimeterX) { return findClosestValueIndex(unquantifiedMillimeterX, mXPositions); diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/AbstractSvgExporter.java b/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/AbstractSvgExporter.java new file mode 100644 index 0000000000000000000000000000000000000000..ed9e16d9c73850c447c6f1fc5bd55d9ce435e4a5 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/AbstractSvgExporter.java @@ -0,0 +1,96 @@ +package de.tudresden.inf.mci.brailleplot.svgexporter; + +import de.tudresden.inf.mci.brailleplot.printabledata.PrintableData; +import de.tudresden.inf.mci.brailleplot.layout.AbstractCanvas; +import org.jfree.graphics2d.svg.SVGGraphics2D; +import org.jfree.graphics2d.svg.SVGUnits; +import org.jfree.graphics2d.svg.ViewBox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; + +/** + * Abstract parent class for all SVG exporter classes. + * @param <T> The type of the Abstract Canvas that is used. + * @param <U> The type of the {@link PrintableData} that is used. + * @author Georg Graßnick + * @version 2019.08.20 + */ +abstract class AbstractSvgExporter<T extends AbstractCanvas, U extends PrintableData> implements SvgExporter<T> { + + protected final Logger mLogger = LoggerFactory.getLogger(getClass()); + + protected final T mCanvas; + protected List<SVGGraphics2D> mSvgs; + private ViewBox mViewBox; + + protected static final int SCALE_FACTOR = 2; + protected static final float STROKE_WIDTH = 1f; + + AbstractSvgExporter(final T canvas) { + Objects.requireNonNull(canvas); + mCanvas = canvas; + } + + @Override + // As long as the implementing class uses the correct generic type, + // the cast for the first parameter of renderPage() is safe. + @SuppressWarnings("unchecked") + public void render() { + final int docWidth = (int) Math.ceil(mCanvas.getPageWidth()); + final int docHeight = (int) Math.ceil(mCanvas.getPageHeight()); + + final int viewBoxWidth = docWidth * SCALE_FACTOR; + final int viewBoxHeight = docHeight * SCALE_FACTOR; + mViewBox = new ViewBox(0, 0, viewBoxWidth, viewBoxHeight); + + ListIterator it = mCanvas.getPageIterator(); + int idx = 0; + while (it.hasNext()) { + SVGGraphics2D svg = new SVGGraphics2D(docWidth, docHeight, SVGUnits.MM); + + svg.setBackground(Color.WHITE); + svg.clearRect(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE); + svg.setStroke(new BasicStroke(STROKE_WIDTH)); + mSvgs.add(svg); + mLogger.debug("Created SVG with StrokeWitdh {}, ScaleFactor {}, DocumentWidth {}mm, DocumentHeight {}mm, ViewPort: ({},{})", STROKE_WIDTH, SCALE_FACTOR, docWidth, docHeight, viewBoxWidth, viewBoxHeight); + + renderPage((((U) it.next())), idx++); + } + } + + @Override + public void dump(final OutputStream os, final int dataIndex) throws IOException { + Objects.requireNonNull(os); + final String doc = mSvgs.get(dataIndex).getSVGElement(null, true, mViewBox, null, null); + mLogger.trace("Start dumping file to stream ..."); + os.write(doc.getBytes()); + mLogger.trace("Finished dumping file to stream"); + } + + @Override + public void dump(final String filePath, final int dataIndex) throws IOException { + Objects.requireNonNull(filePath); + try (FileOutputStream fs = new FileOutputStream(filePath)) { + dump(fs, dataIndex); + } + } + + @Override + public void dump(final String baseFileName) throws IOException { + Objects.requireNonNull(baseFileName); + for (int i = 0; i < mSvgs.size(); i++) { + dump(baseFileName + String.format("_%03d.svg", i), i); + } + } + + protected abstract void renderPage(U mat, int dataIndex); +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/BoolMatrixDataSvgExporter.java b/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/BoolMatrixDataSvgExporter.java new file mode 100644 index 0000000000000000000000000000000000000000..a0649a3e659b609d036460c94769e9473e2870a7 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/BoolMatrixDataSvgExporter.java @@ -0,0 +1,42 @@ +package de.tudresden.inf.mci.brailleplot.svgexporter; + +import de.tudresden.inf.mci.brailleplot.printabledata.MatrixData; +import de.tudresden.inf.mci.brailleplot.layout.RasterCanvas; +import org.jfree.graphics2d.svg.SVGGraphics2D; + +import java.util.ArrayList; +import java.util.List; + +/** + * SVG exporter class that supports {@link RasterCanvas} as input. + * @author Georg Graßnick + * @version 2019.08.20 + */ +public class BoolMatrixDataSvgExporter extends AbstractSvgExporter<RasterCanvas, MatrixData<Boolean>> { + + + public BoolMatrixDataSvgExporter(final RasterCanvas rasterCanvas) { + super(rasterCanvas); + mSvgs = new ArrayList<>(mCanvas.getPageCount()); + } + + @Override + protected void renderPage(final MatrixData<Boolean> mat, final int dataIndex) { + List<Double> xPositions = mCanvas.getXPositions(); + List<Double> yPositions = mCanvas.getYPositions(); + int dotDiameter = (int) mCanvas.getDotDiameter() * SCALE_FACTOR; + mLogger.trace("Dot diameter: {}", dotDiameter); + SVGGraphics2D svg = mSvgs.get(dataIndex); + + for (int y = 0; y < mat.getRowCount(); y++) { + for (int x = 0; x < mat.getColumnCount(); x++) { + if (mat.getValue(y, x)) { + int xPos = (int) Math.round((xPositions.get(x) + mCanvas.getFullConstraintLeft() - dotDiameter / 2) * SCALE_FACTOR); + int yPos = (int) Math.round((yPositions.get(y) + mCanvas.getFullConstraintTop() - dotDiameter / 2) * SCALE_FACTOR); + svg.drawOval(xPos, yPos, dotDiameter, dotDiameter); + mLogger.trace("Drew dot at position ({},{})", xPos, yPos); + } + } + } + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/SvgExporter.java b/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/SvgExporter.java new file mode 100644 index 0000000000000000000000000000000000000000..e955d5ad6f2a01be2bfe5e6fd67995bc241fa333 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/SvgExporter.java @@ -0,0 +1,43 @@ +package de.tudresden.inf.mci.brailleplot.svgexporter; + +import de.tudresden.inf.mci.brailleplot.layout.AbstractCanvas; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Interface for all classes that support exporting PrintableData to SVG files. + * @param <T> The type of the supported PrintableData. + * @author Georg Graßnick + * @version 2019.08.16 + */ +public interface SvgExporter<T extends AbstractCanvas> { + + /** + * Process the actual rendering of the data to SVG. + */ + void render(); + + /** + * Dump a specific rendered SVG to an output stream. + * @param os The stream to dump the SVG to. + * @param dataIndex The index of the {@link de.tudresden.inf.mci.brailleplot.printabledata.PrintableData} in {@link AbstractCanvas#getPageIterator()} + * @throws IOException on any IO related issues. + */ + void dump(OutputStream os, int dataIndex) throws IOException; + + /** + Dump a specific rendered SVG to a file. + * @param filePath The path to dump the file at. + * @param dataIndex The index of the {@link de.tudresden.inf.mci.brailleplot.printabledata.PrintableData} in {@link AbstractCanvas#getPageIterator()} + * @throws IOException on any IO related issues. + */ + void dump(String filePath, int dataIndex) throws IOException; + + /** + * Dump all rendered SVGs to separate files. + * @param baseFileName The basename of all files. Output filename format: ${basename}_${Index}.svg + * @throws IOException on any IO related issues. + */ + void dump(String baseFileName) throws IOException; +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/package-info.java b/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..a2cbe41ca250e340985ab9f4b319f366ad604838 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/svgexporter/package-info.java @@ -0,0 +1,7 @@ +/** + * Provides classes used for creating SVG Documents which provide a preview function + * of the embossed document. + * @author Georg Graßnick + * @version 2019.08.16 + */ +package de.tudresden.inf.mci.brailleplot.svgexporter;