/*
* Copyright (C) 2006-2014 Gabriel Burca (gburca dash virtmus at ebixio dot com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package com.ebixio.virtmus.imgsrc;
import com.ebixio.util.Log;
import com.ebixio.virtmus.Utils;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.renderable.ParameterBlock;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import javax.media.jai.InterpolationBilinear;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.RenderedOp;
import org.icepdf.core.exceptions.PDFException;
import org.icepdf.core.exceptions.PDFSecurityException;
import org.icepdf.core.pobjects.Document;
import org.icepdf.core.pobjects.PRectangle;
import org.icepdf.core.pobjects.Page;
import org.icepdf.core.pobjects.PageTree;
import org.icepdf.core.pobjects.graphics.text.LineText;
import org.icepdf.core.pobjects.graphics.text.PageText;
import org.icepdf.core.util.GraphicsRenderingHints;
import org.openide.util.Exceptions;
/**
* Renders PDF pages using the original IcePdf library.
* @author Gabriel Burca
*/
@XStreamAlias("pdfImg")
public class IcePdfImg extends PdfImg {
transient float pageScale = -1;
transient Rectangle2D.Float pdfCropBox = null;
transient Rectangle2D.Float pdfMediaBox = null;
// By how much we would need to rotate the image so it's straight
transient int pdfRotation = 0;
public IcePdfImg(File sourceFile, int pageNum) {
super(sourceFile, pageNum);
}
@Override
public ImgType getImgType() {
return ImgType.PDF;
}
private float getPageScale() {
if (dimension == null) getDimension();
return pageScale;
}
private Rectangle.Float scale(PRectangle pRect, double scaleW, double scaleH) {
return new Rectangle.Float(
(float)(pRect.x * scaleW), (float)(pRect.y * scaleH),
(float)(pRect.width * scaleW), (float)(pRect.height * scaleH));
}
private Rectangle.Float rectRound(Rectangle.Float r) {
return new Rectangle.Float(Math.round(r.x), Math.round(r.y), Math.round(r.width), Math.round(r.height));
}
// Convert from PDF to Java
// PDF y-axis increases moving up. Java y-axis increases going down
// pdf.y = height - img.y; img.y = height - pdf.y;
// @param rect x,y is the top-left corner (positive height == rectangle extends down)
// @param h The distance b/w the pdf y-intercept and the Java y-intercept
private Rectangle.Float rect2Java(Rectangle.Float rect, float h) {
return new Rectangle.Float(rect.x, h - rect.y, rect.width, rect.height);
}
@Override
public Dimension getDimension() {
if (dimension == null) {
Document doc = getDocument();
if (doc == null) return new Dimension(1, 1);
Page page = doc.getPageTree().getPage(pageNum);
float rot = page.getTotalRotation(0);
// When rot == 270, the "i" in the image has its dot to the left (o-)
// We need to rotate the image 90 degrees clockwise so it's upright
pdfRotation = (Math.round((360 + 360-rot) / 90F) % 4) * 90;
/* All getPageBoundary numbers are in PDF units.
Typically, one PDF unit = 1/72 inch (unless the PDF specifies a
"UserUnit" which is in multiples of 1/72 inches). Except in rare
cases, UserUnit is not used and the default value of 1.0 applies. */
// The crop cropBox is what Acrobat shows on the screen, and what we
// want to display to the user.
PRectangle cropBox = page.getPageBoundary(Page.BOUNDARY_CROPBOX);
/* In the case of a page that contains a single image (a scanned page)
* this is typically the size of the image in PDF units. If we scan a
* 5x10" at 300dpi, we would get a mediaBox size of 5*72x10*72, not
* of 5*300x10*300.
*
* The media box is what the printing press would print on the page,
* so this includes crop marks and color boxes that would normally
* be trimmed off when finishing the page. */
PRectangle mediaBox = page.getPageBoundary(Page.BOUNDARY_MEDIABOX);
// This is the page size after cropping was applied
//PDimension pageDim = page.getSize(0);
Image img = getPageImage(doc);
if (img != null) {
// TODO: How do we get the size synchronously?
int imgW = img.getWidth(null);
int imgH = img.getHeight(null);
// Multiply getPageBoundary() values by this factor to get true sizes
double pdfScaleW = imgW / mediaBox.getWidth();
double pdfScaleH = imgH / mediaBox.getHeight();
//double delta = pdfScaleH - pdfScaleW; // W&H scale in PDF is not always the same
pdfCropBox = scale(cropBox, pdfScaleW, pdfScaleH);
pdfMediaBox = scale(mediaBox, pdfScaleW, pdfScaleH);
pdfCropBox = rect2Java(pdfCropBox, pdfMediaBox.height);
pdfMediaBox = rect2Java(pdfMediaBox, pdfMediaBox.height);
if (pdfRotation == 0 || pdfRotation == 180) {
dimension = new Dimension(Math.round(pdfCropBox.width), Math.round(pdfCropBox.height));
} else {
dimension = new Dimension(Math.round(pdfCropBox.height), Math.round(pdfCropBox.width));
}
pageScale = -1;
} else {
dimension = getDimensionBasedOnDisplay(doc);
}
doc.dispose();
}
return dimension;
}
// If the PDF page is a vector drawing, we try to make the smallest page
// dimension the same as the largest screen dimension
private Dimension getDimensionBasedOnDisplay(Document doc) {
if (doc == null) return new Dimension(1, 1);
Dimension[] dims = Utils.getScreenSizes();
int biggest = -1, idx = -1;
for (int i = 0; i < dims.length; i++) {
if (dims[i].width * dims[i].height > biggest) {
biggest = dims[i].width * dims[i].height;
idx = i;
}
}
Dimension dim = dims[idx];
int max = Math.max(dim.width, dim.height);
Dimension pdim = doc.getPageDimension(pageNum, 0).toDimension();
int min = Math.min(pdim.width, pdim.height);
pageScale = (max * 1F) / min;
return new Dimension((int)(pdim.width * pageScale), (int)(pdim.height * pageScale));
}
private Document getDocument() {
Document document = new Document();
try {
document.setFile(sourceFile.getAbsolutePath());
} catch (PDFException ex) {
pageErr = "Error parsing PDF document";
System.out.println(pageErr + " " + ex);
} catch (PDFSecurityException ex) {
pageErr = "Error PDF encryption not supported";
System.out.println(pageErr + " " + ex);
} catch (FileNotFoundException ex) {
pageErr = "Error file not found";
System.out.println(pageErr + " " + ex);
} catch (IOException ex) {
pageErr = "Error handling PDF document";
System.out.println(pageErr + " " + ex);
}
if (pageErr != null) document = null;
return document;
}
private BufferedImage getFullBufferedImage(float scale) {
Document doc = getDocument();
if (doc == null) {
return null;
}
BufferedImage srcImgFull = (BufferedImage)doc.getPageImage(pageNum,
GraphicsRenderingHints.SCREEN, Page.BOUNDARY_CROPBOX, 0, scale);
Log.log("ImgSz2: " + srcImgFull.getWidth() + "x" + srcImgFull.getHeight());
doc.dispose();
return srcImgFull;
}
@Override
protected RenderedOp getFullRenderedOp() {
Document doc = getDocument();
if (doc == null) {
return null;
}
Image img = getPageImage(doc);
doc.dispose();
ParameterBlock pb;
if (img != null) {
// A single image covers the whole PDF page.
Dimension dim = getDimension(); // Just to make sure the crop boxes are computed
//Log.log("ImgSz1: " + img.getWidth(null) + "x" + img.getHeight(null));
pb = new ParameterBlock();
pb.addSource(img);
// Due to floating point math, pdfCropBox could be slightly larger than the image
// and that would cause the "crop" to throw an exception. Round the crop.
Rectangle.Float crop = rectRound(pdfCropBox);
pb.add(crop.x);
pb.add(crop.y);
pb.add(crop.width - 1);
pb.add(crop.height - 1);
RenderedOp r = JAI.create("crop", pb);
// Cropping leaves the origin at the old topLeftX/Y we need to translate
// the image so the top-left corner is again at 0,0
pb = new ParameterBlock();
pb.addSource(r);
pb.add(-pdfCropBox.x);
pb.add(-pdfCropBox.y);
r = JAI.create("translate", pb);
if (pdfRotation != 0) {
pb = new ParameterBlock();
pb.addSource(r);
pb.add(0F);
pb.add(0F);
pb.add((float)Math.toRadians(pdfRotation));
pb.add(new InterpolationBilinear());
r = JAI.create("rotate", pb);
// Image is now rotated around the 0,0 point, and completely outside
// the view-port. We need to translate it back into the view-port.
Point.Float transl;
switch (pdfRotation) {
case 90:
transl = new Point.Float(pdfCropBox.height, 0F); break;
case 180:
transl = new Point.Float(pdfCropBox.width, pdfCropBox.height); break;
case 270:
transl = new Point.Float(0, pdfCropBox.width); break;
case 0:
default:
transl = new Point.Float(0F, 0F); break;
}
pb = new ParameterBlock();
pb.addSource(r);
pb.add(transl.x); // Positive value moves image to the right
pb.add(transl.y); // Positive value moves image downwards
r = JAI.create("translate", pb);
}
return r;
} else {
// A combination of text and/or images cover the PDF page, so we render
// the page at a suitable scale/resolution.
pb = new ParameterBlock();
pb.add(getFullBufferedImage(getPageScale()));
return JAI.create("AWTImage", pb);
}
}
/**
* We want to see if the PDF page consists of a single image (usually the case
* when the PDF consists of scanned documents), or a combination of images
* and/or text (usually the case when the PDF was generated by some music
* notation software).
*
* If it's a single image, we extract it in its native resolution,
* otherwise we defer to PdfView to render the page since ICEPdf doesn't
* work with files produced by notation SW (it doesn't render the music font
* properly).
*
* @param doc PDF document to use
* @return An image (in the case of a single image per page) or null otherwise.
*/
private Image getPageImage(Document doc) {
ArrayList<LineText> txt;
try {
// Next line causes VirtMus to crash with some PDF files.
// ex: Mendelssohn - Wedding March - Pipe Orgam
// No exception is thrown. The program just terminates.
PageText pt = doc.getPageText(pageNum);
txt = pt.getPageLines();
} catch (Exception e) {
return null;
}
if (txt == null || txt.isEmpty()) {
ArrayList<Image> imgs = new ArrayList<>(doc.getPageImages(pageNum));
if (imgs.size() == 1) {
// Single image and no text == image covers whole page
return imgs.get(0);
} else {
return null;
}
} else { // This page contains text. ICEPdf doesn't render it properly.
return null;
}
}
/**
* Selects the PDF renderer to use.
*
* This class causes VirtMus to crash on some PDF types. See getPageImage()
*
* @return true if we think we can properly render the PDF page with ICEpdf.
*/
public boolean canRender() {
boolean result = false;
Document doc = getDocument();
if (doc != null) {
if (getPageImage(doc) != null) {
result = true;
}
doc.dispose();
}
return result;
}
@Override
public PlanarImage getFullImg() {
// We don't know yet if we have a good PDF. Let's check.
if (getDocument() == null) {
return PlanarImage.wrapRenderedImage(errText(pageErr, new Rectangle(850, 1100)));
}
if (getPageScale() > 0) {
return PlanarImage.wrapRenderedImage(getFullBufferedImage(getPageScale()));
} else {
// Single image in the whole page
return PlanarImage.wrapRenderedImage( getFullRenderedOp() );
}
}
@Override
public File createImageFile() {
try {
tmpImgFile = File.createTempFile("VirtMus", ".jpg");
JAI.create("filestore", getFullImg(), tmpImgFile.getCanonicalPath(), "JPEG");
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
return null;
}
return tmpImgFile;
}
}