/* * Created on 15 nov 2016 * Copyright 2015 by Andrea Vacondio (andrea.vacondio@gmail.com). * Copyright 2017 by Edi Weissmann (edi.weissmann@gmail.com). * * This file is part of Sejda. * * Sejda is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Sejda is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Sejda. If not, see <http://www.gnu.org/licenses/>. */ package org.sejda.impl.sambox.component; import static org.sejda.util.RequireUtils.requireNotNullArg; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.util.Collections; import org.sejda.model.exception.TaskIOException; import org.sejda.model.scale.ScaleType; import org.sejda.sambox.pdmodel.PDDocument; import org.sejda.sambox.pdmodel.PDPage; import org.sejda.sambox.pdmodel.PDPageContentStream; import org.sejda.sambox.pdmodel.PDPageContentStream.AppendMode; import org.sejda.sambox.pdmodel.common.PDRectangle; import org.sejda.sambox.util.Matrix; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Component capable of scaling pages or pages content * * @author Andrea Vacondio * @author Eduard Weissmann * */ public class PdfScaler { private static final Logger LOG = LoggerFactory.getLogger(PdfScaler.class); private ScaleType type; public PdfScaler(ScaleType type) { requireNotNullArg(type, "Scale type cannot be null"); this.type = type; } public void resizePages(PDDocument doc) throws TaskIOException { PDPage firstPage = doc.getPage(0); PDRectangle sizeOfFirstPage = firstPage.getCropBox(); float targetWidth = Math.min(sizeOfFirstPage.getWidth(), sizeOfFirstPage.getHeight()); resizePages(doc, doc.getPages(), targetWidth); } public void resizePages(PDDocument doc, Iterable<PDPage> pages, float targetWidth) throws TaskIOException { for(PDPage page : pages){ PDRectangle cropBox = page.getCropBox(); // handles landscape scenarios, where we'd like the scale to be calculated based on the width1/width2 ratio, not width1/height2 double scale = targetWidth / Math.min(cropBox.getWidth(), cropBox.getHeight()); LOG.debug("Scaling page from {} to {}, factor of {}", cropBox.getWidth(), targetWidth, scale); try (PDPageContentStream contentStream = new PDPageContentStream(doc, page, AppendMode.PREPEND, true)) { Matrix matrix = getMatrix(scale, page.getCropBox(), page.getCropBox()); contentStream.transform(matrix); if (ScaleType.PAGE == type) { scalePageBoxes(scale, page); } else { scaleContentBoxes(scale, page); } } catch (IOException e) { throw new TaskIOException("An error occurred writing scaling the page.", e); } } } public void scale(PDDocument doc, double scale) throws TaskIOException { scale(doc, doc.getPages(), scale); } public void scale(PDDocument doc, PDPage page, double scale) throws TaskIOException { scale(doc, Collections.singletonList(page), scale); } public void scale(PDDocument doc, Iterable<PDPage> pages, double scale) throws TaskIOException { if (scale != 1) { for (PDPage page : pages) { try (PDPageContentStream contentStream = new PDPageContentStream(doc, page, AppendMode.PREPEND, true)) { Matrix matrix = getMatrix(scale, page.getCropBox(), page.getCropBox()); contentStream.transform(matrix); if (ScaleType.PAGE == type) { scalePageBoxes(scale, page); } else { scaleContentBoxes(scale, page); } } catch (IOException e) { throw new TaskIOException("An error occurred writing scaling the page.", e); } } } } private Matrix getMatrix(double scale, PDRectangle crop, PDRectangle toScale) { if (ScaleType.CONTENT == type) { AffineTransform transform = AffineTransform.getTranslateInstance( (crop.getWidth() - (toScale.getWidth() * scale)) / 2, (crop.getHeight() - (toScale.getHeight() * scale)) / 2); transform.scale(scale, scale); return new Matrix(transform); } return new Matrix(AffineTransform.getScaleInstance(scale, scale)); } private void scaleContentBoxes(double scale, PDPage page) { PDRectangle cropBox = page.getCropBox(); // we adjust art and bleed same as Acrobat does if (scale > 1) { page.setBleedBox(cropBox); page.setTrimBox(cropBox); } else { page.setBleedBox(new PDRectangle(page.getBleedBox() .transform(getMatrix(scale, page.getCropBox(), page.getBleedBox())).getBounds2D())); page.setTrimBox(new PDRectangle( page.getTrimBox().transform(getMatrix(scale, page.getCropBox(), page.getTrimBox())).getBounds2D())); } Rectangle2D newArt = page.getArtBox().transform(getMatrix(scale, page.getCropBox(), page.getArtBox())) .getBounds2D(); if (newArt.getX() < cropBox.getLowerLeftX() || newArt.getY() < cropBox.getLowerLeftX()) { // we overlow the cropbox page.setArtBox(page.getCropBox()); } else { page.setArtBox(new PDRectangle(newArt)); } } private void scalePageBoxes(double scale, PDPage page) { page.setArtBox(new PDRectangle( page.getArtBox().transform(getMatrix(scale, page.getCropBox(), page.getArtBox())).getBounds2D())); page.setBleedBox(new PDRectangle( page.getBleedBox().transform(getMatrix(scale, page.getCropBox(), page.getBleedBox())).getBounds2D())); page.setTrimBox(new PDRectangle( page.getTrimBox().transform(getMatrix(scale, page.getCropBox(), page.getTrimBox())).getBounds2D())); page.setCropBox(new PDRectangle( page.getCropBox().transform(getMatrix(scale, page.getCropBox(), page.getCropBox())).getBounds2D())); page.setMediaBox(new PDRectangle( page.getMediaBox().transform(getMatrix(scale, page.getMediaBox(), page.getMediaBox())).getBounds2D())); } }