Files
2026-06-17 14:00:51 +02:00

576 lines
24 KiB
Java

package jmri.util.davidflanagan;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import org.junit.jupiter.api.*;
import jmri.util.JUnitUtil;
import jmri.util.davidflanagan.HardcopyWriter.PrintCanceledException;
import jmri.util.junit.annotations.DisabledIfHeadless;
public class HardcopyWriterTest {
@Test
@DisabledIfHeadless
public void testCtor() {
JFrame frame = new JFrame();
try {
HardcopyWriter hcw = new HardcopyWriter(frame, "test", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72,
true, null, null, null, null, null);
Assertions.assertNotNull(hcw, "HardcopyWriter constructor");
hcw.dispose();
} catch (HardcopyWriter.PrintCanceledException pce) {
// this isn't an error for this test.
}
}
private void performStandardDrawing(HardcopyWriter hcw, int width)
throws IOException, HardcopyWriter.ColumnException {
for (String fontName : new String[]{"Monospaced", "SansSerif", "Serif", "Dialog"}) {
hcw.setFont(fontName, Font.PLAIN, 10);
// Make three columns that are 1/3 of the page width.
hcw.setColumns(new HardcopyWriter.Column[]{
new HardcopyWriter.Column(0, width / 3),
new HardcopyWriter.Column(width / 3, width / 3),
new HardcopyWriter.Column(2 * width / 3, width / 3)
});
hcw.write("Hello World\tHello World\n");
hcw.write("Col1\tCol2\t\n");
hcw.write("\t\tHello from col 3\n");
hcw.setColumns(new HardcopyWriter.Column[]{
new HardcopyWriter.Column(0, width / 3, HardcopyWriter.Align.LEFT),
new HardcopyWriter.Column(width / 3, width / 3, HardcopyWriter.Align.CENTER),
new HardcopyWriter.Column(2 * width / 3, width / 3, HardcopyWriter.Align.RIGHT)
});
hcw.write("Hello World\tHello World\tHello World\n");
hcw.write("\t\tHello from col 3\n");
}
}
@Test
@DisabledIfHeadless
public void testPrintSomething() throws IOException, HardcopyWriter.ColumnException {
JFrame frame = new JFrame();
HardcopyWriter hcw = null;
try {
hcw = new HardcopyWriter(frame, "test", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72, true, null,
null, null, null, new Dimension((int) (8.5 * 72), (int) (11.0 * 72)));
Assertions.assertNotNull(hcw, "HardcopyWriter constructor");
int width = hcw.getPrintablePagesizePoints().width;
performStandardDrawing(hcw, width);
// This is what causes the page to get added to the vector of images.
hcw.pageBreak();
Vector<BufferedImage> images = hcw.getPageImages();
Assertions.assertNotNull(images, "getImages");
Assertions.assertEquals(1, images.size(), "getImages");
Image image = images.get(0);
Assertions.assertNotNull(image, "getImage");
Assertions.assertEquals(850, image.getWidth(null), "image width");
Assertions.assertEquals(1100, image.getHeight(null), "image height");
int totalPixelValue = totalPixelValue((BufferedImage) image, new Rectangle(0, 0, 850, 1100));
// Now we get boxes around bits
Rectangle boxes[] = {
new Rectangle(0, 0, 850, 50),
new Rectangle(50, 50, 120, 400),
new Rectangle(291, 50, 190, 400),
new Rectangle(538, 50, 275, 400)
};
int pixelInsideBox = 0;
for (Rectangle box : boxes) {
pixelInsideBox += totalPixelValue((BufferedImage) image, box);
}
Assertions.assertEquals(totalPixelValue, pixelInsideBox, "totalPixelValue should match the sum of areas");
} catch (HardcopyWriter.PrintCanceledException pce) {
// OK
} finally {
if (hcw != null)
hcw.dispose();
}
}
@Test
@DisabledIfHeadless
public void testNoEmptyPage() throws IOException, HardcopyWriter.ColumnException {
JFrame frame = new JFrame();
HardcopyWriter hcw = null;
try {
hcw = new HardcopyWriter(frame, "test", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72, true, null,
null, null, null, new Dimension((int) (8.5 * 72), (int) (11.0 * 72)));
Assertions.assertNotNull(hcw, "HardcopyWriter constructor");
int pageNumber = hcw.getPageNum();
Assertions.assertEquals(1, pageNumber, "page number");
while (hcw.getPageNum() == 1) {
hcw.write("Hello World\n");
}
pageNumber = hcw.getPageNum();
Assertions.assertEquals(2, pageNumber, "page number");
Vector<BufferedImage> images = hcw.getPageImages();
Assertions.assertNotNull(images, "getImages");
Assertions.assertEquals(1, images.size(), "getImages");
} catch (HardcopyWriter.PrintCanceledException pce) {
// OK
} finally {
if (hcw != null)
hcw.dispose();
}
}
@Test
public void testTabHandling() throws IOException, HardcopyWriter.ColumnException {
HardcopyWriter hcw = null;
try {
hcw = new HardcopyWriter(null, "test", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72, false,
HardcopyWriter.NO_PRINTING_PRINTER,
null, false, null, new Dimension((int) (8.5 * 72), (int) (11.0 * 72)));
Assertions.assertNotNull(hcw, "HardcopyWriter constructor");
hcw.write("a\t\tab\tabcd\tefghijk\tlmn");
List<List<HardcopyWriter.PrintCommand>> pages = hcw.getPageCommands();
Assertions.assertEquals(1, pages.size(), "Should have 1 page recorded");
List<HardcopyWriter.PrintCommand> firstPage = pages.get(0);
HardcopyWriter.DrawString[] ds = firstPage.stream()
.filter(cmd -> cmd instanceof HardcopyWriter.DrawString).toArray(HardcopyWriter.DrawString[]::new);
Assertions.assertEquals(1, ds.length);
Assertions.assertEquals("a ab abcd efghijk lmn", ds[0].getString());
// ......................a.......^.......^.......^.......^.......^
} catch (HardcopyWriter.PrintCanceledException pce) {
// OK
} finally {
if (hcw != null)
hcw.dispose();
}
}
@Test
@DisabledIfHeadless
public void testColumnWrap() throws IOException, HardcopyWriter.ColumnException {
JFrame frame = new JFrame();
HardcopyWriter hcw = null;
try {
hcw = new HardcopyWriter(frame, "test", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72, true, null,
null, null, null, new Dimension((int) (8.5 * 72), (int) (11.0 * 72)));
Assertions.assertNotNull(hcw, "HardcopyWriter constructor");
int width = hcw.getPrintablePagesizePoints().width;
hcw.setFont("Monospaced", Font.PLAIN, 10);
hcw.setColumns(new HardcopyWriter.Column[]{
new HardcopyWriter.Column(0, width / 3, HardcopyWriter.Align.LEFT_WRAP),
new HardcopyWriter.Column(width / 3, width / 3, HardcopyWriter.Align.CENTER_WRAP),
new HardcopyWriter.Column(2 * width / 3, width / 3, HardcopyWriter.Align.RIGHT_WRAP)
});
hcw.write("A long string that should wrap around in the first column (thrice).\n");
hcw.write("\tA long string that should wrap around in the second column (thrice).\n");
hcw.write("\t\tA long string that should wrap around in the third column (thrice).\n");
hcw.write(
"A long string that should wrap around in the first column (thrice).\tNot wrap\tAnd also wrap here in the third column.\n");
hcw.pageBreak();
Vector<BufferedImage> images = hcw.getPageImages();
Assertions.assertEquals(1, images.size(), "getImages");
Image image = images.get(0);
int totalPixelValue = totalPixelValue((BufferedImage) image, new Rectangle(0, 0, 850, 1100));
Rectangle boxes[] = {
new Rectangle(0, 0, 850, 50),
new Rectangle(50, 50, 250, 70),
new Rectangle(300, 98, 250, 70),
new Rectangle(570, 148, 230, 110),
new Rectangle(50, 195, 500, 80)
};
int pixelInsideBox = 0;
for (Rectangle box : boxes) {
pixelInsideBox += totalPixelValue((BufferedImage) image, box);
}
if (totalPixelValue != pixelInsideBox) {
dumpOutImage((BufferedImage) image, boxes, 5, 10);
}
Assertions.assertEquals(totalPixelValue, pixelInsideBox, "totalPixelValue should match the sum of areas");
} catch (HardcopyWriter.PrintCanceledException pce) {
// OK
} finally {
if (hcw != null)
hcw.dispose();
}
}
@Test
public void testStretch() {
List<HardcopyWriter.Column> columns = new ArrayList<>();
columns.add(new HardcopyWriter.Column(0, 1));
columns.add(new HardcopyWriter.Column(0, 2));
columns.add(new HardcopyWriter.Column(0, 3));
columns.add(new HardcopyWriter.Column(0, 4));
int gap = 2;
columns = HardcopyWriter.Column.stretchColumns(columns, 500, gap);
int totalWidth = 0;
int lastColEnd = -gap;
for (HardcopyWriter.Column column : columns) {
Assertions.assertEquals(lastColEnd + gap, column.position, "column position");
totalWidth += column.width;
lastColEnd = column.width + column.position;
}
totalWidth += (columns.size() - 1) * gap;
Assertions.assertEquals(500, totalWidth, "total width");
Assertions.assertEquals(500, lastColEnd, "lastColEnd");
}
@Test
@DisabledIfHeadless
public void testCommandRecording() throws IOException, HardcopyWriter.ColumnException {
JFrame frame = new JFrame();
HardcopyWriter hcwPreview = null;
HardcopyWriter hcwPrint = null;
try {
Dimension pagesize = new Dimension((int) (8.5 * 72), (int) (11.0 * 72));
hcwPreview = new HardcopyWriter(frame, "test-preview", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72,
true, null, null, null, null, pagesize);
hcwPrint = new HardcopyWriter(frame, "test-print", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72,
false, HardcopyWriter.NO_PRINTING_PRINTER, null, null, null, pagesize);
String text = "Test line 1";
hcwPreview.write(text + "\n");
hcwPrint.write(text + "\n");
// Verifying v_pos
Assertions.assertEquals(hcwPreview.getCurrentVPos(), hcwPrint.getCurrentVPos(),
"v_pos should match between preview and print");
// Verifying measure
Rectangle2D boundsPreview = hcwPreview.measure(text);
Rectangle2D boundsPrint = hcwPrint.measure(text);
Assertions.assertEquals(boundsPreview.getWidth(), boundsPrint.getWidth(), 0.01,
"measure width should match");
Assertions.assertEquals(boundsPreview.getHeight(), boundsPrint.getHeight(), 0.01,
"measure height should match");
List<List<HardcopyWriter.PrintCommand>> pages = hcwPrint.getPageCommands();
Assertions.assertEquals(1, pages.size(), "Should have 1 page recorded");
List<HardcopyWriter.PrintCommand> firstPage = pages.get(0);
long drawStringCount = firstPage.stream()
.filter(cmd -> cmd instanceof HardcopyWriter.DrawString)
.count();
Assertions.assertTrue(drawStringCount >= 2,
"Should have recorded at least content strings (header + content)");
} catch (HardcopyWriter.PrintCanceledException pce) {
// OK
} finally {
if (hcwPreview != null)
hcwPreview.dispose();
if (hcwPrint != null)
hcwPrint.dispose();
}
}
@Test
@DisabledIfHeadless
public void testIdenticalMetrics() throws Exception {
JFrame frame = new JFrame();
HardcopyWriter hcwPreview = null;
HardcopyWriter hcwPrint = null;
HardcopyWriter hcw = null;
Dimension pagesize = new Dimension((int) (8.5 * 72), (int) (11.0 * 72));
try {
// 1. Preview mode
hcwPreview = new HardcopyWriter(frame, "test-compare", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72,
true, null,
null, false, null, pagesize);
hcwPrint = new HardcopyWriter(frame, "test-compare", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72,
false, HardcopyWriter.NO_PRINTING_PRINTER,
null, false, null, pagesize);
hcw = new HardcopyWriter(null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 7, null, null);
Assertions.assertEquals(hcwPreview.getCharactersPerLine(), hcwPrint.getCharactersPerLine(),
"Characters per line should match");
Assertions.assertEquals(hcwPreview.getCharactersPerLine(), hcw.getCharactersPerLine(),
"Characters per line should match");
// Test with a string containing a superscript character
String testString = "The cat waited on platform 9¾";
Rectangle2D boundsPreview = hcwPreview.measure(testString);
Rectangle2D boundsPrint = hcwPrint.measure(testString);
Rectangle2D bounds = hcw.measure(testString);
Assertions.assertEquals(boundsPreview.getWidth(), boundsPrint.getWidth(), 0.01, "Width should match");
Assertions.assertEquals(boundsPreview.getHeight(), boundsPrint.getHeight(), 0.01, "Height should match");
Assertions.assertEquals(boundsPreview.getWidth(), bounds.getWidth(), 0.01, "Width should match");
Assertions.assertEquals(boundsPreview.getHeight(), bounds.getHeight(), 0.01, "Height should match");
} catch (HardcopyWriter.PrintCanceledException pce) {
// OK
} finally {
if (hcwPreview != null)
hcwPreview.dispose();
if (hcwPrint != null)
hcwPrint.dispose();
}
}
// brute force test all fonts and sizes, can take a long time! Disabled DAB
// @Test
//@DisabledIfHeadless
// public void testCharsPerLine() throws Exception {
// JFrame frame = new JFrame();
// HardcopyWriter hcwPreview = null;
// HardcopyWriter hcwPrint = null;
// HardcopyWriter hcw = null;
//
// for (String fontName : FontComboUtil.getFonts(FontComboUtil.ALL)) {
// for (int fontSize = 6; fontSize < 16; fontSize++) {
// try {
// // 1. Preview mode
// hcwPreview = new HardcopyWriter(frame, "test-compare " + fontName + fontSize, fontName, null,
// fontSize, .5 * 72, .5 * 72, .5 * 72, .5 * 72, true, null, null, false, null, null);
//
// hcwPrint = new HardcopyWriter(frame, "test-compare", fontName, null, fontSize, .5 * 72, .5 * 72,
// .5 * 72, .5 * 72, false, HardcopyWriter.NO_PRINTING_PRINTER, null, false, null, null);
//
// hcw = new HardcopyWriter(fontName, null, fontSize, .5 * 72, .5 * 72, .5 * 72, .5 * 7, null, null);
//
// Assertions.assertEquals(hcwPreview.getCharactersPerLine(), hcwPrint.getCharactersPerLine(),
// "Characters per line should match");
// Assertions.assertEquals(hcwPreview.getCharactersPerLine(), hcw.getCharactersPerLine(),
// "Characters per line should match");
//
// // Test with a string containing a superscript character
// String testString = "The cat waited on platform 9¾";
//
// Rectangle2D boundsPreview = hcwPreview.measure(testString);
// Rectangle2D boundsPrint = hcwPrint.measure(testString);
// Rectangle2D bounds = hcw.measure(testString);
//
// Assertions.assertEquals(boundsPreview.getWidth(), boundsPrint.getWidth(), 0.01,
// "Width should match");
// Assertions.assertEquals(boundsPreview.getHeight(), boundsPrint.getHeight(), 0.01,
// "Height should match");
// Assertions.assertEquals(boundsPreview.getWidth(), bounds.getWidth(), 0.01, "Width should match");
// Assertions.assertEquals(boundsPreview.getHeight(), bounds.getHeight(), 0.01, "Height should match");
//
// } catch (HardcopyWriter.PrintCanceledException pce) {
// // OK
// } finally {
// if (hcwPreview != null)
// hcwPreview.dispose();
// if (hcwPrint != null)
// hcwPrint.dispose();
// }
// }
// }
// }
@Test
@DisabledIfHeadless
public void testComparePreviewAndPrintRecording() throws Exception {
JFrame frame = new JFrame();
HardcopyWriter hcwPreview = null;
HardcopyWriter hcwPrint = null;
try {
Dimension pagesize = new Dimension((int) (8.5 * 72), (int) (11.0 * 72));
// 1. Preview mode
hcwPreview = new HardcopyWriter(frame, "test-compare", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72,
true, null,
null, false, null, pagesize);
int width = hcwPreview.getPrintablePagesizePoints().width;
performStandardDrawing(hcwPreview, width);
hcwPreview.pageBreak();
Vector<BufferedImage> previewImages = hcwPreview.getPageImages();
BufferedImage previewImg = previewImages.get(0);
// 2. Print mode (bypass dialog)
hcwPrint = new HardcopyWriter(frame, "test-compare", null, null, 10, .5 * 72, .5 * 72, .5 * 72, .5 * 72,
false, HardcopyWriter.NO_PRINTING_PRINTER,
null, false, null, pagesize);
performStandardDrawing(hcwPrint, width);
hcwPrint.pageBreak();
// Replay commands from print mode into a new image
BufferedImage replayedImg = new BufferedImage(850, 1100, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = replayedImg.createGraphics();
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, 850, 1100);
double scale = 100.0 / 72.0;
g2.scale(scale, scale);
PageFormat pf = new PageFormat();
Paper paper = new Paper();
paper.setSize(8.5 * 72, 11 * 72);
paper.setImageableArea(0, 0, 8.5 * 72, 11 * 72);
pf.setPaper(paper);
hcwPrint.print(g2, pf, 0);
g2.dispose();
// Compare images
Assertions.assertEquals(totalPixelValue(previewImg, new Rectangle(0, 0, 850, 1100)),
totalPixelValue(replayedImg, new Rectangle(0, 0, 850, 1100)),
"Total pixel value should be identical for same drawing operations");
} catch (HardcopyWriter.PrintCanceledException pce) {
Assertions.fail("Print job should not have been cancelled");
} finally {
if (hcwPreview != null)
hcwPreview.dispose();
if (hcwPrint != null)
hcwPrint.dispose();
}
}
@Test
public void testMeasurement() throws PrintCanceledException {
HardcopyWriter hcw = null;
try {
hcw = new HardcopyWriter(null, null, 10, 36, 36,
36, 36, null, null);
Rectangle2D bounds = hcw.measure("Hello World");
Assertions.assertTrue(bounds.getWidth() > 40, "measure width");
Assertions.assertTrue(bounds.getHeight() > 5, "measure height");
} catch (HardcopyWriter.PrintCanceledException pce) {
Assertions.fail("Print job should not have been cancelled");
} finally {
if (hcw != null)
hcw.dispose();
}
}
private int totalPixelValue(BufferedImage image, Rectangle bounds) {
int totalRed = 0;
int totalGreen = 0;
int totalBlue = 0;
for (int x = bounds.x; x < bounds.x + bounds.width; x++) {
for (int y = bounds.y; y < bounds.y + bounds.height; y++) {
int rgb = image.getRGB(x, y) ^ 0xffffff;
totalRed += (rgb >> 16) & 0xff;
totalGreen += (rgb >> 8) & 0xff;
totalBlue += rgb & 0xff;
}
}
return totalRed + totalGreen + totalBlue;
}
void dumpOutImage(BufferedImage image, Rectangle[] boxes, int xSize, int ySize) {
for (int y = 0; y < image.getHeight(null); y += ySize) {
String row = String.format("%04d:", y);
for (int x = 0; x < image.getWidth(null); x += xSize) {
if ((x % 100) == 0) {
row += "|";
}
if (totalPixelValue(image, new Rectangle(x, y, xSize, ySize)) > 0) {
// If all the non-zero pixels are inside a box, then use '.' instead
// of '*'.
boolean allInsideBox = true;
for (int ypix = y; ypix < y + ySize && allInsideBox; ypix++) {
for (int xpix = x; xpix < x + xSize; xpix++) {
if (image.getRGB(xpix, ypix) != 0xffffff) {
// This pixel is not white. See if it is inside a box
boolean insideBox = false;
for (Rectangle box : boxes) {
if (box.contains(xpix, ypix)) {
insideBox = true;
break;
}
}
if (!insideBox) {
allInsideBox = false;
break;
}
}
}
}
if (allInsideBox) {
row += ".";
} else {
row += "*";
}
} else {
row += " ";
}
}
System.out.println(row);
}
// Output the image to a png file
try {
File file = new File("/tmp/image.png");
// Copy the image so that we can overwrite it
BufferedImage copy =
new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = copy.createGraphics();
g2d.drawImage(image, 0, 0, null);
// Draw the boxes on the copy
g2d.setColor(Color.RED);
for (Rectangle box : boxes) {
g2d.draw(box);
}
g2d.dispose();
ImageIO.write(copy, "png", file);
} catch (FileNotFoundException e) {
// /tmp is not writable
} catch (IOException e) {
e.printStackTrace();
}
}
@BeforeEach
public void setUp() {
JUnitUtil.setUp();
JUnitUtil.resetProfileManager();
}
@AfterEach
public void tearDown() {
JUnitUtil.tearDown();
}
}