com.orsonpdf.Page Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of orsonpdf Show documentation
Show all versions of orsonpdf Show documentation
OrsonPDF is an API that provides a Graphics2D implementation
that generates PDF output.
/* =====================================================================
* OrsonPDF : a fast, light-weight PDF library for the Java(tm) platform
* =====================================================================
*
* (C)opyright 2013-2015, by Object Refinery Limited. All rights reserved.
*
* Project Info: http://www.object-refinery.com/orsonpdf/index.html
*
* 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 3 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, see .
*
* [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.]
*
* If you do not wish to be bound by the terms of the GPL, an alternative
* commercial license can be purchased. For details, please see visit the
* Orson PDF home page:
*
* http://www.object-refinery.com/orsonpdf/index.html
*
*/
package com.orsonpdf;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Image;
import java.awt.MultipleGradientPaint;
import java.awt.RadialGradientPaint;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.orsonpdf.Pattern.ShadingPattern;
import com.orsonpdf.filter.FlateFilter;
import com.orsonpdf.shading.AxialShading;
import com.orsonpdf.shading.RadialShading;
import com.orsonpdf.shading.Shading;
import com.orsonpdf.util.Args;
import com.orsonpdf.util.GradientPaintKey;
import com.orsonpdf.util.RadialGradientPaintKey;
/**
* Represents a page in a {@link PDFDocument}. Our objective is to be able
* to write to the page using the {@link PDFGraphics2D} class (see the
* {@link #getGraphics2D()} method).
*/
public class Page extends PDFObject {
/** The pages of the document. */
private Pages parent;
/** The page bounds. */
private Rectangle2D bounds;
/** The page contents. */
private GraphicsStream contents;
/** The Graphics2D for writing to the page contents. */
private PDFGraphics2D graphics2d;
/**
* The list of font (names) used on the page. We let the parent take
* care of tracking the font objects.
*/
private List fontsOnPage;
/**
* A map between gradient paints and the names used to define the
* associated pattern in the page resources.
*/
private Map gradientPaintsOnPage;
private Map radialGradientPaintsOnPage;
/** The pattern dictionary for this page. */
private Dictionary patterns;
/** The ExtGState dictionary for the page. */
private Dictionary graphicsStates;
/**
* The transform between Page and Java2D coordinates, used in Shading
* patterns.
*/
private AffineTransform j2DTransform;
private Dictionary xObjects = new Dictionary();
/**
* Creates a new page.
*
* @param number the PDF object number.
* @param generation the PDF object generation number.
* @param parent the parent (manages the pages in the {@code PDFDocument}).
* @param bounds the page bounds ({@code null} not permitted).
*/
Page(int number, int generation, Pages parent, Rectangle2D bounds) {
this(number, generation, parent, bounds, true);
}
/**
* Creates a new page.
*
* @param number the PDF object number.
* @param generation the PDF object generation number.
* @param parent the parent (manages the pages in the {@code PDFDocument}).
* @param bounds the page bounds ({@code null} not permitted).
* @param filter a flag that controls whether or not the graphics stream
* for the page has a FlateFilter applied.
*
* @since 1.4
*/
Page(int number, int generation, Pages parent, Rectangle2D bounds,
boolean filter) {
super(number, generation);
Args.nullNotPermitted(bounds, "bounds");
this.parent = parent;
this.bounds = (Rectangle2D) bounds.clone();
this.fontsOnPage = new ArrayList();
int n = this.parent.getDocument().getNextNumber();
this.contents = new GraphicsStream(n, this);
if (filter) {
this.contents.addFilter(new FlateFilter());
}
this.gradientPaintsOnPage = new HashMap();
this.radialGradientPaintsOnPage = new HashMap();
this.patterns = new Dictionary();
this.graphicsStates = new Dictionary();
this.j2DTransform = AffineTransform.getTranslateInstance(0.0,
bounds.getHeight());
this.j2DTransform.concatenate(AffineTransform.getScaleInstance(1.0,
-1.0));
}
/**
* Returns a new rectangle containing the bounds for this page (as supplied
* to the constructor).
*
* @return The page bounds.
*/
public Rectangle2D getBounds() {
return (Rectangle2D) this.bounds.clone();
}
/**
* Returns the {@code PDFObject} that represents the page content.
*
* @return The {@code PDFObject} that represents the page content.
*/
public PDFObject getContents() {
return this.contents;
}
/**
* Returns the {@link PDFGraphics2D} instance for drawing to the page.
*
* @return The {@code PDFGraphics2D} instance for drawing to the page.
*/
public PDFGraphics2D getGraphics2D() {
if (this.graphics2d == null) {
this.graphics2d = new PDFGraphics2D(this.contents,
(int) this.bounds.getWidth(),
(int) this.bounds.getHeight());
}
return this.graphics2d;
}
/**
* Finds the font reference corresponding to the given Java2D font,
* creating a new one if there isn't one already.
*
* @param font the AWT font.
*
* @return The font reference.
*/
String findOrCreateFontReference(Font font) {
String ref = this.parent.findOrCreateFontReference(font);
if (!this.fontsOnPage.contains(ref)) {
this.fontsOnPage.add(ref);
}
return ref;
}
private Dictionary createFontDictionary() {
Dictionary d = new Dictionary();
for (String name : this.fontsOnPage) {
PDFFont f = this.parent.getFont(name);
d.put(name, f.getReference());
}
return d;
}
/**
* Returns the name of the pattern for the specified {@code GradientPaint},
* reusing an existing pattern if possible, otherwise creating a new
* pattern if necessary.
*
* @param gp the gradient ({@code null} not permitted).
*
* @return The pattern name.
*/
String findOrCreatePattern(GradientPaint gp) {
GradientPaintKey key = new GradientPaintKey(gp);
String patternName = this.gradientPaintsOnPage.get(key);
if (patternName == null) {
PDFDocument doc = this.parent.getDocument();
Function f = new ExponentialInterpolationFunction(
doc.getNextNumber(),
gp.getColor1().getRGBColorComponents(null),
gp.getColor2().getRGBColorComponents(null));
doc.addObject(f);
double[] coords = new double[4];
coords[0] = gp.getPoint1().getX();
coords[1] = gp.getPoint1().getY();
coords[2] = gp.getPoint2().getX();
coords[3] = gp.getPoint2().getY();
Shading s = new AxialShading(doc.getNextNumber(), coords, f);
doc.addObject(s);
Pattern p = new ShadingPattern(doc.getNextNumber(), s,
this.j2DTransform);
doc.addObject(p);
patternName = "/P" + (this.patterns.size() + 1);
this.patterns.put(patternName, p);
this.gradientPaintsOnPage.put(key, patternName);
}
return patternName;
}
/**
* Returns the name of the pattern for the specified
* {@code RadialGradientPaint}, reusing an existing pattern if
* possible, otherwise creating a new pattern if necessary.
*
* @param gp the gradient ({@code null} not permitted).
*
* @return The pattern name.
*/
String findOrCreatePattern(RadialGradientPaint gp) {
RadialGradientPaintKey key = new RadialGradientPaintKey(gp);
String patternName = this.radialGradientPaintsOnPage.get(key);
if (patternName == null) {
PDFDocument doc = this.parent.getDocument();
Function f = createFunctionForMultipleGradient(gp);
doc.addObject(f);
double[] coords = new double[6];
coords[0] = gp.getFocusPoint().getX();
coords[1] = gp.getFocusPoint().getY();
coords[2] = 0.0;
coords[3] = gp.getCenterPoint().getX();
coords[4] = gp.getCenterPoint().getY();
coords[5] = gp.getRadius();
Shading s = new RadialShading(doc.getNextNumber(), coords, f);
doc.addObject(s);
Pattern p = new ShadingPattern(doc.getNextNumber(), s,
this.j2DTransform);
doc.addObject(p);
patternName = "/P" + (this.patterns.size() + 1);
this.patterns.put(patternName, p);
this.radialGradientPaintsOnPage.put(key, patternName);
}
return patternName;
}
private Function createFunctionForMultipleGradient(
MultipleGradientPaint mgp) {
PDFDocument doc = this.parent.getDocument();
if (mgp.getColors().length == 2) {
Function f = new ExponentialInterpolationFunction(
doc.getNextNumber(),
mgp.getColors()[0].getRGBColorComponents(null),
mgp.getColors()[1].getRGBColorComponents(null));
return f;
} else {
int count = mgp.getColors().length - 1;
Function[] functions = new Function[count];
float[] fbounds = new float[count - 1];
float[] encode = new float[count * 2];
for (int i = 0; i < count; i++) {
// create a linear function for each pair of colors
functions[i] = new ExponentialInterpolationFunction(
doc.getNextNumber(),
mgp.getColors()[i].getRGBColorComponents(null),
mgp.getColors()[i + 1].getRGBColorComponents(null));
doc.addObject(functions[i]);
if (i < count - 1) {
fbounds[i] = mgp.getFractions()[i + 1];
}
encode[i * 2] = 0;
encode[i * 2 + 1] = 1;
}
return new StitchingFunction(doc.getNextNumber(), functions,
fbounds, encode);
}
}
private Map alphaDictionaries
= new HashMap();
/**
* Returns the name of the Graphics State Dictionary that can be used
* for the specified alpha value - if there is no existing dictionary
* then a new one is created.
*
* @param alpha the alpha value in the range 0 to 255.
*
* @return The graphics state dictionary reference.
*/
String findOrCreateGSDictionary(int alpha) {
Integer key = Integer.valueOf(alpha);
float alphaValue = alpha / 255f;
String name = this.alphaDictionaries.get(key);
if (name == null) {
PDFDocument pdfDoc = this.parent.getDocument();
GraphicsStateDictionary gsd = new GraphicsStateDictionary(
pdfDoc.getNextNumber());
gsd.setNonStrokeAlpha(alphaValue);
gsd.setStrokeAlpha(alphaValue);
pdfDoc.addObject(gsd);
name = "/GS" + (this.graphicsStates.size() + 1);
this.graphicsStates.put(name, gsd);
this.alphaDictionaries.put(key, name);
}
return name;
}
/**
* Adds a soft mask image to the page. This is called from the
* {@link #addImage(java.awt.Image)} method to support image transparency.
*
* @param img the image ({@code null} not permitted).
*
* @return The soft mask image reference.
*/
String addSoftMaskImage(Image img) {
Args.nullNotPermitted(img, "img");
PDFDocument pdfDoc = this.parent.getDocument();
PDFSoftMaskImage softMaskImage = new PDFSoftMaskImage(
pdfDoc.getNextNumber(), img);
softMaskImage.addFilter(new FlateFilter());
pdfDoc.addObject(softMaskImage);
String reference = "/Image" + this.xObjects.size();
this.xObjects.put(reference, softMaskImage);
return softMaskImage.getReference();
}
/**
* Adds an image to the page. This creates the required PDF object,
* as well as adding a reference in the {@code xObjects} resources.
* You should not call this method directly, it exists for the use of the
* {@link PDFGraphics2D#drawImage(java.awt.Image, int, int, int, int, java.awt.image.ImageObserver)}
* method.
*
* @param img the image ({@code null} not permitted).
*
* @return The image reference name.
*/
String addImage(Image img, boolean addSoftMaskImage) {
Args.nullNotPermitted(img, "img");
PDFDocument pdfDoc = this.parent.getDocument();
String softMaskImageRef = null;
if (addSoftMaskImage) {
softMaskImageRef = addSoftMaskImage(img);
}
PDFImage image = new PDFImage(pdfDoc.getNextNumber(), img,
softMaskImageRef);
image.addFilter(new FlateFilter());
pdfDoc.addObject(image);
String reference = "/Image" + this.xObjects.size();
this.xObjects.put(reference, image);
return reference;
}
@Override
public byte[] getObjectBytes() {
return createDictionary().toPDFBytes();
}
private Dictionary createDictionary() {
Dictionary dictionary = new Dictionary("/Page");
dictionary.put("/Parent", this.parent);
dictionary.put("/MediaBox", this.bounds);
dictionary.put("/Contents", this.contents);
Dictionary resources = new Dictionary();
resources.put("/ProcSet", "[/PDF /Text /ImageB /ImageC /ImageI]");
if (!this.xObjects.isEmpty()) {
resources.put("/XObject", this.xObjects);
}
if (!this.fontsOnPage.isEmpty()) {
resources.put("/Font", createFontDictionary());
}
if (!this.patterns.isEmpty()) {
resources.put("/Pattern", this.patterns);
}
if (!this.graphicsStates.isEmpty()) {
resources.put("/ExtGState", this.graphicsStates);
}
dictionary.put("/Resources", resources);
return dictionary;
}
}