package qz.printer.action; import javafx.print.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import qz.common.Constants; import qz.printer.action.html.WebApp; import qz.printer.action.html.WebAppModel; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; public class WebAppTest { private static final Logger log = LogManager.getLogger(WebAppTest.class); private static final int SPOOLER_WAIT = 2000; // millis private static final Path RASTER_OUTPUT_DIR = Paths.get("./out"); // see ant ${out.dir} private static final String RASTER_OUTPUT_FORMAT = "png"; public static void main(String[] args) { try { WebApp.initialize(); cleanup(); // RASTER// int rasterKnownHeightTests = 1000; if (args.length > 1) { rasterKnownHeightTests = Integer.parseInt(args[1]); } int rasterFittedHeightTests = 1000; if (args.length > 2) { rasterFittedHeightTests = Integer.parseInt(args[2]); } if (!testRasterKnownSize(rasterKnownHeightTests)) { log.error("Testing well defined sizes failed"); } else if (!testRasterFittedSize(rasterFittedHeightTests)) { log.error("Testing fit to height sizing failed"); } else { log.info("All raster tests passed"); } // VECTOR // int vectorKnownHeightPrints = 100; if (args.length > 3) { vectorKnownHeightPrints = Integer.parseInt(args[3]); } int vectorFittedHeightPrints = 100; if (args.length > 4) { vectorFittedHeightPrints = Integer.parseInt(args[4]); } if (!testVectorKnownPrints(vectorKnownHeightPrints)) { log.error("Failed vector prints with defined heights"); } else if (!testVectorFittedPrints(vectorFittedHeightPrints)) { log.error("Failed vector prints with fit to height sizing"); } else { log.info("All vector prints completed"); } } catch(Throwable t) { log.error("Tests failed due to an exception", t); } System.exit(0); //explicit exit since jfx is running in background } public static boolean testRasterKnownSize(int trials) throws Throwable { for(int i = 0; i < trials; i++) { //new size every run double printW = Math.max(2, (int)(Math.random() * 110) / 10d) * 72d; double printH = Math.max(3, (int)(Math.random() * 110) / 10d) * 72d; double zoom = Math.max(0.5d, (int)(Math.random() * 30) / 10d); String id = "known-" + i; WebAppModel model = buildModel(id, printW, printH, zoom, true, (int)(Math.random() * 360)); BufferedImage sample = WebApp.raster(model); if (sample == null) { log.error("Failed to create capture"); return false; } //TODO - check bottom right matches expected color //check capture for dimensional accuracy within 1 pixel of expected (due to int rounding) int expectedWidth = (int)Math.round(printW * (96d / 72d) * zoom); int expectedHeight = (int)Math.round(printH * (96d / 72d) * zoom); boolean passed = true; if (!Arrays.asList(expectedWidth, expectedWidth + 1, expectedWidth - 1).contains(sample.getWidth())) { log.error("Expected width to be {} but got {}", expectedWidth, sample.getWidth()); passed = false; } if (!Arrays.asList(expectedHeight, expectedHeight + 1, expectedHeight - 1).contains(sample.getHeight())) { log.error("Expected height to be {} but got {}", expectedHeight, sample.getHeight()); passed = false; } saveAudit(passed? id:"invalid", sample); if (!passed) { return false; } } return true; } public static boolean testRasterFittedSize(int trials) throws Throwable { for(int i = 0; i < trials; i++) { //new size every run (height always starts at 0) double printW = Math.max(2, (int)(Math.random() * 110) / 10d) * 72d; double zoom = Math.max(0.5d, (int)(Math.random() * 30) / 10d); String id = "fitted-" + i; WebAppModel model = buildModel(id, printW, 0, zoom, true, (int)(Math.random() * 360)); BufferedImage sample = WebApp.raster(model); if (sample == null) { log.error("Failed to create capture"); return false; } //TODO - check bottom right matches expected color //check capture for dimensional accuracy within 1 pixel of expected (due to int rounding) //expected height is not known for these tests int expectedWidth = (int)Math.round(printW * (96d / 72d) * zoom); boolean passed = true; if (!Arrays.asList(expectedWidth, expectedWidth + 1, expectedWidth - 1).contains(sample.getWidth())) { log.error("Expected width to be {} but got {}", expectedWidth, sample.getWidth()); passed = false; } saveAudit(passed? id:"invalid", sample); if (!passed) { return false; } } return true; } public static boolean testVectorKnownPrints(int trials) throws Throwable { PrinterJob job = buildVectorJob("vector-test-known"); for(int i = 0; i < trials; i++) { //new size every run double printW = Math.max(2, (int)(Math.random() * 85) / 10d) * 72d; double printH = Math.max(3, (int)(Math.random() * 110) / 10d) * 72d; String id = "known-" + i; WebAppModel model = buildModel(id, printW, printH, 1, false, (int)(Math.random() * 360)); WebApp.print(job, model); } job.endJob(); try { log.info("Waiting {} seconds for the spooler to catch up.", SPOOLER_WAIT / 1000); Thread.sleep(SPOOLER_WAIT); } catch(InterruptedException ignore) {} return job.getJobStatus() != PrinterJob.JobStatus.ERROR; } public static boolean testVectorFittedPrints(int trials) throws Throwable { PrinterJob job = buildVectorJob("vector-test-fitted"); for(int i = 0; i < trials; i++) { //new size every run double printW = Math.max(2, (int)(Math.random() * 85) / 10d) * 72d; String id = "fitted-" + i; WebAppModel model = buildModel(id, printW, 0, 1, false, (int)(Math.random() * 360)); WebApp.print(job, model); } job.endJob(); try { log.info("Waiting {} seconds for the spooler to catch up.", SPOOLER_WAIT / 1000); Thread.sleep(SPOOLER_WAIT); } catch(InterruptedException ignore) {} return job.getJobStatus() != PrinterJob.JobStatus.ERROR; } private static WebAppModel buildModel(String index, double width, double height, double zoom, boolean scale, int hue) { int level = (int)(Math.random() * 50) + 25; WebAppModel model = new WebAppModel("" + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "
Generated content:" + index + "
Content size:" + width + "x" + height + "
Physical size:" + (width / 72d) + "x" + (height / 72d) + "
Zoomed tox " + zoom + "
" + "" + "", true, width, height, scale, zoom); log.trace("Generating #{} = [({},{}), x{}]", index, model.getWidth(), model.getHeight(), model.getZoom()); return model; } private static PrinterJob buildVectorJob(String name) throws Throwable { Printer defaultPrinter = Printer.getDefaultPrinter(); PrinterJob job = PrinterJob.createPrinterJob(defaultPrinter); // All this to remove margins Constructor plCon = PageLayout.class.getDeclaredConstructor(Paper.class, PageOrientation.class, double.class, double.class, double.class, double.class); plCon.setAccessible(true); Paper paper = defaultPrinter.getDefaultPageLayout().getPaper(); PageLayout layout = plCon.newInstance(paper, PageOrientation.PORTRAIT, 0, 0, 0, 0); Field field = defaultPrinter.getClass().getDeclaredField("defPageLayout"); field.setAccessible(true); field.set(defaultPrinter, layout); JobSettings settings = job.getJobSettings(); settings.setPageLayout(layout); settings.setJobName(name); return job; } private static void cleanup() { File[] files; if ((files = RASTER_OUTPUT_DIR.toFile().listFiles()).length > 0) { for(File file : files) { if (file.getName().endsWith("." + RASTER_OUTPUT_FORMAT) && file.getName().startsWith(String.format("%s-", Constants.DATA_DIR))) { if (!file.delete()) { log.warn("Could not delete {}", file); } } } } } private static void saveAudit(String id, BufferedImage capture) throws IOException { Path image = RASTER_OUTPUT_DIR.resolve(String.format("%s-%s.%s", Constants.DATA_DIR, id, RASTER_OUTPUT_FORMAT)); ImageIO.write(capture, RASTER_OUTPUT_FORMAT, image.toFile()); log.info("Wrote {}: {}", id, image); } }