/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id: PDFContentGenerator.java 1805173 2017-08-16 10:50:04Z ssteiner $ */

package org.apache.fop.render.pdf;

import java.awt.Color;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.fop.pdf.PDFColorHandler;
import org.apache.fop.pdf.PDFDocument;
import org.apache.fop.pdf.PDFFilterList;
import org.apache.fop.pdf.PDFLinearization;
import org.apache.fop.pdf.PDFNumber;
import org.apache.fop.pdf.PDFPaintingState;
import org.apache.fop.pdf.PDFReference;
import org.apache.fop.pdf.PDFResourceContext;
import org.apache.fop.pdf.PDFStream;
import org.apache.fop.pdf.PDFText;
import org.apache.fop.pdf.PDFTextUtil;
import org.apache.fop.pdf.PDFXObject;

Generator class encapsulating all object references and state necessary to generate a PDF content stream.
/** * Generator class encapsulating all object references and state necessary to generate a * PDF content stream. */
public class PDFContentGenerator {
Controls whether comments are written to the PDF stream.
/** Controls whether comments are written to the PDF stream. */
protected static final boolean WRITE_COMMENTS = true; private PDFDocument document; private OutputStream outputStream; private PDFResourceContext resourceContext;
the current stream to add PDF commands to
/** the current stream to add PDF commands to */
private PDFStream currentStream; private PDFColorHandler colorHandler;
drawing state
/** drawing state */
protected PDFPaintingState currentState;
Text generation utility holding the current font status
/** Text generation utility holding the current font status */
protected PDFTextUtil textutil; private boolean inMarkedContentSequence; private boolean inArtifactMode; private AffineTransform transform;
Main constructor. Creates a new PDF stream and additional helper classes for text painting and state management.
Params:
  • document – the PDF document
  • out – the output stream the PDF document is generated to
  • resourceContext – the resource context
/** * Main constructor. Creates a new PDF stream and additional helper classes for text painting * and state management. * @param document the PDF document * @param out the output stream the PDF document is generated to * @param resourceContext the resource context */
public PDFContentGenerator(PDFDocument document, OutputStream out, PDFResourceContext resourceContext) { this.document = document; this.outputStream = out; this.resourceContext = resourceContext; this.currentStream = document.getFactory() .makeStream(PDFFilterList.CONTENT_FILTER, false); this.textutil = new PDFTextUtil() { protected void write(String code) { currentStream.add(code); } protected void write(StringBuffer code) { currentStream.add(code); } }; this.currentState = new PDFPaintingState(); this.colorHandler = new PDFColorHandler(document.getResources()); } public AffineTransform getAffineTransform() { return transform; }
Returns the applicable resource context for the generator.
Returns:the resource context
/** * Returns the applicable resource context for the generator. * @return the resource context */
public PDFDocument getDocument() { return this.document; }
Returns the output stream the PDF document is written to.
Returns:the output stream
/** * Returns the output stream the PDF document is written to. * @return the output stream */
public OutputStream getOutputStream() { return this.outputStream; }
Returns the applicable resource context for the generator.
Returns:the resource context
/** * Returns the applicable resource context for the generator. * @return the resource context */
public PDFResourceContext getResourceContext() { return this.resourceContext; }
Returns the PDFStream associated with this instance.
Returns:the PDF stream
/** * Returns the {@link PDFStream} associated with this instance. * @return the PDF stream */
public PDFStream getStream() { return this.currentStream; }
Returns the PDFPaintingState associated with this instance.
Returns:the PDF state
/** * Returns the {@link PDFPaintingState} associated with this instance. * @return the PDF state */
public PDFPaintingState getState() { return this.currentState; }
Returns the PDFTextUtil associated with this instance.
Returns:the text utility
/** * Returns the {@link PDFTextUtil} associated with this instance. * @return the text utility */
public PDFTextUtil getTextUtil() { return this.textutil; }
Flushes all queued PDF objects ready to be written to the output stream.
Throws:
  • IOException – if an error occurs while flushing the PDF objects
/** * Flushes all queued PDF objects ready to be written to the output stream. * @throws IOException if an error occurs while flushing the PDF objects */
public void flushPDFDoc() throws IOException { if (document.isLinearizationEnabled()) { new PDFLinearization(document).outputPages(outputStream); } this.document.output(this.outputStream); }
Writes out a comment.
Params:
  • text – text for the comment
/** * Writes out a comment. * @param text text for the comment */
protected void comment(String text) { if (WRITE_COMMENTS) { getStream().add("% " + text + "\n"); } }
Save graphics state.
/** Save graphics state. */
protected void saveGraphicsState() { endTextObject(); getState().save(); getStream().add("q\n"); }
Save graphics state with optional layer.
/** Save graphics state with optional layer. */
protected void saveGraphicsState(String layer) { endTextObject(); getState().save(); maybeBeginLayer(layer); getStream().add("q\n"); }
Save graphics state.
Params:
  • structElemType – an element type
  • sequenceNum – a sequence number
/** * Save graphics state. * @param structElemType an element type * @param sequenceNum a sequence number */
protected void saveGraphicsState(String structElemType, int sequenceNum) { endTextObject(); getState().save(); beginMarkedContentSequence(structElemType, sequenceNum); getStream().add("q\n"); }
Begins a new marked content sequence (BDC or BMC). If structElemType is null, a BMC operator with an "Artifact" tag is generated. Otherwise, a BDC operator with structElemType as a tag is generated, and the given mcid stored in its property list.
Params:
  • structElemType – the type of the associated structure element
  • mcid – the marked content identifier
/** * Begins a new marked content sequence (BDC or BMC). If {@code structElemType} is * null, a BMC operator with an "Artifact" tag is generated. Otherwise, a BDC operator * with {@code structElemType} as a tag is generated, and the given mcid stored in its * property list. * * @param structElemType the type of the associated structure element * @param mcid the marked content identifier */
protected void beginMarkedContentSequence(String structElemType, int mcid) { beginMarkedContentSequence(structElemType, mcid, null); }
Begins a new marked content sequence (BDC or BMC). If structElemType is null, a BMC operator with an "Artifact" tag is generated. Otherwise, a BDC operator with structElemType as a tag is generated, and the given mcid and actual text are stored in its property list.
Params:
  • structElemType – the type of the associated structure element
  • mcid – the marked content identifier
  • actualText – the replacement text for the marked content
/** * Begins a new marked content sequence (BDC or BMC). If {@code structElemType} is * null, a BMC operator with an "Artifact" tag is generated. Otherwise, a BDC operator * with {@code structElemType} as a tag is generated, and the given mcid and actual * text are stored in its property list. * * @param structElemType the type of the associated structure element * @param mcid the marked content identifier * @param actualText the replacement text for the marked content */
protected void beginMarkedContentSequence(String structElemType, int mcid, String actualText) { assert !this.inMarkedContentSequence; assert !this.inArtifactMode; if (structElemType != null) { String actualTextProperty = actualText == null ? "" : " /ActualText " + PDFText.escapeText(actualText); getStream().add(structElemType + " <</MCID " + String.valueOf(mcid) + actualTextProperty + ">>\n" + "BDC\n"); } else { getStream().add("/Artifact\nBMC\n"); this.inArtifactMode = true; } this.inMarkedContentSequence = true; } void endMarkedContentSequence() { getStream().add("EMC\n"); this.inMarkedContentSequence = false; this.inArtifactMode = false; }
Restored the graphics state valid before the previous saveGraphicsState().
Params:
  • popState – true if the state should also be popped, false if only the PDF command should be issued
/** * Restored the graphics state valid before the previous {@link #saveGraphicsState()}. * @param popState true if the state should also be popped, false if only the PDF command * should be issued */
protected void restoreGraphicsState(boolean popState) { endTextObject(); getStream().add("Q\n"); maybeEndLayer(); if (popState) { getState().restore(); } }
Same as restoreGraphicsState(boolean), with true as a parameter.
/** * Same as {@link #restoreGraphicsState(boolean)}, with <code>true</code> as * a parameter. */
protected void restoreGraphicsState() { restoreGraphicsState(true); }
Same as restoreGraphicsState(), additionally ending the current marked content sequence if any.
/** * Same as {@link #restoreGraphicsState()}, additionally ending the current * marked content sequence if any. */
protected void restoreGraphicsStateAccess() { endTextObject(); getStream().add("Q\n"); if (this.inMarkedContentSequence) { endMarkedContentSequence(); } getState().restore(); } private void maybeBeginLayer(String layer) { if ((layer != null) && (layer.length() > 0)) { getState().setLayer(layer); beginOptionalContent(layer); } } private void maybeEndLayer() { if (getState().getLayerChanged()) { endOptionalContent(); } } private int ocNameIndex; private void beginOptionalContent(String layerId) { String name; PDFReference layer = document.resolveExtensionReference(layerId); if (layer != null) { name = "oc" + ++ocNameIndex; document.getResources().addProperty(name, layer); } else { name = "unknown"; } getStream().add("/OC /" + name + " BDC\n"); } private void endOptionalContent() { getStream().add("EMC\n"); }
Indicates the beginning of a text object.
/** Indicates the beginning of a text object. */
protected void beginTextObject() { if (!textutil.isInTextObject()) { textutil.beginTextObject(); } }
Indicates the beginning of a marked-content text object.
Params:
  • structElemType – structure element type
  • mcid – sequence number
See Also:
/** * Indicates the beginning of a marked-content text object. * * @param structElemType structure element type * @param mcid sequence number * @see #beginTextObject() * @see #beginMarkedContentSequence(String, int) */
protected void beginTextObject(String structElemType, int mcid) { beginTextObject(structElemType, mcid, null); }
Indicates the beginning of a marked-content text object.
Params:
  • structElemType – structure element type
  • mcid – sequence number
  • actualText – the replacement text for the marked content
See Also:
/** * Indicates the beginning of a marked-content text object. * * @param structElemType structure element type * @param mcid sequence number * @param actualText the replacement text for the marked content * @see #beginTextObject() * @see #beginMarkedContentSequence */
protected void beginTextObject(String structElemType, int mcid, String actualText) { if (!textutil.isInTextObject()) { beginMarkedContentSequence(structElemType, mcid, actualText); textutil.beginTextObject(); } }
Indicates the end of a text object.
/** Indicates the end of a text object. */
protected void endTextObject() { if (textutil.isInTextObject()) { textutil.endTextObject(); if (this.inMarkedContentSequence) { endMarkedContentSequence(); } } }
Concatenates the given transformation matrix with the current one.
Params:
  • transform – the transformation matrix (in points)
/** * Concatenates the given transformation matrix with the current one. * @param transform the transformation matrix (in points) */
public void concatenate(AffineTransform transform) { this.transform = transform; if (!transform.isIdentity()) { getState().concatenate(transform); getStream().add(CTMHelper.toPDFString(transform, false) + " cm\n"); } }
Intersects the current clip region with the given rectangle.
Params:
  • rect – the clip rectangle
/** * Intersects the current clip region with the given rectangle. * @param rect the clip rectangle */
public void clipRect(Rectangle rect) { StringBuffer sb = new StringBuffer(); sb.append(format(rect.x / 1000f)).append(' '); sb.append(format(rect.y / 1000f)).append(' '); sb.append(format(rect.width / 1000f)).append(' '); sb.append(format(rect.height / 1000f)).append(" re W n\n"); add(sb.toString()); }
Adds content to the stream.
Params:
  • content – the PDF content
/** * Adds content to the stream. * @param content the PDF content */
public void add(String content) { getStream().add(content); }
Formats a float value (normally coordinates in points) as Strings.
Params:
  • value – the value
Returns:the formatted value
/** * Formats a float value (normally coordinates in points) as Strings. * @param value the value * @return the formatted value */
public static final String format(float value) { return PDFNumber.doubleOut(value); }
Sets the current line width in points.
Params:
  • width – line width in points
/** * Sets the current line width in points. * @param width line width in points */
public void updateLineWidth(float width) { if (getState().setLineWidth(width)) { //Only write if value has changed WRT the current line width getStream().add(format(width) + " w\n"); } }
Sets the current character spacing (Tc) value.
Params:
  • value – the Tc value (in unscaled text units)
/** * Sets the current character spacing (Tc) value. * @param value the Tc value (in unscaled text units) */
public void updateCharacterSpacing(float value) { if (getState().setCharacterSpacing(value)) { getStream().add(format(value) + " Tc\n"); } }
Establishes a new foreground or fill color.
Params:
  • col – the color to apply
  • fill – true to set the fill color, false for the foreground color
  • stream – the PDFStream to write the PDF code to
/** * Establishes a new foreground or fill color. * @param col the color to apply * @param fill true to set the fill color, false for the foreground color * @param stream the PDFStream to write the PDF code to */
public void setColor(Color col, boolean fill, PDFStream stream) { assert stream != null; StringBuffer sb = new StringBuffer(); setColor(col, fill, sb); stream.add(sb.toString()); }
Establishes a new foreground or fill color.
Params:
  • col – the color to apply
  • fill – true to set the fill color, false for the foreground color
/** * Establishes a new foreground or fill color. * @param col the color to apply * @param fill true to set the fill color, false for the foreground color */
public void setColor(Color col, boolean fill) { setColor(col, fill, getStream()); }
Establishes a new foreground or fill color. In contrast to updateColor this method does not check the PDFState for optimization possibilities.
Params:
  • col – the color to apply
  • fill – true to set the fill color, false for the foreground color
  • pdf – StringBuffer to write the PDF code to, if null, the code is written to the current stream.
/** * Establishes a new foreground or fill color. In contrast to updateColor * this method does not check the PDFState for optimization possibilities. * @param col the color to apply * @param fill true to set the fill color, false for the foreground color * @param pdf StringBuffer to write the PDF code to, if null, the code is * written to the current stream. */
protected void setColor(Color col, boolean fill, StringBuffer pdf) { if (pdf != null) { colorHandler.establishColor(pdf, col, fill); } else { setColor(col, fill, getStream()); } }
Establishes a new foreground or fill color.
Params:
  • col – the color to apply (null skips this operation)
  • fill – true to set the fill color, false for the foreground color
  • pdf – StringBuffer to write the PDF code to, if null, the code is written to the current stream.
/** * Establishes a new foreground or fill color. * @param col the color to apply (null skips this operation) * @param fill true to set the fill color, false for the foreground color * @param pdf StringBuffer to write the PDF code to, if null, the code is * written to the current stream. */
public void updateColor(Color col, boolean fill, StringBuffer pdf) { if (col == null) { return; } boolean update = false; if (fill) { update = getState().setBackColor(col); } else { update = getState().setColor(col); } if (update) { setColor(col, fill, pdf); } }
Places a previously registered image at a certain place on the page.
Params:
  • x – X coordinate
  • y – Y coordinate
  • w – width for image
  • h – height for image
  • xobj – the image XObject
/** * Places a previously registered image at a certain place on the page. * @param x X coordinate * @param y Y coordinate * @param w width for image * @param h height for image * @param xobj the image XObject */
public void placeImage(float x, float y, float w, float h, PDFXObject xobj) { saveGraphicsState(); add(format(w) + " 0 0 " + format(-h) + " " + format(x) + " " + format(y + h) + " cm\n" + xobj.getName() + " Do\n"); restoreGraphicsState(); } public void placeImage(AffineTransform at, String stream) { saveGraphicsState(); concatenate(at); add(stream); restoreGraphicsState(); }
Places a previously registered image at a certain place on the page, bracketing it as a marked-content sequence.
Params:
  • x – X coordinate
  • y – Y coordinate
  • w – width for image
  • h – height for image
  • xobj – the image XObject
  • structElemType – structure element type
  • mcid – sequence number
See Also:
/** * Places a previously registered image at a certain place on the page, * bracketing it as a marked-content sequence. * * @param x X coordinate * @param y Y coordinate * @param w width for image * @param h height for image * @param xobj the image XObject * @param structElemType structure element type * @param mcid sequence number * @see #beginMarkedContentSequence(String, int) */
public void placeImage(float x, float y, float w, float h, PDFXObject xobj, String structElemType, int mcid) { saveGraphicsState(structElemType, mcid); add(format(w) + " 0 0 " + format(-h) + " " + format(x) + " " + format(y + h) + " cm\n" + xobj.getName() + " Do\n"); restoreGraphicsStateAccess(); } }