diff --git a/.gitignore b/.gitignore index 5465036af3679d4d7d3de04e780ee41c4ffe58c3..f930235d667b3385e9c0d64eccd425e3f1dc0d7e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ out *.class *.jar *.tar + +# Miscellaneous +*.DS_Store +*.db \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8c8f4141fd1c7ddac072e3d18ff34ce73899b79c..676341377ea008279797379fb2e1cbcf2fe49632 100644 --- a/build.gradle +++ b/build.gradle @@ -30,8 +30,7 @@ dependencies { // Use JUnit test framework compile group: 'commons-cli', name: 'commons-cli', version: '1.4' testImplementation('org.junit.jupiter:junit-jupiter:5.4.2') - - + // Logging compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.26' compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' @@ -45,8 +44,6 @@ dependencies { compile group: 'com.beust', name: 'jcommander', version: '1.64' // https://mvnrepository.com/artifact/org.slf4j/slf4j-api compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.24' - - } test { diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/AbstractCanvas.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/AbstractCanvas.java new file mode 100644 index 0000000000000000000000000000000000000000..b2e63738a499590c5855c80cbbb9559aa59ae848 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/AbstractCanvas.java @@ -0,0 +1,122 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import de.tudresden.inf.mci.brailleplot.configparser.Format; +import de.tudresden.inf.mci.brailleplot.configparser.Printer; +import de.tudresden.inf.mci.brailleplot.printabledata.PrintableData; + +import java.util.ArrayList; +import java.util.List; +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 + */ +public abstract class AbstractCanvas { + + Printer mPrinter; + Format mFormat; + + Rectangle mPrintableArea; + + List<PrintableData> mPageContainer; + + AbstractCanvas(final Printer printer, final Format format) throws InsufficientRenderingAreaException { + mPrinter = printer; + mFormat = format; + mPageContainer = new ArrayList<>(); + + readConfig(); + } + + private void readConfig() throws InsufficientRenderingAreaException { + + // New approach using a box model: + + // Create a page box + int pageWidth = mFormat.getProperty("page.width").toInt(); + int pageHeight = mFormat.getProperty("page.height").toInt(); + Rectangle pageBox = new Rectangle(0, 0, pageWidth, pageHeight); + + // Create a margin box + int marginTop = mFormat.getProperty("margin.top").toInt(); + int marginLeft = mFormat.getProperty("margin.left").toInt(); + int marginBottom = mFormat.getProperty("margin.bottom").toInt(); + int marginRight = mFormat.getProperty("margin.right").toInt(); + Rectangle marginBox = new Rectangle(pageBox); + try { + marginBox.removeFromTop(marginTop); + marginBox.removeFromLeft(marginLeft); + marginBox.removeFromBottom(marginBottom); + marginBox.removeFromRight(marginRight); + } catch (Rectangle.OutOfSpaceException e) { + throw new InsufficientRenderingAreaException("The sum of the defined margins is bigger than the page size.", e); + } + + // Create a constraint box + double constraintTop = mPrinter.getProperty("constraint.top").toDouble(); + double constraintLeft = mPrinter.getProperty("constraint.left").toDouble(); + double constraintHeight, constraintWidth; + if (mPrinter.getPropertyNames().contains("constraint.height")) { + constraintHeight = mPrinter.getProperty("constraint.height").toDouble(); + } else { + constraintHeight = Integer.MAX_VALUE; + } + if (mPrinter.getPropertyNames().contains("constraint.width")) { + constraintWidth = mPrinter.getProperty("constraint.width").toDouble(); + } else { + constraintWidth = Integer.MAX_VALUE; + } + Rectangle constraintBox = new Rectangle(constraintLeft, constraintTop, constraintWidth, constraintHeight); + + mPrintableArea = calculatePrintingArea(marginBox, constraintBox); + + } + + /** + * A universal help function to calculate the printable area from original page size, desired minimum margins + * and the given area constraints of the printer. + * @param marginBox A rectangle representing the page with cropped edges representing the margins. + * @param constraintBox A rectangle representing the printer constraint as [x = constraint x, y = constraint y, + * w = constraint width, h = constraint height] + * @return A rectangle representing the valid printing area. + */ + final Rectangle calculatePrintingArea(final Rectangle marginBox, final Rectangle constraintBox) { + return marginBox.intersectedWith(constraintBox).translatedBy(-1 * constraintBox.getX(), -1 * constraintBox.getY()); + } + + /** + * This method is supposed to return the full width of the canvas. + * @return The width of the canvas in millimeters. + */ + public double getPrintableWidth() { + return mPrintableArea.getWidth(); + } + + /** + * This method is supposed to return the full height of the canvas. + * @return The height of the canvas in millimeters. + */ + public double getPrintableHeight() { + return mPrintableArea.getHeight(); + } + + /** + * Get the number of pages in the canvas. + * @return The number of pages. + */ + public int getPageCount() { + return mPageContainer.size(); + } + + /** + * 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. + * @return A {@link ListIterator}<{@link PrintableData}>. + */ + public ListIterator<PrintableData> getPageIterator() { + return mPageContainer.listIterator(); + } + +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Axis.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Axis.java new file mode 100644 index 0000000000000000000000000000000000000000..aa6ddc41ad0328221b7ff8981c40cd0d32e420bc --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Axis.java @@ -0,0 +1,187 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import java.util.Map; +import java.util.Objects; + +/** + * The representation of a visible axis with a line, tickmarks and labels. + * @author Leonard Kupper + * @version 2019.07.09 + */ +public class Axis implements Renderable { + + private Type mType; + private double mOriginX; + private double mOriginY; + private double mStepWidth; + private double mTickSize; + private Rectangle mBoundary; + private Map<Integer, String> mLabels; + + /** + * Constructor. Creates an instance of a new axis. Distances and sizes are stored as double and are used in a generic manner. + * The interpretation of the values must be done by the rasterizer or plotter. + * @param type Just changes the orientation of the axis. Can be either {@link Axis.Type#X_AXIS} or {@link Axis.Type#Y_AXIS}. + * @param originX The x coordinate of the position where the axis line and the tickmark and label corresponding to the value '0' is placed. + * @param originY The y coordinate of the position where the axis line and the tickmark and label corresponding to the value '0' is placed. + * @param stepWidth The distance between two tickmarks on the axis. + * @param tickSize The size and orientation of the tickmarks. The absolute value controls the length, the sign controls on which side they are displayed. + */ + public Axis(final Type type, final double originX, final double originY, final double stepWidth, + final double tickSize) { + setType(type); + setOriginX(originX); + setOriginY(originY); + setStepWidth(stepWidth); + setTickSize(tickSize); + } + + /** + * Get the type of the axis. + * @return The axis type as {@link Axis.Type}. + */ + public Type getType() { + return mType; + } + + /** + * Set the type of the axis. + * @param type The axis type as {@link Axis.Type}. + */ + public void setType(final Type type) { + mType = Objects.requireNonNull(type); + } + + /** + * Get the x position of the coordinate origin. The position is not to be mistaken with the coordinate. It + * determines where on the canvas the axis should be positioned, not which x value is positioned at the y-axis. + * @return The x position of the axis on canvas. + */ + public double getOriginX() { + return mOriginX; + } + + /** + * Set the x position of the coordinate origin. The position is not to be mistaken with the coordinate. It + * determines where on the canvas the axis should be positioned, not which x value is positioned at the y-axis. + * @param originX The x position of the axis on canvas. + */ + public void setOriginX(final double originX) { + mOriginX = originX; + } + + /** + * Get the y position of the coordinate origin. The position is not to be mistaken with the coordinate. It + * determines where on the canvas the axis should be positioned, not which y value is positioned at the x-axis. + * @return The y position of the axis on canvas. + */ + public double getOriginY() { + return mOriginY; + } + + /** + * Set the y position of the coordinate origin. The position is not to be mistaken with the coordinate. It + * determines where on the canvas the axis should be positioned, not which y value is positioned at the x-axis. + * @param originY The y position of the axis on canvas. + */ + public void setOriginY(final double originY) { + mOriginY = originY; + } + + /** + * Get the distance between neighboring axis tickmarks. + * @return The tickmark distance. + */ + public double getStepWidth() { + return mStepWidth; + } + + /** + * Set the distance between neighboring axis tickmarks. + * @param stepWidth The tickmark distance. + */ + public void setStepWidth(final double stepWidth) { + if (stepWidth <= 0) { + throw new IllegalArgumentException("Axis step width can't be negative or zero."); + } + mStepWidth = stepWidth; + } + + /** + * Get the length of the axis tickmark lines. The values sign determines the tickmark orientation. A value of zero + * indicates that no visible tickmarks are set. + * @return The tickmark line length. + */ + public double getTickSize() { + return mTickSize; + } + + /** + * Set the length of the axis tickmark lines. The values sign determines the tickmark orientation. A value of zero + * indicates that no visible tickmarks are set. + * @param tickSize The tickmark line length. + */ + public void setTickSize(final double tickSize) { + mTickSize = tickSize; + } + + /** + * Get the labels that are drawn next to the axis tickmarks as {@link Map}. The key determines the position of the + * label (positive values = labels toward positive value range, 0 = at coordinate origin, negative values = labels + * toward negative value range). The value is a String representing the label text. + * Not every position must be supplied with a label. + * @return A {@link Map} containing all labels. + */ + public Map<Integer, String> getLabels() { + return mLabels; + } + /** + * Set the labels that are drawn next to the axis tickmarks as {@link Map}. The key determines the position of the + * label (positive values = labels toward positive value range, 0 = at coordinate origin, negative values = labels + * toward negative value range). The value is a String representing the label text. + * Not every position must be supplied with a label. + * @param labels A {@link Map} containing all labels. + */ + public void setLabels(final Map<Integer, String> labels) { + mLabels = Objects.requireNonNull(labels); + } + + /** + * Check whether any labels are set. This should be done prior to trying accessing the labels via {@link #getLabels()}. + * @return True if labels are set, else False. + */ + public boolean hasLabels() { + return !Objects.isNull(mLabels); + } + + /** + * Get the area of the canvas on which the area is to be drawn. + * @return A {@link Rectangle} representing the area. + */ + public Rectangle getBoundary() { + return Objects.requireNonNull(mBoundary); + } + + /** + * Set the area of the canvas on which the area is to be drawn. + * @param boundary A {@link Rectangle} representing the area. + */ + public void setBoundary(final Rectangle boundary) { + mBoundary = Objects.requireNonNull(boundary); + } + + /** + * Check whether a boundary is set. + * @return True if boundary is set, else False. + */ + public boolean hasBoundary() { + return !Objects.isNull(mBoundary); + } + + /** + * Representation of the axis type / orientation. + */ + enum Type { + X_AXIS, Y_AXIS; + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/BrailleText.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/BrailleText.java new file mode 100644 index 0000000000000000000000000000000000000000..afa027dec3057ae0c1f148e7a8f7a7ea52d2b037 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/BrailleText.java @@ -0,0 +1,56 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import java.util.Objects; + +/** + * Simple representation of a braille text field. + * @author Leonard Kupper + * @version 2019.07.22 + */ +public class BrailleText implements Renderable { + + private String mContent; + private Rectangle mArea; + + /** + * Constructor. Creates a braille text field. + * @param content The actual text of the text field. + * @param area The desired area for the text to be rendered on. + */ + public BrailleText(final String content, final Rectangle area) { + setText(content); + setArea(area); + } + + /** + * Sets a new text content. + * @param content The new content for the text field. + */ + public void setText(final String content) { + mContent = Objects.requireNonNull(content); + } + + /** + * Gets the current text content of the text field. + * @return A {@link String} containing the text. + */ + public String getText() { + return mContent; + } + + /** + * Sets a new area for the text field. + * @param area The new area for the text field. + */ + public void setArea(final Rectangle area) { + mArea = Objects.requireNonNull(area); + } + + /** + * Gets the current area of the text field. + * @return The area of the text field. + */ + public Rectangle getArea() { + return mArea; + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/BrailleTextRasterizer.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/BrailleTextRasterizer.java new file mode 100644 index 0000000000000000000000000000000000000000..eab684ebd70d05f39ec8d485a822d65996ab64b4 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/BrailleTextRasterizer.java @@ -0,0 +1,36 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +/** + * A rasterizer for text on braille grids. This class is still a stub and must be implemented! + * @version 2019.07.21 + * @author Leonard Kupper + */ +public final class BrailleTextRasterizer implements Rasterizer<BrailleText> { + @Override + public void rasterize(final BrailleText data, final RasterCanvas canvas) throws InsufficientRenderingAreaException { + // TODO: rasterize the text (Take different grids into consideration! 6-dot / 8-dot) + // Until then, we just display dummy characters + int x = data.getArea().intWrapper().getX(); + int y = data.getArea().intWrapper().getY(); + for (int i = 0; i < data.getText().length(); i++) { + canvas.getCurrentPage().setValue(y, x, true); + canvas.getCurrentPage().setValue(y + 1, x + 1, true); + canvas.getCurrentPage().setValue(y + 2, x, true); + x += 2; + } + } + + // TODO: Completely replace with help methods to calculate suited area for left or right alignment of given text. + public int calculateRequiredHeight(final String text, final int xPos, final int yPos, final int maxWidth, + final RasterCanvas canvas) { + // TODO: Add calculations for required height to fit the given text into the given canvas. (Linebreaks!) + // Until then we use a dummy value assuming one line of text: + return canvas.getCellHeight(); + } + + public int calculateRequiredWidth(final String text, final int xPos, final int yPos, final RasterCanvas canvas) { + // TODO: Add calculations for required width to fit the given text into the given canvas. (Extra spacing for equidistant grid!) + // Until then we use a dummy value assuming single character on braille grid: + return canvas.getCellWidth(); + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/FunctionalRasterizer.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/FunctionalRasterizer.java new file mode 100644 index 0000000000000000000000000000000000000000..bd9e347ee4d1fb77d5bad6c1831a4cf3367b4c9d --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/FunctionalRasterizer.java @@ -0,0 +1,53 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +/** + * FunctionalRasterizer. This class implements a concrete rasterizer via a functional interface. + * The rasterizing algorithm to be used is passed to the constructor as lambda function, method reference or rasterizer implementation. + * @param <T> The concrete diagram class which can be rasterized with the rasterizer. + * @author Leonard Kupper + * @version 2019.07.20 + */ +public class FunctionalRasterizer<T extends Renderable> implements Rasterizer { + + private Class<? extends T> mSupportedRenderableClass; + private ThrowingBiConsumer<T, RasterCanvas, InsufficientRenderingAreaException> mRasterizingAlgorithm; + + /** + * Constructor. Creates a new rasterizer from either a given rasterizer implementation or (keep in mind that + * Rasterizer is a functional interface) from a ThrowingBiConsumer<T, RasterCanvas, InsufficientRenderingAreaException> method reference. + * @param supportedRenderableClass A reference to the accepted diagram class (e.g. 'BarChart.class') + * @param rasterizer A reference to a Rasterizer instance. + */ + public FunctionalRasterizer( + final Class<T> supportedRenderableClass, + final Rasterizer<T> rasterizer) { + mSupportedRenderableClass = supportedRenderableClass; + mRasterizingAlgorithm = rasterizer::rasterize; + } + + @Override + public void rasterize(final Renderable data, final RasterCanvas canvas) throws InsufficientRenderingAreaException { + // invoke the given rasterizing algorithm + T safeData = safeCast(data); + mRasterizingAlgorithm.accept(safeData, canvas); + } + + final Class<? extends T> getSupportedRenderableClass() { + return mSupportedRenderableClass; + } + + @SuppressWarnings("unchecked") + // This is allowed, because the code that calls the rasterize method does a lookup based on the renderable class beforehand + // and will only select the appropriate rasterizer. (See FunctionalRenderingBase) + // Since the FunctionalRasterizer is package private, there is no way to invoke it with the wrong type from 'outside'. + // Should somebody still force this to happen by intentional tampering, we have no choice but to catch this disgrace. + private T safeCast(final Renderable data) { + try { + return getSupportedRenderableClass().cast(data); + } catch (ClassCastException e) { + // wow + throw new IllegalArgumentException("Wrong renderable type! This rasterizer is not meant to be used with '" + + data.getClass().getCanonicalName() + "'", e); + } + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/FunctionalRenderingBase.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/FunctionalRenderingBase.java new file mode 100644 index 0000000000000000000000000000000000000000..7938933105a95a29757ad23aa27e198f5f728d88 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/FunctionalRenderingBase.java @@ -0,0 +1,75 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import java.util.HashMap; +import java.util.Objects; + +/** + * FunctionalRenderingBase. This class acts as a wrapper for multiple {@link FunctionalRasterizer} instances. + * The rasterizer instances can be registered at runtime. The main purpose of the class is to take {@link Renderable} + * representations of any type and select the correct concrete rasterizer. + * @author Leonard Kupper + * @version 2019.07.22 + */ +public class FunctionalRenderingBase { + + private HashMap<Class<? extends Renderable>, FunctionalRasterizer> mRasterizingAlgorithms; + private RasterCanvas mRaster; + + public FunctionalRenderingBase() { + mRasterizingAlgorithms = new HashMap<>(); + } + + /** + * Rasterizes any given {@link Renderable} by passing it to the appropriate registered {@link FunctionalRasterizer}. + * @param renderData Any instance of a class implementing {@link Renderable}. + * @throws InsufficientRenderingAreaException If too few space is available on the currently set {@link RasterCanvas} + * to display the amount of data contained in the given renderable representation. + * @exception IllegalStateException If no {@link RasterCanvas} is set. Call {@link #setRasterCanvas(RasterCanvas)} beforehand. + * @exception IllegalArgumentException If no rasterizer is registered for the given renderable type. + */ + public void rasterize(final Renderable renderData) throws InsufficientRenderingAreaException { + // First, check if a raster is set. No rasterizing without raster. + if (Objects.isNull(mRaster)) { + throw new IllegalStateException("No raster was set. The method 'setRasterCanvas' must be called before invoking the 'rasterize' method."); + } + // Then, look at the type of the renderData + Class<? extends Renderable> diagramClass = renderData.getClass(); + // Is a rasterizer for the given renderData type available? + if (mRasterizingAlgorithms.containsKey(diagramClass)) { + // dispatch to concrete rasterizer implementation + FunctionalRasterizer selectedRasterizer = mRasterizingAlgorithms.get(diagramClass); + selectedRasterizer.rasterize(renderData, mRaster); + } else { + throw new IllegalArgumentException("No rasterizer registered for renderData class: '" + + diagramClass.getCanonicalName() + "'"); + } + } + + /** + * Registers a {@link FunctionalRasterizer} instance to the rendering base. The rendering base can ony hold one rasterizer + * per {@link Renderable} type at the same time. This means that any rasterizer that has been registered for the same + * type before will be replaced by the new instance. + * @param rasterizer The instance of {@link FunctionalRasterizer} to be registered. + */ + public void registerRasterizer(final FunctionalRasterizer<? extends Renderable> rasterizer) { + mRasterizingAlgorithms.put(rasterizer.getSupportedRenderableClass(), rasterizer); + } + + /** + * Sets a new canvas for any rasterizing operations performed by this rendering base. The rasterizing results are + * 'drawn' on the currently selected canvas instance. There are no restrictions on the raster canvas. It is also + * possible to pass a canvas which already contains data to 'overlay' the new data. + * @param raster The {@link AbstractCanvas} instance which will be used for all subsequent rasterizing operations. + */ + public void setRasterCanvas(final RasterCanvas raster) { + mRaster = Objects.requireNonNull(raster); + } + + /** + * Gets the currently set {@link AbstractCanvas} of the rendering base. + * @return An instance of {@link AbstractCanvas}. + */ + public RasterCanvas getRaster() { + return mRaster; + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Image.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Image.java new file mode 100644 index 0000000000000000000000000000000000000000..1bbc3ca7a66d4931b8259bba2ee85a2cfb1f2745 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Image.java @@ -0,0 +1,35 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.Objects; + +/** + * A representation of an (raster graphic) image. Basically just a wrapper for {@link javax.imageio.ImageIO} and + * {@link java.awt.image.BufferedImage}. + * @author Leonard Kupper + * @version 2019.07.22 + */ +public class Image implements Renderable { + + private BufferedImage imageData; + + /** + * Constructor. Creates a new renderable representation from an image file. + * @param imageFile A file containing an raster graphic image. (Different types supported. BMP, PNG, JPEG, ...) + * @throws java.io.IOException If an I/O exception of some sort has occurred while reading the image file. + */ + public Image(final File imageFile) throws java.io.IOException { + imageData = ImageIO.read(Objects.requireNonNull(imageFile)); + } + + /** + * Get the loaded image as {@link BufferedImage}. + * @return An instance of {@link BufferedImage}. + */ + BufferedImage getBufferedImage() { + return imageData; + } + +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/ImageRasterizer.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/ImageRasterizer.java new file mode 100644 index 0000000000000000000000000000000000000000..85031da37879a928d58d8d815ca4328d8f8819b3 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/ImageRasterizer.java @@ -0,0 +1,212 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import de.tudresden.inf.mci.brailleplot.printabledata.MatrixData; + +import java.awt.image.BufferedImage; + +import static java.lang.Math.min; +import static java.lang.Math.round; + +/** + * A rasterizer that is able to re-raster a raster graphics onto a canvas. + * @author Leonard Kupper + * @version 2019.07.22 + */ +public class ImageRasterizer implements Rasterizer<Image> { + + // This Rasterizer is a basic example on how to create a rasterizer. + // Each rasterizer is the implementation of the Rasterizer interface. To create a rasterizer, implement the + // Rasterizer interface by overriding the rasterize method. Since Rasterizer is a functional interface, + // it is technically also possible to implement a rasterizer as a method independent from a class, as long + // as it takes a renderable and a canvas as parameters. + + // Switches + private boolean mPreventOverStretch; + private boolean mPreserveAspectRatio; + private boolean mQuantifiedPositions; + + // Output threshold: A dot will be set for gray scale values below (darker than/equal) this threshold. + private int mLowThreshold; + private final int mDefaultThreshold = 80; + + /** + * Constructor. Creates a new {@link Rasterizer} for instances of {@link Image} with default settings. + */ + public ImageRasterizer() { + mPreventOverStretch = true; + mPreserveAspectRatio = true; + mQuantifiedPositions = true; + mLowThreshold = mDefaultThreshold; + } + + /** + * Constructor. Creates a new {@link Rasterizer} for instances of {@link Image}. + * @param preventOverStretch In case that the given images resolution is smaller than the grid on at least one + * dimension this flag prevents it to be 'stretched' on the output, leading to 'cuts' in + * former solid lines. + * @param preserveAspectRatio This flag will cause the rasterizer to select the smaller of both scaling ratios for + * both dimensions to keep aspect ratio the same in the output. + * @param useQuantifiedPositions Algorithm selection flag: + * If set to true, output dot positions will be quantified. + * If set to false, simple linear mapping will be applied instead. + * @param threshold Gray scale threshold which determines whether a pixel in the original image sets a dot in the output. + */ + public ImageRasterizer( + final boolean preventOverStretch, + final boolean preserveAspectRatio, + final boolean useQuantifiedPositions, + final int threshold) { + mPreventOverStretch = preventOverStretch; + mPreserveAspectRatio = preserveAspectRatio; + mQuantifiedPositions = useQuantifiedPositions; + mLowThreshold = threshold; + } + + /** + * Rasterizes a {@link Image} instance onto a {@link RasterCanvas}. + * @param imgData A instance of {@link Image} representing the renderable image. + * @param canvas A instance of {@link RasterCanvas} representing the target for the rasterizer output. + */ + @Override + public void rasterize(final Image imgData, final RasterCanvas canvas) { + + // Each rasterizer essentially works by taking an instance of a Renderable (in this case Image) and then + // creating a graphical representation of the object on the raster canvas. + + // In this example a raster image will be rasterized onto the canvas by two selectable methods: + // - linear mapping + // - quantified positions + + // Both methods are similar, since the given image is already represented on a raster, which means that the + // basic question is just how to map from one raster to the other. This is were the methods differ. + // Another question is when to set a dot, which in this example is done by a simple threshold for + // the grey scale value of each pixel. + // A more sophisticated implementation could utilize an edge finding algorithm. + + // First, a readable representation of the is retrieved. + BufferedImage imgBuf = imgData.getBufferedImage(); + + // Then the selected method is applied. + // Implementing the rasterizer as class comes in handy. Subtasks can be splitted into help + // methods and different modular rasterizers can be reused by calling them inside the rasterize method. + if (mQuantifiedPositions) { + quantifiedPositionMapping(imgBuf, canvas); + } else { + linearMapping(imgBuf, canvas); + } + } + + private void linearMapping(final BufferedImage imgBuf, final RasterCanvas canvas) { + + // A canvas is basically a wrapper for multiple representations of printable data, each representing a page. + // These representations can be acquired by either requesting the current page or creating a new page. + MatrixData<Boolean> data = canvas.getNewPage(); + + // The raster canvas delivers meta information about the underlying format and raster geometry. + // A vital information are the cell- and dot-rectangle of the canvas. Every rasterizer should take care of at + // least these rectangles which represent the portion of the raster that the rasterizer is expected to work on. + Rectangle availableArea = canvas.getDotRectangle(); + // A rasterizer trying to set values outside of the dot rectangle will cause an exception. + // Other information about the grid (spacing of cells and dots, ...) is available but must not always be + // regarded, depending on the use case. + + // Calculate the ratios between original image and target raster. (resolution 'shrink' factor) + double hRatio = (availableArea.getWidth() - 1) / imgBuf.getWidth(); + double vRatio = (availableArea.getHeight() - 1) / imgBuf.getHeight(); + + if (mPreventOverStretch) { + // In case that the given images resolution is smaller than the grid on at least one dimension + // this prevents it to be 'stretched' on the output, leading to 'cuts' in former solid lines. + // The maximum ratio is 1 for the linear mapping, meaning that the pixel position would be the + // exact dot position in the output. + // A ratio smaller than 1 would mean that some pixels will be mapped to the same dot to fit the output, + // but without any regard to the spacing between the single dots. + hRatio = min(hRatio, 1); + vRatio = min(hRatio, 1); + } + if (mPreserveAspectRatio) { + // This selects the smaller of both ratios for both dimensions to keep aspect ratio the same in the output. + hRatio = min(hRatio, vRatio); + vRatio = min(vRatio, vRatio); + } + + // Linear Mapping: The pixel position of the original image is linearly mapped to the dot position. + // This can lead to distortions because the original pixel raster is equidistant, but the output raster + // does not have to be equidistant. + + // Scan through each pixel of the original image + for (int x = 0; x < imgBuf.getWidth(); x++) { + // Convert from original pixel x-position to braille dot x-position. + // Linear mapping: The conversion happens disregarding the grid spacing (dot and cell distances) + int column = (int) round(hRatio * (x + 1)); + for (int y = 0; y < imgBuf.getHeight(); y++) { + // Convert from original pixel y-position to braille dot x-position. + int row = (int) round(vRatio * (y + 1)); + // Calculate gray scale value and compare against threshold. + int value = toGrayScaleValue(imgBuf.getRGB(x, y)); + if (value <= mLowThreshold) { + data.setValue(row, column, true); + } + } + } + } + + private void quantifiedPositionMapping(final BufferedImage imgBuf, final RasterCanvas canvas) { + + MatrixData<Boolean> data = canvas.getNewPage(); + + // Instead of using the dot rectangle a rectangle representing the target printing space in millimeters + // is built from the canvas information. + Rectangle availableArea = new Rectangle(0, 0, canvas.getPrintableWidth(), canvas.getPrintableHeight()); + + // Calculate the ratios between original image and target printable area. (mm / pixel) + double hRatio = (availableArea.getWidth() / imgBuf.getWidth()); + double vRatio = (availableArea.getHeight() / imgBuf.getHeight()); + + if (mPreventOverStretch) { + // Here, the maximum ratio is not 1 as in the linear mapping but instead equal to the regarding dot + // distances. This is because the ratios are not measured in dots/pixel but mm/pixel. + hRatio = min(hRatio, canvas.getHorizontalDotDistance()); + vRatio = min(vRatio, canvas.getVerticalDotDistance()); + } + if (mPreserveAspectRatio) { + hRatio = min(hRatio, vRatio); + vRatio = min(hRatio, vRatio); + } + + // Quantified Positions: The pixel position of the original image is linearly mapped to the respective + // millimeter position on the printed area of the page. This step preserves the original distance ratios. + // In a second step, the calculated exact position is quantified to fit a dot position on the raster. + // Distortions can still be introduced but are minimized. + + // Scan through all pixels of the original image + for (int x = 0; x < imgBuf.getWidth(); x++) { + // Convert from original pixel x-position to printed dot x-position in millimeters. + // In contrast to the linear mapping, this will try to preserve the original distance ratios. + double columnMM = hRatio * (x + 1); + for (int y = 0; y < imgBuf.getHeight(); y++) { + // Convert from original pixel y-position to printed dot y-position in millimeters. + double rowMM = vRatio * (y + 1); + // Calculate gray scale value and compare against threshold. + int value = toGrayScaleValue(imgBuf.getRGB(x, y)); + if (value <= mLowThreshold) { + // The target dot position in millimeters has to be quantified regarding the raster. + int row = canvas.quantifyY(rowMM); + int column = canvas.quantifyX(columnMM); + data.setValue(row, column, true); + } + } + } + } + + + private int toGrayScaleValue(final int rgb) { + final int colorChannels = 3; + final int bitsPerChannel = 8; + final int byteMask = 0xff; + int r = (rgb >> 2 * bitsPerChannel) & byteMask; + int g = (rgb >> bitsPerChannel) & byteMask; + int b = (rgb) & byteMask; + return ((r + g + b) / colorChannels); + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/InsufficientRenderingAreaException.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/InsufficientRenderingAreaException.java new file mode 100644 index 0000000000000000000000000000000000000000..a422a1c69aacc7ecbb2c7123466fbfc37fed55ce --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/InsufficientRenderingAreaException.java @@ -0,0 +1,25 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +/** + * Exception that indicates too few space available to display the amount of data contained in the given renderable representation. + * Typical circumstances that lead the rasterizer/plotter to throw this exception are that there are simply too much elements to + * display them physically in the given raster/area or that the value range cannot be mapped to the given output resolution. + * @author Leonard Kupper + * @version 2019.07.20 + */ +public class InsufficientRenderingAreaException extends Exception { + + public InsufficientRenderingAreaException() { } + + public InsufficientRenderingAreaException(final String message) { + super(message); + } + + public InsufficientRenderingAreaException(final Throwable cause) { + super(cause); + } + + public InsufficientRenderingAreaException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LinearMappingAxisRasterizer.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LinearMappingAxisRasterizer.java new file mode 100644 index 0000000000000000000000000000000000000000..208fb2b0b0efc28cf9209c5dec05923c57c61e9a --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/LinearMappingAxisRasterizer.java @@ -0,0 +1,122 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import de.tudresden.inf.mci.brailleplot.printabledata.MatrixData; + +import static de.tudresden.inf.mci.brailleplot.rendering.Axis.Type.X_AXIS; +import static de.tudresden.inf.mci.brailleplot.rendering.Axis.Type.Y_AXIS; +import static java.lang.Math.abs; + +/** + * A rasterizer for instances of {@link Axis} which is using a simple approach by linear mapping. + * @author Leonard Kupper + * @version 2019.07.20 + */ + +public class LinearMappingAxisRasterizer implements Rasterizer<Axis> { + + private BrailleTextRasterizer mTextRasterizer = new BrailleTextRasterizer(); + private RasterCanvas mCanvas; + + /** + * Rasterizes a {@link Axis} instance onto a {@link RasterCanvas}. + * @param axis A instance of {@link Axis} representing the visual diagram axis. + * @param canvas A instance of {@link RasterCanvas} representing the target for the rasterizer output. + * @throws InsufficientRenderingAreaException If too few space is available on the {@link RasterCanvas} + * to display the given axis. + */ + @Override + public void rasterize(final Axis axis, final RasterCanvas canvas) throws InsufficientRenderingAreaException { + + mCanvas = canvas; + MatrixData<Boolean> data = mCanvas.getCurrentPage(); + + int dotX, startY, endY, dotY, startX, endX; + int stepWidth = (int) axis.getStepWidth(); + int tickSize = (int) axis.getTickSize(); + boolean setTicks = (abs(tickSize) > 0); + boolean hasLabels = axis.hasLabels(); + + if (axis.getType() == X_AXIS) { + Rectangle bound; + dotY = (int) axis.getOriginY(); + if (axis.hasBoundary()) { + bound = axis.getBoundary(); + } else { + bound = mCanvas.getDotRectangle(); + } + startX = bound.intWrapper().getX(); + endX = bound.intWrapper().getRight(); + Rasterizer.fill(startX, dotY, endX, dotY, data, true); + + if (setTicks) { + int i; + startY = dotY; + endY = dotY + tickSize; + i = 0; + for (dotX = (int) axis.getOriginX(); dotX <= endX; dotX += stepWidth) { + Rasterizer.fill(dotX, startY, dotX, endY, data, true); + // TODO: refactor to have labeling functionality in extra method. + if (hasLabels && axis.getLabels().containsKey(i)) { + String label = axis.getLabels().get(i); + Rectangle labelArea = new Rectangle(dotX - 1, endY + 1, stepWidth, mCanvas.getCellHeight()); + mTextRasterizer.rasterize(new BrailleText(label, labelArea), mCanvas); + } + i++; + } + i = -1; + for (dotX = (int) axis.getOriginX() - stepWidth; dotX >= startX; dotX -= stepWidth) { + Rasterizer.fill(dotX, startY, dotX, endY, data, true); + if (hasLabels && axis.getLabels().containsKey(i)) { + String label = axis.getLabels().get(i); + Rectangle labelArea = new Rectangle(dotX - 1, endY + 1, stepWidth, mCanvas.getCellHeight()); + mTextRasterizer.rasterize(new BrailleText(label, labelArea), mCanvas); + } + i--; + } + } + } + + if (axis.getType() == Y_AXIS) { + Rectangle bound; + dotX = (int) axis.getOriginX(); + if (axis.hasBoundary()) { + bound = axis.getBoundary(); + } else { + bound = mCanvas.getDotRectangle(); + } + startY = bound.intWrapper().getY(); + endY = bound.intWrapper().getBottom(); + Rasterizer.fill(dotX, startY, dotX, endY, data, true); + + if (setTicks) { + int i; + startX = dotX; + endX = dotX + tickSize; + i = 0; + for (dotY = (int) axis.getOriginY(); dotY <= endY; dotY += stepWidth) { + Rasterizer.fill(startX, dotY, endX, dotY, data, true); + /* + if (hasLabels && axis.getLabels().containsKey(i)) { + String label = axis.getLabels().get(i); + Rectangle labelArea = new Rectangle(endX + Integer.signum(tickSize), dotY, stepWidth, mCanvas.getCellHeight()); + mTextRasterizer.rasterize(new BrailleText(label, labelArea), mCanvas); + } + */ + i++; + } + i = -1; + for (dotY = (int) axis.getOriginY() - stepWidth; dotY >= startY; dotY -= stepWidth) { + Rasterizer.fill(startX, dotY, endX, dotY, data, true); + /* + if (hasLabels && axis.getLabels().containsKey(i)) { + String label = axis.getLabels().get(i); + Rectangle labelArea = new Rectangle(endX + Integer.signum(tickSize), dotY, stepWidth, mCanvas.getCellHeight()); + mTextRasterizer.rasterize(new BrailleText(label, labelArea), mCanvas); + } + */ + i--; + } + } + } + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/MasterRenderer.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/MasterRenderer.java new file mode 100644 index 0000000000000000000000000000000000000000..241f0a5d64c22244f81986370884a2731001fe13 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/MasterRenderer.java @@ -0,0 +1,92 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import de.tudresden.inf.mci.brailleplot.configparser.Format; +import de.tudresden.inf.mci.brailleplot.configparser.Printer; + +import java.util.Objects; + +/** + * MasterRenderer. This class is the main interface for conversion of a diagram representation into a printable format and manages the current rendering context. + * The MasterRenderer takes representations of any diagram type, calculates the available raster/area from the given printer and format configuration and dispatches + * any calls to the 'rasterize' and 'plot' methods to the given {@link FunctionalRenderingBase}. + * @author Leonard Kupper + * @version 2019.07.20 + */ +public final class MasterRenderer { + + Printer mPrinter; + Format mFormat; + FunctionalRenderingBase mRenderingBase; + + public MasterRenderer(final Printer printer, final Format format) { + // if no rendering base is given, create own rendering base with default set of algorithms + FunctionalRenderingBase renderingBase = new FunctionalRenderingBase(); + + // Default Algorithms: + + // Rasterizer<BarChart> uniformTexture = new UniformTextureBarChartRasterizer(); + Rasterizer<Image> linearImageMapping = new ImageRasterizer(); + + // renderingBase.registerRasterizer(new FunctionalRasterizer<BarChart>(BarChart.class, uniformTexture)); + renderingBase.registerRasterizer(new FunctionalRasterizer<Image>(Image.class, linearImageMapping)); + //renderingBase.registerRasterizer(new FunctionalRasterizer<ScatterPlot>(ScatterPlot.class, ScatterPlotRasterizing::fooRasterizing)); + //... + + setRenderingContext(printer, format, renderingBase); + } + + public MasterRenderer(final Printer printer, final Format format, final FunctionalRenderingBase renderingBase) { + setRenderingContext(printer, format, renderingBase); + } + + public RasterCanvas rasterize(final Renderable data) throws InsufficientRenderingAreaException { + RasterCanvas canvas = createCompatibleRasterCanvas(); + mRenderingBase.setRasterCanvas(canvas); + mRenderingBase.rasterize(data); + return canvas; + } + + private RasterCanvas createCompatibleRasterCanvas() throws InsufficientRenderingAreaException { + + return new SixDotBrailleRasterCanvas(mPrinter, mFormat); + + /* + TODO: support 6 and 8 dot layout# + String rasterType = mPrinter.getProperty("raster.type").toString(); + if (rasterType == "6-dot") { + return new SixDotBrailleRasterCanvas(mPrinter, mFormat); + } else { + ... + } + */ + } + + // Getter & Setter + + public void setRenderingContext(final Printer printer, final Format format, final FunctionalRenderingBase renderingBase) { + setPrinter(printer); + setFormat(format); + setRenderingBase(renderingBase); + } + + public void setPrinter(final Printer printer) { + mPrinter = Objects.requireNonNull(printer); + } + public Printer getPrinter() { + return mPrinter; + } + + public void setFormat(final Format format) { + mFormat = Objects.requireNonNull(format); + } + public Format getFormat() { + return mFormat; + } + + public void setRenderingBase(final FunctionalRenderingBase renderingBase) { + mRenderingBase = Objects.requireNonNull(renderingBase); + } + public FunctionalRenderingBase getRenderingBase() { + return mRenderingBase; + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/RasterCanvas.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/RasterCanvas.java new file mode 100644 index 0000000000000000000000000000000000000000..541345770c4c402273af81ba0f60f406d1fdb6d5 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/RasterCanvas.java @@ -0,0 +1,269 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import de.tudresden.inf.mci.brailleplot.configparser.Format; +import de.tudresden.inf.mci.brailleplot.configparser.Printer; +import de.tudresden.inf.mci.brailleplot.printabledata.MatrixData; +import de.tudresden.inf.mci.brailleplot.printabledata.SimpleMatrixDataImpl; + +import java.util.ArrayList; + +import static java.lang.Math.abs; +import static java.lang.Math.ceil; +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 + */ +public class RasterCanvas extends AbstractCanvas { + + private ArrayList<Double> mXPositions; + private ArrayList<Double> mYPositions; + + // Raster size + private int mHorizontalCellCount; + private int mVerticalCellCount; + private int mColumnCount; + private int mRowCount; + + // Printing area rectangle + private Rectangle mPrintingAreaCells; + private Rectangle mPrintingAreaDots; + + // Cell size + private int mCellWidth; // dots + private int mCellHeight; // dots + private double mCellHorizontalMM; // millimeters + private double mCellVerticalMM; // millimeters + + // Spacing + private double mHorizontalDotDistance; + private double mVerticalDotDistance; + private double mHorizontalCellDistance; + private double mVerticalCellDistance; + private double mDotDiameter; + + /** + * Constructor. Creates a new RasterCanvas, which is a canvas that represents it pages as instances of + * {@link MatrixData} and holds information about the layout and spacing of the underlying raster grid. + * The described grid is build from uniform 'cells' consisting of a variable amount of dots. + * It is used as a target on which can be drawn by a {@link Rasterizer}. + * @param printer The {@link Printer} configuration to be used. + * @param format The {@link Format} configuration to be used. + * @param cellWidth The horizontal count of dots in a cell. + * @param cellHeight The vertical count of dots in a cell. + * @throws InsufficientRenderingAreaException If the given configuration leads to an printable area of negative + * size or zero size, e.g. if the sum of defined margins and constraints adds up to be greater than the original page size. + */ + RasterCanvas(final Printer printer, final Format format, final int cellWidth, final int cellHeight) + throws InsufficientRenderingAreaException { + + super(printer, format); + + // Cell size in dots + mCellWidth = cellWidth; + mCellHeight = cellHeight; + + readConfig(); + calculateRasterSize(); + calculateSpacing(); + + } + + public final MatrixData<Boolean> getNewPage() { + mPageContainer.add(new SimpleMatrixDataImpl<Boolean>(mPrinter, mFormat, mRowCount, mColumnCount, false)); + 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); + } + + private void readConfig() { + + // What are the dot and cell distances in mm? + mHorizontalDotDistance = mPrinter.getProperty("raster.dotDistance.horizontal").toDouble(); + mVerticalDotDistance = mPrinter.getProperty("raster.dotDistance.vertical").toDouble(); + mHorizontalCellDistance = mPrinter.getProperty("raster.cellDistance.horizontal").toDouble(); + mVerticalCellDistance = mPrinter.getProperty("raster.cellDistance.vertical").toDouble(); + mDotDiameter = mPrinter.getProperty("raster.dotDiameter").toDouble(); + + // Calculate cell size in mm + mCellHorizontalMM = mHorizontalDotDistance * (mCellWidth - 1) + mHorizontalCellDistance; // Full width of one cell + padding in mm + mCellVerticalMM = mVerticalDotDistance * (mCellHeight - 1) + mVerticalCellDistance; // Full height of one cell + padding in mm + + } + + private void calculateRasterSize() throws InsufficientRenderingAreaException { + + // New approach using a box model: + + // Dividing the printable area into cells to create a cell raster box. + int cellRasterX = (int) ceil(mPrintableArea.getX() / mCellHorizontalMM); + int cellRasterY = (int) ceil(mPrintableArea.getY() / mCellVerticalMM); + int cellRasterR = (int) floor((mPrintableArea.getRight() + mHorizontalCellDistance) / mCellHorizontalMM); + int cellRasterB = (int) floor((mPrintableArea.getBottom() + mVerticalCellDistance) / mCellVerticalMM); + Rectangle cellRasterBox = new Rectangle( + cellRasterX, cellRasterY, + cellRasterR - cellRasterX, + cellRasterB - cellRasterY + ); + + // The following properties impact the printing area, but are specific to rasterizing. (That's why they weren't read before in the AbstractCanvas) + // The abstract parent class (AbstractCanvas) already calculated indentations based on millimeters, but it is + // also possible to set a raster.indentation counted in amount of cells and lines. Those must be removed additionally. + + // Create a raster constraint box + int rasterConstraintTop = mPrinter.getProperty("raster.constraint.top").toInt(); + int rasterConstraintLeft = mPrinter.getProperty("raster.constraint.left").toInt(); + int rasterConstraintHeight, rasterConstraintWidth; + if (mPrinter.getPropertyNames().contains("raster.constraint.height")) { + rasterConstraintHeight = mPrinter.getProperty("raster.constraint.height").toInt(); + } else { + rasterConstraintHeight = Integer.MAX_VALUE; + } + if (mPrinter.getPropertyNames().contains("raster.constraint.width")) { + rasterConstraintWidth = mPrinter.getProperty("raster.constraint.width").toInt(); + } else { + rasterConstraintWidth = Integer.MAX_VALUE; + } + Rectangle rasterConstraintBox = new Rectangle(rasterConstraintLeft, rasterConstraintTop, + rasterConstraintWidth, rasterConstraintHeight); + + mPrintingAreaCells = calculatePrintingArea(cellRasterBox, rasterConstraintBox); + + // The following values are set to keep track of the 'real' size of the internal data representation, because + // the margins are created virtually by printing some empty cells at the pages top / left edge. + // Rasterizers are only presented with a sub-area rectangle, representing the valid printing area. + + // How many rows and columns of full cells fit inside the given page area (ignoring margins and raster constraints) + mHorizontalCellCount = mPrintingAreaCells.intWrapper().getRight() + 1; // How many full cells fit horizontally? + mVerticalCellCount = mPrintingAreaCells.intWrapper().getBottom() + 1; // How many full cells fit vertically? + + // To how many dots does this raster size correspond? + mPrintingAreaDots = toDotRectangle(mPrintingAreaCells); + mColumnCount = mPrintingAreaDots.intWrapper().getWidth(); + mRowCount = mPrintingAreaDots.intWrapper().getHeight(); + + + } + + private void calculateSpacing() { + + mXPositions = calculateQuantizedPositions(mHorizontalDotDistance, mHorizontalCellDistance, mCellWidth, mHorizontalCellCount); + mYPositions = calculateQuantizedPositions(mVerticalDotDistance, mVerticalCellDistance, mCellHeight, mVerticalCellCount); + + } + + private ArrayList<Double> calculateQuantizedPositions( + final double dotSpacing, + final double cellSpacing, + final int cellSize, + final int cellCount + ) { + ArrayList<Double> positions = new ArrayList<>(); + double position = 0; + for (int i = 0; i < cellCount; i++) { + for (int j = 0; j < cellSize; j++) { + positions.add(position); + if (j < (cellSize - 1)) { + position += dotSpacing; + } + } + position += cellSpacing; + } + return positions; + } + + public final int getCellWidth() { + return mCellWidth; + } + public final int getCellHeight() { + return mCellHeight; + } + + public final double getHorizontalDotDistance() { + return mHorizontalDotDistance; + } + public final double getVerticalDotDistance() { + return mVerticalDotDistance; + } + public final double getHorizontalCellDistance() { + return mHorizontalCellDistance; + } + public final double getVerticalCellDistance() { + return mVerticalCellDistance; + } + public final double getDotDiameter() { + return mDotDiameter; + } + public final Rectangle getCellRectangle() { + return new Rectangle(mPrintingAreaCells); + } + public final Rectangle getDotRectangle() { + return mPrintingAreaDots; + } + public final Rectangle toDotRectangle(final Rectangle cellRectangle) { + return cellRectangle.scaledBy(mCellWidth, mCellHeight); + } + + @Override + public double getPrintableWidth() { + return mXPositions.get(getDotRectangle().intWrapper().getRight()) - mXPositions.get(getDotRectangle().intWrapper().getX()); + } + + @Override + public double getPrintableHeight() { + return mYPositions.get(getDotRectangle().intWrapper().getBottom()) - mYPositions.get(getDotRectangle().intWrapper().getY()); + } + + /** + * Returns the x coordinate (counted in cells) of the cell containing the dot with given x coordinate (counted in dots). + * @param dotX The dot x coordinate. In other words its columns number. + * @return The cell x coordinate. In other words the cells columns number. + */ + public int getCellXFromDotX(final int dotX) { + return dotX / mCellWidth; + } + /** + * Returns the y coordinate (counted in cells) of the cell containing the dot with given y coordinate (counted in dots). + * @param dotY The dot y coordinate. In other words its rows number. + * @return The cell y coordinate. In other words the cells columns number. + */ + public int getCellYFromDotY(final int dotY) { + return dotY / mCellHeight; + } + + + public final int quantifyX(final double unquantifiedMillimeterX) { + return findClosestValueIndex(unquantifiedMillimeterX, mXPositions); + } + + public final int quantifyY(final double unquantifiedMillimeterY) { + return findClosestValueIndex(unquantifiedMillimeterY, mYPositions); + } + + private int findClosestValueIndex(final Double value, final ArrayList<Double> list) { + double minDistance = Double.POSITIVE_INFINITY; + for (int index = 0; index < list.size(); index++) { + double distance = abs(list.get(index) - value); + if (distance < minDistance) { + minDistance = distance; + } else { + // possible, because we know that the positions are sorted. + return index - 1; + } + + } + // last value is the closest. + return (list.size() - 1); + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Rasterizer.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Rasterizer.java new file mode 100644 index 0000000000000000000000000000000000000000..094f03a649868d9cc06171f2fd590351ff0a6909 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Rasterizer.java @@ -0,0 +1,83 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + + +import de.tudresden.inf.mci.brailleplot.printabledata.MatrixData; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +/** + * Rasterizer. A functional interface for anything that is able to rasterize renderable data onto a raster. + * This interface also defines a static set of tool methods for basic operations on a raster's data container ({@link MatrixData}). + * @param <T> The concrete class implementing {@link Renderable} which can be rasterized with the rasterizer. + * @author Leonard Kupper + * @version 2019.07.22 + */ +@FunctionalInterface +public interface Rasterizer<T extends Renderable> { + + /** + * Rasterizes a {@link Renderable} instance onto a {@link RasterCanvas}. + * @param data The renderable representation. + * @param canvas An instance of {@link RasterCanvas} representing the target for the rasterizer output. + * @throws InsufficientRenderingAreaException If too few space is available on the {@link RasterCanvas} + * to display the given data. + */ + void rasterize(T data, RasterCanvas canvas) throws InsufficientRenderingAreaException; + + // Basic geometric rasterizing toolset: + + /** + * Fills the space on the raster between two arbitrary opposite points with a given value. + * @param x1 X coordinate of first point. + * @param y1 Y coordinate of first point. + * @param x2 X coordinate of second point. + * @param y2 Y coordinate of second point. + * @param data The target raster data container. + * @param value The value to fill the area with. + */ + static void fill(int x1, int y1, int x2, int y2, MatrixData<Boolean> data, boolean value) { + int xMin = min(x1, x2); + int xMax = max(x1, x2); + int yMin = min(y1, y2); + int yMax = max(y1, y2); + for (int y = yMin; y <= yMax; y++) { + for (int x = xMin; x <= xMax; x++) { + data.setValue(y, x, value); + } + } + } + + /** + * Draws a rectangle border with a given value onto the raster. The rectangle is defined by two arbitrary opposite points. + * @param x1 X coordinate of first point. + * @param y1 Y coordinate of first point. + * @param x2 X coordinate of second point. + * @param y2 Y coordinate of second point. + * @param data The target raster data container. + * @param value The value to fill the area with. + */ + static void rectangle(int x1, int y1, int x2, int y2, MatrixData<Boolean> data, boolean value) { + int xMin = min(x1, x2); + int xMax = max(x1, x2); + int yMin = min(y1, y2); + int yMax = max(y1, y2); + rectangle(new Rectangle(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1), data, value); + } + + /** + * Draws a rectangle border with a given value onto the raster. + * @param rect The {@link Rectangle} instance to draw. + * @param data The target raster data container. + * @param value The value to fill the area with. + */ + static void rectangle(Rectangle rect, MatrixData<Boolean> data, boolean value) { + Rectangle.IntWrapper intRect = rect.intWrapper(); + int x2 = max(intRect.getX() + intRect.getWidth() - 1, 0); + int y2 = max(intRect.getY() + intRect.getHeight() - 1, 0); + fill(intRect.getX(), intRect.getY(), intRect.getX(), y2, data, value); + fill(intRect.getX(), y2, x2, y2, data, value); + fill(x2, intRect.getY(), x2, y2, data, value); + fill(intRect.getX(), intRect.getY(), x2, intRect.getY(), data, value); + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Rectangle.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Rectangle.java new file mode 100644 index 0000000000000000000000000000000000000000..b0289a10b097dfc84f6f5f1038f612d697445c36 --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Rectangle.java @@ -0,0 +1,411 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import java.util.Objects; + +import static java.lang.Math.round; +import static java.lang.Math.min; +import static java.lang.Math.max; + +/** + * Represents a rectangle that can be continuously divided into partitions. Can be used for doing chart layout and as + * boundary of renderable objects. + * @author Leonard Kupper + * @version 2019.07.12 + */ +public class Rectangle { + + private double mX, mY, mW, mH; + + /** + * Constructor. Creates a rectangle with given position and size. + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param w The width of the rectangle, meaning its rightward expanse. + * @param h The height of the rectangle, meaning its downward expanse. + */ + public Rectangle(final double x, final double y, final double w, final double h) { + setX(x); + setY(y); + setWidth(w); + setHeight(h); + } + + /** + * Copy constructor. Creates a copy of a rectangle. + * @param rect The rectangle to be copied. + */ + public Rectangle(final Rectangle rect) { + setX(rect.getX()); + setY(rect.getY()); + setWidth(rect.getWidth()); + setHeight(rect.getHeight()); + } + + /** + * Removes a partition from the rectangles top and returns it. + * @param height The height of the partition that will be removed. + * @return A rectangle representing the cropped partition. + * @throws OutOfSpaceException If the requested partition is greater than the underlying rectangle itself. + */ + public Rectangle removeFromTop(final double height) throws OutOfSpaceException { + Rectangle removedPartition = fromTop(height); + double newY = (getY() + height); + double newHeight = (getHeight() - height); + setY(newY); + setHeight(newHeight); + return removedPartition; + } + + /** + * Removes a partition from the rectangles bottom and returns it. + * @param height The height of the partition that will be removed. + * @return A rectangle representing the cropped partition. + * @throws OutOfSpaceException If the requested partition is greater than the underlying rectangle itself. + */ + public Rectangle removeFromBottom(final double height) throws OutOfSpaceException { + Rectangle removedPartition = fromBottom(height); + double newHeight = (getHeight() - height); + setHeight(newHeight); + return removedPartition; + } + + /** + * Removes a partition from the rectangles left side and returns it. + * @param width The width of the partition that will be removed. + * @return A rectangle representing the cropped partition. + * @throws OutOfSpaceException If the requested partition is greater than the underlying rectangle itself. + */ + public Rectangle removeFromLeft(final double width) throws OutOfSpaceException { + Rectangle removedPartition = fromLeft(width); + double newX = (getX() + width); + double newWidth = (getWidth() - width); + setX(newX); + setWidth(newWidth); + return removedPartition; + } + + /** + * Removes a partition from the rectangles right side and returns it. + * @param width The width of the partition that will be removed. + * @return A rectangle representing the cropped partition. + * @throws OutOfSpaceException If the requested partition is greater than the underlying rectangle itself. + */ + public Rectangle removeFromRight(final double width) throws OutOfSpaceException { + Rectangle removedPartition = fromRight(width); + double newWidth = (getWidth() - width); + setWidth(newWidth); + return removedPartition; + } + + // Methods to get a rectangle partition + + /** + * Gets a partition from the rectangles top without removing it from the original instance. + * @param height The height of the selected partition. + * @return A rectangle representing the selected partition. + * @throws OutOfSpaceException If the requested partition is greater than the underlying rectangle itself. + */ + public Rectangle fromTop(final double height) throws OutOfSpaceException { + checkHeight(height); + return new Rectangle(getX(), getY(), getWidth(), height); + } + + /** + * Gets a partition from the rectangles left side without removing it from the original instance. + * @param width The width of the selected partition. + * @return A rectangle representing the selected partition. + * @throws OutOfSpaceException If the requested partition is greater than the underlying rectangle itself. + */ + public Rectangle fromLeft(final double width) throws OutOfSpaceException { + checkWidth(width); + return new Rectangle(getX(), getY(), width, getHeight()); + } + + /** + * Gets a partition from the rectangles bottom without removing it from the original instance. + * @param height The height of the selected partition. + * @return A rectangle representing the selected partition. + * @throws OutOfSpaceException If the requested partition is greater than the underlying rectangle itself. + */ + public Rectangle fromBottom(final double height) throws OutOfSpaceException { + checkHeight(height); + double newY = (getY() + (getHeight() - height)); + return new Rectangle(getX(), newY, getWidth(), height); + } + + /** + * Gets a partition from the rectangles right side without removing it from the original instance. + * @param width The width of the selected partition. + * @return A rectangle representing the selected partition. + * @throws OutOfSpaceException If the requested partition is greater than the underlying rectangle itself. + */ + public Rectangle fromRight(final double width) throws OutOfSpaceException { + checkWidth(width); + double newX = (getX() + (getWidth() - width)); + return new Rectangle(newX, getY(), width, getHeight()); + } + + // Help methods for validity check of requested partition + + private void checkHeight(final double h) throws OutOfSpaceException { + if (h > getHeight()) { + throw new OutOfSpaceException("The rectangle partition height cannot be greater than its parent rectangle height." + + "(" + h + ">" + getHeight() + ")"); + } + } + private void checkWidth(final double w) throws OutOfSpaceException { + if (w > getWidth()) { + throw new OutOfSpaceException("The rectangle partition width cannot be greater than its parent rectangle width." + + "(" + w + ">" + getWidth() + ")"); + } + } + + // Getters for edge positions and size + + /** + * Gets the rectangles x position. + * @return The x coordinate of the upper left corner. + */ + public double getX() { + return mX; + } + + /** + * Gets the rectangles y position. + * @return The y coordinate of the upper left corner. + */ + public double getY() { + return mY; + } + + /** + * Gets the rectangles width. + * @return The distance between the rectangles left and right edge. + */ + public double getWidth() { + return mW; + } + + /** + * Gets the rectangles height. + * @return The distance between the rectangles top and bottom edge. + */ + public double getHeight() { + return mH; + } + + /** + * Gets the rectangles right edges position. + * @return The x coordinate of the lower right corner. + */ + public double getRight() { + return mX + mW; + } + + /** + * Gets the rectangles bottom edges position. + * @return The y coordinate of the lower right corner. + */ + public double getBottom() { + return mY + mH; + } + + /** + * Sets a new x position for the rectangle. + * @param x The new x coordinate of the upper left corner. + */ + public void setX(final double x) { + mX = x; + } + + /** + * Sets a new y position for the rectangle. + * @param y The new y coordinate of the upper left corner. + */ + public void setY(final double y) { + mY = y; + } + + /** + * Sets a new width for the rectangle. + * @param width The new width value. + */ + public void setWidth(final double width) { + if (width < 0) { + throw new IllegalArgumentException("The width can't be negative."); + } + mW = width; + } + + /** + * Sets a new height for the rectangle. + * @param height The new height value. + */ + public void setHeight(final double height) { + if (height < 0) { + throw new IllegalArgumentException("The height can't be negative."); + } + mH = height; + } + + /** + * Returns a scaled version of the original rectangle. + * @param xScale The x-axis scale factor + * @param yScale The y-axis scale factor + * @return New rectangle with scaled position and size. + */ + public Rectangle scaledBy(final double xScale, final double yScale) { + return new Rectangle(mX * xScale, mY * yScale, mW * xScale, mH * yScale); + } + + /** + * Returns a new rectangle representing the intersection of this rectangle with another rectangle. + * @param otherRectangle The other rectangle to intersect with this. + * @return New rectangle representing the intersection. + */ + public Rectangle intersectedWith(final Rectangle otherRectangle) { + double itsctX = max(getX(), otherRectangle.getX()); + double itsctY = max(getY(), otherRectangle.getY()); + double itsctB = min(getBottom(), otherRectangle.getBottom()); + double itsctR = min(getRight(), otherRectangle.getRight()); + return new Rectangle(itsctX, itsctY, max(0, itsctR - itsctX), max(0, itsctB - itsctY)); + } + + /** + * Returns a translated copy of this rectangle. + * @param alongX The distance to move the copy along x axis. + * @param alongY The distance to move the copy along y axis. + * @return A new rectangle representing a translated copy of this rectangle. + */ + public Rectangle translatedBy(final double alongX, final double alongY) { + return new Rectangle(getX() + alongX, getY() + alongY, getWidth(), getHeight()); + } + + @Override + public String toString() { + return "x:" + getX() + ", y:" + getY() + ", w:" + getWidth() + ", h:" + getHeight(); + } + + // Wrapper to make it easy to read integer values from rectangle. + + /** + * Retrieves a proxy object as integer coordinate representation of the rectangle. This is meant as a shortcut + * for otherwise frequent int-casting when using rectangles on a integer based coordinate system. + * @return An instance of {@link IntWrapper} proxying this rectangle. + */ + public IntWrapper intWrapper() { + return new IntWrapper(this); + } + + /** + * Wrapper of rectangle for integer coordinates. + * @author Leonard Kupper + * @version 2019.07.22 + */ + public final class IntWrapper { + + private Rectangle mRectangle; + + /** + * Constructor. Creates a wrapper as proxy object for an integer coordinate representation of the given rectangle. + * @param rectangle The rectangle to be wrapped. + */ + public IntWrapper(final Rectangle rectangle) { + this.mRectangle = Objects.requireNonNull(rectangle); + } + + + private int wrapInt(final double value) { + return Math.toIntExact(round(value)); + } + + /** + * Gets the rectangles x position. + * @return The x coordinate of the upper left corner. + */ + public int getX() { + return wrapInt(mRectangle.getX()); + } + + /** + * Gets the rectangles y position. + * @return The y coordinate of the upper left corner. + */ + public int getY() { + return wrapInt(mRectangle.getY()); + } + + /** + * Gets the rectangles width. + * @return The distance between the rectangles left and right edge. + */ + public int getWidth() { + return wrapInt(mRectangle.getWidth()); + } + + /** + * Gets the rectangles height. + * @return The distance between the rectangles top and bottom edge. + */ + public int getHeight() { + return wrapInt(mRectangle.getHeight()); + } + + /** + * Gets the rectangles right edges position (<b>Important:</b> The IntWrapper treats the rectangle as + * representation of a 'whole' area composed of single countable units (e.g. dots or cells) so this method will + * return the position of the rightmost contained coordinate, which is x+width-1) + * @return The x coordinate of the inner contained right edge. + */ + public int getRight() { + return wrapInt(mRectangle.getRight()) - 1; + } + + /** + * Gets the rectangles bottom edges position (<b>Important:</b> The IntWrapper treats the rectangle as + * representation of a 'whole' area composed of single countable units (e.g. dots or cells) so this method will + * return the position of the bottommost contained coordinate, which is y+height-1) + * @return The y coordinate of the inner contained bottom edge. + */ + public int getBottom() { + return wrapInt(mRectangle.getBottom()) - 1; + } + + /** + * Get the original rectangle, wrapped by this IntWrapper. + * @return The wrapped instance of {@link Rectangle}. + */ + public Rectangle getRectangle() { + return mRectangle; + } + + @Override + public String toString() { + return "x:" + getX() + ", y:" + getY() + ", w:" + getWidth() + ", h:" + getHeight(); + } + } + + /** + * Exception that indicates that an operation on the rectangle has failed because there was too few space available. + * Typically this is caused by trying to get a partition of the rectangle which is bigger than its parent rectangle itself. + * @author Leonard Kupper + * @version 2019.07.12 + */ + public class OutOfSpaceException extends Exception { + + public OutOfSpaceException() { } + + public OutOfSpaceException(final String message) { + super(message); + } + + public OutOfSpaceException(final Throwable cause) { + super(cause); + } + + public OutOfSpaceException(final String message, final Throwable cause) { + super(message, cause); + } + } + +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Renderable.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Renderable.java new file mode 100644 index 0000000000000000000000000000000000000000..992d9826d40a917af66322710e406681f28d245d --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/Renderable.java @@ -0,0 +1,10 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +/** + * An interface for everything that can be rasterized and plotted. + * @author Leonard Kupper + * @version 2019.07.04 + */ +public interface Renderable { + // Not much going on here... +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/SixDotBrailleRasterCanvas.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/SixDotBrailleRasterCanvas.java new file mode 100644 index 0000000000000000000000000000000000000000..62b57a9468f1c18c4b966d1ac6d35f518935eb4b --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/SixDotBrailleRasterCanvas.java @@ -0,0 +1,19 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import de.tudresden.inf.mci.brailleplot.configparser.Format; +import de.tudresden.inf.mci.brailleplot.configparser.Printer; + +/** + * Represents a raster consisting of 6-dot braille cells. (May be removed completely in favor of dynamic {@link RasterCanvas}) + * @author Leonard Kupper + * @version 2019.07.20 + */ +class SixDotBrailleRasterCanvas extends RasterCanvas { + + private static final int CELL_WIDTH = 2; + private static final int CELL_HEIGHT = 3; + + SixDotBrailleRasterCanvas(final Printer printer, final Format format) throws InsufficientRenderingAreaException { + super(printer, format, CELL_WIDTH, CELL_HEIGHT); + } +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/ThrowingBiConsumer.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/ThrowingBiConsumer.java new file mode 100644 index 0000000000000000000000000000000000000000..8118436deeee1ab8fc52fc5d22d5af98a459e82a --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/ThrowingBiConsumer.java @@ -0,0 +1,15 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +/** + * A functional interface representing a void function taking two parameters of types T and U which can throw an + * exception E on failure. + * @param <T> First parameter type. + * @param <U> Second parameter type. + * @param <E> Exception type. + * @author Leonard Kupper + * @version 2019.07.09 + */ +@FunctionalInterface +interface ThrowingBiConsumer<T, U, E extends Exception> { + void accept(T t, U u) throws E; +} diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/UniformTextureBarChartRasterizer.java.txt b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/UniformTextureBarChartRasterizer.java.txt new file mode 100644 index 0000000000000000000000000000000000000000..06f33fa6b4bd09044cd751b4d7d96901c94668db --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/UniformTextureBarChartRasterizer.java.txt @@ -0,0 +1,312 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import de.tudresden.inf.mci.brailleplot.printabledata.MatrixData; +import de.tudresden.inf.mci.brailleplot.diagrams.BarChart; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static java.lang.Math.*; + +/** + * A rasterizer for instances of {@link BarChart} which is based on an algorithm that constructs horizontal category + * bars that are filled with a uniform texture. The rasterizer is 'cell' based, working on 6-dot or 8-dot layouts. + * @author Leonard Kupper + * @version 2019.07.20 + */ +final class UniformTextureBarChartRasterizer implements Rasterizer<BarChart> { + + BarChart mDiagram; + RasterCanvas mCanvas; + MatrixData<Boolean> mData; + + // TODO: move some of these into the format config. + // algorithm specific constants + private final double[] mUnitScalings = {0.1, 0.125, 0.25, 0.5, 1.0}; + private final int mTextureUnitSize = 2; // dots + private final int mBarMinThickness = 5; // dots + private final int mBarMaxThickness = 9; // dots + private final int mBarDotPadding = 1; + private final int mExtraBarCellPadding = 0; // cells + private final int mBarInCellPadding = 2; // dots + private final boolean mLeftCaption = true; + private final int mCaptionLength = 6; // cells + + // associated rasterizers + private BrailleTextRasterizer mTextRasterizer; + private LinearMappingAxisRasterizer mAxisRasterizer; + //private Rasterizer<Legend> mLegendRasterizer; + + /** + * Constructor. Create a new rasterizer for instances of {@link BarChart}. + */ + UniformTextureBarChartRasterizer() { + mTextRasterizer = new BrailleTextRasterizer(); + mAxisRasterizer = new LinearMappingAxisRasterizer(); + //mLegendRasterizer = new LegendRasterizer(); + } + + /** + * Rasterizes a {@link BarChart} instance onto a {@link RasterCanvas}. + * @param diagram A instance of {@link BarChart} representing the bar chart diagram. + * @param canvas A instance of {@link RasterCanvas} representing the target for the rasterizer output. + * @throws InsufficientRenderingAreaException If too few space is available on the {@link RasterCanvas} + * to display the given diagram. + */ + @Override + public void rasterize(final BarChart diagram, final RasterCanvas canvas) + throws InsufficientRenderingAreaException { + + // The comments here can only give a very short overview, please see the wiki for a full explanation. + + mDiagram = Objects.requireNonNull(diagram); + mCanvas = Objects.requireNonNull(canvas); + mData = mCanvas.getCurrentPage(); + + checkValidBrailleRaster(); + + + // Basic chart layout happens in the following steps (1-4): + + String strDiagramTitle = "I am a bar chart"; // TODO: WHERE DO WE GET THIS INFO FROM?? + Rectangle barArea = mCanvas.getCellRectangle(); + int titleBarHeight; + Rectangle titleArea, xAxisArea; + + try { + // 1. Reserve space for the diagram title on the top + titleBarHeight = mTextRasterizer.calculateRequiredHeight(strDiagramTitle, 0, 0, + barArea.intWrapper().getWidth() * mCanvas.getCellWidth(), mCanvas); + titleArea = barArea.removeFromTop(mCanvas.getCellYFromDotY(titleBarHeight)); + + // 2. Reserve space for the x-axis on the bottom + xAxisArea = barArea.removeFromBottom(2); + + // 3. Reserve space for bar captions on the left or right side + if (mLeftCaption) { + barArea.removeFromLeft(mCaptionLength); + } else { + barArea.removeFromRight(mCaptionLength); + } + + // 4. One extra cell is removed, because there must always be one cell of space for the y-axis. + barArea.removeFromRight(1); + } catch (Rectangle.OutOfSpaceException e) { + // If the rectangle throws an OutOfSpaceException, this just means that there is no way to fit all + // components of the chart inside the given area. + throw new InsufficientRenderingAreaException("Not enough space to build bar chart layout.", e); + } + + + // The remaining rectangle 'barArea' does now represent the available area for the bars to be displayed. + + + // Now the charts value range and categories are analyzed to figure out where the y-axis (x = 0) shall be + // placed and how to scale the bars to still fit the available space. + + double negValueRangeSize = abs(min(diagram.getMinY(), 0)); + double posValueRangeSize = max(diagram.getMaxY(), 0); + // The complete value range is calculated in a way that it always includes zero, even if all category values + // are positive or negative with absolute values > 0, because the y axis will always be positioned at x = 0. + double valueRangeSize = negValueRangeSize + posValueRangeSize; + + // Calculate the amount of distinguishable units / steps (on the x-axis) that fit into the given space. + int availableUnits = (int) floor(barArea.getWidth() * mCanvas.getCellWidth() / mTextureUnitSize); + + // The width of a single x-axis step depends on the value range and the available units that can be displayed. + // A little helper algorithm tries to find a good scaling such that the axis divisions correspond to the original + // value range in a rational way. (e.g. one step on the x-axis is a half of the order of magnitude of the value range.) + double xAxisStepWidth = findAxisScaling(valueRangeSize, availableUnits); + + // Divide the bar area into a negative and a positive fragment, depending on the value range. + int negUnits = (int) round((negValueRangeSize / valueRangeSize) * availableUnits); + int posUnits = availableUnits - negUnits; + + // The x coordinate of the origin is (logically) set to be exactly between negative and positive range. + int xAxisOriginPosition = barArea.intWrapper().getX() + mCanvas.getCellXFromDotX(negUnits * mTextureUnitSize); + + // Now the available vertical space is analyzed to figure out the bar thickness. + // Again, this is done by a help algorithm. + int availableCells = barArea.intWrapper().getHeight(); + int barThickness = findBarThickness(availableCells); + + + + // Now everything is ready to be rasterized onto the canvas. + + // 1. Rasterize the diagram title + BrailleText diagramTitle = new BrailleText(strDiagramTitle, titleArea.scaledBy(mCanvas.getCellWidth(), mCanvas.getCellHeight())); + mTextRasterizer.rasterize(diagramTitle, mCanvas); + + // 2. Draw the individual bars for each category. + int refCellX = xAxisOriginPosition; // align bars with y-axis. + int refCellY = barArea.intWrapper().getBottom(); // start with the bottommost bar. + for (int i = 0; i < mDiagram.getCategoryCount(); i++) { + double value = mDiagram.getDataSet(0).get(i).getY(); + // calculate how to represent value with the current scaling + int barLength = (int) round(value / xAxisStepWidth) * mTextureUnitSize; + // draw the bar including its caption and then move the reference cell y position up + String categoryName = "foobar"; // TODO: read real name + refCellY = drawHorizontalBar(refCellX, refCellY, barLength, barThickness, categoryName); + } + + // 3. Rasterize both axes + // First calculate axis positions and bounds + // (these are conversions from 'cell-based' rectangles into a 'dot-based' representation, because the axis + // rasterizer uses linear mapping, needs 'dot coordinates' and does not care about cell borders) + Rectangle yAxisBound = barArea.scaledBy(mCanvas.getCellWidth(), mCanvas.getCellHeight()); + Rectangle xAxisBound = xAxisArea.scaledBy(mCanvas.getCellWidth(), mCanvas.getCellHeight()); + int originX = (xAxisOriginPosition + 1) * mCanvas.getCellWidth() - 1; // convert cell position to dot position + int originY = xAxisBound.intWrapper().getY(); + + // y-axis: no units, no tickmarks + Axis yAxis = new Axis(Axis.Type.Y_AXIS, originX, originY, 1, 0); + yAxis.setBoundary(yAxisBound); + mAxisRasterizer.rasterize(yAxis, mCanvas); + + // x-axis: tickmarks for every second (full) line of the uniform texture. + int xAxisStepDots = mTextureUnitSize * 2; + Axis xAxis = new Axis(Axis.Type.X_AXIS, originX, originY, xAxisStepDots, 2); + xAxis.setBoundary(xAxisBound); + // a bit more complicated than y-axis here: building a map for the axis labels + Map<Integer, String> xAxisLabels = new HashMap<>(); + char labelLetter = 'A'; + for (int axisTick = (negUnits / 2) * -1; axisTick <= (posUnits / 2); axisTick++) { + xAxisLabels.put(axisTick, Character.toString(labelLetter)); + labelLetter++; + } + xAxis.setLabels(xAxisLabels); + mAxisRasterizer.rasterize(xAxis, mCanvas); + + // Finished. + + } + + // Layout help methods + + private double findAxisScaling(final double valueRangeSize, final int availableUnits) { + double minRangePerUnit = valueRangeSize / availableUnits; // this range must fit into one 'axis step' + double orderOfMagnitude = pow(10, ceil(log10(minRangePerUnit))); + double scaledRange = 0; + for (double scaling : mUnitScalings) { + scaledRange = (scaling * orderOfMagnitude); + if (scaledRange >= minRangePerUnit) { + break; + } + } + return scaledRange; + } + + private int findBarThickness(final int availableCells) throws InsufficientRenderingAreaException { + + int barThickness = mBarMaxThickness; + if ((barThickness % 2) == 0) { + barThickness++; // Make bar thickness an uneven number. Needed for the uniform texture. + } + + // Probe the maximum possible bar thickness + int requiredCells; + while (availableCells < (requiredCells = requiredCells(barThickness))) { + barThickness -= 2; + if (barThickness < mBarMinThickness) { + throw new InsufficientRenderingAreaException("Not enough space to render given amount of categories in " + + "bar chart. " + mDiagram.getCategoryCount() + " categories given. " + requiredCells + + " cells required but only " + availableCells + " available. " + + "(Minimum bar thickness is set to " + mBarMinThickness + " dots)"); + } + } + return barThickness; + } + + private int requiredCells(final int barThickness) { + int cellHeight = mCanvas.getCellHeight(); + // basic thickness of single bar + int barSize = mBarInCellPadding + barThickness; + // additional padding between neighboring bars + int sizeInclusive = barSize + (mExtraBarCellPadding * cellHeight) + mBarDotPadding + 1; + // how many cells needed? -> Can borders of neighboring cells 'share' a cell? + int barCells = (int) ceil(barSize / (double) cellHeight); // important cast, else int division happens + int cellsInclusive = (int) ceil(sizeInclusive / (double) cellHeight); + // --> Linear equation + return barCells + (cellsInclusive - 1) * (mDiagram.getCategoryCount() - 1); + } + + + /** + * Draws a horizontal category bar to the canvas. + * @param cellX The reference cell x position. + * @param cellY The reference cell y position. + * @param length The length of the bar in dots (can also be negative). + * @param thickness The thickness of the bar in dots. + * @param categoryName The caption to be displayed next to the bar. + * @return The y position of the next reference cell for the next bar. + * @throws InsufficientRenderingAreaException If the underlying text rasterizer throws it while rasterizing the bar caption. + */ + private int drawHorizontalBar(final int cellX, final int cellY, final int length, final int thickness, + final String categoryName) throws InsufficientRenderingAreaException { + // the bar is drawn according to the bottom right dot of the cell as reference + int bottomRightDotX = (cellX + 1) * mCanvas.getCellWidth() - 1; + int bottomRightDotY = (cellY + 1) * mCanvas.getCellHeight() - 1; + + // first, a rectangle is drawn between two points: 'lower' (at y-axis) and 'upper' (at bars end) + int lowerX = bottomRightDotX; + int lowerY = bottomRightDotY - mBarInCellPadding; // position relative to reference point + int upperX = lowerX + length; // add the horizontal size of the bar (representing the category value) + int upperY = lowerY - (thickness - 1); // substract the bar thickness (up = towards smaller y) + Rasterizer.rectangle(lowerX, lowerY, upperX, upperY, mData, true); + + // then the rectangle is filled with the uniform texture + int textureStep = Integer.signum(upperX - lowerX) * mTextureUnitSize; + int i = 0; + for (int dotX = lowerX; dotX != upperX; dotX += textureStep) { + // alternate between dotted and solid line + if ((i % 2) == 0) { + // solid line + Rasterizer.fill(dotX, lowerY, dotX, upperY, mData, true); + } else { + // dotted line + int j = 0; + for (int dotY = lowerY; dotY > upperY; dotY--) { + mData.setValue(dotY, dotX, ((j % 2) == 0)); + j++; + } + } + i++; + } + + // finally, rasterize the bar caption text + int captionCellX, captionCellY; + captionCellY = mCanvas.getCellYFromDotY(upperY + (thickness / 2)); + if (mLeftCaption) { + captionCellX = mCanvas.getCellXFromDotX(min(lowerX, upperX) - 1) - mCaptionLength; + } else { + captionCellX = mCanvas.getCellXFromDotX(max(lowerX, upperX) + 1); + } + Rectangle captionArea = new Rectangle(captionCellX, captionCellY, mCaptionLength, 1); + mTextRasterizer.rasterize(new BrailleText(categoryName, + captionArea.scaledBy(mCanvas.getCellWidth(), mCanvas.getCellHeight())), mCanvas); + + return mCanvas.getCellYFromDotY(upperY - (mBarDotPadding + 1)) - mExtraBarCellPadding; + } + + private void checkValidBrailleRaster() throws InsufficientRenderingAreaException { + boolean isValidBrailleRaster = ((mCanvas.getCellWidth() == 2) + && (mCanvas.getCellHeight() >= 3) && (mCanvas.getCellHeight() <= 4)); + if (!isValidBrailleRaster) { + // TODO: Maybe refactor to have different rendering exceptions? + throw new InsufficientRenderingAreaException("This rasterizer can only work with a 6-dot or 8-dot " + + "braille raster."); + } + } + + /* + private boolean isEquidistantRaster() { + return ( + (canvas.getHorizontalCellDistance() == canvas.getVerticalCellDistance()) + && (canvas.getVerticalCellDistance() == canvas.getHorizontalDotDistance()) + && (canvas.getHorizontalDotDistance() == canvas.getVerticalDotDistance()) + ); + } + */ +} \ No newline at end of file diff --git a/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/package-info.java b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..2ed44fe9a99fb3a78c6861911d3dc46e98303b1c --- /dev/null +++ b/src/main/java/de/tudresden/inf/mci/brailleplot/rendering/package-info.java @@ -0,0 +1,4 @@ +/** + * This package contains all the required classes and interfaces to rasterize or plot an internal diagram representation into printable data. + */ +package de.tudresden.inf.mci.brailleplot.rendering; \ No newline at end of file diff --git a/src/main/resources/Bundle.properties b/src/main/resources/Bundle.properties deleted file mode 100644 index 85508b57507ad34b4ff4e2b0af3dc0a3c5e86bf5..0000000000000000000000000000000000000000 --- a/src/main/resources/Bundle.properties +++ /dev/null @@ -1,163 +0,0 @@ -param.title = Titel der Grafik [--title "Sinus- und Cosinusfunktion"] -param.size = Gr��e der Grafik in Millimeter [--size 300,500] -param.xrange = Darstellungsbereich der x-Achse und abweichender Titel. Wird automatisch auf den Datenbereich erweitert, wenn "--autoscale" angegeben ist. Ist "--diagramtype FunctionPlot" gesetzt, wird der Bereich erweitert, so dass er 0 enth�lt. [--xrange "Jahre::-3:5"] -param.yrange = Darstellungsbereich der y-Achse und abweichender Titel. Wird automatisch auf den Datenbereich erweitert, wenn "--autoscale" angegeben ist. Ist "--diagramtype FunctionPlot" gesetzt, wird der Bereich erweitert, so dass er 0 enth�lt. [--yrange -3:5] -param.pi = Einteilung der x-Achse in Vielfache von pi -param.xlines = Hilfslinien auf der x-Achse, durch Leerzeichen getrennt [--xlines "1 3.5"] -param.ylines = Hilfslinien auf der y-Achse, durch Leerzeichen getrennt [--ylines "1 3.5"] -param.css = Direkte Angabe von zus�tzlichen CSS-Anweisungen oder Pfad zu einer CSS-Datei [--css "#grid { stroke: #444444 }"] oder [--css stylesheet.css] -param.gnuplot = Pfad zum Gnuplot-Programm [--gnuplot "C:\gnuplot.exe"] -param.output = Pfad zur Ausgabedatei [--output "output/sinus.svg"] -param.help = Hilfe -param.points = Liste von Punkten die markiert werden sollen. Es k�nnen mehrere Listen angegeben werden, welche duch {} zu grupieren sind. Punkte werden durch Leerzeichen getrennt. X- und Y-Werte jeweils durch ein Komma getrennt. Jeder Liste kann ein Titel gegeben werden ["Liste 1"::{1.2,3 4.5,6}{-1,-2.3}]. Wird ignoriert wenn --csvdata angegeben und ein valider Pfad ist. -param.integral = Zeichnet eine Integralfl�che zwischen zwei Funktionen oder der X-Achse. Nur wenn "--diagramtype FunctionPlot" gesetzt ist. [--integral "Wahrscheinlichkeit::1,2[-2:2]" ] -param.device = Ausgabeger�t -param.csvpath = Pfad zur CSV-Datei, aus der die Punkt-, Linien- oder Balkendaten gelesen werden (�berschreibt --points) [--csvpath data.csv] -param.csvtype = Aufbau der CSV-Datei, kann abgek�rzt werden [--csvtype x_aligned_categories] oder [--csvtype xac] -param.csvorientation = Orientierung der CSV-Datei [--csvorientation horizontal] oder [--csvo h] -param.autoscale = Wenn angegeben, wird das Diagramm automatisch auf den Datenbereich skaliert, wobei eine angegebene --xrange bzw. --yrange den minimal Rahmen darstellen. Wenn "--diagramtype FunctionPlot" gesetzt ist, wird der Parameter ignoriert. [--autoscale] -param.showhorizontalgrid = Horizontale Gitterlinien zeigen [--hgrid on] oder [--hgrid off]. Wird weder vgrid noch hgrid angegeben, wird je nach Diagrammtyp eine Standarddarstellung gew�hlt. -param.showverticalgrid = Horizontale Gitterlinien zeigen [--vgrid on] oder [--vgrid off]. Wird weder vgrid noch hgrid angegeben, wird je nach Diagrammtyp eine Standarddarstellung gew�hlt. -param.diagramtype = Der Typ des Diagramms. Beeinflusst, welche weiteren Parameter ausgewertet werden. -param.trendline = Eine Funktion zur Berechnung der Trendline. "--diagramtype ScatterPlot" muss gesetzt sein. Parameter werden hinter dem Funktionsnamen angegeben. M�gliche Linientypen (Parameter in Klammern) sind mit Standardwerten: "MovingAverage n" (n: ganzzahlig und gr��er als 0, Filtergr��e ist 2*n+1), "ExponentialSmoothing alpha" (alpha: zwischen 0 und 1), "BrownLES alpha forecast" (alpha: zwischen 0 und 1; forecast: gr��er als 0, wieviele Werte extrapoliert werden), "LinearRegression" (keine Parameter). Beispiel: "--trendline BrownLES 0.2 10" -param.hideoriginalpoints = Stellt die originalen Datenpunkte nicht dar, wenn "--trendline" gesetzt ist. -param.showdoubleaxes = Doppelte Achsen zeigen [--daxes on] oder [--daxes off]. Nicht f�r [--diagramtype FunctionPlot] verwendbar. Wird der Parameter nicht angegeben, wird je nach Diagrammtyp eine Wahl vorgegeben. -param.showlinepoints = Datenpunkte auf den Linien eines Liniendiagramms markieren [--lp on] oder [--lp off]. Nur f�r [--diagramtype LineChart] verwendbar. Wird der Parameter nicht angegeben, werden die Punkte nur angezeigt, wenn genug Platz vorhanden ist. -param.pointsborderless = Wenn angegeben, erhalten Punktsymbole keinen Rand. Kann die Darstellung von Liniendiagrammen mit Datenpunkten verbessern. -param.baraccumulation = W�hlt, auf welche Art mehrere Datenreihen in Balkendiagrammen akkumuliert werden [--ba stacked] -param.colors = Manuelle Farbwahl. Die Farben k�nnen durch Leerzeichen getrennt angegeben werden. Wurden keine oder zu wenige Farben angegeben, wird die Liste aus der Vorgabereihenfolge aufgef�llt [--colors rot gr�n blau] -param.sorting = Sortieren von nominalen Daten [--sorting <algorithm>] -param.sortdescending = Wenn [--sorting <algorithm>] verwendet wird, sortiere absteigend [--sortdescending] -param.xtitle = Titel der X-Achse [--xtitle Titel]. Titel, die zu gro� f�r eine Zeile sind, werden zu Darstellungsfehlern f�hren. -param.ytitle = Titel der Y-Achse [--ytitle Titel]. Titel, die zu gro� f�r eine Zeile sind, werden zu Darstellungsfehlern f�hren. -param.xunit = Einheit der x-Achse [--xunit "1000 Menschen"] -param.yunit = Einheit der y-Achse [--yunit "m^2"] - -error.onoffparameter = Ung�ltiger Wert "{0}". Erlaubt sind: "on" und "off". - -xaxis = X-Achse -yaxis = Y-Achse - -legend = Legende -legend.xrange = X-Bereich{2}: {0} bis {1} -legend.yrange = Y-Bereich{2}: {0} bis {1} -legend.xtic = Skaleneinteilung: {0} -legend.ytic = Skaleneinteilung: {0} - -legend.poi_1 = Punkt {0} -legend.poi_n = Punkte {0} - -legend.integral_0 = Integral von {0} bis {1} �ber {2}(x)dx -legend.integral_1 = Integral von {0} bis {1} �ber {2}(x)dx und {3}(x)dx - -desc = Beschreibung - -desc.intro_0 = Es wird ein leeres Koordinatensystem im x-Bereich{6} von {0} bis {1} (Skaleneinteilung: {2}) und y-Bereich{7} von {3} bis {4} (Skaleneinteilung: {5}) dargestellt. -desc.intro_1 = Es wird eine�Funktion im x-Bereich{6} von {0} bis {1} (Skaleneinteilung: {2}) und y-Bereich{7} von {3} bis {4} (Skaleneinteilung: {5}) dargestellt: -desc.intro_n = Es werden {8}�Funktionen im x-Bereich{6} von {0} bis {1} (Skaleneinteilung: {2}) und y-Bereich{7} von {3} bis {4} (Skaleneinteilung: {5}) dargestellt: - -desc.intersections_0 = Die Funktionen haben keine Schnittpunkte. -desc.intersections_1 = Die Funktionen {0} und {1} haben einen Schnittpunkt: -desc.intersections_n = Die Funktionen {0} und {1} haben {2} Schnittpunkte: - -desc.extrema_0 = Die Funktion{0} hat keine Extrempunkte. -desc.extrema_1 = Die Funktion{0} hat einen Extrempunkt: -desc.extrema_n = Die Funktion{0} hat {1} Extrempunkte: - -desc.roots_0 = Sie hat keine Nullstellen. -desc.roots_1 = Sie hat eine Nullstelle: -desc.roots_n = Sie hat {0} Nullstellen: - -legend.poi.intro_1 = Folgender Punkt ist eingezeichnet: -legend.poi.intro_n = Folgende Punkte sind eingezeichnet: - -legend.poi.list_1 = Punkt {0}: -legend.poi.list_n = Punktliste {0}: - -desc.integral_0 = Angezeigt wird das Integral von {0} bis {1} �ber {2}(x)dx. -desc.integral_1 = Angezeigt wird das Integral von {0} bis {1} �ber {2}(x)dx und {3}(x)dx. - -desc.note = Die Angaben sind N�herungswerte und beziehen sich nur auf den sichtbaren Bereich. - -### Descriptions for diagrams (not graphs) - -desc.diagramtype_title = Dieses {0} tr�gt den Titel "{1}". -desc.diagramtype_notitle = Es wird ein {0} dargestellt. -desc.diagramtype_barcharttype = Die Balken sind vertikal {0}. - -# New diagram types should get their mName setup here. -# The format is desc.diagramtype.<DiagramType.toString()> -desc.diagramtype.ScatterPlot = Punktdiagramm -desc.diagramtype.LineChart = Liniendiagramm -desc.diagramtype.BarChart = Balkendiagramm - -# single is used when only one data set is displayed -desc.diagramtype_barcharttype.single = ausgerichtet -desc.diagramtype_barcharttype.stacked = gestapelt -desc.diagramtype_barcharttype.grouped = gruppiert - -desc.axis_position_first = Die Achsen und ihre Beschriftungen befinden sich links des Diagramms und darunter -desc.axis_position_first.intersect = und schneiden sich im Punkt {0} -desc.axis_position_second_start.both = Beide Achsen werden -desc.axis_position_second_start.vertical = Die vertikale Achse wird -desc.axis_position_second = auch auf der gegen�berliegenden Seite des Diagramms dargestellt. - -desc.vertical_det = vertikale -desc.vertical_neutral = vertikales - -desc.horizontal_det = horizontale -desc.horizontal_neutral = horizontales - -# Maybe the following three strings have to be changed to a more -# complex representation if other languages are added. -desc.axis_detail = Die {0} Achse ist{1}{2} von {3} bis {4} in Intervallen von {5} markiert. -desc.axis_detail_title = als "{0}" bezeichnet und -desc.axis_detail_unit = in der Einheit {0} - -desc.axis_grid = Es wird ein {0} Gitter dargestellt. - -### Data descriptions - -desc.datacount_0 = Es wird ein leeres Diagramm dargestellt. -desc.datacount_1 = Es wird eine Datenreihe dargestellt -desc.datacount_n = Es werden {0} Datenreihen dargestellt - -desc.data_0 = Keine Punkte -desc.data_1 = Ein Punkt -desc.data_n = {0} Punkte - -### Line chart specific descriptions - -desc.line.minmax = Hinter ihren Namen stehen die Maximal- bzw. Minimalwerte. - -### Bar chart specific descriptions - -desc.barchart.sorting = Die Balken sind {0} und von links nach rechts folgenderma�en beschriftet: - -# {0}: sorting criterium, {1}: ascending/descending, {2}: sorted -desc.barchart.sorting.sortingstring = {0} {1} {2} - -desc.barchart.sorting.sorted = sortiert -desc.barchart.sorting.notsorted = nicht sortiert - -desc.barchart.sorting.MaxFirstDataSet = nach der ersten Datenreihe -desc.barchart.sorting.Alphabetical = nach ihrem Titel alphabetisch -desc.barchart.sorting.CategorialSum = nach der Summe jeder Kategorie - -desc.barchart.sorting.asc = aufsteigend -desc.barchart.sorting.desc = absteigend - -### Trendline descriptions - -#{0}: desc.trendline_points_[1|n] {1}: desc.trendline_algorithm.<mName> {2} TrendlineAlgorithm.getAlgorithmParams() -desc.trendline_points = Zu {0} wird eine Trendlinie, die mit dem Verfahren "{1}" {2} berechnet wurde, dargestellt. -desc.trendline_points_1 = der Datenreihe -desc.trendline_points_n = jeder Datenreihe - -desc.trendline_algorithm.BrownLES = lineare exponentielle Gl�ttung nach Brown -desc.trendline_algorithm.MovingAverage = gleitendes Mittel -desc.trendline_algorithm.LinearRegression = lineare Regression -desc.trendline_algorithm.ExponentialSmoothing = exponentielle Gl�ttung - -desc.trendline_only_1 = Bei der dargestellten Linien handelt es sich um eine Trendlinie, die mit dem Verfahren {0} {1} berechnet wurde. Sie tr�gt den Titel "{2}". -desc.trendline_only_n = Bei den dargestellten Linien handelt es sich um Trendlinien, die mit dem Verfahren "{0}" {1} berechnet wurden. Es werden {2} Datenreihen dargestellt: diff --git a/src/main/resources/parser_bar.csv b/src/main/resources/parser_bar.csv deleted file mode 100644 index 9de9a79bb52db16bebda4c6594bc4387b0ebc9f3..0000000000000000000000000000000000000000 --- a/src/main/resources/parser_bar.csv +++ /dev/null @@ -1,4 +0,0 @@ -,Reihe a,Reihe b,Reihe c -Kat.1,3,"2,5",1 -Kat.2,4,3,2 -Kat.3,"4,5",3,1 \ No newline at end of file diff --git a/src/main/resources/parser_line.csv b/src/main/resources/parser_line.csv deleted file mode 100644 index 73ed49d88f0ea2da2d675d78edeb181f8f35a2d7..0000000000000000000000000000000000000000 --- a/src/main/resources/parser_line.csv +++ /dev/null @@ -1,5 +0,0 @@ -Linie1 ,1,7,9,2,10 - ,1,2,5,4,10 -Linie2 ,0,2,7,9,1,4 - ,3,9,4,2,5,7 - diff --git a/src/main/resources/parser_scatter.csv b/src/main/resources/parser_scatter.csv deleted file mode 100644 index 8a9f0a45859a1a67592e19ec6978d961cc88eebf..0000000000000000000000000000000000000000 --- a/src/main/resources/parser_scatter.csv +++ /dev/null @@ -1,6 +0,0 @@ -Erste Gruppe,1,2,3,4,5 -,1,2,3,4,5 -Zweite Gruppe,5,4,3,2,1 -,5,4,3,2,1 -Dritte Gruppe,1,2,3,4,7 -,5,4,3,2,0 \ No newline at end of file diff --git a/src/resources/Bundle.properties b/src/resources/Bundle.properties deleted file mode 100644 index 5d686412512320ad16fd6ba2dd2e938b3632997f..0000000000000000000000000000000000000000 --- a/src/resources/Bundle.properties +++ /dev/null @@ -1,163 +0,0 @@ -param.title = Titel der Grafik [--title "Sinus- und Cosinusfunktion"] -param.size = Größe der Grafik in Millimeter [--size 300,500] -param.xrange = Darstellungsbereich der x-Achse und abweichender Titel. Wird automatisch auf den Datenbereich erweitert, wenn "--autoscale" angegeben ist. Ist "--diagramtype FunctionPlot" gesetzt, wird der Bereich erweitert, so dass er 0 enthält. [--xrange "Jahre::-3:5"] -param.yrange = Darstellungsbereich der y-Achse und abweichender Titel. Wird automatisch auf den Datenbereich erweitert, wenn "--autoscale" angegeben ist. Ist "--diagramtype FunctionPlot" gesetzt, wird der Bereich erweitert, so dass er 0 enthält. [--yrange -3:5] -param.pi = Einteilung der x-Achse in Vielfache von pi -param.xlines = Hilfslinien auf der x-Achse, durch Leerzeichen getrennt [--xlines "1 3.5"] -param.ylines = Hilfslinien auf der y-Achse, durch Leerzeichen getrennt [--ylines "1 3.5"] -param.css = Direkte Angabe von zusätzlichen CSS-Anweisungen oder Pfad zu einer CSS-Datei [--css "#grid { stroke: #444444 }"] oder [--css stylesheet.css] -param.gnuplot = Pfad zum Gnuplot-Programm [--gnuplot "C:\gnuplot.exe"] -param.output = Pfad zur Ausgabedatei [--output "output/sinus.svg"] -param.help = Hilfe -param.points = Liste von Punkten die markiert werden sollen. Es können mehrere Listen angegeben werden, welche duch {} zu grupieren sind. Punkte werden durch Leerzeichen getrennt. X- und Y-Werte jeweils durch ein Komma getrennt. Jeder Liste kann ein Titel gegeben werden ["Liste 1"::{1.2,3 4.5,6}{-1,-2.3}]. Wird ignoriert wenn --csvdata angegeben und ein valider Pfad ist. -param.integral = Zeichnet eine Integralfläche zwischen zwei Funktionen oder der X-Achse. Nur wenn "--diagramtype FunctionPlot" gesetzt ist. [--integral "Wahrscheinlichkeit::1,2[-2:2]" ] -param.device = Ausgabegerät -param.csvpath = Pfad zur CSV-Datei, aus der die Punkt-, Linien- oder Balkendaten gelesen werden (überschreibt --points) [--csvpath data.csv] -param.csvtype = Aufbau der CSV-Datei, kann abgekürzt werden [--csvtype x_aligned_categories] oder [--csvtype xac] -param.csvorientation = Orientierung der CSV-Datei [--csvorientation horizontal] oder [--csvo h] -param.autoscale = Wenn angegeben, wird das Diagramm automatisch auf den Datenbereich skaliert, wobei eine angegebene --xrange bzw. --yrange den minimal Rahmen darstellen. Wenn "--diagramtype FunctionPlot" gesetzt ist, wird der Parameter ignoriert. [--autoscale] -param.showhorizontalgrid = Horizontale Gitterlinien zeigen [--hgrid on] oder [--hgrid off]. Wird weder vgrid noch hgrid angegeben, wird je nach Diagrammtyp eine Standarddarstellung gewählt. -param.showverticalgrid = Horizontale Gitterlinien zeigen [--vgrid on] oder [--vgrid off]. Wird weder vgrid noch hgrid angegeben, wird je nach Diagrammtyp eine Standarddarstellung gewählt. -param.diagramtype = Der Typ des Diagramms. Beeinflusst, welche weiteren Parameter ausgewertet werden. -param.trendline = Eine Funktion zur Berechnung der Trendline. "--diagramtype ScatterPlot" muss gesetzt sein. Parameter werden hinter dem Funktionsnamen angegeben. Mögliche Linientypen (Parameter in Klammern) sind mit Standardwerten: "MovingAverage n" (n: ganzzahlig und größer als 0, Filtergröße ist 2*n+1), "ExponentialSmoothing alpha" (alpha: zwischen 0 und 1), "BrownLES alpha forecast" (alpha: zwischen 0 und 1; forecast: größer als 0, wieviele Werte extrapoliert werden), "LinearRegression" (keine Parameter). Beispiel: "--trendline BrownLES 0.2 10" -param.hideoriginalpoints = Stellt die originalen Datenpunkte nicht dar, wenn "--trendline" gesetzt ist. -param.showdoubleaxes = Doppelte Achsen zeigen [--daxes on] oder [--daxes off]. Nicht für [--diagramtype FunctionPlot] verwendbar. Wird der Parameter nicht angegeben, wird je nach Diagrammtyp eine Wahl vorgegeben. -param.showlinepoints = Datenpunkte auf den Linien eines Liniendiagramms markieren [--lp on] oder [--lp off]. Nur für [--diagramtype LineChart] verwendbar. Wird der Parameter nicht angegeben, werden die Punkte nur angezeigt, wenn genug Platz vorhanden ist. -param.pointsborderless = Wenn angegeben, erhalten Punktsymbole keinen Rand. Kann die Darstellung von Liniendiagrammen mit Datenpunkten verbessern. -param.baraccumulation = Wählt, auf welche Art mehrere Datenreihen in Balkendiagrammen akkumuliert werden [--ba stacked] -param.colors = Manuelle Farbwahl. Die Farben können durch Leerzeichen getrennt angegeben werden. Wurden keine oder zu wenige Farben angegeben, wird die Liste aus der Vorgabereihenfolge aufgefüllt [--colors rot grün blau] -param.sorting = Sortieren von nominalen Daten [--sorting <algorithm>] -param.sortdescending = Wenn [--sorting <algorithm>] verwendet wird, sortiere absteigend [--sortdescending] -param.xtitle = Titel der X-Achse [--xtitle Titel]. Titel, die zu groß für eine Zeile sind, werden zu Darstellungsfehlern führen. -param.ytitle = Titel der Y-Achse [--ytitle Titel]. Titel, die zu groß für eine Zeile sind, werden zu Darstellungsfehlern führen. -param.xunit = Einheit der x-Achse [--xunit "1000 Menschen"] -param.yunit = Einheit der y-Achse [--yunit "m^2"] - -error.onoffparameter = Ungültiger Wert "{0}". Erlaubt sind: "on" und "off". - -xaxis = X-Achse -yaxis = Y-Achse - -legend = Legende -legend.xrange = X-Bereich{2}: {0} bis {1} -legend.yrange = Y-Bereich{2}: {0} bis {1} -legend.xtic = Skaleneinteilung: {0} -legend.ytic = Skaleneinteilung: {0} - -legend.poi_1 = Punkt {0} -legend.poi_n = Punkte {0} - -legend.integral_0 = Integral von {0} bis {1} über {2}(x)dx -legend.integral_1 = Integral von {0} bis {1} über {2}(x)dx und {3}(x)dx - -desc = Beschreibung - -desc.intro_0 = Es wird ein leeres Koordinatensystem im x-Bereich{6} von {0} bis {1} (Skaleneinteilung: {2}) und y-Bereich{7} von {3} bis {4} (Skaleneinteilung: {5}) dargestellt. -desc.intro_1 = Es wird eine Funktion im x-Bereich{6} von {0} bis {1} (Skaleneinteilung: {2}) und y-Bereich{7} von {3} bis {4} (Skaleneinteilung: {5}) dargestellt: -desc.intro_n = Es werden {8} Funktionen im x-Bereich{6} von {0} bis {1} (Skaleneinteilung: {2}) und y-Bereich{7} von {3} bis {4} (Skaleneinteilung: {5}) dargestellt: - -desc.intersections_0 = Die Funktionen haben keine Schnittpunkte. -desc.intersections_1 = Die Funktionen {0} und {1} haben einen Schnittpunkt: -desc.intersections_n = Die Funktionen {0} und {1} haben {2} Schnittpunkte: - -desc.extrema_0 = Die Funktion{0} hat keine Extrempunkte. -desc.extrema_1 = Die Funktion{0} hat einen Extrempunkt: -desc.extrema_n = Die Funktion{0} hat {1} Extrempunkte: - -desc.roots_0 = Sie hat keine Nullstellen. -desc.roots_1 = Sie hat eine Nullstelle: -desc.roots_n = Sie hat {0} Nullstellen: - -legend.poi.intro_1 = Folgender Punkt ist eingezeichnet: -legend.poi.intro_n = Folgende Punkte sind eingezeichnet: - -legend.poi.list_1 = Punkt {0}: -legend.poi.list_n = Punktliste {0}: - -desc.integral_0 = Angezeigt wird das Integral von {0} bis {1} über {2}(x)dx. -desc.integral_1 = Angezeigt wird das Integral von {0} bis {1} über {2}(x)dx und {3}(x)dx. - -desc.note = Die Angaben sind Näherungswerte und beziehen sich nur auf den sichtbaren Bereich. - -### Descriptions for diagrams (not graphs) - -desc.diagramtype_title = Dieses {0} trägt den Titel "{1}". -desc.diagramtype_notitle = Es wird ein {0} dargestellt. -desc.diagramtype_barcharttype = Die Balken sind vertikal {0}. - -# New diagram types should get their name setup here. -# The format is desc.diagramtype.<DiagramType.toString()> -desc.diagramtype.ScatterPlot = Punktdiagramm -desc.diagramtype.LineChart = Liniendiagramm -desc.diagramtype.BarChart = Balkendiagramm - -# single is used when only one data set is displayed -desc.diagramtype_barcharttype.single = ausgerichtet -desc.diagramtype_barcharttype.stacked = gestapelt -desc.diagramtype_barcharttype.grouped = gruppiert - -desc.axis_position_first = Die Achsen und ihre Beschriftungen befinden sich links des Diagramms und darunter -desc.axis_position_first.intersect = und schneiden sich im Punkt {0} -desc.axis_position_second_start.both = Beide Achsen werden -desc.axis_position_second_start.vertical = Die vertikale Achse wird -desc.axis_position_second = auch auf der gegenüberliegenden Seite des Diagramms dargestellt. - -desc.vertical_det = vertikale -desc.vertical_neutral = vertikales - -desc.horizontal_det = horizontale -desc.horizontal_neutral = horizontales - -# Maybe the following three strings have to be changed to a more -# complex representation if other languages are added. -desc.axis_detail = Die {0} Achse ist{1}{2} von {3} bis {4} in Intervallen von {5} markiert. -desc.axis_detail_title = als "{0}" bezeichnet und -desc.axis_detail_unit = in der Einheit {0} - -desc.axis_grid = Es wird ein {0} Gitter dargestellt. - -### Data descriptions - -desc.datacount_0 = Es wird ein leeres Diagramm dargestellt. -desc.datacount_1 = Es wird eine Datenreihe dargestellt -desc.datacount_n = Es werden {0} Datenreihen dargestellt - -desc.data_0 = Keine Punkte -desc.data_1 = Ein Punkt -desc.data_n = {0} Punkte - -### Line chart specific descriptions - -desc.line.minmax = Hinter ihren Namen stehen die Maximal- bzw. Minimalwerte. - -### Bar chart specific descriptions - -desc.barchart.sorting = Die Balken sind {0} und von links nach rechts folgendermaßen beschriftet: - -# {0}: sorting criterium, {1}: ascending/descending, {2}: sorted -desc.barchart.sorting.sortingstring = {0} {1} {2} - -desc.barchart.sorting.sorted = sortiert -desc.barchart.sorting.notsorted = nicht sortiert - -desc.barchart.sorting.MaxFirstDataSet = nach der ersten Datenreihe -desc.barchart.sorting.Alphabetical = nach ihrem Titel alphabetisch -desc.barchart.sorting.CategorialSum = nach der Summe jeder Kategorie - -desc.barchart.sorting.asc = aufsteigend -desc.barchart.sorting.desc = absteigend - -### Trendline descriptions - -#{0}: desc.trendline_points_[1|n] {1}: desc.trendline_algorithm.<name> {2} TrendlineAlgorithm.getAlgorithmParams() -desc.trendline_points = Zu {0} wird eine Trendlinie, die mit dem Verfahren "{1}" {2} berechnet wurde, dargestellt. -desc.trendline_points_1 = der Datenreihe -desc.trendline_points_n = jeder Datenreihe - -desc.trendline_algorithm.BrownLES = lineare exponentielle Glättung nach Brown -desc.trendline_algorithm.MovingAverage = gleitendes Mittel -desc.trendline_algorithm.LinearRegression = lineare Regression -desc.trendline_algorithm.ExponentialSmoothing = exponentielle Glättung - -desc.trendline_only_1 = Bei der dargestellten Linien handelt es sich um eine Trendlinie, die mit dem Verfahren {0} {1} berechnet wurde. Sie trägt den Titel "{2}". -desc.trendline_only_n = Bei den dargestellten Linien handelt es sich um Trendlinien, die mit dem Verfahren "{0}" {1} berechnet wurden. Es werden {2} Datenreihen dargestellt: diff --git a/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/FunctionalRasterizerTest.java b/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/FunctionalRasterizerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..61c0cc354167439d77fc7c6e9c4fdc68432794c6 --- /dev/null +++ b/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/FunctionalRasterizerTest.java @@ -0,0 +1,56 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import de.tudresden.inf.mci.brailleplot.configparser.ConfigurationParser; +import de.tudresden.inf.mci.brailleplot.configparser.Format; +import de.tudresden.inf.mci.brailleplot.configparser.JavaPropertiesConfigurationParser; +import de.tudresden.inf.mci.brailleplot.configparser.Printer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; + + +public class FunctionalRasterizerTest { + + public static final String mDefaultConfig = getResource("config/rasterizer_test_default.properties").getAbsolutePath(); + public static final String mBaseConfig = getResource("config/base_format.properties").getAbsolutePath(); + public static Printer mPrinter; + public static Format mFormat; + + public static File getResource(String fileName) { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + File resourceFile = new File(classLoader.getResource(fileName).getFile()); + return resourceFile; + } + + @BeforeAll + public static void initialize() { + Assertions.assertDoesNotThrow( + () -> { + ConfigurationParser parser = new JavaPropertiesConfigurationParser(mBaseConfig, mDefaultConfig); + mPrinter = parser.getPrinter(); + mFormat = parser.getFormat("test"); + } + ); + } + + // Invalid use test cases. + + @Test + public void testInvalidDirectCall() { + // Create FunctionalRasterizer for BrailleText + FunctionalRasterizer<BrailleText> textRasterizer = new FunctionalRasterizer<>(BrailleText.class, (data, canvas) -> { + // dummy + }); + + // The FunctionalRasterizer should not be called directly, it is meant to be called by its RenderingBase + // which decides which rasterizer to use based on the Renderable type. + // Directly passing the wrong Renderable type must cause exception: + Assertions.assertThrows(IllegalArgumentException.class, () -> { + RasterCanvas testCanvas = new SixDotBrailleRasterCanvas(mPrinter, mFormat); + // Pass Image to BrailleText rasterizer. + textRasterizer.rasterize(new Image(getResource("examples/img/dummy.bmp")), testCanvas); + }); + } +} diff --git a/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/MasterRendererTest.java b/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/MasterRendererTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5bf15320f9afa71d734b6e80f26de37e400efe7c --- /dev/null +++ b/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/MasterRendererTest.java @@ -0,0 +1,98 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import de.tudresden.inf.mci.brailleplot.configparser.ConfigurationParser; +import de.tudresden.inf.mci.brailleplot.configparser.Format; +import de.tudresden.inf.mci.brailleplot.configparser.JavaPropertiesConfigurationParser; +import de.tudresden.inf.mci.brailleplot.configparser.Printer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.File; + + +public class MasterRendererTest { + + public static final String mDefaultConfig = getResource("config/rasterizer_test_default.properties").getAbsolutePath(); + public static final String mBaseConfig = getResource("config/base_format.properties").getAbsolutePath(); + public static Printer mPrinter; + public static Format mFormat; + + public static File getResource(String fileName) { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + File resourceFile = new File(classLoader.getResource(fileName).getFile()); + return resourceFile; + } + + @BeforeAll + public static void initialize() { + Assertions.assertDoesNotThrow( + () -> { + ConfigurationParser parser = new JavaPropertiesConfigurationParser(mBaseConfig, mDefaultConfig); + mPrinter = parser.getPrinter(); + mFormat = parser.getFormat("test"); + } + ); + } + + // Valid use test cases. + + @Test + public void testRasterizerSelection() { + Assertions.assertDoesNotThrow( + () -> { + // Create own rendering base + FunctionalRenderingBase renderingBase = new FunctionalRenderingBase(); + + // Register two different rasterizers for two different types. + // The rasterizers are later distinguished by the number of pages they generate. + FunctionalRasterizer<BrailleText> rasterizerRef1 = new FunctionalRasterizer<>(BrailleText.class, (data, canvas) -> { + for (int i = 0; i < 1; i++) { + canvas.getNewPage(); + } + }); + FunctionalRasterizer<Image> rasterizerRef2 = new FunctionalRasterizer<>(Image.class, (data, canvas) -> { + for (int i = 0; i < 2; i++) { + canvas.getNewPage(); + } + }); + renderingBase.registerRasterizer(rasterizerRef1); + renderingBase.registerRasterizer(rasterizerRef2); + + // create renderer from rendering base + MasterRenderer renderer = new MasterRenderer(mPrinter, mFormat, renderingBase); + + // Test rasterizer selection + RasterCanvas result; + + result= renderer.rasterize(new BrailleText("dummy text", new Rectangle(0,0,1,1))); + Assertions.assertEquals(1, result.getPageCount()); + + result = renderer.rasterize(new Image(getResource("examples/img/dummy.bmp"))); + Assertions.assertEquals(2, result.getPageCount()); + + // Test replacement of rasterizer + FunctionalRasterizer<Image> rasterizerRef3 = new FunctionalRasterizer<>(Image.class, (data, canvas) -> { + for (int i = 0; i < 3; i++) { + canvas.getNewPage(); + } + }); + renderingBase.registerRasterizer(rasterizerRef3); + + result = renderer.rasterize(new Image(getResource("examples/img/dummy.bmp"))); + Assertions.assertEquals(3, result.getPageCount()); + + } + ); + } + + // Invalid use test cases. + + @Test + public void testRasterizerNotAvailable() { + // Create MasterRenderer with empty rendering base. + MasterRenderer empty = new MasterRenderer(mPrinter, mFormat, new FunctionalRenderingBase()); + + Assertions.assertThrows(IllegalArgumentException.class, () -> empty.rasterize(new Image(getResource("examples/img/dummy.bmp")))); + } +} diff --git a/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/RasterCanvasTest.java b/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/RasterCanvasTest.java new file mode 100644 index 0000000000000000000000000000000000000000..17d31b7376d62e331c9fa6042f7f57b6ddf0320c --- /dev/null +++ b/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/RasterCanvasTest.java @@ -0,0 +1,189 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import de.tudresden.inf.mci.brailleplot.configparser.ConfigurationParser; +import de.tudresden.inf.mci.brailleplot.configparser.JavaPropertiesConfigurationParser; +import de.tudresden.inf.mci.brailleplot.printabledata.MatrixData; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.ListIterator; + + +public class RasterCanvasTest { + + public static final String mDefaultConfig = getResource("config/rasterizer_test_default.properties").getAbsolutePath(); + public static final String mBaseConfig = getResource("config/base_format.properties").getAbsolutePath(); + public static final String mMarginsOnlyConfig = getResource("config/margins_only.properties").getAbsolutePath(); + public static final String mConstraintOnlyConfig = getResource("config/constraint_only.properties").getAbsolutePath(); + public static final String mBothConfig = getResource("config/margins_and_constraint.properties").getAbsolutePath(); + + public static File getResource(String fileName) { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + File resourceFile = new File(classLoader.getResource(fileName).getFile()); + return resourceFile; + } + + @Test + public void testBaseFormat() { + Assertions.assertDoesNotThrow( + () -> { + ConfigurationParser parser = new JavaPropertiesConfigurationParser(mBaseConfig, mDefaultConfig); + RasterCanvas canvas = new SixDotBrailleRasterCanvas(parser.getPrinter(), parser.getFormat("test")); + + // pre-calculated and measured correct values: + int x = 0; + int y = 0; + int w = 35; + int h = 30; + double printW = (w * (2.5 + 3.5)) - 3.5; // 206.5 mm + double printH = (h * (2 * 2.5 + 5.0)) - 5.0; // 295.0 mm + + // Test the calculated raster against the pre-calculated values + Rectangle raster = canvas.getCellRectangle(); + Assertions.assertEquals(x, raster.getX()); + Assertions.assertEquals(y, raster.getY()); + Assertions.assertEquals(w, raster.getWidth()); + Assertions.assertEquals(h, raster.getHeight()); + Assertions.assertEquals(x, raster.intWrapper().getX()); + Assertions.assertEquals(y, raster.intWrapper().getY()); + Assertions.assertEquals(w, raster.intWrapper().getWidth()); + Assertions.assertEquals(h, raster.intWrapper().getHeight()); + Assertions.assertEquals(printW, canvas.getPrintableWidth()); + Assertions.assertEquals(printH, canvas.getPrintableHeight()); + + // Test quantification (by equivalence class partitioning testing) + Assertions.assertEquals(0, canvas.quantifyX(-5)); + Assertions.assertEquals(0, canvas.quantifyX(0)); + Assertions.assertEquals(9, canvas.quantifyX(28.25)); + Assertions.assertEquals(10, canvas.quantifyX(28.26)); + Assertions.assertEquals(69, canvas.quantifyX(206.5)); + Assertions.assertEquals(69, canvas.quantifyX(250)); + + Assertions.assertEquals(0, canvas.quantifyY(-5)); + Assertions.assertEquals(0, canvas.quantifyY(0)); + Assertions.assertEquals(15, canvas.quantifyY(51.25)); + Assertions.assertEquals(16, canvas.quantifyY(51.26)); + Assertions.assertEquals(89, canvas.quantifyY(295.0)); + Assertions.assertEquals(89, canvas.quantifyY(350.0)); + } + ); + } + + @Test + public void testMarginsOnly() { + Assertions.assertDoesNotThrow( + () -> { + ConfigurationParser parser = new JavaPropertiesConfigurationParser(mMarginsOnlyConfig, mDefaultConfig); + RasterCanvas canvas = new SixDotBrailleRasterCanvas(parser.getPrinter(), parser.getFormat("test")); + + // pre-calculated and measured correct values: + // 6 mm left margin -> 1 cell border + // 12 mm top margin -> ~ 1.2 cell sizes -> 2 cell border + // 30 mm bottom margin -> 3 cell border + int x = 1; + int y = 2; + int w = 34; // 35 - 1 + int h = 25; // 30 - 2 - 3 + double printW = (w * (2.5 + 3.5)) - 3.5; // 200.5 mm + double printH = (h * (2 * 2.5 + 5.0)) - 5.0; // 245.0 mm + + // Test the calculated raster against the pre-calculated values + Rectangle raster = canvas.getCellRectangle(); + Assertions.assertEquals(x, raster.getX()); + Assertions.assertEquals(y, raster.getY()); + Assertions.assertEquals(w, raster.getWidth()); + Assertions.assertEquals(h, raster.getHeight()); + Assertions.assertEquals(x, raster.intWrapper().getX()); + Assertions.assertEquals(y, raster.intWrapper().getY()); + Assertions.assertEquals(w, raster.intWrapper().getWidth()); + Assertions.assertEquals(h, raster.intWrapper().getHeight()); + Assertions.assertEquals(printW, canvas.getPrintableWidth()); + Assertions.assertEquals(printH, canvas.getPrintableHeight()); + } + ); + } + + @Test + public void testConstraintOnly() { + Assertions.assertDoesNotThrow( + () -> { + ConfigurationParser parser = new JavaPropertiesConfigurationParser(mConstraintOnlyConfig, mDefaultConfig); + RasterCanvas canvas = new SixDotBrailleRasterCanvas(parser.getPrinter(), parser.getFormat("test")); + + // pre-calculated and measured correct values: + // width-constraint: 190.0 mm -> fits 32 cells h. + // height-constraint: 250.0 mm -> fits 25 cells v. (-1 because top constraint of 1 cell) -> 24 cells v. + int x = 0; // zero because constraint + int y = 0; // moves reference point. + int w = 30; // because raster.constraint.width = 30 < 32 (will pick minimum) + int h = 24; // because 25 < raster.constraint.height = 28 + double printW = (w * (2.5 + 3.5)) - 3.5; // 176.5 mm + double printH = (h * (2 * 2.5 + 5.0)) - 5.0; // 245.0 mm + + // Test the calculated raster against the pre-calculated values + Rectangle raster = canvas.getCellRectangle(); + Assertions.assertEquals(x, raster.getX()); + Assertions.assertEquals(y, raster.getY()); + Assertions.assertEquals(w, raster.getWidth()); + Assertions.assertEquals(h, raster.getHeight()); + Assertions.assertEquals(x, raster.intWrapper().getX()); + Assertions.assertEquals(y, raster.intWrapper().getY()); + Assertions.assertEquals(w, raster.intWrapper().getWidth()); + Assertions.assertEquals(h, raster.intWrapper().getHeight()); + Assertions.assertEquals(printW, canvas.getPrintableWidth()); + Assertions.assertEquals(printH, canvas.getPrintableHeight()); + } + ); + } + + @Test + public void testBoth() { + Assertions.assertDoesNotThrow( + () -> { + ConfigurationParser parser = new JavaPropertiesConfigurationParser(mBothConfig, mDefaultConfig); + RasterCanvas canvas = new SixDotBrailleRasterCanvas(parser.getPrinter(), parser.getFormat("test")); + + // pre-calculated and measured correct values: + // width-constraint: 190.0 mm -> fits 32 cells h. + // height-constraint: 250.0 mm -> fits 25 cells v. + int x = 0; + int y = 1; + int w = 30; + int h = 24; + double printW = (w * (2.5 + 3.5)) - 3.5; // 176.5 mm + double printH = (h * (2 * 2.5 + 5.0)) - 5.0; // 235.0 mm + + // Test the calculated raster against the pre-calculated values + Rectangle raster = canvas.getCellRectangle(); + Assertions.assertEquals(x, raster.getX()); + Assertions.assertEquals(y, raster.getY()); + Assertions.assertEquals(w, raster.getWidth()); + Assertions.assertEquals(h, raster.getHeight()); + Assertions.assertEquals(x, raster.intWrapper().getX()); + Assertions.assertEquals(y, raster.intWrapper().getY()); + Assertions.assertEquals(w, raster.intWrapper().getWidth()); + Assertions.assertEquals(h, raster.intWrapper().getHeight()); + Assertions.assertEquals(printW, canvas.getPrintableWidth()); + Assertions.assertEquals(printH, canvas.getPrintableHeight()); + } + ); + } + + @Test @SuppressWarnings("unchecked") + // RasterCanvas is guaranteed to create MatrixData instances for its pages + public void testPageIterator() { + Assertions.assertDoesNotThrow( + () -> { + ConfigurationParser parser = new JavaPropertiesConfigurationParser(mBaseConfig, mDefaultConfig); + MasterRenderer renderer = new MasterRenderer(parser.getPrinter(), parser.getFormat("test")); + RasterCanvas result = renderer.rasterize(new Image(getResource("examples/img/dummy.bmp"))); + ListIterator iter = result.getPageIterator(); + while (iter.hasNext()) { + MatrixData<Boolean> page = (MatrixData<Boolean>) iter.next(); + Assertions.assertNotNull(page); + } + } + ); + } +} diff --git a/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/RectangleTest.java b/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/RectangleTest.java new file mode 100644 index 0000000000000000000000000000000000000000..126a146a38b45c8728986f5f325aca32530d0240 --- /dev/null +++ b/src/test/java/de/tudresden/inf/mci/brailleplot/rendering/RectangleTest.java @@ -0,0 +1,142 @@ +package de.tudresden.inf.mci.brailleplot.rendering; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +public class RectangleTest { + + Rectangle rect1 = new Rectangle(4.5, 2.75, 8.3, 16); + Rectangle rect2 = new Rectangle(8.5, 6.75, 8.3, 16); + Rectangle rect3 = new Rectangle(0, 0, 3, 2); + + @Test + public void testDimensions() { + Assertions.assertEquals(4.5, rect1.getX()); + Assertions.assertEquals(2.75, rect1.getY()); + Assertions.assertEquals(8.3, rect1.getWidth()); + Assertions.assertEquals(16, rect1.getHeight()); + Assertions.assertEquals(12.8, rect1.getRight()); + Assertions.assertEquals(18.75, rect1.getBottom()); + } + + @Test + public void testIntegerCoordinates() { + Rectangle.IntWrapper intRect; + + // with rational original coordinates (get rounded) + intRect = rect1.intWrapper(); + Assertions.assertEquals(5, intRect.getX()); + Assertions.assertEquals(3, intRect.getY()); + Assertions.assertEquals(8, intRect.getWidth()); + Assertions.assertEquals(16, intRect.getHeight()); + Assertions.assertEquals(12, intRect.getRight()); + Assertions.assertEquals(18, intRect.getBottom()); + + // with natural original coordinates + intRect = rect3.intWrapper(); + Assertions.assertEquals(0, intRect.getX()); + Assertions.assertEquals(0, intRect.getY()); + Assertions.assertEquals(3, intRect.getWidth()); + Assertions.assertEquals(2, intRect.getHeight()); + Assertions.assertEquals(2, intRect.getRight()); + Assertions.assertEquals(1, intRect.getBottom()); + } + + @Test + public void testScaling() { + Rectangle scaledRect = rect1.scaledBy(5.5, 0.4); + Assertions.assertEquals(4.5 * 5.5, scaledRect.getX()); + Assertions.assertEquals(2.75 * 0.4, scaledRect.getY()); + Assertions.assertEquals(8.3 * 5.5, scaledRect.getWidth()); + Assertions.assertEquals(16 * 0.4, scaledRect.getHeight()); + Assertions.assertEquals(12.8 * 5.5, scaledRect.getRight()); + Assertions.assertEquals(18.75 * 0.4, scaledRect.getBottom()); + } + + @Test + public void testIntersecting() { + Rectangle itsct; + + // Non-empty intersection + itsct = rect1.intersectedWith(rect2); + Assertions.assertEquals(8.5, itsct.getX()); + Assertions.assertEquals(6.75, itsct.getY()); + Assertions.assertEquals(12.8 - 8.5, itsct.getWidth()); + Assertions.assertEquals(18.75 - 6.75, itsct.getHeight()); + Assertions.assertEquals(12.8, itsct.getRight()); + Assertions.assertEquals(18.75, itsct.getBottom()); + + // Swapping order of rectangles gives equivalent intersection + itsct = rect2.intersectedWith(rect1); + Assertions.assertEquals(8.5, itsct.getX()); + Assertions.assertEquals(6.75, itsct.getY()); + Assertions.assertEquals(12.8 - 8.5, itsct.getWidth()); + Assertions.assertEquals(18.75 - 6.75, itsct.getHeight()); + Assertions.assertEquals(12.8, itsct.getRight()); + Assertions.assertEquals(18.75, itsct.getBottom()); + + // Empty intersection + itsct = rect1.intersectedWith(rect3); + Assertions.assertEquals(4.5, itsct.getX()); + Assertions.assertEquals(2.75, itsct.getY()); + Assertions.assertEquals(0, itsct.getWidth()); + Assertions.assertEquals(0, itsct.getHeight()); + Assertions.assertEquals(4.5, itsct.getRight()); + Assertions.assertEquals(2.75, itsct.getBottom()); + } + + @Test + public void testCropping() { + Assertions.assertDoesNotThrow(() -> { + Rectangle copy, crop; + + // Create copy by selecting whole width / height (should not throw) + copy = rect1.fromTop(16); + copy = rect1.fromBottom(16); + copy = rect1.fromLeft(8.3); + copy = rect1.fromBottom(8.3); + + // Create copy with copy constructor + copy = new Rectangle(rect1); + + crop = copy.removeFromTop(1); + Assertions.assertEquals(2.75, crop.getY()); + Assertions.assertEquals(1, crop.getHeight()); + + crop = copy.removeFromLeft(2); + Assertions.assertEquals(4.5, crop.getX()); + Assertions.assertEquals(2, crop.getWidth()); + + crop = copy.removeFromBottom(3); + Assertions.assertEquals(18.75, crop.getBottom()); + Assertions.assertEquals(3, crop.getHeight()); + + crop = copy.removeFromRight(4); + Assertions.assertEquals(12.8, crop.getRight()); + Assertions.assertEquals(4, crop.getWidth()); + + Assertions.assertEquals(4.5 + 2, copy.getX()); + Assertions.assertEquals(2.75 + 1, copy.getY()); + Assertions.assertEquals(8.3 - 2 - 4, copy.getWidth()); + Assertions.assertEquals(16 - 1 - 3, copy.getHeight()); + Assertions.assertEquals(12.8 - 4, copy.getRight()); + Assertions.assertEquals(18.75 - 3, copy.getBottom()); + + }); + } + + @Test + public void testOutOfSpace() { + Assertions.assertThrows(Rectangle.OutOfSpaceException.class, () -> { + Rectangle copy, crop; + + copy = new Rectangle(rect1); + + // selecting more space than available + crop = copy.fromTop(16.01); + + }); + } + +} diff --git a/src/test/resources/concrete.properties b/src/test/resources/concrete.properties deleted file mode 100644 index 78a56cbb7497b0fbefd5d685d9b822a519286469..0000000000000000000000000000000000000000 --- a/src/test/resources/concrete.properties +++ /dev/null @@ -1,50 +0,0 @@ -# JProperties Printer & Format Configuration -# -# Embosser: Dummy Printer -# Test Revision (19-07-18) (DON'T USE FOR PRINTING) -# -# Description: -# This is the main configuration file for use with the braille plot application -# when embossing with the 'Index Everest-D V4'. -# The configuration specifies the general printer abilities and defines -# pre-selectable formats for this embosser. -# -# https://gitlab.hrz.tu-chemnitz.de/s9444737--tu-dresden.de/brailleplot/wikis/Software%20Design#configuration-files -# ============================================================================= - -### General Printer Properties -### ========================== - -printer.name=Dummy Printer -printer.mode=normal -printer.brailletable=src/test/resources/mapping.properties -printer.floatingDot.support=true -printer.floatingDot.resolution=0.05 - -# The following values represent the fixed indentation and maximum technical printing area of the embosser. -# If the outputs don't fit on the page you might want to tweak this values. (Check the format too.) -printer.constraint.top=5.0 -printer.constraint.left=0 - -# The following properties define the exact grid spacing. -printer.raster.cellDistance.horizontal=3.6 -printer.raster.cellDistance.vertical=4.8 -printer.raster.dotDistance.horizontal=2.5 -printer.raster.dotDistance.vertical=2.5 -printer.raster.dotDiameter=1.5 - -### Format Definitions -### ================== - -# A4 Format -format.A4.page.width=210 -format.A4.page.height=297 -format.A4.margin.left=0 - -# A5 Format -format.A5.page.width=148 -format.A5.page.height=210 -format.A5.margin.top=0 -format.A5.margin.left=0 -format.A5.margin.bottom=0 -format.A5.margin.right=0 \ No newline at end of file diff --git a/src/test/resources/default.properties b/src/test/resources/default.properties deleted file mode 100644 index 9642c43bd28cdc162d3a668e69cab4c0114002d1..0000000000000000000000000000000000000000 --- a/src/test/resources/default.properties +++ /dev/null @@ -1,55 +0,0 @@ -# JProperties Printer & Format Configuration -# -# Embosser: Dummy Default -# Test Revision (19-07-18) (DON'T USE FOR PRINTING) -# -# Description: -# This is the default configuration file for the braille plot application. -# The configuration specifies the default values of required properties. -# -# https://gitlab.hrz.tu-chemnitz.de/s9444737--tu-dresden.de/brailleplot/wikis/Software%20Design#configuration-files -# ============================================================================= - -# ATTENTION: Changes to this configuration will affect settings for ALL printer and format definitions which -# are not overriding the defaults. - -printer.mode=normal -printer.brailletable=src/test/resources/mapping.properties -printer.floatingDot.support=false - -# The following values represent the fixed indentation and maximum technical printing area of the embosser. -# If the outputs don't fit on the page you might want to tweak this values. (Check the format too.) -printer.constraint.top=0 -printer.constraint.left=0 -# The second constraint in the printer.raster namespace helps to limit the available printing area in steps of -# whole cells, for example if the printer enforces a maximum char per line limit or borders are activated. -printer.raster.constraint.top=0 -printer.raster.constraint.left=0 -printer.raster.constraint.width=200 -printer.raster.constraint.height=300 - -# Overall grid layout / type -printer.raster.type=6-dot - -# The following properties define the exact grid spacing. Standard values based on the -# 'Marburg Medium' publication standard as described in the FFI braille technical guideline: -# https://www.ffi.de/assets/Uploads/Technische-Richtlinie-Blindenschrift.pdf -# See also: # https://codes.iccsafe.org/content/ICCA117_12003/chapter-7-communication-elements-and-features#ICCA117.1_2003_Ch07_Sec703 -printer.raster.cellDistance.horizontal=3.5 -printer.raster.cellDistance.vertical=5.0 -printer.raster.dotDistance.horizontal=2.5 -printer.raster.dotDistance.vertical=2.5 -printer.raster.dotDiameter=1.5 - - -### Format Definitions -### ================== - -# Default Format Definition -format.default.page.height=297 -format.default.margin.top=10 -format.default.margin.left=10 -format.default.margin.bottom=10 -format.default.margin.right=10 - -# This is a template. Do not define concrete formats in this file. Use the specific user config file for this purpose. \ No newline at end of file diff --git a/src/test/resources/illegalPropertyNameExample.properties b/src/test/resources/illegalPropertyNameExample.properties deleted file mode 100644 index fcceefffc126943232cc159f4aa9d24f24b11f44..0000000000000000000000000000000000000000 --- a/src/test/resources/illegalPropertyNameExample.properties +++ /dev/null @@ -1,59 +0,0 @@ -# JProperties Printer & Format Configuration -# -# Embosser: Dummy Default with illegal property mName -# Test Revision (19-07-18) (DON'T USE FOR PRINTING) -# -# Description: -# This is the default configuration file for the braille plot application. -# The configuration specifies the default values of required properties. -# -# https://gitlab.hrz.tu-chemnitz.de/s9444737--tu-dresden.de/brailleplot/wikis/Software%20Design#configuration-files -# ============================================================================= - -# ATTENTION: Changes to this configuration will affect settings for ALL printer and format definitions which -# are not overriding the defaults. - -printer.mode=normal -printer.brailletable=src/test/resources/mapping.properties -printer.floatingDot.support=false - -# Illegal property mName example -printer.garbageProperty=illegal - -# The following values represent the fixed indentation and maximum technical printing area of the embosser. -# If the outputs don't fit on the page you might want to tweak this values. (Check the format too.) -printer.constraint.top=0 -printer.constraint.left=0 -# The second constraint in the printer.raster namespace helps to limit the available printing area in steps of -# whole cells, for example if the printer enforces a maximum char per line limit or borders are activated. -printer.raster.constraint.top=0 -printer.raster.constraint.left=0 -printer.raster.constraint.width=200 -printer.raster.constraint.height=300 - -# Overall grid layout / type -printer.raster.type=6-dot - -# The following properties define the exact grid spacing. Standard values based on the -# 'Marburg Medium' publication standard as described in the FFI braille technical guideline: -# https://www.ffi.de/assets/Uploads/Technische-Richtlinie-Blindenschrift.pdf -# See also: # https://codes.iccsafe.org/content/ICCA117_12003/chapter-7-communication-elements-and-features#ICCA117.1_2003_Ch07_Sec703 -printer.raster.cellDistance.horizontal=3.5 -printer.raster.cellDistance.vertical=5.0 -printer.raster.dotDistance.horizontal=2.5 -printer.raster.dotDistance.vertical=2.5 -printer.raster.dotDiameter=1.5 - - -### Format Definitions -### ================== - -# Default Format Definition (assume A4 portrait) -format.default.page.width=210 -format.default.page.height=297 -format.default.margin.top=10 -format.default.margin.left=10 -format.default.margin.bottom=10 -format.default.margin.right=10 - -# This is a template. Do not define concrete formats in this file. Use the specific user config file for this purpose. \ No newline at end of file diff --git a/src/test/resources/illegalPropertyValueExample.properties b/src/test/resources/illegalPropertyValueExample.properties deleted file mode 100644 index 9e6f8f2eb6156cffbeb4c126853c390973d437d2..0000000000000000000000000000000000000000 --- a/src/test/resources/illegalPropertyValueExample.properties +++ /dev/null @@ -1,59 +0,0 @@ -# JProperties Printer & Format Configuration -# -# Embosser: Dummy Default with illegal property value -# Test Revision (19-07-18) (DON'T USE FOR PRINTING) -# -# Description: -# This is the default configuration file for the braille plot application. -# The configuration specifies the default values of required properties. -# -# https://gitlab.hrz.tu-chemnitz.de/s9444737--tu-dresden.de/brailleplot/wikis/Software%20Design#configuration-files -# ============================================================================= - -# ATTENTION: Changes to this configuration will affect settings for ALL printer and format definitions which -# are not overriding the defaults. - -printer.mode=normal -printer.brailletable=src/test/resources/mapping.properties -printer.floatingDot.support=false - -# The following values represent the fixed indentation and maximum technical printing area of the embosser. -# If the outputs don't fit on the page you might want to tweak this values. (Check the format too.) -printer.constraint.top=0 -printer.constraint.left=0 -# The second constraint in the printer.raster namespace helps to limit the available printing area in steps of -# whole cells, for example if the printer enforces a maximum char per line limit or borders are activated. -printer.raster.constraint.top=0 -printer.raster.constraint.left=0 -printer.raster.constraint.width=200 -printer.raster.constraint.height=300 - -# Overall grid layout / type -printer.raster.type=6-dot - -# The following properties define the exact grid spacing. Standard values based on the -# 'Marburg Medium' publication standard as described in the FFI braille technical guideline: -# https://www.ffi.de/assets/Uploads/Technische-Richtlinie-Blindenschrift.pdf -# See also: # https://codes.iccsafe.org/content/ICCA117_12003/chapter-7-communication-elements-and-features#ICCA117.1_2003_Ch07_Sec703 -printer.raster.cellDistance.horizontal=3.5 -printer.raster.cellDistance.vertical=5.0 -printer.raster.dotDistance.horizontal=2.5 -printer.raster.dotDistance.vertical=2.5 -printer.raster.dotDiameter=1.5 - - -### Format Definitions -### ================== - -# Default Format Definition (assume A4 portrait) -format.default.page.width=210 - -# Illegal property value example -format.default.page.height=two hundred ninety seven - -format.default.margin.top=10 -format.default.margin.left=10 -format.default.margin.bottom=10 -format.default.margin.right=10 - -# This is a template. Do not define concrete formats in this file. Use the specific user config file for this purpose. \ No newline at end of file diff --git a/src/test/resources/mapping.properties b/src/test/resources/mapping.properties deleted file mode 100644 index 8251bb944bf5666048c401e4679c75d3aa990fc3..0000000000000000000000000000000000000000 --- a/src/test/resources/mapping.properties +++ /dev/null @@ -1,146 +0,0 @@ -# JProperties Mapping BrailleTable -# -# Table: de-chardefs6 -# Version 1 Rev. 2 (19-07-11) -# -# Description: -# This table contains a mapping from 6-bit-strings to decimal ascii byte values. -# It is used by the printer backend to encode data sent to the embosser. -# The pairs are ordered by ascending ascii byte value. -# -# ============================================================================= - -# 0-31:NUL-US (non visible characters) -# Space -000000=32 -# ! -000010=33 -# " -000100=34 -# # -001111=35 -# $ -000101=36 -# % -111111=37 -# & -111101=38 -# ' -000001=39 -# ( -011001=40 -# ) -001011=41 -# * -001010=42 -# + -011010=43 -# , -010000=44 -# - -001001=45 -# . -001000=46 -# / -010011=47 -# 0 -001101=48 -# 1 -100001=49 -# 2 -110001=50 -# 3 -100101=51 -# 4 -100111=52 -# 5 -100011=53 -# 6 -110101=54 -# 7 -110111=55 -# 8 -110011=56 -# 9 -010101=57 -# : -010010=58 -# ; -011000=59 -# < -000011=60 -# = -011011=61 -# > -000110=62 -# ? -010001=63 -# 64-90:@-Z -# [ -111011=91 -# 92:\ -# 93:] -# 94:^ -# _ -000111=95 -# ` -001110=96 -# a -100000=97 -# b -110000=98 -# c -100100=99 -# d -100110=100 -# e -100010=101 -# f -110100=102 -# g -110110=103 -# h -110010=104 -# i -010100=105 -# j -010110=106 -# k -101000=107 -# l -111000=108 -# m -101100=109 -# n -101110=110 -# o -101010=111 -# mP -111100=112 -# q -111110=113 -# r -111010=114 -# s -011100=115 -# t -011110=116 -# u -101001=117 -# v -111001=118 -# w -010111=119 -# mX -101101=120 -# mY -101111=121 -# z -101011=122 -# { -011111=123 -# | -001100=124 -# ~ -011101=126 -# 127:DEL \ No newline at end of file diff --git a/src/test/resources/missingRequiredPropertyExample.properties b/src/test/resources/missingRequiredPropertyExample.properties deleted file mode 100644 index 1c453f75f7362d799ab06ffe7a1ff5dfbd6bd40d..0000000000000000000000000000000000000000 --- a/src/test/resources/missingRequiredPropertyExample.properties +++ /dev/null @@ -1,52 +0,0 @@ -# JProperties Printer & Format Configuration -# -# Embosser: Dummy Printer -# Test Revision (19-07-18) (DON'T USE FOR PRINTING) -# -# Description: -# This is the main configuration file for use with the braille plot application -# when embossing with the 'Index Everest-D V4'. -# The configuration specifies the general printer abilities and defines -# pre-selectable formats for this embosser. -# -# https://gitlab.hrz.tu-chemnitz.de/s9444737--tu-dresden.de/brailleplot/wikis/Software%20Design#configuration-files -# ============================================================================= - -### General Printer Properties -### ========================== - -# Missing required property (not specified by default): -# printer.mName=Dummy Printer -printer.mode=normal -printer.brailletable=src/test/resources/mapping.properties -printer.floatingDot.support=true -printer.floatingDot.resolution=0.05 - -# The following values represent the fixed indentation and maximum technical printing area of the embosser. -# If the outputs don't fit on the page you might want to tweak this values. (Check the format too.) -printer.constraint.top=5.0 -printer.constraint.left=0 - -# The following properties define the exact grid spacing. -printer.raster.cellDistance.horizontal=3.6 -printer.raster.cellDistance.vertical=4.8 -printer.raster.dotDistance.horizontal=2.5 -printer.raster.dotDistance.vertical=2.5 -printer.raster.dotDiameter=1.5 - -### Format Definitions -### ================== - -# A4 Format -format.A4.page.width=210 -format.A4.page.height=297 -format.A4.margin.left=0 - -# A5 Format -# Missing required property (not specified by default): -# format.A5.page.width=148 -format.A5.page.height=210 -format.A5.margin.top=0 -format.A5.margin.left=0 -format.A5.margin.bottom=0 -format.A5.margin.right=0 \ No newline at end of file