package jnpf.bizcommon.onlyoffice.service.impl; import com.google.zxing.BinaryBitmap; import com.google.zxing.MultiFormatReader; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeProperties; import jnpf.dmsEntity.preview.model.DmsFilePreviewTicket; import jnpf.dmsEntity.preview.model.DmsWatermarkPositionSnapshot; import jnpf.dmsEntity.preview.model.DmsWatermarkSnapshot; import jnpf.exception.DataException; import org.apache.pdfbox.Loader; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.rendering.PDFRenderer; import org.junit.Test; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.math.BigDecimal; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class DmsOnlyOfficePrintPdfComposerTest { static { System.setProperty("java.awt.headless", "true"); } @Test public void extractsOnlySelectedPagesAndStampsEachCopyBarcode() throws Exception { DmsOnlyOfficePrintPdfComposer composer = new DmsOnlyOfficePrintPdfComposer(new OnlyOfficeProperties()); byte[] result = composer.compose(basePdf(3), List.of( ticket("1,3", "COPY-1"), ticket("1,3", "COPY-2"))); try (PDDocument pdf = Loader.loadPDF(result)) { assertEquals(4, pdf.getNumberOfPages()); PDFRenderer renderer = new PDFRenderer(pdf); assertEquals("COPY-1", decode(renderer, 0)); assertEquals("COPY-1", decode(renderer, 1)); assertEquals("COPY-2", decode(renderer, 2)); assertEquals("COPY-2", decode(renderer, 3)); } } @Test public void placesPrintBarcodeAtLandscapeCoordinatesOnRotatedPages() throws Exception { DmsOnlyOfficePrintPdfComposer composer = new DmsOnlyOfficePrintPdfComposer(new OnlyOfficeProperties()); DmsFilePreviewTicket ticket = ticket("1", "ROTATED"); DmsWatermarkPositionSnapshot position = ticket.getWatermarks().get(0).getPositions().get(0); position.setLandscapeXMm(new BigDecimal("180")); position.setLandscapeYMm(new BigDecimal("40")); DmsWatermarkPositionSnapshot a3 = new DmsWatermarkPositionSnapshot(); a3.setPaperType("A3"); a3.setPaperWidthMm(new BigDecimal("297")); a3.setPaperHeightMm(new BigDecimal("420")); a3.setPortraitXMm(new BigDecimal("10")); a3.setPortraitYMm(new BigDecimal("20")); a3.setLandscapeXMm(new BigDecimal("330")); a3.setLandscapeYMm(new BigDecimal("60")); ticket.getWatermarks().get(0).setPositions(List.of(position, a3)); for (int rotation : List.of(90, 180, 270)) { boolean isA3 = rotation == 270; byte[] source; try (PDDocument document = new PDDocument(); ByteArrayOutputStream output = new ByteArrayOutputStream()) { PDPage page = new PDPage(isA3 ? PDRectangle.A3 : PDRectangle.A4); page.setRotation(rotation); document.addPage(page); document.save(output); source = output.toByteArray(); } try (PDDocument stamped = Loader.loadPDF(composer.composePreview(source, ticket))) { PDFRenderer renderer = new PDFRenderer(stamped); assertEquals("ROTATED", decode(renderer, 0)); BufferedImage image = renderer.renderImageWithDPI(0, 72); int visible = 0; int xMm = rotation == 180 ? 65 : isA3 ? 330 : 180; int yMm = rotation == 180 ? 100 : isA3 ? 60 : 40; for (int y = (int) (yMm * 72D / 25.4D); y < (int) ((yMm + 80) * 72D / 25.4D); y++) { for (int x = (int) (xMm * 72D / 25.4D); x < (int) ((xMm + 80) * 72D / 25.4D); x++) { if ((image.getRGB(x, y) & 0x00ffffff) < 0x00808080) visible++; } } assertTrue("Expected barcode at landscape coordinates", visible > 0); } } } @Test public void filtersPagesBeyondRenderedDocumentForPrintAndPreview() throws Exception { DmsOnlyOfficePrintPdfComposer composer = new DmsOnlyOfficePrintPdfComposer(new OnlyOfficeProperties()); byte[] printed = composer.compose(basePdf(3), List.of(ticket("1-5", "PRINT"))); byte[] previewed = composer.composePreview(basePdf(3), ticket("2,4", "PREVIEW")); try (PDDocument pdf = Loader.loadPDF(printed)) { assertEquals(3, pdf.getNumberOfPages()); } try (PDDocument pdf = Loader.loadPDF(previewed)) { assertEquals(1, pdf.getNumberOfPages()); } } @Test(expected = DataException.class) public void rejectsPrintWhenAllRequestedPagesAreBeyondRenderedDocument() throws Exception { DmsOnlyOfficePrintPdfComposer composer = new DmsOnlyOfficePrintPdfComposer(new OnlyOfficeProperties()); composer.compose(basePdf(3), List.of(ticket("4-5", "PRINT"))); } @Test(expected = DataException.class) public void rejectsBatchOverConfiguredPageLimit() throws Exception { OnlyOfficeProperties properties = new OnlyOfficeProperties(); properties.getDmsPrint().setMaxBatchPages(3); DmsOnlyOfficePrintPdfComposer composer = new DmsOnlyOfficePrintPdfComposer(properties); composer.compose(basePdf(3), List.of(ticket("1,3", "COPY-1"), ticket("1,3", "COPY-2"))); } @Test(expected = DataException.class) public void appliesBatchByteLimitToSingleCopy() throws Exception { OnlyOfficeProperties properties = new OnlyOfficeProperties(); properties.getDmsPrint().setMaxBatchPdfBytes(1); DmsOnlyOfficePrintPdfComposer composer = new DmsOnlyOfficePrintPdfComposer(properties); composer.compose(basePdf(1), List.of(ticket("1", "COPY-1"))); } private String decode(PDFRenderer renderer, int pageIndex) throws Exception { BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer( new BufferedImageLuminanceSource(renderer.renderImageWithDPI(pageIndex, 144)))); return new MultiFormatReader().decode(bitmap).getText(); } private byte[] basePdf(int pages) throws Exception { try (PDDocument document = new PDDocument(); ByteArrayOutputStream output = new ByteArrayOutputStream()) { for (int page = 0; page < pages; page++) document.addPage(new PDPage(PDRectangle.A4)); document.save(output); return output.toByteArray(); } } private DmsFilePreviewTicket ticket(String pages, String content) { DmsFilePreviewTicket ticket = new DmsFilePreviewTicket(); ticket.setPrintPages(pages); ticket.setWatermarks(List.of(watermark(content))); return ticket; } private DmsWatermarkSnapshot watermark(String content) { DmsWatermarkPositionSnapshot position = new DmsWatermarkPositionSnapshot(); position.setPaperWidthMm(new BigDecimal("210")); position.setPaperHeightMm(new BigDecimal("297")); position.setPortraitXMm(new BigDecimal("65")); position.setPortraitYMm(new BigDecimal("100")); DmsWatermarkSnapshot watermark = new DmsWatermarkSnapshot(); watermark.setCustomBarcode(true); watermark.setBarcodeFormat("QR_CODE"); watermark.setContent(content); watermark.setShowBarcodeText(false); watermark.setMarkWidthMm(new BigDecimal("80")); watermark.setMarkHeightMm(new BigDecimal("80")); watermark.setOpacity(100); watermark.setPositions(List.of(position)); return watermark; } }