package jnpf.fileStorage.dms;
|
|
import jnpf.dmsEntity.preview.model.DmsWatermarkPositionSnapshot;
|
import jnpf.dmsEntity.preview.model.DmsWatermarkSnapshot;
|
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
import org.junit.Test;
|
|
import javax.imageio.ImageIO;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
import java.awt.Rectangle;
|
import java.awt.image.BufferedImage;
|
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayOutputStream;
|
import java.math.BigDecimal;
|
import java.nio.charset.StandardCharsets;
|
import java.util.ArrayList;
|
import java.util.Base64;
|
import java.util.List;
|
import java.util.regex.Pattern;
|
|
import static org.junit.Assert.*;
|
import static org.mockito.Mockito.mock;
|
|
public class DmsWatermarkAlignmentTest {
|
private final DmsDocxWatermarkService service = new DmsDocxWatermarkService(mock(DmsCommonFileStorage.class));
|
|
@Test
|
public void configurationPreviewUsesTheExportPixelsAndRejectsUnboundedCanvases() throws Exception {
|
DmsWatermarkSnapshot watermark = mark();
|
watermark.setHorizontalAlign("CENTER");
|
watermark.setVerticalAlign("MIDDLE");
|
for (double[] page : List.of(new double[]{210, 297}, new double[]{297, 210})) {
|
assertArrayEquals(service.renderPdfOverlay(page[0], page[1], List.of(watermark)),
|
service.preview(page[0], page[1], watermark));
|
}
|
assertThrows(jnpf.exception.DataException.class, () -> service.preview(Double.NaN, 297, watermark));
|
assertThrows(jnpf.exception.DataException.class, () -> service.preview(210, 10000, watermark));
|
watermark.setMarkWidthMm(new BigDecimal("10000"));
|
assertThrows(jnpf.exception.DataException.class, () -> service.preview(210, 297, watermark));
|
}
|
|
@Test
|
public void allNineAlignmentsPlaceEachLineAndTheWholeBlockInsideTheRectangle() throws Exception {
|
List<Rectangle> legacyLines = lineInk(pdf(mark()));
|
for (String horizontal : List.of("LEFT", "CENTER", "RIGHT")) {
|
for (String vertical : List.of("TOP", "MIDDLE", "BOTTOM")) {
|
DmsWatermarkSnapshot mark = mark();
|
mark.setHorizontalAlign(horizontal);
|
mark.setVerticalAlign(vertical);
|
BufferedImage image = pdf(mark);
|
List<Rectangle> lines = lineInk(image);
|
assertEquals(2, lines.size());
|
Rectangle block = lines.get(0).union(lines.get(1));
|
assertAligned(block.y, image.getHeight() - block.y - block.height, vertical);
|
for (int index = 0; index < lines.size(); index++) {
|
Rectangle line = lines.get(index);
|
if ("LEFT".equals(horizontal)) assertEquals(legacyLines.get(index).x, line.x);
|
else assertAligned(line.x, image.getWidth() - line.x - line.width, horizontal);
|
}
|
}
|
}
|
}
|
|
@Test
|
public void missingFieldsAndTiledLayoutKeepLegacyPixels() throws Exception {
|
DmsWatermarkSnapshot mark = mark();
|
int[] legacy = pixels(pdf(mark));
|
mark.setHorizontalAlign("LEFT");
|
mark.setVerticalAlign("TOP");
|
assertArrayEquals(legacy, pixels(pdf(mark)));
|
mark.setLayoutMode("TILED");
|
int[] tiled = pixels(pdf(mark));
|
mark.setHorizontalAlign("RIGHT");
|
mark.setVerticalAlign("BOTTOM");
|
assertArrayEquals(tiled, pixels(pdf(mark)));
|
}
|
|
@Test
|
public void emptyAndOversizedTextRemainBounded() throws Exception {
|
DmsWatermarkSnapshot mark = mark();
|
mark.setHorizontalAlign("RIGHT");
|
mark.setVerticalAlign("BOTTOM");
|
mark.setContent("\n\n");
|
assertTrue(lineInk(pdf(mark)).isEmpty());
|
mark.setContent("W".repeat(200) + "\n" + "I\n".repeat(50));
|
BufferedImage image = pdf(mark);
|
assertFalse(lineInk(image).isEmpty());
|
assertEquals((int) Math.round(60 * 144 / 25.4), image.getWidth());
|
assertEquals((int) Math.round(40 * 144 / 25.4), image.getHeight());
|
}
|
|
@Test
|
public void docxAndSelectedPagePicturesKeepAlignmentAndRectangleCoordinates() throws Exception {
|
DmsWatermarkSnapshot mark = mark();
|
mark.setHorizontalAlign("RIGHT");
|
mark.setVerticalAlign("BOTTOM");
|
byte[] source = source();
|
BufferedImage expected = pdf(mark);
|
try (XWPFDocument docx = new XWPFDocument(new ByteArrayInputStream(service.apply(source, List.of(mark))))) {
|
var picture = docx.getPackage().getPartsByName(Pattern.compile("/word/media/.*")).get(0);
|
BufferedImage page = ImageIO.read(picture.getInputStream());
|
Rectangle ink = union(lineInk(page));
|
assertEquals(union(lineInk(expected)), ink);
|
assertEquals("Body stays unchanged", docx.getParagraphArray(0).getText());
|
}
|
mark.setRenderPages(List.of(1));
|
var variant = service.pageOverlays(source, List.of(mark)).get(0).getVariants().get(0);
|
assertEquals(0, variant.getOffsetXMm(), 0.001);
|
assertEquals(0, variant.getOffsetYMm(), 0.001);
|
BufferedImage selected = ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(variant.getPngBase64())));
|
assertEquals(union(lineInk(expected)), union(lineInk(selected)));
|
|
BufferedImage raster = new BufferedImage(expected.getWidth(), expected.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
service.applyImageWatermarks(raster, List.of(mark));
|
Rectangle rasterInk = union(lineInk(raster));
|
assertAligned(rasterInk.x, raster.getWidth() - rasterInk.x - rasterInk.width, "RIGHT");
|
assertAligned(rasterInk.y, raster.getHeight() - rasterInk.y - rasterInk.height, "BOTTOM");
|
}
|
|
@Test
|
public void pageFieldsStayLiveAndUseNativeHorizontalAndVerticalAlignment() throws Exception {
|
String[] horizontal = {"LEFT", "CENTER", "RIGHT"};
|
String[] vertical = {"TOP", "MIDDLE", "BOTTOM"};
|
String[] anchors = {"t", "ctr", "b"};
|
for (String align : horizontal) {
|
for (int index = 0; index < vertical.length; index++) {
|
DmsWatermarkSnapshot mark = mark();
|
mark.setHorizontalAlign(align);
|
mark.setVerticalAlign(vertical[index]);
|
mark.setContent("Page {/CurrPage} / {/TotalPage}");
|
try (XWPFDocument docx = new XWPFDocument(new ByteArrayInputStream(service.apply(source(), List.of(mark))))) {
|
String xml = docx.getHeaderList().get(0)._getHdrFtr().xmlText();
|
assertTrue(xml.contains(" PAGE "));
|
assertTrue(xml.contains(" NUMPAGES "));
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
factory.setNamespaceAware(true);
|
var header = factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
|
var paragraphAlignment = (org.w3c.dom.Element) header.getElementsByTagNameNS("*", "jc").item(0);
|
assertEquals(align.toLowerCase(java.util.Locale.ROOT), paragraphAlignment.getAttributeNS(
|
"http://schemas.openxmlformats.org/wordprocessingml/2006/main", "val"));
|
var body = (org.w3c.dom.Element) header.getElementsByTagNameNS("*", "bodyPr").item(0);
|
assertEquals(anchors[index], body.getAttribute("anchor"));
|
}
|
}
|
}
|
}
|
|
private void assertAligned(int before, int after, String alignment) {
|
if ("LEFT".equals(alignment) || "TOP".equals(alignment)) assertTrue(alignment + " margin " + before, before <= 1);
|
else if ("RIGHT".equals(alignment) || "BOTTOM".equals(alignment)) assertTrue(alignment + " margin " + after, after <= 1);
|
else assertTrue("Center margins differ: " + before + "/" + after, Math.abs(before - after) <= 2);
|
}
|
|
private BufferedImage pdf(DmsWatermarkSnapshot mark) throws Exception {
|
return ImageIO.read(new ByteArrayInputStream(service.renderPdfOverlay(60, 40, List.of(mark))));
|
}
|
|
private int[] pixels(BufferedImage image) {
|
return image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
|
}
|
|
private Rectangle union(List<Rectangle> lines) {
|
return lines.stream().reduce(Rectangle::union).orElseThrow();
|
}
|
|
private List<Rectangle> lineInk(BufferedImage image) {
|
List<Rectangle> lines = new ArrayList<>();
|
Rectangle line = null;
|
for (int y = 0; y < image.getHeight(); y++) {
|
Rectangle row = null;
|
for (int x = 0; x < image.getWidth(); x++) {
|
if ((image.getRGB(x, y) >>> 24) != 0) {
|
Rectangle pixel = new Rectangle(x, y, 1, 1);
|
row = row == null ? pixel : row.union(pixel);
|
}
|
}
|
if (row != null) line = line == null ? row : line.union(row);
|
else if (line != null) {
|
lines.add(line);
|
line = null;
|
}
|
}
|
if (line != null) lines.add(line);
|
return lines;
|
}
|
|
private DmsWatermarkSnapshot mark() {
|
DmsWatermarkSnapshot mark = new DmsWatermarkSnapshot();
|
mark.setWatermarkType("TEXT");
|
mark.setContent("WWWW\nI");
|
mark.setFontFamily("SansSerif");
|
mark.setFontColor("#000000");
|
mark.setFontSize(14);
|
mark.setOpacity(100);
|
mark.setRotationAngle(0);
|
mark.setLayoutMode("POSITION");
|
mark.setMarkWidthMm(new BigDecimal("60"));
|
mark.setMarkHeightMm(new BigDecimal("40"));
|
DmsWatermarkPositionSnapshot position = new DmsWatermarkPositionSnapshot();
|
position.setPaperWidthMm(new BigDecimal("40"));
|
position.setPaperHeightMm(new BigDecimal("60"));
|
position.setPortraitXMm(BigDecimal.ZERO);
|
position.setPortraitYMm(BigDecimal.ZERO);
|
position.setLandscapeXMm(BigDecimal.ZERO);
|
position.setLandscapeYMm(BigDecimal.ZERO);
|
mark.setPositions(List.of(position));
|
return mark;
|
}
|
|
private byte[] source() throws Exception {
|
try (XWPFDocument docx = new XWPFDocument(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
docx.createParagraph().createRun().setText("Body stays unchanged");
|
docx.write(output);
|
return output.toByteArray();
|
}
|
}
|
}
|