Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* {{{ header & license
* Copyright (c) 2006 Wisconsin Court System
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* }}}
*/
package com.openhtmltopdf.pdfboxout;
import com.openhtmltopdf.bidi.BidiReorderer;
import com.openhtmltopdf.bidi.SimpleBidiReorderer;
import com.openhtmltopdf.css.constants.IdentValue;
import com.openhtmltopdf.css.parser.FSCMYKColor;
import com.openhtmltopdf.css.parser.FSColor;
import com.openhtmltopdf.css.parser.FSRGBColor;
import com.openhtmltopdf.css.style.CalculatedStyle;
import com.openhtmltopdf.css.style.CssContext;
import com.openhtmltopdf.css.value.FontSpecification;
import com.openhtmltopdf.extend.FSImage;
import com.openhtmltopdf.extend.OutputDevice;
import com.openhtmltopdf.extend.OutputDeviceGraphicsDrawer;
import com.openhtmltopdf.layout.SharedContext;
import com.openhtmltopdf.outputdevice.helper.FontResolverHelper;
import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver.FontDescription;
import com.openhtmltopdf.pdfboxout.PdfBoxPerDocumentFormState;
import com.openhtmltopdf.render.*;
import com.openhtmltopdf.util.ArrayUtil;
import com.openhtmltopdf.util.Configuration;
import com.openhtmltopdf.util.XRLog;
import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D;
import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2DFontTextDrawer;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.state.RenderingMode;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageFitHeightDestination;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.RenderingHints.Key;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.List;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.regex.Pattern;
public class PdfBoxOutputDevice extends AbstractOutputDevice implements OutputDevice {
//
// A discussion on units:
// PDF points are defined as 1/72 inch.
// CSS pixels are defined as 1/96 inch.
// PDF text units are defined as 1/1000 of a PDF point.
// OpenHTMLtoPDF dots are defined as 1/20 of a CSS pixel.
// Therefore dots per point is 20 * 96/72 or about 26.66.
// Dividing by _dotsPerPoint will convert OpenHTMLtoPDF dots to PDF points.
// Theoretically, this is all configurable, but not tested at all with other values.
//
private static final int FILL = 1;
private static final int STROKE = 2;
private static final int CLIP = 3;
private static final AffineTransform IDENTITY = new AffineTransform();
private static final BasicStroke STROKE_ONE = new BasicStroke(1);
private static final boolean ROUND_RECT_DIMENSIONS_DOWN = Configuration.isTrue("xr.pdf.round.rect.dimensions.down", false);
// The current PDF page.
private PDPage _page;
// A wrapper around the IOException throwing content stream methods which only throws runtime exceptions.
// Created for every page.
private PdfContentStreamAdapter _cp;
// We need the page height because the project uses top down units which PDFs use bottom up units.
// This is in PDF points unit (1/72 inch).
private float _pageHeight;
// The desired font as set by setFont.
// This may not yet be set on the PDF text stream.
private PdfBoxFSFont _font;
// This transform is a scale and translate.
// It scales from internal dots to PDF points.
// It translates positions to implement page margins.
private AffineTransform _transform = new AffineTransform();
// A stack of currently in force transforms on the PDF graphics state.
// NOTE: Transforms are cumulative and order is important.
// After the graphics state is restored in setClip we must appropriately reapply the transforms
// that should be in effect.
private final Deque transformStack = new ArrayDeque();
// An index into the transformStack. When we save state we set this to the length of transformStack
// then we know we have to reapply those transforms set after saving state upon restoring state.
private int clipTransformIndex;
// We use these to keep track of where the current transform-origin is in absolute internal dots units.
private float _absoluteTransformOriginX = 0;
private float _absoluteTransformOriginY = 0;
// The desired color as set by setColor.
// To make sure this color is set on the PDF graphics stream call ensureFillColor or ensureStrokeColor.
private FSColor _color = FSRGBColor.BLACK;
// The actual fill and stroke colors set on the PDF graphics stream.
// We keep these so we don't bloat the PDF with unneeded color calls.
private FSColor _fillColor;
private FSColor _strokeColor;
// The currently set stroke. This will not yet be set on the PDF graphics stream.
// This is already transformed to PDF points units.
// Call setStrokeDiff to set this on the PDF graphics stream.
private Stroke _stroke = null;
// Same as _stroke, but not transformed. That is, it is in internal dots units.
private Stroke _originalStroke = null;
// The currently set stroke on the PDF graphics stream. When we call setStokeDiff
// this is compared with _stroke and only the differences are output to the graphics stream.
private Stroke _oldStroke = null;
// The clipped area, as set on the PDF graphics stream, in PDF points units.
private Area _clip;
// Essentially per-run global variables.
private SharedContext _sharedContext;
// The project internal dots per PDF point unit. See discussion of units above.
private float _dotsPerPoint;
// The PDF document. Note: We are not responsible for closing it.
private PDDocument _writer;
// The default destination for the current page.
// This is used to create bookmarks without a valid destination.
private PDDestination _defaultDestination;
// Contains a list of bookmarks for the document.
private final List _bookmarks = new ArrayList();
// Contains a list of metadata items for the document.
private final List _metadata = new ArrayList();
// Contains all the state needed to manage form controls
private final PdfBoxPerDocumentFormState _formState = new PdfBoxPerDocumentFormState();
// The root box in the document. We keep this so we can search for specific boxes below it
// such as links or form controls which we need to position.
private Box _root;
// In theory, we can append to a PDF document, rather than creating new. This keeps the start page
// so we can use it to offset when we need to know the PDF page number.
// NOTE: Not tested recently, this feature may be broken.
private int _startPageNo;
// Whether we are in test mode, currently not used here, but keep around in case we need it down the track.
@SuppressWarnings("unused")
private final boolean _testMode;
// Link manage handles a links. We add the link in paintBackground and then output links when the document is finished.
private PdfBoxLinkManager _linkManager;
// Not used currently.
private RenderingContext _renderingContext;
// The bidi reorderer is responsible for shaping Arabic text, deshaping and
// converting RTL text into its visual order.
private BidiReorderer _reorderer = new SimpleBidiReorderer();
// Font Mapping for the Graphics2D output
private PdfBoxGraphics2DFontTextDrawer _fontTextDrawer;
public PdfBoxOutputDevice(float dotsPerPoint, boolean testMode) {
_dotsPerPoint = dotsPerPoint;
_testMode = testMode;
}
public void setWriter(PDDocument writer) {
_writer = writer;
}
public PDDocument getWriter() {
return _writer;
}
/**
* Start a page. A new PDF page starts a new content stream so all graphics state has to be
* set back to default.
*/
public void initializePage(PDPageContentStream currentPage, PDPage page, float height) {
_cp = new PdfContentStreamAdapter(currentPage);
_page = page;
_pageHeight = height;
if (!isFastRenderer()) {
// We call saveGraphics so we can get back to a raw (unclipped) state after we have clipped.
// restoreGraphics is only used by setClip and page finish (unless the fast renderer is in use).
_cp.saveGraphics();
}
_transform = new AffineTransform();
_transform.scale(1.0d / _dotsPerPoint, 1.0d / _dotsPerPoint);
_absoluteTransformOriginX = 0;
_absoluteTransformOriginY += height * _dotsPerPoint;
_stroke = transformStroke(STROKE_ONE);
_originalStroke = _stroke;
_oldStroke = _stroke;
setStrokeDiff(_stroke, null);
if (_defaultDestination == null) {
// Create a default destination to the top of the first page.
PDPageFitHeightDestination dest = new PDPageFitHeightDestination();
dest.setPage(page);
}
}
public void finishPage() {
if (!isFastRenderer()) {
_cp.restoreGraphics();
}
_cp.closeContent();
}
public void paintReplacedElement(RenderingContext c, BlockBox box) {
PdfBoxReplacedElement element = (PdfBoxReplacedElement) box.getReplacedElement();
element.paint(c, this, box);
}
/**
* We use paintBackground to do extra stuff such as processing links, forms and form controls.
*/
@Override
public void paintBackground(RenderingContext c, Box box) {
super.paintBackground(c, box);
// processLinkLater will take care of making sure it is actually a link.
_linkManager.processLinkLater(c, box, _page, _pageHeight, _transform);
if (box.getElement() != null && box.getElement().getNodeName().equals("form")) {
_formState.addFormIfRequired(box, this);
} else if (box.getElement() != null &&
ArrayUtil.isOneOf(box.getElement().getNodeName(), "input", "textarea", "button", "select", "openhtmltopdf-combo")) {
// Add controls to list to process later. We do this in case we paint a control background
// before its associated form.
_formState.addControlIfRequired(box, _page, _transform, c, _pageHeight);
}
}
private void processControls() {
_formState.processControls(_sharedContext, _writer, _root);
}
/**
* Given a value in dots units, converts to PDF points.
*/
public float getDeviceLength(float length) {
return length / _dotsPerPoint;
}
public void drawBorderLine(Shape bounds, int side, int lineWidth, boolean solid) {
draw(bounds);
}
public void setColor(FSColor color) {
if (color instanceof FSRGBColor) {
_color = color;
} else if (color instanceof FSCMYKColor) {
_color = color;
} else {
assert(_color instanceof FSRGBColor || _color instanceof FSCMYKColor);
}
}
public void draw(Shape s) {
followPath(s, STROKE);
}
protected void drawLine(int x1, int y1, int x2, int y2) {
Line2D line = new Line2D.Double(x1, y1, x2, y2);
draw(line);
}
public void drawRect(int x, int y, int width, int height) {
draw(new Rectangle(x, y, width, height));
}
public void drawOval(int x, int y, int width, int height) {
Ellipse2D oval = new Ellipse2D.Float(x, y, width, height);
draw(oval);
}
public void fill(Shape s) {
followPath(s, FILL);
}
public void fillRect(int x, int y, int width, int height) {
if (ROUND_RECT_DIMENSIONS_DOWN) {
fill(new Rectangle(x, y, width - 1, height - 1));
} else {
fill(new Rectangle(x, y, width, height));
}
}
public void fillOval(int x, int y, int width, int height) {
Ellipse2D oval = new Ellipse2D.Float(x, y, width, height);
fill(oval);
}
public void translate(double tx, double ty) {
_transform.translate(tx, ty);
}
public Object getRenderingHint(Key key) {
return null;
}
public void setRenderingHint(Key key, Object value) {
}
public void setFont(FSFont font) {
_font = ((PdfBoxFSFont) font);
}
/**
* This returns a matrix that will convert y values to bottom up coordinate space (as used by PDFs).
*/
private AffineTransform normalizeMatrix(AffineTransform current) {
double[] mx = new double[6];
AffineTransform result = new AffineTransform();
result.getMatrix(mx);
mx[3] = -1;
mx[5] = _pageHeight;
result = new AffineTransform(mx);
result.concatenate(current);
return result;
}
public void drawString(String s, float x, float y, JustificationInfo info) {
PDFont firstFont = _font.getFontDescription().get(0).getFont();
// First check if the string will print with the current font entirely.
try {
firstFont.getStringWidth(s);
// We got here, so all is good.
drawStringFast(s, x, y, info, _font.getFontDescription().get(0), _font.getSize2D());
return;
}
catch (Exception e) {
// Fallthrough, we'll have to process the string into font runs.
}
List fontRuns = PdfBoxTextRenderer.divideIntoFontRuns(_font, s, _reorderer);
float xOffset = 0f;
for (FontRun run : fontRuns) {
drawStringFast(run.str, x + xOffset, y, info, run.des, _font.getSize2D());
try {
xOffset += (run.des.getFont().getStringWidth(run.str) / 1000f) * _font.getSize2D();
} catch (Exception e) {
XRLog.render(Level.WARNING, "BUG. Font didn't contain expected character.", e);
}
}
}
public void drawStringFast(String s, float x, float y, JustificationInfo info, FontDescription desc, float fontSize) {
if (s.length() == 0)
return;
ensureFillColor();
AffineTransform at = (AffineTransform) getTransform().clone();
at.translate(x, y);
AffineTransform inverse = normalizeMatrix(at);
AffineTransform flipper = AffineTransform.getScaleInstance(1, -1);
inverse.concatenate(flipper);
inverse.scale(_dotsPerPoint, _dotsPerPoint);
double[] mx = new double[6];
inverse.getMatrix(mx);
float b = (float) mx[1];
float c = (float) mx[2];
fontSize = fontSize / _dotsPerPoint;
boolean resetMode = false;
FontSpecification fontSpec = getFontSpecification();
if (fontSpec != null) {
int need = FontResolverHelper.convertWeightToInt(fontSpec.fontWeight);
int have = desc.getWeight();
if (need > have) {
_cp.setRenderingMode(RenderingMode.FILL_STROKE);
float lineWidth = fontSize * 0.04f; // 4% of font size
_cp.setLineWidth(lineWidth);
resetMode = true;
ensureStrokeColor();
}
if ((fontSpec.fontStyle == IdentValue.ITALIC) && (desc.getStyle() != IdentValue.ITALIC)) {
b = 0f;
c = 0.21256f;
}
}
_cp.beginText();
_cp.setFont(desc.getFont(), fontSize);
_cp.setTextMatrix((float) mx[0], b, c, (float) mx[3], (float) mx[4], (float) mx[5]);
if (info != null ) {
// Note: Justification info is also used
// to implement letter-spacing CSS property.
// Justification must be done through TJ rendering
// because Tw param does not work for UNICODE fonts
Object[] array = makeJustificationArray(s, info);
_cp.drawStringWithPositioning(array);
} else {
_cp.drawString(s);
}
_cp.endText();
if (resetMode) {
_cp.setRenderingMode(RenderingMode.FILL);
_cp.setLineWidth(1);
}
}
public static class FontRun {
String str;
FontDescription des;
}
private Object[] makeJustificationArray(String s, JustificationInfo info) {
List