Source Code Cross Referenced for PDFRenderer.java in  » Graphic-Library » fop » org » apache » fop » render » pdf » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Graphic Library » fop » org.apache.fop.render.pdf 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         * Licensed to the Apache Software Foundation (ASF) under one or more
0003:         * contributor license agreements.  See the NOTICE file distributed with
0004:         * this work for additional information regarding copyright ownership.
0005:         * The ASF licenses this file to You under the Apache License, Version 2.0
0006:         * (the "License"); you may not use this file except in compliance with
0007:         * the License.  You may obtain a copy of the License at
0008:         *
0009:         *      http://www.apache.org/licenses/LICENSE-2.0
0010:         *
0011:         * Unless required by applicable law or agreed to in writing, software
0012:         * distributed under the License is distributed on an "AS IS" BASIS,
0013:         * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014:         * See the License for the specific language governing permissions and
0015:         * limitations under the License.
0016:         */
0017:
0018:        /* $Id: PDFRenderer.java 542237 2007-05-28 14:31:24Z jeremias $ */
0019:
0020:        package org.apache.fop.render.pdf;
0021:
0022:        // Java
0023:        import java.io.IOException;
0024:        import java.io.InputStream;
0025:        import java.io.OutputStream;
0026:        import java.net.URL;
0027:        import java.awt.geom.Point2D;
0028:        import java.awt.Color;
0029:        import java.awt.color.ColorSpace;
0030:        import java.awt.color.ICC_Profile;
0031:        import java.awt.geom.Rectangle2D;
0032:        import java.awt.geom.AffineTransform;
0033:        import java.util.Iterator;
0034:        import java.util.Map;
0035:        import java.util.List;
0036:
0037:        import javax.xml.transform.Source;
0038:        import javax.xml.transform.stream.StreamSource;
0039:
0040:        // XML
0041:        import org.w3c.dom.Document;
0042:
0043:        // Avalon
0044:        import org.apache.commons.io.IOUtils;
0045:
0046:        // FOP
0047:        import org.apache.fop.apps.FOPException;
0048:        import org.apache.fop.apps.FOUserAgent;
0049:        import org.apache.fop.apps.MimeConstants;
0050:        import org.apache.fop.area.Area;
0051:        import org.apache.fop.area.Block;
0052:        import org.apache.fop.area.CTM;
0053:        import org.apache.fop.area.LineArea;
0054:        import org.apache.fop.area.OffDocumentExtensionAttachment;
0055:        import org.apache.fop.area.PageViewport;
0056:        import org.apache.fop.area.RegionViewport;
0057:        import org.apache.fop.area.Trait;
0058:        import org.apache.fop.area.OffDocumentItem;
0059:        import org.apache.fop.area.BookmarkData;
0060:        import org.apache.fop.area.inline.AbstractTextArea;
0061:        import org.apache.fop.area.inline.TextArea;
0062:        import org.apache.fop.area.inline.Image;
0063:        import org.apache.fop.area.inline.Leader;
0064:        import org.apache.fop.area.inline.InlineArea;
0065:        import org.apache.fop.area.inline.InlineParent;
0066:        import org.apache.fop.area.inline.WordArea;
0067:        import org.apache.fop.area.inline.SpaceArea;
0068:        import org.apache.fop.fonts.Typeface;
0069:        import org.apache.fop.fonts.Font;
0070:        import org.apache.fop.image.FopImage;
0071:        import org.apache.fop.image.ImageFactory;
0072:        import org.apache.fop.image.XMLImage;
0073:        import org.apache.fop.pdf.PDFAction;
0074:        import org.apache.fop.pdf.PDFAMode;
0075:        import org.apache.fop.pdf.PDFAnnotList;
0076:        import org.apache.fop.pdf.PDFColor;
0077:        import org.apache.fop.pdf.PDFConformanceException;
0078:        import org.apache.fop.pdf.PDFDocument;
0079:        import org.apache.fop.pdf.PDFEncryptionManager;
0080:        import org.apache.fop.pdf.PDFEncryptionParams;
0081:        import org.apache.fop.pdf.PDFFactory;
0082:        import org.apache.fop.pdf.PDFFilterList;
0083:        import org.apache.fop.pdf.PDFGoTo;
0084:        import org.apache.fop.pdf.PDFICCBasedColorSpace;
0085:        import org.apache.fop.pdf.PDFICCStream;
0086:        import org.apache.fop.pdf.PDFInfo;
0087:        import org.apache.fop.pdf.PDFLink;
0088:        import org.apache.fop.pdf.PDFMetadata;
0089:        import org.apache.fop.pdf.PDFNumber;
0090:        import org.apache.fop.pdf.PDFOutline;
0091:        import org.apache.fop.pdf.PDFOutputIntent;
0092:        import org.apache.fop.pdf.PDFPage;
0093:        import org.apache.fop.pdf.PDFResourceContext;
0094:        import org.apache.fop.pdf.PDFResources;
0095:        import org.apache.fop.pdf.PDFState;
0096:        import org.apache.fop.pdf.PDFStream;
0097:        import org.apache.fop.pdf.PDFText;
0098:        import org.apache.fop.pdf.PDFXMode;
0099:        import org.apache.fop.pdf.PDFXObject;
0100:        import org.apache.fop.render.AbstractPathOrientedRenderer;
0101:        import org.apache.fop.render.Graphics2DAdapter;
0102:        import org.apache.fop.render.RendererContext;
0103:        import org.apache.fop.util.CharUtilities;
0104:        import org.apache.fop.util.ColorProfileUtil;
0105:        import org.apache.fop.fo.Constants;
0106:        import org.apache.fop.fo.extensions.ExtensionAttachment;
0107:        import org.apache.fop.fo.extensions.xmp.XMPMetadata;
0108:        import org.apache.xmlgraphics.xmp.Metadata;
0109:        import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter;
0110:        import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema;
0111:
0112:        import org.apache.fop.area.DestinationData;
0113:
0114:        /**
0115:         * Renderer that renders areas to PDF.
0116:         */
0117:        public class PDFRenderer extends AbstractPathOrientedRenderer {
0118:
0119:            /**
0120:             * The mime type for pdf
0121:             */
0122:            public static final String MIME_TYPE = MimeConstants.MIME_PDF;
0123:
0124:            /** Normal PDF resolution (72dpi) */
0125:            public static final int NORMAL_PDF_RESOLUTION = 72;
0126:
0127:            /** PDF encryption parameter: all parameters as object, datatype: PDFEncryptionParams */
0128:            public static final String ENCRYPTION_PARAMS = "encryption-params";
0129:            /** PDF encryption parameter: user password, datatype: String */
0130:            public static final String USER_PASSWORD = "user-password";
0131:            /** PDF encryption parameter: owner password, datatype: String */
0132:            public static final String OWNER_PASSWORD = "owner-password";
0133:            /** PDF encryption parameter: Forbids printing, datatype: Boolean or "true"/"false" */
0134:            public static final String NO_PRINT = "noprint";
0135:            /** PDF encryption parameter: Forbids copying content, datatype: Boolean or "true"/"false" */
0136:            public static final String NO_COPY_CONTENT = "nocopy";
0137:            /** PDF encryption parameter: Forbids editing content, datatype: Boolean or "true"/"false" */
0138:            public static final String NO_EDIT_CONTENT = "noedit";
0139:            /** PDF encryption parameter: Forbids annotations, datatype: Boolean or "true"/"false" */
0140:            public static final String NO_ANNOTATIONS = "noannotations";
0141:            /** Rendering Options key for the PDF/A mode. */
0142:            public static final String PDF_A_MODE = "pdf-a-mode";
0143:            /** Rendering Options key for the PDF/X mode. */
0144:            public static final String PDF_X_MODE = "pdf-x-mode";
0145:            /** Rendering Options key for the ICC profile for the output intent. */
0146:            public static final String KEY_OUTPUT_PROFILE = "output-profile";
0147:
0148:            /** Controls whether comments are written to the PDF stream. */
0149:            protected static final boolean WRITE_COMMENTS = true;
0150:
0151:            /**
0152:             * the PDF Document being created
0153:             */
0154:            protected PDFDocument pdfDoc;
0155:
0156:            /** the PDF/A mode (Default: disabled) */
0157:            protected PDFAMode pdfAMode = PDFAMode.DISABLED;
0158:
0159:            /** the PDF/X mode (Default: disabled) */
0160:            protected PDFXMode pdfXMode = PDFXMode.DISABLED;
0161:
0162:            /**
0163:             * Map of pages using the PageViewport as the key
0164:             * this is used for prepared pages that cannot be immediately
0165:             * rendered
0166:             */
0167:            protected Map pages = null;
0168:
0169:            /**
0170:             * Maps unique PageViewport key to PDF page reference
0171:             */
0172:            protected Map pageReferences = new java.util.HashMap();
0173:
0174:            /**
0175:             *  Maps unique PageViewport key back to PageViewport itself
0176:             */
0177:            protected Map pvReferences = new java.util.HashMap();
0178:
0179:            /**
0180:             * Maps XSL-FO element IDs to their on-page XY-positions
0181:             * Must be used in conjunction with the page reference to fully specify the PDFGoTo details
0182:             */
0183:            protected Map idPositions = new java.util.HashMap();
0184:
0185:            /**
0186:             * Maps XSL-FO element IDs to PDFGoTo objects targeting the corresponding areas
0187:             * These objects may not all be fully filled in yet
0188:             */
0189:            protected Map idGoTos = new java.util.HashMap();
0190:
0191:            /**
0192:             * The PDFGoTos in idGoTos that are not complete yet
0193:             */
0194:            protected List unfinishedGoTos = new java.util.ArrayList();
0195:            // can't use a Set because PDFGoTo.equals returns true if the target is the same,
0196:            // even if the object number differs
0197:
0198:            /**
0199:             * The output stream to write the document to
0200:             */
0201:            protected OutputStream ostream;
0202:
0203:            /**
0204:             * the /Resources object of the PDF document being created
0205:             */
0206:            protected PDFResources pdfResources;
0207:
0208:            /**
0209:             * the current stream to add PDF commands to
0210:             */
0211:            protected PDFStream currentStream;
0212:
0213:            /**
0214:             * the current annotation list to add annotations to
0215:             */
0216:            protected PDFResourceContext currentContext = null;
0217:
0218:            /**
0219:             * the current page to add annotations to
0220:             */
0221:            protected PDFPage currentPage;
0222:
0223:            /**
0224:             * the current page's PDF reference string (to avoid numerous function calls)
0225:             */
0226:            protected String currentPageRef;
0227:
0228:            /** the (optional) encryption parameters */
0229:            protected PDFEncryptionParams encryptionParams;
0230:
0231:            /** the ICC stream used as output profile by this document for PDF/A and PDF/X functionality. */
0232:            protected PDFICCStream outputProfile;
0233:            /** the ICC stream for the sRGB color space. */
0234:            //protected PDFICCStream sRGBProfile;
0235:            /** the default sRGB color space. */
0236:            protected PDFICCBasedColorSpace sRGBColorSpace;
0237:
0238:            /** Optional URI to an output profile to be used. */
0239:            protected String outputProfileURI;
0240:
0241:            /** drawing state */
0242:            protected PDFState currentState = null;
0243:
0244:            /** Name of currently selected font */
0245:            protected String currentFontName = "";
0246:            /** Size of currently selected font */
0247:            protected int currentFontSize = 0;
0248:            /** page height */
0249:            protected int pageHeight;
0250:
0251:            /** Registry of PDF filters */
0252:            protected Map filterMap;
0253:
0254:            /**
0255:             * true if a BT command has been written. 
0256:             */
0257:            protected boolean inTextMode = false;
0258:
0259:            /**
0260:             * create the PDF renderer
0261:             */
0262:            public PDFRenderer() {
0263:            }
0264:
0265:            private boolean booleanValueOf(Object obj) {
0266:                if (obj instanceof  Boolean) {
0267:                    return ((Boolean) obj).booleanValue();
0268:                } else if (obj instanceof  String) {
0269:                    return Boolean.valueOf((String) obj).booleanValue();
0270:                } else {
0271:                    throw new IllegalArgumentException(
0272:                            "Boolean or \"true\" or \"false\" expected.");
0273:                }
0274:            }
0275:
0276:            /**
0277:             * @see org.apache.fop.render.Renderer#setUserAgent(FOUserAgent)
0278:             */
0279:            public void setUserAgent(FOUserAgent agent) {
0280:                super .setUserAgent(agent);
0281:                PDFEncryptionParams params = (PDFEncryptionParams) agent
0282:                        .getRendererOptions().get(ENCRYPTION_PARAMS);
0283:                if (params != null) {
0284:                    this .encryptionParams = params; //overwrite if available
0285:                }
0286:                String pwd;
0287:                pwd = (String) agent.getRendererOptions().get(USER_PASSWORD);
0288:                if (pwd != null) {
0289:                    if (encryptionParams == null) {
0290:                        this .encryptionParams = new PDFEncryptionParams();
0291:                    }
0292:                    this .encryptionParams.setUserPassword(pwd);
0293:                }
0294:                pwd = (String) agent.getRendererOptions().get(OWNER_PASSWORD);
0295:                if (pwd != null) {
0296:                    if (encryptionParams == null) {
0297:                        this .encryptionParams = new PDFEncryptionParams();
0298:                    }
0299:                    this .encryptionParams.setOwnerPassword(pwd);
0300:                }
0301:                Object setting;
0302:                setting = agent.getRendererOptions().get(NO_PRINT);
0303:                if (setting != null) {
0304:                    if (encryptionParams == null) {
0305:                        this .encryptionParams = new PDFEncryptionParams();
0306:                    }
0307:                    this .encryptionParams
0308:                            .setAllowPrint(!booleanValueOf(setting));
0309:                }
0310:                setting = agent.getRendererOptions().get(NO_COPY_CONTENT);
0311:                if (setting != null) {
0312:                    if (encryptionParams == null) {
0313:                        this .encryptionParams = new PDFEncryptionParams();
0314:                    }
0315:                    this .encryptionParams
0316:                            .setAllowCopyContent(!booleanValueOf(setting));
0317:                }
0318:                setting = agent.getRendererOptions().get(NO_EDIT_CONTENT);
0319:                if (setting != null) {
0320:                    if (encryptionParams == null) {
0321:                        this .encryptionParams = new PDFEncryptionParams();
0322:                    }
0323:                    this .encryptionParams
0324:                            .setAllowEditContent(!booleanValueOf(setting));
0325:                }
0326:                setting = agent.getRendererOptions().get(NO_ANNOTATIONS);
0327:                if (setting != null) {
0328:                    if (encryptionParams == null) {
0329:                        this .encryptionParams = new PDFEncryptionParams();
0330:                    }
0331:                    this .encryptionParams
0332:                            .setAllowEditAnnotations(!booleanValueOf(setting));
0333:                }
0334:                String s = (String) agent.getRendererOptions().get(PDF_A_MODE);
0335:                if (s != null) {
0336:                    this .pdfAMode = PDFAMode.valueOf(s);
0337:                }
0338:                s = (String) agent.getRendererOptions().get(PDF_X_MODE);
0339:                if (s != null) {
0340:                    this .pdfXMode = PDFXMode.valueOf(s);
0341:                }
0342:                s = (String) agent.getRendererOptions().get(KEY_OUTPUT_PROFILE);
0343:                if (s != null) {
0344:                    this .outputProfileURI = s;
0345:                }
0346:            }
0347:
0348:            /**
0349:             * @see org.apache.fop.render.Renderer#startRenderer(OutputStream)
0350:             */
0351:            public void startRenderer(OutputStream stream) throws IOException {
0352:                if (userAgent == null) {
0353:                    throw new IllegalStateException(
0354:                            "UserAgent must be set before starting the renderer");
0355:                }
0356:                ostream = stream;
0357:                this .pdfDoc = new PDFDocument(
0358:                        userAgent.getProducer() != null ? userAgent
0359:                                .getProducer() : "");
0360:                this .pdfDoc.getProfile().setPDFAMode(this .pdfAMode);
0361:                this .pdfDoc.getProfile().setPDFXMode(this .pdfXMode);
0362:                this .pdfDoc.getInfo().setCreator(userAgent.getCreator());
0363:                this .pdfDoc.getInfo().setCreationDate(
0364:                        userAgent.getCreationDate());
0365:                this .pdfDoc.getInfo().setAuthor(userAgent.getAuthor());
0366:                this .pdfDoc.getInfo().setTitle(userAgent.getTitle());
0367:                this .pdfDoc.getInfo().setKeywords(userAgent.getKeywords());
0368:                this .pdfDoc.setFilterMap(filterMap);
0369:                this .pdfDoc.outputHeader(stream);
0370:
0371:                //Setup encryption if necessary
0372:                PDFEncryptionManager.setupPDFEncryption(encryptionParams,
0373:                        this .pdfDoc);
0374:
0375:                addsRGBColorSpace();
0376:                if (this .outputProfileURI != null) {
0377:                    addDefaultOutputProfile();
0378:                }
0379:                if (pdfXMode != PDFXMode.DISABLED) {
0380:                    log.debug(pdfXMode + " is active.");
0381:                    log
0382:                            .warn("Note: "
0383:                                    + pdfXMode
0384:                                    + " support is work-in-progress and not fully implemented, yet!");
0385:                    addPDFXOutputIntent();
0386:                }
0387:                if (pdfAMode.isPDFA1LevelB()) {
0388:                    log
0389:                            .debug("PDF/A is active. Conformance Level: "
0390:                                    + pdfAMode);
0391:                    addPDFA1OutputIntent();
0392:                }
0393:
0394:            }
0395:
0396:            private void addsRGBColorSpace() throws IOException {
0397:                if (this .sRGBColorSpace != null) {
0398:                    return;
0399:                }
0400:                ICC_Profile profile;
0401:                PDFICCStream sRGBProfile = pdfDoc.getFactory()
0402:                        .makePDFICCStream();
0403:                InputStream in = PDFDocument.class
0404:                        .getResourceAsStream("sRGB Color Space Profile.icm");
0405:                if (in != null) {
0406:                    try {
0407:                        profile = ICC_Profile.getInstance(in);
0408:                    } finally {
0409:                        IOUtils.closeQuietly(in);
0410:                    }
0411:                } else {
0412:                    //Fallback: Use the sRGB profile from the JRE (about 140KB)
0413:                    profile = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
0414:                }
0415:                sRGBProfile.setColorSpace(profile, null);
0416:
0417:                //Map sRGB as default RGB profile for DeviceRGB
0418:                this .sRGBColorSpace = pdfDoc
0419:                        .getFactory()
0420:                        .makeICCBasedColorSpace(null, "DefaultRGB", sRGBProfile);
0421:            }
0422:
0423:            private void addDefaultOutputProfile() throws IOException {
0424:                if (this .outputProfile != null) {
0425:                    return;
0426:                }
0427:                ICC_Profile profile;
0428:                InputStream in = null;
0429:                if (this .outputProfileURI != null) {
0430:                    this .outputProfile = pdfDoc.getFactory().makePDFICCStream();
0431:                    Source src = userAgent.resolveURI(this .outputProfileURI);
0432:                    if (src == null) {
0433:                        throw new IOException("Output profile not found: "
0434:                                + this .outputProfileURI);
0435:                    }
0436:                    if (src instanceof  StreamSource) {
0437:                        in = ((StreamSource) src).getInputStream();
0438:                    } else {
0439:                        in = new URL(src.getSystemId()).openStream();
0440:                    }
0441:                    try {
0442:                        profile = ICC_Profile.getInstance(in);
0443:                    } finally {
0444:                        IOUtils.closeQuietly(in);
0445:                    }
0446:                    this .outputProfile.setColorSpace(profile, null);
0447:                } else {
0448:                    //Fall back to sRGB profile
0449:                    outputProfile = sRGBColorSpace.getICCStream();
0450:                }
0451:            }
0452:
0453:            /**
0454:             * Adds an OutputIntent to the PDF as mandated by PDF/A-1 when uncalibrated color spaces
0455:             * are used (which is true if we use DeviceRGB to represent sRGB colors).
0456:             * @throws IOException in case of an I/O problem
0457:             */
0458:            private void addPDFA1OutputIntent() throws IOException {
0459:                addDefaultOutputProfile();
0460:
0461:                String desc = ColorProfileUtil
0462:                        .getICCProfileDescription(this .outputProfile
0463:                                .getICCProfile());
0464:                PDFOutputIntent outputIntent = pdfDoc.getFactory()
0465:                        .makeOutputIntent();
0466:                outputIntent.setSubtype(PDFOutputIntent.GTS_PDFA1);
0467:                outputIntent.setDestOutputProfile(this .outputProfile);
0468:                outputIntent.setOutputConditionIdentifier(desc);
0469:                outputIntent.setInfo(outputIntent
0470:                        .getOutputConditionIdentifier());
0471:                pdfDoc.getRoot().addOutputIntent(outputIntent);
0472:            }
0473:
0474:            /**
0475:             * Adds an OutputIntent to the PDF as mandated by PDF/X when uncalibrated color spaces
0476:             * are used (which is true if we use DeviceRGB to represent sRGB colors).
0477:             * @throws IOException in case of an I/O problem
0478:             */
0479:            private void addPDFXOutputIntent() throws IOException {
0480:                addDefaultOutputProfile();
0481:
0482:                String desc = ColorProfileUtil
0483:                        .getICCProfileDescription(this .outputProfile
0484:                                .getICCProfile());
0485:                int deviceClass = this .outputProfile.getICCProfile()
0486:                        .getProfileClass();
0487:                if (deviceClass != ICC_Profile.CLASS_OUTPUT) {
0488:                    throw new PDFConformanceException(
0489:                            pdfDoc.getProfile().getPDFXMode()
0490:                                    + " requires that"
0491:                                    + " the DestOutputProfile be an Output Device Profile. "
0492:                                    + desc
0493:                                    + " does not match that requirement.");
0494:                }
0495:                PDFOutputIntent outputIntent = pdfDoc.getFactory()
0496:                        .makeOutputIntent();
0497:                outputIntent.setSubtype(PDFOutputIntent.GTS_PDFX);
0498:                outputIntent.setDestOutputProfile(this .outputProfile);
0499:                outputIntent.setOutputConditionIdentifier(desc);
0500:                outputIntent.setInfo(outputIntent
0501:                        .getOutputConditionIdentifier());
0502:                pdfDoc.getRoot().addOutputIntent(outputIntent);
0503:            }
0504:
0505:            /**
0506:             * Checks if there are any unfinished PDFGoTos left in the list and resolves them
0507:             * to a default position on the page. Logs a warning, as this should not happen.
0508:             */
0509:            protected void finishOpenGoTos() {
0510:                int count = unfinishedGoTos.size();
0511:                if (count > 0) {
0512:                    // TODO : page height may not be the same for all targeted pages
0513:                    Point2D.Float defaultPos = new Point2D.Float(0f,
0514:                            pageHeight / 1000f); // top-o-page
0515:                    while (!unfinishedGoTos.isEmpty()) {
0516:                        PDFGoTo gt = (PDFGoTo) unfinishedGoTos.get(0);
0517:                        finishIDGoTo(gt, defaultPos);
0518:                    }
0519:                    boolean one = count == 1;
0520:                    String pl = one ? "" : "s";
0521:                    String ww = one ? "was" : "were";
0522:                    String ia = one ? "is" : "are";
0523:                    log.warn("" + count + " link target" + pl
0524:                            + " could not be fully resolved and " + ww
0525:                            + " now point to the top of the page or " + ia
0526:                            + " dysfunctional."); // dysfunctional if pageref is null
0527:                }
0528:            }
0529:
0530:            /**
0531:             * @see org.apache.fop.render.Renderer#stopRenderer()
0532:             */
0533:            public void stopRenderer() throws IOException {
0534:                finishOpenGoTos();
0535:
0536:                pdfDoc.getResources().addFonts(pdfDoc, fontInfo);
0537:                pdfDoc.outputTrailer(ostream);
0538:
0539:                this .pdfDoc = null;
0540:                ostream = null;
0541:
0542:                pages = null;
0543:
0544:                pageReferences.clear();
0545:                pvReferences.clear();
0546:                pdfResources = null;
0547:                currentStream = null;
0548:                currentContext = null;
0549:                currentPage = null;
0550:                currentState = null;
0551:                currentFontName = "";
0552:
0553:                idPositions.clear();
0554:                idGoTos.clear();
0555:            }
0556:
0557:            /**
0558:             * @see org.apache.fop.render.Renderer#supportsOutOfOrder()
0559:             */
0560:            public boolean supportsOutOfOrder() {
0561:                //return false;
0562:                return true;
0563:            }
0564:
0565:            /**
0566:             * @see org.apache.fop.render.Renderer#processOffDocumentItem(OffDocumentItem)
0567:             */
0568:            public void processOffDocumentItem(OffDocumentItem odi) {
0569:                if (odi instanceof  DestinationData) {
0570:                    // render Destinations
0571:                    renderDestination((DestinationData) odi);
0572:                } else if (odi instanceof  BookmarkData) {
0573:                    // render Bookmark-Tree
0574:                    renderBookmarkTree((BookmarkData) odi);
0575:                } else if (odi instanceof  OffDocumentExtensionAttachment) {
0576:                    ExtensionAttachment attachment = ((OffDocumentExtensionAttachment) odi)
0577:                            .getAttachment();
0578:                    if (XMPMetadata.CATEGORY.equals(attachment.getCategory())) {
0579:                        renderXMPMetadata((XMPMetadata) attachment);
0580:                    }
0581:                }
0582:            }
0583:
0584:            private void renderDestination(DestinationData dd) {
0585:                String targetID = dd.getIDRef();
0586:                if (targetID != null && targetID.length() > 0) {
0587:                    PageViewport pv = dd.getPageViewport();
0588:                    if (pv == null) {
0589:                        log.warn("Unresolved destination item received: "
0590:                                + dd.getIDRef());
0591:                    }
0592:                    PDFGoTo gt = getPDFGoToForID(targetID, pv.getKey());
0593:                    pdfDoc.getFactory().makeDestination(dd.getIDRef(),
0594:                            gt.makeReference());
0595:                } else {
0596:                    log
0597:                            .warn("DestinationData item with null or empty IDRef received.");
0598:                }
0599:            }
0600:
0601:            /**
0602:             * Renders a Bookmark-Tree object
0603:             * @param bookmarks the BookmarkData object containing all the Bookmark-Items
0604:             */
0605:            protected void renderBookmarkTree(BookmarkData bookmarks) {
0606:                for (int i = 0; i < bookmarks.getCount(); i++) {
0607:                    BookmarkData ext = bookmarks.getSubData(i);
0608:                    renderBookmarkItem(ext, null);
0609:                }
0610:            }
0611:
0612:            private void renderBookmarkItem(BookmarkData bookmarkItem,
0613:                    PDFOutline parentBookmarkItem) {
0614:                PDFOutline pdfOutline = null;
0615:
0616:                String targetID = bookmarkItem.getIDRef();
0617:                if (targetID != null && targetID.length() > 0) {
0618:                    PageViewport pv = bookmarkItem.getPageViewport();
0619:                    if (pv != null) {
0620:                        String pvKey = pv.getKey();
0621:                        PDFGoTo gt = getPDFGoToForID(targetID, pvKey);
0622:                        // create outline object:
0623:                        PDFOutline parent = parentBookmarkItem != null ? parentBookmarkItem
0624:                                : pdfDoc.getOutlineRoot();
0625:                        pdfOutline = pdfDoc.getFactory().makeOutline(parent,
0626:                                bookmarkItem.getBookmarkTitle(), gt,
0627:                                bookmarkItem.showChildItems());
0628:                    } else {
0629:                        log.warn("Bookmark with IDRef \"" + targetID
0630:                                + "\" has a null PageViewport.");
0631:                    }
0632:                } else {
0633:                    log
0634:                            .warn("Bookmark item with null or empty IDRef received.");
0635:                }
0636:
0637:                for (int i = 0; i < bookmarkItem.getCount(); i++) {
0638:                    renderBookmarkItem(bookmarkItem.getSubData(i), pdfOutline);
0639:                }
0640:            }
0641:
0642:            private void renderXMPMetadata(XMPMetadata metadata) {
0643:                Metadata docXMP = metadata.getMetadata();
0644:                Metadata fopXMP = PDFMetadata.createXMPFromUserAgent(pdfDoc);
0645:                //Merge FOP's own metadata into the one from the XSL-FO document
0646:                fopXMP.mergeInto(docXMP);
0647:                XMPBasicAdapter xmpBasic = XMPBasicSchema.getAdapter(docXMP);
0648:                //Metadata was changed so update metadata date
0649:                xmpBasic.setMetadataDate(new java.util.Date());
0650:                PDFMetadata.updateInfoFromMetadata(docXMP, pdfDoc.getInfo());
0651:
0652:                PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
0653:                        docXMP, metadata.isReadOnly());
0654:                pdfDoc.getRoot().setMetadata(pdfMetadata);
0655:            }
0656:
0657:            /** @see org.apache.fop.render.Renderer#getGraphics2DAdapter() */
0658:            public Graphics2DAdapter getGraphics2DAdapter() {
0659:                return new PDFGraphics2DAdapter(this );
0660:            }
0661:
0662:            /** 
0663:             * writes out a comment.
0664:             * @param text text for the comment
0665:             */
0666:            protected void comment(String text) {
0667:                if (WRITE_COMMENTS) {
0668:                    currentStream.add("% " + text + "\n");
0669:                }
0670:            }
0671:
0672:            /** Saves the graphics state of the rendering engine. */
0673:            protected void saveGraphicsState() {
0674:                endTextObject();
0675:                currentStream.add("q\n");
0676:            }
0677:
0678:            /** Restores the last graphics state of the rendering engine. */
0679:            protected void restoreGraphicsState() {
0680:                endTextObject();
0681:                currentStream.add("Q\n");
0682:            }
0683:
0684:            /** Indicates the beginning of a text object. */
0685:            protected void beginTextObject() {
0686:                if (!inTextMode) {
0687:                    currentStream.add("BT\n");
0688:                    currentFontName = "";
0689:                    inTextMode = true;
0690:                }
0691:            }
0692:
0693:            /** Indicates the end of a text object. */
0694:            protected void endTextObject() {
0695:                closeText();
0696:                if (inTextMode) {
0697:                    currentStream.add("ET\n");
0698:                    inTextMode = false;
0699:                }
0700:            }
0701:
0702:            /**
0703:             * Start the next page sequence.
0704:             * For the pdf renderer there is no concept of page sequences
0705:             * but it uses the first available page sequence title to set
0706:             * as the title of the pdf document.
0707:             *
0708:             * @param seqTitle the title of the page sequence
0709:             */
0710:            public void startPageSequence(LineArea seqTitle) {
0711:                if (seqTitle != null) {
0712:                    String str = convertTitleToString(seqTitle);
0713:                    PDFInfo info = this .pdfDoc.getInfo();
0714:                    if (info.getTitle() == null) {
0715:                        info.setTitle(str);
0716:                    }
0717:                }
0718:                if (pdfDoc.getRoot().getMetadata() == null) {
0719:                    //If at this time no XMP metadata for the overall document has been set, create it
0720:                    //from the PDFInfo object.
0721:                    Metadata xmp = PDFMetadata.createXMPFromUserAgent(pdfDoc);
0722:                    PDFMetadata pdfMetadata = pdfDoc.getFactory().makeMetadata(
0723:                            xmp, true);
0724:                    pdfDoc.getRoot().setMetadata(pdfMetadata);
0725:                }
0726:            }
0727:
0728:            /**
0729:             * The pdf page is prepared by making the page.
0730:             * The page is made in the pdf document without any contents
0731:             * and then stored to add the contents later.
0732:             * The page objects is stored using the area tree PageViewport
0733:             * as a key.
0734:             *
0735:             * @param page the page to prepare
0736:             */
0737:            public void preparePage(PageViewport page) {
0738:                setupPage(page);
0739:                if (pages == null) {
0740:                    pages = new java.util.HashMap();
0741:                }
0742:                pages.put(page, currentPage);
0743:            }
0744:
0745:            private void setupPage(PageViewport page) {
0746:                this .pdfResources = this .pdfDoc.getResources();
0747:
0748:                Rectangle2D bounds = page.getViewArea();
0749:                double w = bounds.getWidth();
0750:                double h = bounds.getHeight();
0751:                currentPage = this .pdfDoc.getFactory().makePage(
0752:                        this .pdfResources, (int) Math.round(w / 1000),
0753:                        (int) Math.round(h / 1000), page.getPageIndex());
0754:                pageReferences.put(page.getKey(), currentPage.referencePDF());
0755:                pvReferences.put(page.getKey(), page);
0756:            }
0757:
0758:            /**
0759:             * This method creates a pdf stream for the current page
0760:             * uses it as the contents of a new page. The page is written
0761:             * immediately to the output stream.
0762:             * @see org.apache.fop.render.Renderer#renderPage(PageViewport)
0763:             */
0764:            public void renderPage(PageViewport page) throws IOException,
0765:                    FOPException {
0766:                if (pages != null
0767:                        && (currentPage = (PDFPage) pages.get(page)) != null) {
0768:                    //Retrieve previously prepared page (out-of-line rendering)
0769:                    pages.remove(page);
0770:                } else {
0771:                    setupPage(page);
0772:                }
0773:                currentPageRef = currentPage.referencePDF();
0774:
0775:                Rectangle2D bounds = page.getViewArea();
0776:                double h = bounds.getHeight();
0777:                pageHeight = (int) h;
0778:
0779:                currentStream = this .pdfDoc.getFactory().makeStream(
0780:                        PDFFilterList.CONTENT_FILTER, false);
0781:
0782:                currentState = new PDFState();
0783:                // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFRenderer's
0784:                AffineTransform basicPageTransform = new AffineTransform(1, 0,
0785:                        0, -1, 0, pageHeight / 1000f);
0786:                currentState.concatenate(basicPageTransform);
0787:                currentStream.add(CTMHelper.toPDFString(basicPageTransform,
0788:                        false)
0789:                        + " cm\n");
0790:
0791:                currentFontName = "";
0792:
0793:                super .renderPage(page);
0794:
0795:                this .pdfDoc.registerObject(currentStream);
0796:                currentPage.setContents(currentStream);
0797:                PDFAnnotList annots = currentPage.getAnnotations();
0798:                if (annots != null) {
0799:                    this .pdfDoc.addObject(annots);
0800:                }
0801:                this .pdfDoc.addObject(currentPage);
0802:                this .pdfDoc.output(ostream);
0803:            }
0804:
0805:            /**
0806:             * @see org.apache.fop.render.AbstractRenderer#startVParea(CTM, Rectangle2D)
0807:             */
0808:            protected void startVParea(CTM ctm, Rectangle2D clippingRect) {
0809:                // Set the given CTM in the graphics state
0810:                currentState.push();
0811:                currentState.concatenate(new AffineTransform(CTMHelper
0812:                        .toPDFArray(ctm)));
0813:
0814:                saveGraphicsState();
0815:                if (clippingRect != null) {
0816:                    clipRect((float) clippingRect.getX() / 1000f,
0817:                            (float) clippingRect.getY() / 1000f,
0818:                            (float) clippingRect.getWidth() / 1000f,
0819:                            (float) clippingRect.getHeight() / 1000f);
0820:                }
0821:                // multiply with current CTM
0822:                currentStream.add(CTMHelper.toPDFString(ctm) + " cm\n");
0823:            }
0824:
0825:            /**
0826:             * @see org.apache.fop.render.AbstractRenderer#endVParea()
0827:             */
0828:            protected void endVParea() {
0829:                restoreGraphicsState();
0830:                currentState.pop();
0831:            }
0832:
0833:            /**
0834:             * Handle the traits for a region
0835:             * This is used to draw the traits for the given page region.
0836:             * (See Sect. 6.4.1.2 of XSL-FO spec.)
0837:             * @param region the RegionViewport whose region is to be drawn
0838:             */
0839:            protected void handleRegionTraits(RegionViewport region) {
0840:                currentFontName = "";
0841:                super .handleRegionTraits(region);
0842:            }
0843:
0844:            /**
0845:             * Formats a float value (normally coordinates) as Strings.
0846:             * @param value the value
0847:             * @return the formatted value
0848:             */
0849:            protected static final String format(float value) {
0850:                return PDFNumber.doubleOut(value);
0851:            }
0852:
0853:            /** @see org.apache.fop.render.AbstractPathOrientedRenderer */
0854:            protected void drawBorderLine(float x1, float y1, float x2,
0855:                    float y2, boolean horz, boolean startOrBefore, int style,
0856:                    Color col) {
0857:                float w = x2 - x1;
0858:                float h = y2 - y1;
0859:                if ((w < 0) || (h < 0)) {
0860:                    log
0861:                            .error("Negative extent received. Border won't be painted.");
0862:                    return;
0863:                }
0864:                switch (style) {
0865:                case Constants.EN_DASHED:
0866:                    setColor(col, false, null);
0867:                    if (horz) {
0868:                        float unit = Math.abs(2 * h);
0869:                        int rep = (int) (w / unit);
0870:                        if (rep % 2 == 0) {
0871:                            rep++;
0872:                        }
0873:                        unit = w / rep;
0874:                        currentStream.add("[" + format(unit) + "] 0 d ");
0875:                        currentStream.add(format(h) + " w\n");
0876:                        float ym = y1 + (h / 2);
0877:                        currentStream.add(format(x1) + " " + format(ym) + " m "
0878:                                + format(x2) + " " + format(ym) + " l S\n");
0879:                    } else {
0880:                        float unit = Math.abs(2 * w);
0881:                        int rep = (int) (h / unit);
0882:                        if (rep % 2 == 0) {
0883:                            rep++;
0884:                        }
0885:                        unit = h / rep;
0886:                        currentStream.add("[" + format(unit) + "] 0 d ");
0887:                        currentStream.add(format(w) + " w\n");
0888:                        float xm = x1 + (w / 2);
0889:                        currentStream.add(format(xm) + " " + format(y1) + " m "
0890:                                + format(xm) + " " + format(y2) + " l S\n");
0891:                    }
0892:                    break;
0893:                case Constants.EN_DOTTED:
0894:                    setColor(col, false, null);
0895:                    currentStream.add("1 J ");
0896:                    if (horz) {
0897:                        float unit = Math.abs(2 * h);
0898:                        int rep = (int) (w / unit);
0899:                        if (rep % 2 == 0) {
0900:                            rep++;
0901:                        }
0902:                        unit = w / rep;
0903:                        currentStream.add("[0 " + format(unit) + "] 0 d ");
0904:                        currentStream.add(format(h) + " w\n");
0905:                        float ym = y1 + (h / 2);
0906:                        currentStream.add(format(x1) + " " + format(ym) + " m "
0907:                                + format(x2) + " " + format(ym) + " l S\n");
0908:                    } else {
0909:                        float unit = Math.abs(2 * w);
0910:                        int rep = (int) (h / unit);
0911:                        if (rep % 2 == 0) {
0912:                            rep++;
0913:                        }
0914:                        unit = h / rep;
0915:                        currentStream.add("[0 " + format(unit) + " ] 0 d ");
0916:                        currentStream.add(format(w) + " w\n");
0917:                        float xm = x1 + (w / 2);
0918:                        currentStream.add(format(xm) + " " + format(y1) + " m "
0919:                                + format(xm) + " " + format(y2) + " l S\n");
0920:                    }
0921:                    break;
0922:                case Constants.EN_DOUBLE:
0923:                    setColor(col, false, null);
0924:                    currentStream.add("[] 0 d ");
0925:                    if (horz) {
0926:                        float h3 = h / 3;
0927:                        currentStream.add(format(h3) + " w\n");
0928:                        float ym1 = y1 + (h3 / 2);
0929:                        float ym2 = ym1 + h3 + h3;
0930:                        currentStream.add(format(x1) + " " + format(ym1)
0931:                                + " m " + format(x2) + " " + format(ym1)
0932:                                + " l S\n");
0933:                        currentStream.add(format(x1) + " " + format(ym2)
0934:                                + " m " + format(x2) + " " + format(ym2)
0935:                                + " l S\n");
0936:                    } else {
0937:                        float w3 = w / 3;
0938:                        currentStream.add(format(w3) + " w\n");
0939:                        float xm1 = x1 + (w3 / 2);
0940:                        float xm2 = xm1 + w3 + w3;
0941:                        currentStream.add(format(xm1) + " " + format(y1)
0942:                                + " m " + format(xm1) + " " + format(y2)
0943:                                + " l S\n");
0944:                        currentStream.add(format(xm2) + " " + format(y1)
0945:                                + " m " + format(xm2) + " " + format(y2)
0946:                                + " l S\n");
0947:                    }
0948:                    break;
0949:                case Constants.EN_GROOVE:
0950:                case Constants.EN_RIDGE: {
0951:                    float colFactor = (style == EN_GROOVE ? 0.4f : -0.4f);
0952:                    currentStream.add("[] 0 d ");
0953:                    if (horz) {
0954:                        Color uppercol = lightenColor(col, -colFactor);
0955:                        Color lowercol = lightenColor(col, colFactor);
0956:                        float h3 = h / 3;
0957:                        currentStream.add(format(h3) + " w\n");
0958:                        float ym1 = y1 + (h3 / 2);
0959:                        setColor(uppercol, false, null);
0960:                        currentStream.add(format(x1) + " " + format(ym1)
0961:                                + " m " + format(x2) + " " + format(ym1)
0962:                                + " l S\n");
0963:                        setColor(col, false, null);
0964:                        currentStream.add(format(x1) + " " + format(ym1 + h3)
0965:                                + " m " + format(x2) + " " + format(ym1 + h3)
0966:                                + " l S\n");
0967:                        setColor(lowercol, false, null);
0968:                        currentStream.add(format(x1) + " "
0969:                                + format(ym1 + h3 + h3) + " m " + format(x2)
0970:                                + " " + format(ym1 + h3 + h3) + " l S\n");
0971:                    } else {
0972:                        Color leftcol = lightenColor(col, -colFactor);
0973:                        Color rightcol = lightenColor(col, colFactor);
0974:                        float w3 = w / 3;
0975:                        currentStream.add(format(w3) + " w\n");
0976:                        float xm1 = x1 + (w3 / 2);
0977:                        setColor(leftcol, false, null);
0978:                        currentStream.add(format(xm1) + " " + format(y1)
0979:                                + " m " + format(xm1) + " " + format(y2)
0980:                                + " l S\n");
0981:                        setColor(col, false, null);
0982:                        currentStream.add(format(xm1 + w3) + " " + format(y1)
0983:                                + " m " + format(xm1 + w3) + " " + format(y2)
0984:                                + " l S\n");
0985:                        setColor(rightcol, false, null);
0986:                        currentStream.add(format(xm1 + w3 + w3) + " "
0987:                                + format(y1) + " m " + format(xm1 + w3 + w3)
0988:                                + " " + format(y2) + " l S\n");
0989:                    }
0990:                    break;
0991:                }
0992:                case Constants.EN_INSET:
0993:                case Constants.EN_OUTSET: {
0994:                    float colFactor = (style == EN_OUTSET ? 0.4f : -0.4f);
0995:                    currentStream.add("[] 0 d ");
0996:                    Color c = col;
0997:                    if (horz) {
0998:                        c = lightenColor(c, (startOrBefore ? 1 : -1)
0999:                                * colFactor);
1000:                        currentStream.add(format(h) + " w\n");
1001:                        float ym1 = y1 + (h / 2);
1002:                        setColor(c, false, null);
1003:                        currentStream.add(format(x1) + " " + format(ym1)
1004:                                + " m " + format(x2) + " " + format(ym1)
1005:                                + " l S\n");
1006:                    } else {
1007:                        c = lightenColor(c, (startOrBefore ? 1 : -1)
1008:                                * colFactor);
1009:                        currentStream.add(format(w) + " w\n");
1010:                        float xm1 = x1 + (w / 2);
1011:                        setColor(c, false, null);
1012:                        currentStream.add(format(xm1) + " " + format(y1)
1013:                                + " m " + format(xm1) + " " + format(y2)
1014:                                + " l S\n");
1015:                    }
1016:                    break;
1017:                }
1018:                case Constants.EN_HIDDEN:
1019:                    break;
1020:                default:
1021:                    setColor(col, false, null);
1022:                    currentStream.add("[] 0 d ");
1023:                    if (horz) {
1024:                        currentStream.add(format(h) + " w\n");
1025:                        float ym = y1 + (h / 2);
1026:                        currentStream.add(format(x1) + " " + format(ym) + " m "
1027:                                + format(x2) + " " + format(ym) + " l S\n");
1028:                    } else {
1029:                        currentStream.add(format(w) + " w\n");
1030:                        float xm = x1 + (w / 2);
1031:                        currentStream.add(format(xm) + " " + format(y1) + " m "
1032:                                + format(xm) + " " + format(y2) + " l S\n");
1033:                    }
1034:                }
1035:            }
1036:
1037:            /**
1038:             * Sets the current line width in points.
1039:             * @param width line width in points
1040:             */
1041:            private void updateLineWidth(float width) {
1042:                if (currentState.setLineWidth(width)) {
1043:                    //Only write if value has changed WRT the current line width
1044:                    currentStream.add(format(width) + " w\n");
1045:                }
1046:            }
1047:
1048:            /**
1049:             * Clip a rectangular area.
1050:             * write a clipping operation given coordinates in the current
1051:             * transform.
1052:             * @param x the x coordinate
1053:             * @param y the y coordinate
1054:             * @param width the width of the area
1055:             * @param height the height of the area
1056:             */
1057:            protected void clipRect(float x, float y, float width, float height) {
1058:                currentStream.add(format(x) + " " + format(y) + " "
1059:                        + format(width) + " " + format(height) + " re ");
1060:                clip();
1061:            }
1062:
1063:            /**
1064:             * Clip an area.
1065:             */
1066:            protected void clip() {
1067:                currentStream.add("W\n");
1068:                currentStream.add("n\n");
1069:            }
1070:
1071:            /**
1072:             * Moves the current point to (x, y), omitting any connecting line segment. 
1073:             * @param x x coordinate
1074:             * @param y y coordinate
1075:             */
1076:            protected void moveTo(float x, float y) {
1077:                currentStream.add(format(x) + " " + format(y) + " m ");
1078:            }
1079:
1080:            /**
1081:             * Appends a straight line segment from the current point to (x, y). The 
1082:             * new current point is (x, y). 
1083:             * @param x x coordinate
1084:             * @param y y coordinate
1085:             */
1086:            protected void lineTo(float x, float y) {
1087:                currentStream.add(format(x) + " " + format(y) + " l ");
1088:            }
1089:
1090:            /**
1091:             * Closes the current subpath by appending a straight line segment from 
1092:             * the current point to the starting point of the subpath.
1093:             */
1094:            protected void closePath() {
1095:                currentStream.add("h ");
1096:            }
1097:
1098:            /** 
1099:             * @see org.apache.fop.render.AbstractPathOrientedRenderer#fillRect(float, float, float, float)
1100:             */
1101:            protected void fillRect(float x, float y, float w, float h) {
1102:                if (w != 0 && h != 0) {
1103:                    currentStream.add(format(x) + " " + format(y) + " "
1104:                            + format(w) + " " + format(h) + " re f\n");
1105:                }
1106:            }
1107:
1108:            /**
1109:             * Draw a line.
1110:             *
1111:             * @param startx the start x position
1112:             * @param starty the start y position
1113:             * @param endx the x end position
1114:             * @param endy the y end position
1115:             */
1116:            private void drawLine(float startx, float starty, float endx,
1117:                    float endy) {
1118:                currentStream
1119:                        .add(format(startx) + " " + format(starty) + " m ");
1120:                currentStream.add(format(endx) + " " + format(endy) + " l S\n");
1121:            }
1122:
1123:            /**
1124:             * Breaks out of the state stack to handle fixed block-containers.
1125:             * @return the saved state stack to recreate later
1126:             */
1127:            protected List breakOutOfStateStack() {
1128:                List breakOutList = new java.util.ArrayList();
1129:                PDFState.Data data;
1130:                while (true) {
1131:                    data = currentState.getData();
1132:                    if (currentState.pop() == null) {
1133:                        break;
1134:                    }
1135:                    if (breakOutList.size() == 0) {
1136:                        comment("------ break out!");
1137:                    }
1138:                    breakOutList.add(0, data); //Insert because of stack-popping
1139:                    restoreGraphicsState();
1140:                }
1141:                return breakOutList;
1142:            }
1143:
1144:            /**
1145:             * Restores the state stack after a break out.
1146:             * @param breakOutList the state stack to restore.
1147:             */
1148:            protected void restoreStateStackAfterBreakOut(List breakOutList) {
1149:                CTM tempctm;
1150:                comment("------ restoring context after break-out...");
1151:                PDFState.Data data;
1152:                Iterator i = breakOutList.iterator();
1153:                double[] matrix = new double[6];
1154:                while (i.hasNext()) {
1155:                    data = (PDFState.Data) i.next();
1156:                    currentState.push();
1157:                    saveGraphicsState();
1158:                    AffineTransform at = data.getTransform();
1159:                    if (!at.isIdentity()) {
1160:                        currentState.concatenate(at);
1161:                        at.getMatrix(matrix);
1162:                        tempctm = new CTM(matrix[0], matrix[1], matrix[2],
1163:                                matrix[3], matrix[4] * 1000, matrix[5] * 1000);
1164:                        currentStream.add(CTMHelper.toPDFString(tempctm)
1165:                                + " cm\n");
1166:                    }
1167:                    //TODO Break-out: Also restore items such as line width and color
1168:                    //Left out for now because all this painting stuff is very
1169:                    //inconsistent. Some values go over PDFState, some don't.
1170:                }
1171:                comment("------ done.");
1172:            }
1173:
1174:            /**
1175:             * Returns area's id if it is the first area in the document with that id 
1176:             * (i.e. if the area qualifies as a link target).
1177:             * Otherwise, or if the area has no id, null is returned.
1178:             *
1179:             * NOTE : area must be on currentPageViewport, otherwise result may be wrong!
1180:             *
1181:             * @param area the area for which to return the id
1182:             */
1183:            protected String getTargetableID(Area area) {
1184:                String id = (String) area.getTrait(Trait.PROD_ID);
1185:                if (id == null || id.length() == 0
1186:                        || !currentPageViewport.isFirstWithID(id)
1187:                        || idPositions.containsKey(id)) {
1188:                    return null;
1189:                } else {
1190:                    return id;
1191:                }
1192:            }
1193:
1194:            /**
1195:             * Set XY position in the PDFGoTo and add it to the PDF trailer.
1196:             *
1197:             * @param gt the PDFGoTo object
1198:             * @param position the X,Y position to set
1199:             */
1200:            protected void finishIDGoTo(PDFGoTo gt, Point2D.Float position) {
1201:                gt.setPosition(position);
1202:                pdfDoc.addTrailerObject(gt);
1203:                unfinishedGoTos.remove(gt);
1204:            }
1205:
1206:            /**
1207:             * Set page reference and XY position in the PDFGoTo and add it to the PDF trailer.
1208:             *
1209:             * @param gt the PDFGoTo object
1210:             * @param pdfPageRef the PDF reference string of the target page object
1211:             * @param position the X,Y position to set
1212:             */
1213:            protected void finishIDGoTo(PDFGoTo gt, String pdfPageRef,
1214:                    Point2D.Float position) {
1215:                gt.setPageReference(pdfPageRef);
1216:                finishIDGoTo(gt, position);
1217:            }
1218:
1219:            /**
1220:             * Get a PDFGoTo pointing to the given id. Create one if necessary.
1221:             * It is possible that the PDFGoTo is not fully resolved yet. In that case
1222:             * it must be completed (and added to the PDF trailer) later.
1223:             *
1224:             * @param targetID the target id of the PDFGoTo
1225:             * @param pvKey the unique key of the target PageViewport
1226:             *
1227:             * @return the PDFGoTo that was found or created
1228:             */
1229:            protected PDFGoTo getPDFGoToForID(String targetID, String pvKey) {
1230:                // Already a PDFGoTo present for this target? If not, create.
1231:                PDFGoTo gt = (PDFGoTo) idGoTos.get(targetID);
1232:                if (gt == null) {
1233:                    String pdfPageRef = (String) pageReferences.get(pvKey);
1234:                    Point2D.Float position = (Point2D.Float) idPositions
1235:                            .get(targetID);
1236:                    // can the GoTo already be fully filled in?
1237:                    if (pdfPageRef != null && position != null) {
1238:                        // getPDFGoTo shares PDFGoTo objects as much as possible.
1239:                        // It also takes care of assignObjectNumber and addTrailerObject.
1240:                        gt = pdfDoc.getFactory().getPDFGoTo(pdfPageRef,
1241:                                position);
1242:                    } else {
1243:                        // Not complete yet, can't use getPDFGoTo:
1244:                        gt = new PDFGoTo(pdfPageRef);
1245:                        pdfDoc.assignObjectNumber(gt);
1246:                        // pdfDoc.addTrailerObject() will be called later, from finishIDGoTo()
1247:                        unfinishedGoTos.add(gt);
1248:                    }
1249:                    idGoTos.put(targetID, gt);
1250:                }
1251:                return gt;
1252:            }
1253:
1254:            /**
1255:             * Saves id's absolute position on page for later retrieval by PDFGoTos
1256:             *
1257:             * @param id the id of the area whose position must be saved
1258:             * @param pdfPageRef the PDF page reference string
1259:             * @param relativeIPP the *relative* IP position in millipoints
1260:             * @param relativeBPP the *relative* BP position in millipoints
1261:             * @param tf the transformation to apply once the relative positions have been 
1262:             *           converted to points
1263:             */
1264:            protected void saveAbsolutePosition(String id, String pdfPageRef,
1265:                    int relativeIPP, int relativeBPP, AffineTransform tf) {
1266:                Point2D.Float position = new Point2D.Float(relativeIPP / 1000f,
1267:                        relativeBPP / 1000f);
1268:                tf.transform(position, position);
1269:                idPositions.put(id, position);
1270:                // is there already a PDFGoTo waiting to be completed?
1271:                PDFGoTo gt = (PDFGoTo) idGoTos.get(id);
1272:                if (gt != null) {
1273:                    finishIDGoTo(gt, pdfPageRef, position);
1274:                }
1275:                /*
1276:                 // The code below auto-creates a named destination for every id in the document.
1277:                 // This should probably be controlled by a user-configurable setting, as it may
1278:                 // make the PDF file grow noticeably.
1279:                 // *** NOT YET WELL-TESTED ! ***
1280:                 if (true) {
1281:                 PDFFactory factory = pdfDoc.getFactory();
1282:                 if (gt == null) {
1283:                 gt = factory.getPDFGoTo(pdfPageRef, position);
1284:                 idGoTos.put(id, gt);  // so others can pick it up too
1285:                 }
1286:                 factory.makeDestination(id, gt.referencePDF(), currentPageViewport);
1287:                 // Note: using currentPageViewport is only correct if the id is indeed on
1288:                 // the current PageViewport. But even if incorrect, it won't interfere with
1289:                 // what gets created in the PDF.
1290:                 // For speedup, we should also create a lookup map id -> PDFDestination
1291:                 }
1292:                 */
1293:            }
1294:
1295:            /**
1296:             * Saves id's absolute position on page for later retrieval by PDFGoTos,
1297:             * using the currently valid transformation and the currently valid PDF page reference
1298:             *
1299:             * @param id the id of the area whose position must be saved
1300:             * @param relativeIPP the *relative* IP position in millipoints
1301:             * @param relativeBPP the *relative* BP position in millipoints
1302:             */
1303:            protected void saveAbsolutePosition(String id, int relativeIPP,
1304:                    int relativeBPP) {
1305:                saveAbsolutePosition(id, currentPageRef, relativeIPP,
1306:                        relativeBPP, currentState.getTransform());
1307:            }
1308:
1309:            /**
1310:             * If the given block area is a possible link target, its id + absolute position will 
1311:             * be saved. The saved position is only correct if this function is called at the very 
1312:             * start of renderBlock!
1313:             *
1314:             * @param block the block area in question
1315:             */
1316:            protected void saveBlockPosIfTargetable(Block block) {
1317:                String id = getTargetableID(block);
1318:                if (id != null) {
1319:                    // FIXME: Like elsewhere in the renderer code, absolute and relative
1320:                    //        directions are happily mixed here. This makes sure that the
1321:                    //        links point to the right location, but it is not correct.
1322:                    int ipp = block.getXOffset();
1323:                    int bpp = block.getYOffset() + block.getSpaceBefore();
1324:                    int positioning = block.getPositioning();
1325:                    if (!(positioning == Block.FIXED || positioning == Block.ABSOLUTE)) {
1326:                        ipp += currentIPPosition;
1327:                        bpp += currentBPPosition;
1328:                    }
1329:                    AffineTransform tf = positioning == Block.FIXED ? currentState
1330:                            .getBaseTransform()
1331:                            : currentState.getTransform();
1332:                    saveAbsolutePosition(id, currentPageRef, ipp, bpp, tf);
1333:                }
1334:            }
1335:
1336:            /**
1337:             * If the given inline area is a possible link target, its id + absolute position will
1338:             * be saved. The saved position is only correct if this function is called at the very
1339:             * start of renderInlineArea!
1340:             *
1341:             * @param inlineArea the inline area in question
1342:             */
1343:            protected void saveInlinePosIfTargetable(InlineArea inlineArea) {
1344:                String id = getTargetableID(inlineArea);
1345:                if (id != null) {
1346:                    int extraMarginBefore = 5000; // millipoints
1347:                    int ipp = currentIPPosition;
1348:                    int bpp = currentBPPosition + inlineArea.getOffset()
1349:                            - extraMarginBefore;
1350:                    saveAbsolutePosition(id, ipp, bpp);
1351:                }
1352:            }
1353:
1354:            /**
1355:             * @see org.apache.fop.render.AbstractRenderer#renderBlock(Block)
1356:             */
1357:            protected void renderBlock(Block block) {
1358:                saveBlockPosIfTargetable(block);
1359:                super .renderBlock(block);
1360:            }
1361:
1362:            /**
1363:             * @see org.apache.fop.render.AbstractRenderer#renderLineArea(LineArea)
1364:             */
1365:            protected void renderLineArea(LineArea line) {
1366:                super .renderLineArea(line);
1367:                closeText();
1368:            }
1369:
1370:            /**
1371:             * @see org.apache.fop.render.AbstractRenderer#renderInlineArea(InlineArea)
1372:             */
1373:            protected void renderInlineArea(InlineArea inlineArea) {
1374:                saveInlinePosIfTargetable(inlineArea);
1375:                super .renderInlineArea(inlineArea);
1376:            }
1377:
1378:            /**
1379:             * Render inline parent area.
1380:             * For pdf this handles the inline parent area traits such as
1381:             * links, border, background.
1382:             * @param ip the inline parent area
1383:             */
1384:            public void renderInlineParent(InlineParent ip) {
1385:
1386:                boolean annotsAllowed = pdfDoc.getProfile()
1387:                        .isAnnotationAllowed();
1388:
1389:                // stuff we only need if a link must be created:
1390:                Rectangle2D ipRect = null;
1391:                PDFFactory factory = null;
1392:                PDFAction action = null;
1393:                if (annotsAllowed) {
1394:                    // make sure the rect is determined *before* calling super!
1395:                    int ipp = currentIPPosition;
1396:                    int bpp = currentBPPosition + ip.getOffset();
1397:                    ipRect = new Rectangle2D.Float(ipp / 1000f, bpp / 1000f, ip
1398:                            .getIPD() / 1000f, ip.getBPD() / 1000f);
1399:                    AffineTransform transform = currentState.getTransform();
1400:                    ipRect = transform.createTransformedShape(ipRect)
1401:                            .getBounds2D();
1402:
1403:                    factory = pdfDoc.getFactory();
1404:                }
1405:
1406:                // render contents
1407:                super .renderInlineParent(ip);
1408:
1409:                boolean linkTraitFound = false;
1410:
1411:                // try INTERNAL_LINK first
1412:                Trait.InternalLink intLink = (Trait.InternalLink) ip
1413:                        .getTrait(Trait.INTERNAL_LINK);
1414:                if (intLink != null) {
1415:                    linkTraitFound = true;
1416:                    String pvKey = intLink.getPVKey();
1417:                    String idRef = intLink.getIDRef();
1418:                    boolean pvKeyOK = pvKey != null && pvKey.length() > 0;
1419:                    boolean idRefOK = idRef != null && idRef.length() > 0;
1420:                    if (pvKeyOK && idRefOK) {
1421:                        if (annotsAllowed) {
1422:                            action = getPDFGoToForID(idRef, pvKey);
1423:                        }
1424:                    } else if (pvKeyOK) {
1425:                        log.warn("Internal link trait with PageViewport key "
1426:                                + pvKey + " contains no ID reference.");
1427:                    } else if (idRefOK) {
1428:                        log.warn("Internal link trait with ID reference "
1429:                                + idRef + " contains no PageViewport key.");
1430:                    } else {
1431:                        log
1432:                                .warn("Internal link trait received with neither PageViewport key"
1433:                                        + " nor ID reference.");
1434:                    }
1435:                }
1436:
1437:                // no INTERNAL_LINK, look for EXTERNAL_LINK
1438:                if (!linkTraitFound) {
1439:                    String extDest = (String) ip.getTrait(Trait.EXTERNAL_LINK);
1440:                    if (extDest != null && extDest.length() > 0) {
1441:                        linkTraitFound = true;
1442:                        if (annotsAllowed) {
1443:                            action = factory.getExternalAction(extDest);
1444:                        }
1445:                    }
1446:                }
1447:
1448:                // warn if link trait found but not allowed, else create link
1449:                if (linkTraitFound) {
1450:                    if (!annotsAllowed) {
1451:                        log
1452:                                .warn("Skipping annotation for a link due to PDF profile: "
1453:                                        + pdfDoc.getProfile());
1454:                    } else if (action != null) {
1455:                        PDFLink pdfLink = factory.makeLink(ipRect, action);
1456:                        currentPage.addAnnotation(pdfLink);
1457:                    }
1458:                }
1459:            }
1460:
1461:            /**
1462:             * @see org.apache.fop.render.AbstractRenderer#renderText(TextArea)
1463:             */
1464:            public void renderText(TextArea text) {
1465:                renderInlineAreaBackAndBorders(text);
1466:                beginTextObject();
1467:                StringBuffer pdf = new StringBuffer();
1468:
1469:                String fontName = getInternalFontNameForArea(text);
1470:                int size = ((Integer) text.getTrait(Trait.FONT_SIZE))
1471:                        .intValue();
1472:
1473:                // This assumes that *all* CIDFonts use a /ToUnicode mapping
1474:                Typeface tf = (Typeface) fontInfo.getFonts().get(fontName);
1475:                boolean useMultiByte = tf.isMultiByte();
1476:
1477:                updateFont(fontName, size, pdf);
1478:                Color ct = (Color) text.getTrait(Trait.COLOR);
1479:                updateColor(ct, true, pdf);
1480:
1481:                // word.getOffset() = only height of text itself
1482:                // currentBlockIPPosition: 0 for beginning of line; nonzero
1483:                //  where previous line area failed to take up entire allocated space
1484:                int rx = currentIPPosition
1485:                        + text.getBorderAndPaddingWidthStart();
1486:                int bl = currentBPPosition + text.getOffset()
1487:                        + text.getBaselineOffset();
1488:
1489:                pdf.append("1 0 0 -1 " + format(rx / 1000f) + " "
1490:                        + format(bl / 1000f) + " Tm "
1491:                /*+ format(text.getTextLetterSpaceAdjust() / 1000f) + " Tc\n"*/
1492:                /*+ format(text.getTextWordSpaceAdjust() / 1000f) + " Tw ["*/);
1493:
1494:                pdf.append("[");
1495:                currentStream.add(pdf.toString());
1496:
1497:                super .renderText(text);
1498:
1499:                currentStream.add("] TJ\n");
1500:
1501:                renderTextDecoration(tf, size, text, bl, rx);
1502:            }
1503:
1504:            /**
1505:             * @see org.apache.fop.render.AbstractRenderer#renderWord(WordArea)
1506:             */
1507:            public void renderWord(WordArea word) {
1508:                Font font = getFontFromArea(word.getParentArea());
1509:                Typeface tf = (Typeface) fontInfo.getFonts().get(
1510:                        font.getFontName());
1511:                boolean useMultiByte = tf.isMultiByte();
1512:
1513:                StringBuffer pdf = new StringBuffer();
1514:
1515:                String s = word.getWord();
1516:                escapeText(s, word.getLetterAdjustArray(), font,
1517:                        (AbstractTextArea) word.getParentArea(), useMultiByte,
1518:                        pdf);
1519:
1520:                currentStream.add(pdf.toString());
1521:
1522:                super .renderWord(word);
1523:            }
1524:
1525:            /**
1526:             * @see org.apache.fop.render.AbstractRenderer#renderSpace(SpaceArea)
1527:             */
1528:            public void renderSpace(SpaceArea space) {
1529:                Font font = getFontFromArea(space.getParentArea());
1530:                Typeface tf = (Typeface) fontInfo.getFonts().get(
1531:                        font.getFontName());
1532:                boolean useMultiByte = tf.isMultiByte();
1533:
1534:                String s = space.getSpace();
1535:
1536:                StringBuffer pdf = new StringBuffer();
1537:
1538:                AbstractTextArea textArea = (AbstractTextArea) space
1539:                        .getParentArea();
1540:                escapeText(s, null, font, textArea, useMultiByte, pdf);
1541:
1542:                if (space.isAdjustable()) {
1543:                    int tws = -((TextArea) space.getParentArea())
1544:                            .getTextWordSpaceAdjust()
1545:                            - 2 * textArea.getTextLetterSpaceAdjust();
1546:
1547:                    if (tws != 0) {
1548:                        pdf.append(format(tws / (font.getFontSize() / 1000f)));
1549:                        pdf.append(" ");
1550:                    }
1551:                }
1552:
1553:                currentStream.add(pdf.toString());
1554:
1555:                super .renderSpace(space);
1556:            }
1557:
1558:            /**
1559:             * Escapes text according to PDF rules.
1560:             * @param s Text to escape
1561:             * @param letterAdjust an array of widths for letter adjustment (may be null)
1562:             * @param fs Font state
1563:             * @param parentArea the parent text area to retrieve certain traits from
1564:             * @param useMultiByte Indicates the use of multi byte convention
1565:             * @param pdf target buffer for the escaped text
1566:             */
1567:            public void escapeText(String s, int[] letterAdjust, Font fs,
1568:                    AbstractTextArea parentArea, boolean useMultiByte,
1569:                    StringBuffer pdf) {
1570:                String startText = useMultiByte ? "<" : "(";
1571:                String endText = useMultiByte ? "> " : ") ";
1572:
1573:                /*
1574:                boolean kerningAvailable = false;
1575:                Map kerning = fs.getKerning();
1576:                if (kerning != null && !kerning.isEmpty()) {
1577:                    //kerningAvailable = true;
1578:                    //TODO Reenable me when the layout engine supports kerning, too
1579:                    log.warn("Kerning support is disabled until it is supported by the layout engine!");
1580:                }
1581:                 */
1582:
1583:                int l = s.length();
1584:
1585:                float fontSize = fs.getFontSize() / 1000f;
1586:                boolean startPending = true;
1587:                for (int i = 0; i < l; i++) {
1588:                    char orgChar = s.charAt(i);
1589:                    char ch;
1590:                    float glyphAdjust = 0;
1591:                    if (fs.hasChar(orgChar)) {
1592:                        ch = fs.mapChar(orgChar);
1593:                        int tls = (i < l - 1 ? parentArea
1594:                                .getTextLetterSpaceAdjust() : 0);
1595:                        glyphAdjust -= tls;
1596:                    } else {
1597:                        if (CharUtilities.isFixedWidthSpace(orgChar)) {
1598:                            //Fixed width space are rendered as spaces so copy/paste works in a reader
1599:                            ch = fs.mapChar(CharUtilities.SPACE);
1600:                            glyphAdjust = fs.getCharWidth(ch)
1601:                                    - fs.getCharWidth(orgChar);
1602:                        } else {
1603:                            ch = fs.mapChar(orgChar);
1604:                        }
1605:                    }
1606:                    if (letterAdjust != null && i < l - 1) {
1607:                        glyphAdjust -= letterAdjust[i + 1];
1608:                    }
1609:
1610:                    if (startPending) {
1611:                        pdf.append(startText);
1612:                        startPending = false;
1613:                    }
1614:                    if (!useMultiByte) {
1615:                        if (ch > 127) {
1616:                            pdf.append("\\");
1617:                            pdf.append(Integer.toOctalString((int) ch));
1618:                        } else {
1619:                            switch (ch) {
1620:                            case '(':
1621:                            case ')':
1622:                            case '\\':
1623:                                pdf.append("\\");
1624:                                break;
1625:                            default:
1626:                            }
1627:                            pdf.append(ch);
1628:                        }
1629:                    } else {
1630:                        pdf.append(PDFText.toUnicodeHex(ch));
1631:                    }
1632:
1633:                    float adjust = glyphAdjust / fontSize;
1634:
1635:                    if (adjust != 0) {
1636:                        pdf.append(endText).append(format(adjust)).append(' ');
1637:                        startPending = true;
1638:                    }
1639:
1640:                }
1641:                if (!startPending) {
1642:                    pdf.append(endText);
1643:                }
1644:            }
1645:
1646:            /**
1647:             * Checks to see if we have some text rendering commands open
1648:             * still and writes out the TJ command to the stream if we do
1649:             */
1650:            protected void closeText() {
1651:                /*
1652:                if (textOpen) {
1653:                    currentStream.add("] TJ\n");
1654:                    textOpen = false;
1655:                    prevWordX = 0;
1656:                    prevWordY = 0;
1657:                    currentFontName = "";
1658:                }*/
1659:            }
1660:
1661:            /**
1662:             * Establishes a new foreground or fill color. In contrast to updateColor
1663:             * this method does not check the PDFState for optimization possibilities.
1664:             * @param col the color to apply 
1665:             * @param fill true to set the fill color, false for the foreground color
1666:             * @param pdf StringBuffer to write the PDF code to, if null, the code is
1667:             *     written to the current stream.
1668:             */
1669:            protected void setColor(Color col, boolean fill, StringBuffer pdf) {
1670:                PDFColor color = new PDFColor(this .pdfDoc, col);
1671:
1672:                closeText();
1673:
1674:                if (pdf != null) {
1675:                    pdf.append(color.getColorSpaceOut(fill));
1676:                } else {
1677:                    currentStream.add(color.getColorSpaceOut(fill));
1678:                }
1679:            }
1680:
1681:            /**
1682:             * Establishes a new foreground or fill color.
1683:             * @param col the color to apply (null skips this operation)
1684:             * @param fill true to set the fill color, false for the foreground color
1685:             * @param pdf StringBuffer to write the PDF code to, if null, the code is
1686:             *     written to the current stream.
1687:             */
1688:            private void updateColor(Color col, boolean fill, StringBuffer pdf) {
1689:                if (col == null) {
1690:                    return;
1691:                }
1692:                boolean update = false;
1693:                if (fill) {
1694:                    update = currentState.setBackColor(col);
1695:                } else {
1696:                    update = currentState.setColor(col);
1697:                }
1698:
1699:                if (update) {
1700:                    setColor(col, fill, pdf);
1701:                }
1702:            }
1703:
1704:            /** @see org.apache.fop.render.AbstractPathOrientedRenderer */
1705:            protected void updateColor(Color col, boolean fill) {
1706:                updateColor(col, fill, null);
1707:            }
1708:
1709:            private void updateFont(String name, int size, StringBuffer pdf) {
1710:                if ((!name.equals(this .currentFontName))
1711:                        || (size != this .currentFontSize)) {
1712:                    closeText();
1713:
1714:                    this .currentFontName = name;
1715:                    this .currentFontSize = size;
1716:                    pdf = pdf.append("/" + name + " "
1717:                            + format((float) size / 1000f) + " Tf\n");
1718:                }
1719:            }
1720:
1721:            /**
1722:             * @see org.apache.fop.render.AbstractRenderer#renderImage(Image, Rectangle2D)
1723:             */
1724:            public void renderImage(Image image, Rectangle2D pos) {
1725:                endTextObject();
1726:                String url = image.getURL();
1727:                putImage(url, pos);
1728:            }
1729:
1730:            /** @see org.apache.fop.render.AbstractPathOrientedRenderer */
1731:            protected void drawImage(String url, Rectangle2D pos,
1732:                    Map foreignAttributes) {
1733:                endTextObject();
1734:                putImage(url, pos);
1735:            }
1736:
1737:            /**
1738:             * Adds a PDF XObject (a bitmap) to the PDF that will later be referenced.
1739:             * @param url URL of the bitmap
1740:             * @param pos Position of the bitmap
1741:             */
1742:            protected void putImage(String url, Rectangle2D pos) {
1743:                PDFXObject xobject = pdfDoc.getImage(url);
1744:                if (xobject != null) {
1745:                    float w = (float) pos.getWidth() / 1000f;
1746:                    float h = (float) pos.getHeight() / 1000f;
1747:                    placeImage((float) pos.getX() / 1000f,
1748:                            (float) pos.getY() / 1000f, w, h, xobject
1749:                                    .getXNumber());
1750:                    return;
1751:                }
1752:
1753:                url = ImageFactory.getURL(url);
1754:                ImageFactory fact = userAgent.getFactory().getImageFactory();
1755:                FopImage fopimage = fact.getImage(url, userAgent);
1756:                if (fopimage == null) {
1757:                    return;
1758:                }
1759:                if (!fopimage.load(FopImage.DIMENSIONS)) {
1760:                    return;
1761:                }
1762:                String mime = fopimage.getMimeType();
1763:                if ("text/xml".equals(mime)) {
1764:                    if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
1765:                        return;
1766:                    }
1767:                    Document doc = ((XMLImage) fopimage).getDocument();
1768:                    String ns = ((XMLImage) fopimage).getNameSpace();
1769:
1770:                    renderDocument(doc, ns, pos, null);
1771:                } else if ("image/svg+xml".equals(mime)) {
1772:                    if (!fopimage.load(FopImage.ORIGINAL_DATA)) {
1773:                        return;
1774:                    }
1775:                    Document doc = ((XMLImage) fopimage).getDocument();
1776:                    String ns = ((XMLImage) fopimage).getNameSpace();
1777:
1778:                    renderDocument(doc, ns, pos, null);
1779:                } else if ("image/eps".equals(mime)) {
1780:                    FopPDFImage pdfimage = new FopPDFImage(fopimage, url);
1781:                    int xobj = pdfDoc.addImage(currentContext, pdfimage)
1782:                            .getXNumber();
1783:                    fact.releaseImage(url, userAgent);
1784:
1785:                    float w = (float) pos.getWidth() / 1000f;
1786:                    float h = (float) pos.getHeight() / 1000f;
1787:                    placeImage((float) pos.getX() / 1000,
1788:                            (float) pos.getY() / 1000, w, h, xobj);
1789:                } else if ("image/jpeg".equals(mime)
1790:                        || "image/tiff".equals(mime)) {
1791:                    FopPDFImage pdfimage = new FopPDFImage(fopimage, url);
1792:                    int xobj = pdfDoc.addImage(currentContext, pdfimage)
1793:                            .getXNumber();
1794:                    fact.releaseImage(url, userAgent);
1795:
1796:                    float w = (float) pos.getWidth() / 1000f;
1797:                    float h = (float) pos.getHeight() / 1000f;
1798:                    placeImage((float) pos.getX() / 1000,
1799:                            (float) pos.getY() / 1000, w, h, xobj);
1800:                } else {
1801:                    if (!fopimage.load(FopImage.BITMAP)) {
1802:                        return;
1803:                    }
1804:                    FopPDFImage pdfimage = new FopPDFImage(fopimage, url);
1805:                    int xobj = pdfDoc.addImage(currentContext, pdfimage)
1806:                            .getXNumber();
1807:                    fact.releaseImage(url, userAgent);
1808:
1809:                    float w = (float) pos.getWidth() / 1000f;
1810:                    float h = (float) pos.getHeight() / 1000f;
1811:                    placeImage((float) pos.getX() / 1000f,
1812:                            (float) pos.getY() / 1000f, w, h, xobj);
1813:                }
1814:
1815:                // output new data
1816:                try {
1817:                    this .pdfDoc.output(ostream);
1818:                } catch (IOException ioe) {
1819:                    // ioexception will be caught later
1820:                }
1821:            }
1822:
1823:            /**
1824:             * Places a previously registered image at a certain place on the page.
1825:             * @param x X coordinate
1826:             * @param y Y coordinate
1827:             * @param w width for image
1828:             * @param h height for image
1829:             * @param xobj object number of the referenced image
1830:             */
1831:            protected void placeImage(float x, float y, float w, float h,
1832:                    int xobj) {
1833:                saveGraphicsState();
1834:                currentStream.add(format(w) + " 0 0 " + format(-h) + " "
1835:                        + format(currentIPPosition / 1000f + x) + " "
1836:                        + format(currentBPPosition / 1000f + h + y) + " cm\n"
1837:                        + "/Im" + xobj + " Do\n");
1838:                restoreGraphicsState();
1839:            }
1840:
1841:            /**
1842:             * @see org.apache.fop.render.PrintRenderer#createRendererContext(
1843:             *          int, int, int, int, java.util.Map)
1844:             */
1845:            protected RendererContext createRendererContext(int x, int y,
1846:                    int width, int height, Map foreignAttributes) {
1847:                RendererContext context = super .createRendererContext(x, y,
1848:                        width, height, foreignAttributes);
1849:                context.setProperty(PDFRendererContextConstants.PDF_DOCUMENT,
1850:                        pdfDoc);
1851:                context.setProperty(PDFRendererContextConstants.OUTPUT_STREAM,
1852:                        ostream);
1853:                context.setProperty(PDFRendererContextConstants.PDF_STATE,
1854:                        currentState);
1855:                context.setProperty(PDFRendererContextConstants.PDF_PAGE,
1856:                        currentPage);
1857:                context.setProperty(PDFRendererContextConstants.PDF_CONTEXT,
1858:                        currentContext == null ? currentPage : currentContext);
1859:                context.setProperty(PDFRendererContextConstants.PDF_CONTEXT,
1860:                        currentContext);
1861:                context.setProperty(PDFRendererContextConstants.PDF_STREAM,
1862:                        currentStream);
1863:                context.setProperty(PDFRendererContextConstants.PDF_FONT_INFO,
1864:                        fontInfo);
1865:                context.setProperty(PDFRendererContextConstants.PDF_FONT_NAME,
1866:                        currentFontName);
1867:                context.setProperty(PDFRendererContextConstants.PDF_FONT_SIZE,
1868:                        new Integer(currentFontSize));
1869:                return context;
1870:            }
1871:
1872:            /**
1873:             * Render leader area.
1874:             * This renders a leader area which is an area with a rule.
1875:             * @param area the leader area to render
1876:             */
1877:            public void renderLeader(Leader area) {
1878:                renderInlineAreaBackAndBorders(area);
1879:
1880:                currentState.push();
1881:                saveGraphicsState();
1882:                int style = area.getRuleStyle();
1883:                float startx = (currentIPPosition + area
1884:                        .getBorderAndPaddingWidthStart()) / 1000f;
1885:                float starty = (currentBPPosition + area.getOffset()) / 1000f;
1886:                float endx = (currentIPPosition
1887:                        + area.getBorderAndPaddingWidthStart() + area.getIPD()) / 1000f;
1888:                float ruleThickness = area.getRuleThickness() / 1000f;
1889:                Color col = (Color) area.getTrait(Trait.COLOR);
1890:
1891:                switch (style) {
1892:                case EN_SOLID:
1893:                case EN_DASHED:
1894:                case EN_DOUBLE:
1895:                    drawBorderLine(startx, starty, endx,
1896:                            starty + ruleThickness, true, true, style, col);
1897:                    break;
1898:                case EN_DOTTED:
1899:                    clipRect(startx, starty, endx - startx, ruleThickness);
1900:                    //This displaces the dots to the right by half a dot's width
1901:                    //TODO There's room for improvement here
1902:                    currentStream.add("1 0 0 1 " + format(ruleThickness / 2)
1903:                            + " 0 cm\n");
1904:                    drawBorderLine(startx, starty, endx,
1905:                            starty + ruleThickness, true, true, style, col);
1906:                    break;
1907:                case EN_GROOVE:
1908:                case EN_RIDGE:
1909:                    float half = area.getRuleThickness() / 2000f;
1910:
1911:                    setColor(lightenColor(col, 0.6f), true, null);
1912:                    currentStream.add(format(startx) + " " + format(starty)
1913:                            + " m\n");
1914:                    currentStream.add(format(endx) + " " + format(starty)
1915:                            + " l\n");
1916:                    currentStream.add(format(endx) + " "
1917:                            + format(starty + 2 * half) + " l\n");
1918:                    currentStream.add(format(startx) + " "
1919:                            + format(starty + 2 * half) + " l\n");
1920:                    currentStream.add("h\n");
1921:                    currentStream.add("f\n");
1922:                    setColor(col, true, null);
1923:                    if (style == EN_GROOVE) {
1924:                        currentStream.add(format(startx) + " " + format(starty)
1925:                                + " m\n");
1926:                        currentStream.add(format(endx) + " " + format(starty)
1927:                                + " l\n");
1928:                        currentStream.add(format(endx) + " "
1929:                                + format(starty + half) + " l\n");
1930:                        currentStream.add(format(startx + half) + " "
1931:                                + format(starty + half) + " l\n");
1932:                        currentStream.add(format(startx) + " "
1933:                                + format(starty + 2 * half) + " l\n");
1934:                    } else {
1935:                        currentStream.add(format(endx) + " " + format(starty)
1936:                                + " m\n");
1937:                        currentStream.add(format(endx) + " "
1938:                                + format(starty + 2 * half) + " l\n");
1939:                        currentStream.add(format(startx) + " "
1940:                                + format(starty + 2 * half) + " l\n");
1941:                        currentStream.add(format(startx) + " "
1942:                                + format(starty + half) + " l\n");
1943:                        currentStream.add(format(endx - half) + " "
1944:                                + format(starty + half) + " l\n");
1945:                    }
1946:                    currentStream.add("h\n");
1947:                    currentStream.add("f\n");
1948:                    break;
1949:                default:
1950:                    throw new UnsupportedOperationException(
1951:                            "rule style not supported");
1952:                }
1953:
1954:                restoreGraphicsState();
1955:                currentState.pop();
1956:                beginTextObject();
1957:                super .renderLeader(area);
1958:            }
1959:
1960:            /** @see org.apache.fop.render.AbstractRenderer */
1961:            public String getMimeType() {
1962:                return MIME_TYPE;
1963:            }
1964:
1965:            public void setAMode(PDFAMode mode) {
1966:                this .pdfAMode = mode;
1967:            }
1968:
1969:            public void setXMode(PDFXMode mode) {
1970:                this .pdfXMode = mode;
1971:            }
1972:
1973:            public void setOutputProfileURI(String outputProfileURI) {
1974:                this .outputProfileURI = outputProfileURI;
1975:            }
1976:
1977:            public void setFilterMap(Map filterMap) {
1978:                this.filterMap = filterMap;
1979:            }
1980:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.