123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- /*
- * 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$ */
-
- 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.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.
- */
- public class PDFContentGenerator {
-
- /** 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 */
- private PDFStream currentStream;
-
- private PDFColorHandler colorHandler;
-
- /** drawing state */
- protected PDFPaintingState currentState;
- /** 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.
- * @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.
- * @return the resource context
- */
- public PDFDocument getDocument() {
- return this.document;
- }
-
- /**
- * 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.
- * @return the resource context
- */
- public PDFResourceContext getResourceContext() {
- return this.resourceContext;
- }
-
- /**
- * Returns the {@link PDFStream} associated with this instance.
- * @return the PDF stream
- */
- public PDFStream getStream() {
- return this.currentStream;
- }
-
- /**
- * Returns the {@link PDFPaintingState} associated with this instance.
- * @return the PDF state
- */
- public PDFPaintingState getState() {
- return this.currentState;
- }
-
- /**
- * 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
- */
- public void flushPDFDoc() throws IOException {
- this.document.output(this.outputStream);
- }
-
- /**
- * 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. */
- protected void saveGraphicsState() {
- endTextObject();
- getState().save();
- getStream().add("q\n");
- }
-
- /** Save graphics state with optional layer. */
- protected void saveGraphicsState(String layer) {
- endTextObject();
- getState().save();
- maybeBeginLayer(layer);
- getStream().add("q\n");
- }
-
- /**
- * 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 {@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 {@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 {@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 {@link #restoreGraphicsState(boolean)}, with <code>true</code> as
- * a parameter.
- */
- protected void restoreGraphicsState() {
- restoreGraphicsState(true);
- }
-
- /**
- * 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 = 0;
-
- 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. */
- protected void beginTextObject() {
- if (!textutil.isInTextObject()) {
- textutil.beginTextObject();
- }
- }
-
- /**
- * 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.
- *
- * @param structElemType structure element type
- * @param mcid sequence number
- * @param actualText the replacement text for the marked content
- * @see #beginTextObject()
- * @see #beginMarkedContentSequence(String, int, String))
- */
- 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. */
- protected void endTextObject() {
- if (textutil.isInTextObject()) {
- textutil.endTextObject();
- if (this.inMarkedContentSequence) {
- endMarkedContentSequence();
- }
- }
- }
-
- /**
- * 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.
- * @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.
- * @param content the PDF content
- */
- public void add(String content) {
- getStream().add(content);
- }
-
- /**
- * 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.
- * @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.
- * @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.
- * @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.
- * @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.
- * @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.
- * @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.
- * @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.
- *
- * @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();
- }
-
- }
|