New upstream version 2.0.27
Markus Koschany
1 year, 6 months ago
0 | Release Notes -- Apache PDFBox -- Version 2.0.26 | |
0 | Release Notes -- Apache PDFBox -- Version 2.0.27 | |
1 | 1 | |
2 | 2 | Introduction |
3 | 3 | ------------ |
4 | 4 | |
5 | 5 | The Apache PDFBox library is an open source Java tool for working with PDF documents. |
6 | 6 | |
7 | This is an incremental bugfix release based on the earlier 2.0.25 release. It contains | |
7 | This is an incremental bugfix release based on the earlier 2.0.26 release. It contains | |
8 | 8 | a couple of fixes and small improvements. |
9 | 9 | |
10 | 10 | For more details on these changes and all the other fixes and improvements |
13 | 13 | |
14 | 14 | Bug |
15 | 15 | |
16 | [PDFBOX-4623] - COSParser: Infinite recursion | |
17 | [PDFBOX-5203] - TestCreateSignature.testCreateSignedTimeStamp checkLTV build test fail | |
18 | [PDFBOX-5283] - No Content - xRef / Obj Parsing | |
19 | [PDFBOX-5305] - Pdf-A/1b Validation | |
20 | [PDFBOX-5339] - A list of bugs found (70 bugs in total) | |
21 | [PDFBOX-5342] - Text size option for PDFBox Debugger | |
22 | [PDFBOX-5345] - IllegalArgumentException: Input buffer too short in StandardSecurityHandler.computeRC4key | |
23 | [PDFBOX-5352] - ArrayIndexOutOfBoundsException in PDSeparation.tintTransform() | |
24 | [PDFBOX-5360] - EOFException: Can't read 20 bytes | |
25 | [PDFBOX-5361] - Wrong datatype for OPM in PDExtendedGraphicsState | |
26 | [PDFBOX-5366] - Unhandled IOException thrown from BaseParser creates issue in PDFStreamEngine.processStreamOperators | |
27 | [PDFBOX-5372] - *LOADS of* "WARNING: key node000xxxxx already exists in destination IDTree" | |
28 | [PDFBOX-5373] - NullPointerException in PDRange.getMin() | |
29 | [PDFBOX-5376] - Image interpolation when there shouldn't be | |
30 | [PDFBOX-5377] - pDAcroForm.flatten() does not remove /SigFlags in /Catalog object | |
31 | [PDFBOX-5380] - Could not read embedded TTF for font | |
32 | [PDFBOX-5387] - ToUnicodeWriter.writeTo allows byte overflow in bfrange operator | |
33 | [PDFBOX-5390] - TextToPDF appends space to each line | |
34 | [PDFBOX-5393] - NegativeArraySizeException in pfb parser with 0 byte pfb font file | |
35 | [PDFBOX-5395] - Hangup in COSFilterInputStream.nextRange | |
36 | [PDFBOX-5397] - Certain PDF cannot be processed | |
37 | [PDFBOX-5398] - Parsing fails in 2.0.26 that worked in 2.0.25 | |
38 | [PDFBOX-5399] - Object must be defined and must not be compressed object | |
39 | [PDFBOX-5400] - Page tree root must be a dictionary | |
40 | [PDFBOX-5401] - A carefully crafted pdf can trigger an infinite loop while parsing | |
41 | [PDFBOX-5402] - POCIDFontType2 (Wingdings) encode throws a NullPointerException | |
42 | [PDFBOX-5410] - Possible loop detection is triggered in 2.0.26 but file works in 2.0.25 | |
43 | [PDFBOX-5412] - IOException: object reference 112 0 R at offset 18355 in content stream | |
44 | [PDFBOX-5413] - Field text missing | |
45 | [PDFBOX-5418] - NPE during page render | |
46 | [PDFBOX-5419] - Parsing shows 1 empty page with 2.0.26 and 7 with 2.0.25 | |
16 | [PDFBOX-4925] - Invalid stream Length validation in StreamValidationProcess | |
17 | [PDFBOX-5389] - To set compressed on buffered image while creating a PDF | |
18 | [PDFBOX-5403] - Blurry / distorted rendering | |
19 | [PDFBOX-5424] - java.lang.IndexOutOfBoundsException (2) | |
20 | [PDFBOX-5427] - PDFDebugger does not remove listeners for PagePane when opening new File | |
21 | [PDFBOX-5428] - PDFRenderer.renderImageWithDPI thows EOFException in PDF | |
22 | [PDFBOX-5429] - PDFCloneUtility.checkForRecursion breaks support for some existing PDFs | |
23 | [PDFBOX-5430] - PDFStreamEngine.showTextStrings with font switch | |
24 | [PDFBOX-5453] - ClassCastException (PDColor.java:66) | |
25 | [PDFBOX-5459] - NullPointerException in PDFunctionType3.eval() | |
26 | [PDFBOX-5460] - Deadlock in TrueTypeFont and RAFDataStream | |
27 | [PDFBOX-5463] - illegalArgumentException for rendering PDF (image extraction) | |
28 | [PDFBOX-5465] - NullPointerException in CmapSubtable.getCharCode | |
29 | [PDFBOX-5470] - PDActionEmbeddedGoTo does not accept a Destination with a page number or string | |
30 | [PDFBOX-5471] - NPE when Transparency Group is missing the BBox | |
31 | [PDFBOX-5484] - PDFRenderer does not render letters when converting page to image | |
32 | [PDFBOX-5488] - JPEG image rendered with wrong colors when using TwelveMonkeys | |
33 | [PDFBOX-5499] - Performance issue since 2.0.18 | |
34 | [PDFBOX-5500] - NullPointerException in PDType0Font.readCode() if cMap is null | |
35 | [PDFBOX-5504] - NullPointerException in CFFParser.parseFont() | |
36 | [PDFBOX-5505] - IndexOutOfBoundsException in PDCIDFont.readWidths() | |
37 | [PDFBOX-5506] - IndexOutOfBoundsException in Type1Parser.java | |
38 | [PDFBOX-5507] - ClassCastException in CMapParser.parseBeginbfchar | |
39 | [PDFBOX-5508] - ClassCastException in PDXObject.createXObject() | |
40 | [PDFBOX-5509] - ClassCastException in PDAcroForm.getFields() | |
41 | [PDFBOX-5510] - ClassCastException in PDDocumentCatalog.getAcroForm() | |
42 | [PDFBOX-5511] - ClassCastException in PDResources.getIndirect() | |
43 | [PDFBOX-5513] - getPageLayout throws IllegalArgumentException for empty mode | |
44 | [PDFBOX-5514] - Font not found because of case issues | |
47 | 45 | |
48 | 46 | Improvement |
49 | 47 | |
50 | [PDFBOX-5347] - Create push button example | |
51 | [PDFBOX-5348] - FontMapper should also take into account the user's font directory on Windows operating systems | |
52 | [PDFBOX-5363] - Don't log warnings if there are not fonts to cache | |
53 | [PDFBOX-5379] - support multiple widgets in PDTerminalField.importFDF() | |
54 | [PDFBOX-5385] - Improve AddValidationInformation to handle exceptional situations better | |
55 | [PDFBOX-5388] - Avoid duplicate certificates in AddValidation example | |
56 | [PDFBOX-5394] - Render symbol for file attachment annotations | |
57 | ||
58 | Task | |
59 | ||
60 | [PDFBOX-5356] - Add test of PFB font | |
61 | [PDFBOX-5396] - Add maven enforcer rule to ensure that JAVA_HOME is set | |
48 | [PDFBOX-5448] - Clarify access permission for high / low print quality | |
49 | [PDFBOX-5450] - Create Opaque PDFRenderer example | |
50 | [PDFBOX-5494] - "invalid object number value" is too scary | |
62 | 51 | |
63 | 52 | Release Contents |
64 | 53 | ---------------- |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.26</version> | |
25 | <version>2.0.27</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.26</version> | |
25 | <version>2.0.27</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 |
62 | 62 | new Object[]{9, "can fill in form fields", ap.canFillInForm()}, |
63 | 63 | new Object[]{10, "can extract for accessibility", ap.canExtractForAccessibility()}, |
64 | 64 | new Object[]{11, "can assemble document", ap.canAssembleDocument()}, |
65 | new Object[]{12, "can print degraded", ap.canPrintDegraded()}, | |
65 | new Object[]{12, "can print faithful", ap.canPrintFaithful()}, | |
66 | 66 | }; |
67 | 67 | } |
68 | 68 | } |
307 | 307 | |
308 | 308 | PDFDebugger.allowSubsampling.setEnabled(false); |
309 | 309 | PDFDebugger.allowSubsampling.removeActionListener(this); |
310 | ||
311 | PDFDebugger.repairAcroFormMenuItem.setEnabled(false); | |
312 | PDFDebugger.repairAcroFormMenuItem.removeActionListener(this); | |
310 | 313 | } |
311 | 314 | |
312 | 315 | @Override |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.26</version> | |
25 | <version>2.0.27</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.26</version> | |
25 | <version>2.0.27</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 |
0 | /* | |
1 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
2 | * contributor license agreements. See the NOTICE file distributed with | |
3 | * this work for additional information regarding copyright ownership. | |
4 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
5 | * (the "License"); you may not use this file except in compliance with | |
6 | * the License. You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.apache.pdfbox.examples.printing; | |
17 | ||
18 | import java.awt.print.Printable; | |
19 | import java.awt.print.PrinterException; | |
20 | import java.awt.print.PrinterJob; | |
21 | import java.io.IOException; | |
22 | import java.io.InputStream; | |
23 | import java.net.URL; | |
24 | import java.util.List; | |
25 | import javax.print.PrintServiceLookup; | |
26 | import org.apache.commons.logging.Log; | |
27 | import org.apache.commons.logging.LogFactory; | |
28 | import org.apache.pdfbox.contentstream.operator.MissingOperandException; | |
29 | import org.apache.pdfbox.contentstream.operator.Operator; | |
30 | import org.apache.pdfbox.contentstream.operator.OperatorName; | |
31 | import org.apache.pdfbox.contentstream.operator.graphics.GraphicsOperatorProcessor; | |
32 | import org.apache.pdfbox.cos.COSBase; | |
33 | import org.apache.pdfbox.cos.COSName; | |
34 | import org.apache.pdfbox.pdmodel.MissingResourceException; | |
35 | import org.apache.pdfbox.pdmodel.PDDocument; | |
36 | import org.apache.pdfbox.pdmodel.graphics.PDXObject; | |
37 | import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; | |
38 | import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; | |
39 | import org.apache.pdfbox.printing.PDFPrintable; | |
40 | import org.apache.pdfbox.printing.Scaling; | |
41 | import org.apache.pdfbox.rendering.PDFRenderer; | |
42 | import org.apache.pdfbox.rendering.PageDrawer; | |
43 | import org.apache.pdfbox.rendering.PageDrawerParameters; | |
44 | ||
45 | /** | |
46 | * PDF documents with transparency groups are sometimes printed slowly and in poor quality, see | |
47 | * <a href="https://issues.apache.org/jira/browse/PDFBOX-4123">PDFBOX-4123</a>. If the transparency | |
48 | * groups aren't really needed (e.g. for most labels), we can use a custom PDFRenderer / PageDrawer | |
49 | * that uses a custom DrawObject class which doesn't call showTransparencyGroup() but only | |
50 | * showForm(). | |
51 | * <p> | |
52 | * This OpaquePDFRenderer class object can be passed to the "long" constructor of | |
53 | * {@link PDFPrintable#PDFPrintable(org.apache.pdfbox.pdmodel.PDDocument, org.apache.pdfbox.printing.Scaling, boolean, float, boolean, org.apache.pdfbox.rendering.PDFRenderer)}. | |
54 | * | |
55 | * @author Tilman Hausherr | |
56 | */ | |
57 | public class OpaquePDFRenderer extends PDFRenderer | |
58 | { | |
59 | ||
60 | public static void main(String[] args) throws IOException, PrinterException | |
61 | { | |
62 | // PDF from the QZ Tray project, who reported this problem. | |
63 | InputStream is = new URL("https://github.com/qzind/tray/files/1749977/test.pdf").openStream(); | |
64 | PDDocument doc = PDDocument.load(is); | |
65 | is.close(); | |
66 | PDFRenderer renderer = new OpaquePDFRenderer(doc); | |
67 | Printable printable = new PDFPrintable(doc, Scaling.SCALE_TO_FIT, false, 0, true, renderer); | |
68 | PrinterJob job = PrinterJob.getPrinterJob(); | |
69 | job.setPrintService(PrintServiceLookup.lookupDefaultPrintService()); | |
70 | job.setPrintable(printable); | |
71 | if (job.printDialog()) | |
72 | { | |
73 | job.print(); | |
74 | } | |
75 | doc.close(); | |
76 | } | |
77 | ||
78 | public OpaquePDFRenderer(PDDocument document) | |
79 | { | |
80 | super(document); | |
81 | } | |
82 | ||
83 | @Override | |
84 | protected PageDrawer createPageDrawer(PageDrawerParameters parameters) throws IOException | |
85 | { | |
86 | return new OpaquePageDrawer(parameters); | |
87 | } | |
88 | ||
89 | private class OpaquePageDrawer extends PageDrawer | |
90 | { | |
91 | ||
92 | public OpaquePageDrawer(PageDrawerParameters parameters) throws IOException | |
93 | { | |
94 | super(parameters); | |
95 | addOperator(new OpaqueDrawObject()); | |
96 | } | |
97 | } | |
98 | ||
99 | // copied from org.apache.pdfbox.contentstream.operator.graphics.DrawObject() | |
100 | // but doesn't call showTransparencyGroup | |
101 | private static class OpaqueDrawObject extends GraphicsOperatorProcessor | |
102 | { | |
103 | ||
104 | private static final Log LOG = LogFactory.getLog(OpaqueDrawObject.class); | |
105 | ||
106 | @Override | |
107 | public void process(Operator operator, List<COSBase> operands) throws IOException | |
108 | { | |
109 | if (operands.isEmpty()) | |
110 | { | |
111 | throw new MissingOperandException(operator, operands); | |
112 | } | |
113 | COSBase base0 = operands.get(0); | |
114 | if (!(base0 instanceof COSName)) | |
115 | { | |
116 | return; | |
117 | } | |
118 | COSName objectName = (COSName) base0; | |
119 | PDXObject xobject = context.getResources().getXObject(objectName); | |
120 | ||
121 | if (xobject == null) | |
122 | { | |
123 | throw new MissingResourceException("Missing XObject: " + objectName.getName()); | |
124 | } | |
125 | else if (xobject instanceof PDImageXObject) | |
126 | { | |
127 | PDImageXObject image = (PDImageXObject) xobject; | |
128 | context.drawImage(image); | |
129 | } | |
130 | else if (xobject instanceof PDFormXObject) | |
131 | { | |
132 | try | |
133 | { | |
134 | context.increaseLevel(); | |
135 | if (context.getLevel() > 50) | |
136 | { | |
137 | LOG.error("recursion is too deep, skipping form XObject"); | |
138 | return; | |
139 | } | |
140 | context.showForm((PDFormXObject) xobject); | |
141 | } | |
142 | finally | |
143 | { | |
144 | context.decreaseLevel(); | |
145 | } | |
146 | } | |
147 | } | |
148 | ||
149 | @Override | |
150 | public String getName() | |
151 | { | |
152 | return OperatorName.DRAW_OBJECT; | |
153 | } | |
154 | } | |
155 | } |
20 | 20 | <parent> |
21 | 21 | <groupId>org.apache.pdfbox</groupId> |
22 | 22 | <artifactId>pdfbox-parent</artifactId> |
23 | <version>2.0.26</version> | |
23 | <version>2.0.27</version> | |
24 | 24 | <relativePath>../parent/pom.xml</relativePath> |
25 | 25 | </parent> |
26 | 26 |
491 | 491 | int charStringsOffset = charStringsEntry.getNumber(0).intValue(); |
492 | 492 | input.setPosition(charStringsOffset); |
493 | 493 | byte[][] charStringsIndex = readIndexData(input); |
494 | ||
494 | if (charStringsIndex == null) | |
495 | { | |
496 | throw new IOException("CharStringsIndex is missing"); | |
497 | } | |
498 | ||
495 | 499 | // charset |
496 | 500 | DictData.Entry charsetEntry = topDict.getEntry("charset"); |
497 | 501 | CFFCharset charset; |
273 | 273 | checkExpectedOperator((Operator) nextToken, "endcodespacerange", "codespacerange"); |
274 | 274 | break; |
275 | 275 | } |
276 | if (!(nextToken instanceof byte[])) | |
277 | { | |
278 | throw new IOException("start range missing"); | |
279 | } | |
276 | 280 | byte[] startRange = (byte[]) nextToken; |
277 | 281 | byte[] endRange = (byte[]) parseNextToken(cmapStream); |
278 | 282 | try |
296 | 300 | checkExpectedOperator((Operator) nextToken, "endbfchar", "bfchar"); |
297 | 301 | break; |
298 | 302 | } |
303 | if (!(nextToken instanceof byte[])) | |
304 | { | |
305 | throw new IOException("input code missing"); | |
306 | } | |
299 | 307 | byte[] inputCode = (byte[]) nextToken; |
300 | 308 | nextToken = parseNextToken(cmapStream); |
301 | 309 | if (nextToken instanceof byte[]) |
325 | 333 | { |
326 | 334 | checkExpectedOperator((Operator) nextToken, "endcidrange", "cidrange"); |
327 | 335 | break; |
336 | } | |
337 | if (!(nextToken instanceof byte[])) | |
338 | { | |
339 | throw new IOException("start range missing"); | |
328 | 340 | } |
329 | 341 | byte[] startCode = (byte[]) nextToken; |
330 | 342 | int start = createIntFromBytes(startCode); |
367 | 379 | checkExpectedOperator((Operator) nextToken, "endcidchar", "cidchar"); |
368 | 380 | break; |
369 | 381 | } |
382 | if (!(nextToken instanceof byte[])) | |
383 | { | |
384 | throw new IOException("start code missing"); | |
385 | } | |
370 | 386 | byte[] inputCode = (byte[]) nextToken; |
371 | 387 | int mappedCode = (Integer) parseNextToken(cmapStream); |
372 | 388 | int mappedCID = createIntFromBytes(inputCode); |
379 | 395 | for (int j = 0; j < cosCount.intValue(); j++) |
380 | 396 | { |
381 | 397 | Object nextToken = parseNextToken(cmapStream); |
382 | if (nextToken == null) | |
383 | { | |
384 | throw new IOException("start code missing"); | |
385 | } | |
386 | 398 | if (nextToken instanceof Operator) |
387 | 399 | { |
388 | 400 | checkExpectedOperator((Operator) nextToken, "endbfrange", "bfrange"); |
389 | 401 | break; |
390 | 402 | } |
403 | if (!(nextToken instanceof byte[])) | |
404 | { | |
405 | throw new IOException("start code missing"); | |
406 | } | |
391 | 407 | byte[] startCode = (byte[]) nextToken; |
392 | 408 | nextToken = parseNextToken(cmapStream); |
393 | if (nextToken == null) | |
394 | { | |
395 | throw new IOException("end code missing"); | |
396 | } | |
397 | 409 | if (nextToken instanceof Operator) |
398 | 410 | { |
399 | 411 | checkExpectedOperator((Operator) nextToken, "endbfrange", "bfrange"); |
400 | 412 | break; |
413 | } | |
414 | if (!(nextToken instanceof byte[])) | |
415 | { | |
416 | throw new IOException("end code missing"); | |
401 | 417 | } |
402 | 418 | byte[] endCode = (byte[]) nextToken; |
403 | 419 | int start = CMap.toInt(startCode, startCode.length); |
166 | 166 | } |
167 | 167 | if (size > pfbdata.length - pointer) |
168 | 168 | { |
169 | throw new IOException("PFB record size (" + size + | |
170 | ") doesn't fit in buffer, position: " + pointer + | |
171 | ", total length: " + pfbdata.length); | |
169 | throw new EOFException("attempted to read " + size + " bytes at position " + pointer + | |
170 | " into array of size " + pfbdata.length + ", but only space for " + | |
171 | (pfbdata.length - pointer) + " bytes left"); | |
172 | 172 | } |
173 | 173 | int got = in.read(pfbdata, pointer, size); |
174 | 174 | if (got < 0) |
665 | 665 | |
666 | 666 | private int getCharCode(int gid) |
667 | 667 | { |
668 | if (gid < 0 || gid >= glyphIdToCharacterCode.length) | |
668 | if (gid < 0 || glyphIdToCharacterCode == null || gid >= glyphIdToCharacterCode.length) | |
669 | 669 | { |
670 | 670 | return -1; |
671 | 671 | } |
38 | 38 | |
39 | 39 | private int cached = 0; |
40 | 40 | |
41 | private HorizontalMetricsTable hmt = null; | |
42 | ||
41 | 43 | /** |
42 | 44 | * Don't even bother to cache huge fonts. |
43 | 45 | */ |
74 | 76 | |
75 | 77 | // we don't actually read the complete table here because it can contain tens of thousands of glyphs |
76 | 78 | this.data = data; |
79 | ||
80 | // PDFBOX-5460: read hmtx table early to avoid deadlock if getGlyph() locks "data" | |
81 | // and then locks TrueTypeFont to read this table, while another thread | |
82 | // locks TrueTypeFont and then tries to lock "data" | |
83 | hmt = font.getHorizontalMetrics(); | |
84 | ||
77 | 85 | initialized = true; |
78 | 86 | } |
79 | 87 | |
206 | 214 | private GlyphData getGlyphData(int gid) throws IOException |
207 | 215 | { |
208 | 216 | GlyphData glyph = new GlyphData(); |
209 | HorizontalMetricsTable hmt = font.getHorizontalMetrics(); | |
210 | 217 | int leftSideBearing = hmt == null ? 0 : hmt.getLeftSideBearing(gid); |
211 | 218 | glyph.initData(this, data, leftSideBearing); |
212 | 219 | // resolve composite glyph |
743 | 743 | |
744 | 744 | // RD |
745 | 745 | Token charstring = read(Token.CHARSTRING); |
746 | font.subrs.set(index.intValue(), decrypt(charstring.getData(), CHARSTRING_KEY, lenIV)); | |
746 | int j = index.intValue(); | |
747 | if (j < font.subrs.size()) | |
748 | { | |
749 | font.subrs.set(j, decrypt(charstring.getData(), CHARSTRING_KEY, lenIV)); | |
750 | } | |
747 | 751 | readPut(); |
748 | 752 | } |
749 | 753 | readDef(); |
32 | 32 | |
33 | 33 | <groupId>org.apache.pdfbox</groupId> |
34 | 34 | <artifactId>pdfbox-parent</artifactId> |
35 | <version>2.0.26</version> | |
35 | <version>2.0.27</version> | |
36 | 36 | <packaging>pom</packaging> |
37 | 37 | |
38 | 38 | <name>PDFBox parent</name> |
202 | 202 | <plugin> |
203 | 203 | <groupId>org.owasp</groupId> |
204 | 204 | <artifactId>dependency-check-maven</artifactId> |
205 | <version>7.0.4</version> | |
205 | <version>7.1.2</version> | |
206 | 206 | <configuration> |
207 | 207 | <failBuildOnAnyVulnerability>true</failBuildOnAnyVulnerability> |
208 | 208 | <!-- https://github.com/jeremylong/DependencyCheck/issues/1574 --> |
351 | 351 | <plugin> |
352 | 352 | <groupId>org.apache.rat</groupId> |
353 | 353 | <artifactId>apache-rat-plugin</artifactId> |
354 | <version>0.15</version> | |
354 | 355 | <configuration> |
355 | 356 | <excludes> |
356 | 357 | <exclude>release.properties</exclude> |
538 | 539 | </developers> |
539 | 540 | |
540 | 541 | <scm> |
541 | <connection>scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/2.0.26/pdfbox-parent</connection> | |
542 | <developerConnection>scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/2.0.26/pdfbox-parent</developerConnection> | |
543 | <url>http://svn.apache.org/viewvc/maven/pom/tags/2.0.26/pdfbox-parent</url> | |
542 | <connection>scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/2.0.27/pdfbox-parent</connection> | |
543 | <developerConnection>scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/2.0.27/pdfbox-parent</developerConnection> | |
544 | <url>http://svn.apache.org/viewvc/maven/pom/tags/2.0.27/pdfbox-parent</url> | |
544 | 545 | </scm> |
545 | 546 | </project> |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.26</version> | |
25 | <version>2.0.27</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 | |
78 | 78 | <dependency> |
79 | 79 | <groupId>org.mockito</groupId> |
80 | 80 | <artifactId>mockito-core</artifactId> |
81 | <version>4.4.0</version> | |
81 | <version>4.8.0</version> | |
82 | 82 | <scope>test</scope> |
83 | 83 | </dependency> |
84 | 84 | <!-- For legal reasons (incompatible license), these two dependencies below |
715 | 715 | <sha512>37c73b41d1e00d66717c3715e6c45724c3f163d3d5b045c67e90c3713746d39eef96b4f0e6f368d0679d4c73d02ca01cfe5141d8a526e46ea15a4579ea1e75a2</sha512> |
716 | 716 | </configuration> |
717 | 717 | </execution> |
718 | <execution> | |
719 | <id>PDFBOX-5484</id> | |
720 | <phase>generate-test-resources</phase> | |
721 | <goals> | |
722 | <goal>wget</goal> | |
723 | </goals> | |
724 | <configuration> | |
725 | <url>https://issues.apache.org/jira/secure/attachment/13047577/PDFBOX-5484.ttf</url> | |
726 | <outputDirectory>${project.build.directory}/fonts</outputDirectory> | |
727 | <outputFileName>PDFBOX-5484.ttf</outputFileName> | |
728 | <sha512>7c3d8bbc18654315d6341a277dcd5c66218b95c43baf190b6e32f77817d17bab421ef76f2c904b46c97f84c49b00d58525449cff970897010534d6aa2812a4e2</sha512> | |
729 | </configuration> | |
730 | </execution> | |
718 | 731 | </executions> |
719 | 732 | </plugin> |
720 | 733 | </plugins> |
681 | 681 | } |
682 | 682 | else if (obj instanceof COSArray) |
683 | 683 | { |
684 | LOG.error("Nested arrays are not allowed in an array for TJ operation:" + obj); | |
684 | LOG.error("Nested arrays are not allowed in an array for TJ operation: " + obj); | |
685 | 685 | } |
686 | 686 | else |
687 | 687 | { |
688 | throw new IOException("Unknown type " + obj.getClass().getSimpleName() | |
689 | + " in array for TJ operation:" + obj); | |
688 | LOG.error("Unknown type " + obj.getClass().getSimpleName() | |
689 | + " in array for TJ operation: " + obj); | |
690 | 690 | } |
691 | 691 | } |
692 | 692 | } |
21 | 21 | import java.util.Arrays; |
22 | 22 | import java.util.Calendar; |
23 | 23 | import java.util.Collection; |
24 | import java.util.LinkedHashMap; | |
24 | 25 | import java.util.List; |
25 | 26 | import java.util.Map; |
26 | 27 | import java.util.Set; |
39 | 40 | public class COSDictionary extends COSBase implements COSUpdateInfo |
40 | 41 | { |
41 | 42 | private static final String PATH_SEPARATOR = "/"; |
43 | private static final int MAP_THRESHOLD = 1000; | |
42 | 44 | private boolean needToBeUpdated; |
43 | 45 | |
44 | 46 | /** |
61 | 63 | */ |
62 | 64 | public COSDictionary(COSDictionary dict) |
63 | 65 | { |
64 | items.putAll(dict.items); | |
66 | addAll(dict); | |
65 | 67 | } |
66 | 68 | |
67 | 69 | /** |
213 | 215 | } |
214 | 216 | else |
215 | 217 | { |
218 | if (items instanceof SmallMap && items.size() >= MAP_THRESHOLD) | |
219 | { | |
220 | items = new LinkedHashMap<COSName, COSBase>(items); | |
221 | } | |
216 | 222 | items.put(key, value); |
217 | 223 | } |
218 | 224 | } |
1437 | 1443 | * This will add all of the dictionaries keys/values to this dictionary. Existing key/value pairs will be |
1438 | 1444 | * overwritten. |
1439 | 1445 | * |
1440 | * @param dic The dictionaries to get the key/value pairs from. | |
1441 | */ | |
1442 | public void addAll(COSDictionary dic) | |
1443 | { | |
1444 | for (Map.Entry<COSName, COSBase> entry : dic.entrySet()) | |
1445 | { | |
1446 | setItem(entry.getKey(), entry.getValue()); | |
1447 | } | |
1446 | * @param dict The dictionaries to get the key/value pairs from. | |
1447 | */ | |
1448 | public void addAll(COSDictionary dict) | |
1449 | { | |
1450 | if (items instanceof SmallMap && items.size() + dict.items.size() >= MAP_THRESHOLD) | |
1451 | { | |
1452 | items = new LinkedHashMap<COSName, COSBase>(items); | |
1453 | } | |
1454 | items.putAll(dict.items); | |
1448 | 1455 | } |
1449 | 1456 | |
1450 | 1457 | /** |
15 | 15 | */ |
16 | 16 | package org.apache.pdfbox.cos; |
17 | 17 | |
18 | import java.util.Calendar; | |
19 | ||
20 | import org.apache.pdfbox.pdmodel.common.COSObjectable; | |
18 | import java.util.Collections; | |
21 | 19 | |
22 | 20 | /** |
23 | 21 | * An unmodifiable COSDictionary. |
32 | 30 | UnmodifiableCOSDictionary(COSDictionary dict) |
33 | 31 | { |
34 | 32 | super(); |
35 | items = dict.items; | |
36 | } | |
37 | ||
38 | /** | |
39 | * {@inheritDoc} | |
40 | */ | |
41 | @Override | |
42 | public void clear() | |
43 | { | |
44 | throw new UnsupportedOperationException(); | |
45 | } | |
46 | ||
47 | /** | |
48 | * {@inheritDoc} | |
49 | */ | |
50 | @Override | |
51 | public void setItem(COSName key, COSBase value) | |
52 | { | |
53 | throw new UnsupportedOperationException(); | |
54 | } | |
55 | ||
56 | /** | |
57 | * {@inheritDoc} | |
58 | */ | |
59 | @Override | |
60 | public void setItem(COSName key, COSObjectable value) | |
61 | { | |
62 | throw new UnsupportedOperationException(); | |
63 | } | |
64 | ||
65 | /** | |
66 | * {@inheritDoc} | |
67 | */ | |
68 | @Override | |
69 | public void setItem(String key, COSObjectable value) | |
70 | { | |
71 | throw new UnsupportedOperationException(); | |
72 | } | |
73 | ||
74 | /** | |
75 | * {@inheritDoc} | |
76 | */ | |
77 | @Override | |
78 | public void setBoolean(String key, boolean value) | |
79 | { | |
80 | throw new UnsupportedOperationException(); | |
81 | } | |
82 | ||
83 | /** | |
84 | * {@inheritDoc} | |
85 | */ | |
86 | @Override | |
87 | public void setBoolean(COSName key, boolean value) | |
88 | { | |
89 | throw new UnsupportedOperationException(); | |
90 | } | |
91 | ||
92 | /** | |
93 | * {@inheritDoc} | |
94 | */ | |
95 | @Override | |
96 | public void setItem(String key, COSBase value) | |
97 | { | |
98 | throw new UnsupportedOperationException(); | |
99 | } | |
100 | ||
101 | /** | |
102 | * {@inheritDoc} | |
103 | */ | |
104 | @Override | |
105 | public void setName(String key, String value) | |
106 | { | |
107 | throw new UnsupportedOperationException(); | |
108 | } | |
109 | ||
110 | /** | |
111 | * {@inheritDoc} | |
112 | */ | |
113 | @Override | |
114 | public void setName(COSName key, String value) | |
115 | { | |
116 | throw new UnsupportedOperationException(); | |
117 | } | |
118 | ||
119 | /** | |
120 | * {@inheritDoc} | |
121 | */ | |
122 | @Override | |
123 | public void setDate(String key, Calendar date) | |
124 | { | |
125 | throw new UnsupportedOperationException(); | |
126 | } | |
127 | ||
128 | /** | |
129 | * {@inheritDoc} | |
130 | */ | |
131 | @Override | |
132 | public void setDate(COSName key, Calendar date) | |
133 | { | |
134 | throw new UnsupportedOperationException(); | |
135 | } | |
136 | ||
137 | /** | |
138 | * {@inheritDoc} | |
139 | */ | |
140 | @Override | |
141 | public void setEmbeddedDate(String embedded, String key, Calendar date) | |
142 | { | |
143 | throw new UnsupportedOperationException(); | |
144 | } | |
145 | ||
146 | /** | |
147 | * {@inheritDoc} | |
148 | */ | |
149 | @Override | |
150 | public void setEmbeddedDate(String embedded, COSName key, Calendar date) | |
151 | { | |
152 | throw new UnsupportedOperationException(); | |
153 | } | |
154 | ||
155 | /** | |
156 | * {@inheritDoc} | |
157 | */ | |
158 | @Override | |
159 | public void setString(String key, String value) | |
160 | { | |
161 | throw new UnsupportedOperationException(); | |
162 | } | |
163 | ||
164 | /** | |
165 | * {@inheritDoc} | |
166 | */ | |
167 | @Override | |
168 | public void setString(COSName key, String value) | |
169 | { | |
170 | throw new UnsupportedOperationException(); | |
171 | } | |
172 | ||
173 | /** | |
174 | * {@inheritDoc} | |
175 | */ | |
176 | @Override | |
177 | public void setEmbeddedString(String embedded, String key, String value) | |
178 | { | |
179 | throw new UnsupportedOperationException(); | |
180 | } | |
181 | ||
182 | /** | |
183 | * {@inheritDoc} | |
184 | */ | |
185 | @Override | |
186 | public void setEmbeddedString(String embedded, COSName key, String value) | |
187 | { | |
188 | throw new UnsupportedOperationException(); | |
189 | } | |
190 | ||
191 | /** | |
192 | * {@inheritDoc} | |
193 | */ | |
194 | @Override | |
195 | public void setInt(String key, int value) | |
196 | { | |
197 | throw new UnsupportedOperationException(); | |
198 | } | |
199 | ||
200 | /** | |
201 | * {@inheritDoc} | |
202 | */ | |
203 | @Override | |
204 | public void setInt(COSName key, int value) | |
205 | { | |
206 | throw new UnsupportedOperationException(); | |
207 | } | |
208 | ||
209 | /** | |
210 | * {@inheritDoc} | |
211 | */ | |
212 | @Override | |
213 | public void setLong(String key, long value) | |
214 | { | |
215 | throw new UnsupportedOperationException(); | |
216 | } | |
217 | ||
218 | /** | |
219 | * {@inheritDoc} | |
220 | */ | |
221 | @Override | |
222 | public void setLong(COSName key, long value) | |
223 | { | |
224 | throw new UnsupportedOperationException(); | |
225 | } | |
226 | ||
227 | /** | |
228 | * {@inheritDoc} | |
229 | */ | |
230 | @Override | |
231 | public void setEmbeddedInt(String embeddedDictionary, String key, int value) | |
232 | { | |
233 | throw new UnsupportedOperationException(); | |
234 | } | |
235 | ||
236 | /** | |
237 | * {@inheritDoc} | |
238 | */ | |
239 | @Override | |
240 | public void setEmbeddedInt(String embeddedDictionary, COSName key, int value) | |
241 | { | |
242 | throw new UnsupportedOperationException(); | |
243 | } | |
244 | ||
245 | /** | |
246 | * {@inheritDoc} | |
247 | */ | |
248 | @Override | |
249 | public void setFloat(String key, float value) | |
250 | { | |
251 | throw new UnsupportedOperationException(); | |
252 | } | |
253 | ||
254 | /** | |
255 | * {@inheritDoc} | |
256 | */ | |
257 | @Override | |
258 | public void setFloat(COSName key, float value) | |
259 | { | |
260 | throw new UnsupportedOperationException(); | |
261 | } | |
262 | ||
263 | /** | |
264 | * {@inheritDoc} | |
265 | */ | |
266 | @Override | |
267 | public void removeItem(COSName key) | |
268 | { | |
269 | throw new UnsupportedOperationException(); | |
270 | } | |
271 | ||
272 | /** | |
273 | * {@inheritDoc} | |
274 | */ | |
275 | @Override | |
276 | public void addAll(COSDictionary dic) | |
277 | { | |
278 | throw new UnsupportedOperationException(); | |
33 | items = Collections.unmodifiableMap(dict.items); | |
279 | 34 | } |
280 | 35 | |
281 | 36 | /** |
291 | 46 | * {@inheritDoc} |
292 | 47 | */ |
293 | 48 | @Override |
294 | public void setFlag(COSName field, int bitFlag, boolean value) | |
295 | { | |
296 | throw new UnsupportedOperationException(); | |
297 | } | |
298 | ||
299 | /** | |
300 | * {@inheritDoc} | |
301 | */ | |
302 | @Override | |
303 | 49 | public void setNeedToBeUpdated(boolean flag) |
304 | 50 | { |
305 | 51 | throw new UnsupportedOperationException(); |
30 | 30 | import javax.imageio.metadata.IIOMetadata; |
31 | 31 | import javax.imageio.metadata.IIOMetadataNode; |
32 | 32 | import javax.imageio.stream.ImageInputStream; |
33 | import javax.xml.xpath.XPathExpression; | |
34 | import javax.xml.xpath.XPathExpressionException; | |
35 | import javax.xml.xpath.XPathFactory; | |
33 | 36 | |
34 | 37 | import org.apache.commons.logging.Log; |
35 | 38 | import org.apache.commons.logging.LogFactory; |
49 | 52 | |
50 | 53 | private static final int POS_TRANSFORM = 11; |
51 | 54 | private static final String ADOBE = "Adobe"; |
55 | ||
56 | private static XPathExpression xPathExpression; | |
57 | ||
58 | static | |
59 | { | |
60 | try | |
61 | { | |
62 | xPathExpression = XPathFactory.newInstance().newXPath().compile("Chroma/ColorSpaceType/@name"); | |
63 | } | |
64 | catch (XPathExpressionException ex) | |
65 | { | |
66 | // shouldn't happen unless you changed the expression | |
67 | LOG.error(ex.getMessage(), ex); | |
68 | } | |
69 | } | |
52 | 70 | |
53 | 71 | @Override |
54 | 72 | public DecodeResult decode(InputStream encoded, OutputStream decoded, COSDictionary |
179 | 197 | Element adobe = (Element) app14AdobeNodeList.item(0); |
180 | 198 | return Integer.parseInt(adobe.getAttribute("transform")); |
181 | 199 | } |
200 | ||
201 | // PDFBOX-5488: plan B: use ColorSpaceType from the other metadata tree. | |
202 | try | |
203 | { | |
204 | String value = xPathExpression.evaluate(metadata.getAsTree("javax_imageio_1.0")); | |
205 | if ("YCbCr".equals(value)) | |
206 | { | |
207 | return 1; | |
208 | } | |
209 | if ("YCCK".equals(value)) | |
210 | { | |
211 | return 2; | |
212 | } | |
213 | } | |
214 | catch (XPathExpressionException ex) | |
215 | { | |
216 | return 0; | |
217 | } | |
182 | 218 | return 0; |
183 | 219 | } |
184 | 220 | |
201 | 237 | // match |
202 | 238 | a = 0; |
203 | 239 | long afterAdobePos = iis.getStreamPosition(); |
204 | iis.seek(iis.getStreamPosition() - 9); | |
240 | iis.seek(afterAdobePos - 9); | |
205 | 241 | int tag = iis.readUnsignedShort(); |
206 | 242 | if (tag != 0xFFEE) |
207 | 243 | { |
125 | 125 | COSBase objAtIndex = array.getObject(index); |
126 | 126 | if (objAtIndex instanceof COSDictionary) |
127 | 127 | { |
128 | return (COSDictionary)array.getObject(index); | |
128 | return (COSDictionary) objAtIndex; | |
129 | 129 | } |
130 | 130 | } |
131 | 131 | } |
286 | 286 | for (Map.Entry<COSName, COSBase> entry : orgDict.entrySet()) |
287 | 287 | { |
288 | 288 | COSName key = entry.getKey(); |
289 | if (!filter.contains(key.getName())) | |
289 | if (filter.contains(key.getName())) | |
290 | 290 | { |
291 | continue; | |
291 | targetDict.setItem(key, cloner.cloneForNewDocument(entry.getValue())); | |
292 | 292 | } |
293 | targetDict.setItem(key, cloner.cloneForNewDocument(entry.getValue())); | |
294 | 293 | } |
295 | 294 | } |
296 | 295 |
23 | 23 | import java.io.OutputStream; |
24 | 24 | import java.math.BigDecimal; |
25 | 25 | import java.util.ArrayList; |
26 | import java.util.Collections; | |
26 | 27 | import java.util.HashMap; |
27 | 28 | import java.util.HashSet; |
28 | 29 | import java.util.List; |
96 | 97 | /** |
97 | 98 | * This will add overlays to a document. |
98 | 99 | * |
99 | * @param specificPageOverlayFile Optional map of overlay files for specific pages. The page | |
100 | * numbers are 1-based. The map must be empty (but not null) if no specific mappings are used. | |
100 | * @param specificPageOverlayFile Optional map of overlay files of which the first page will be | |
101 | * used for specific pages of the input document. The page numbers are 1-based. The map must be | |
102 | * empty (but not null) if no specific mappings are used. | |
101 | 103 | * |
102 | 104 | * @return The modified input PDF document, which has to be saved and closed by the caller. If |
103 | 105 | * the input document was passed by {@link #setInputPDF(PDDocument) setInputPDF(PDDocument)} |
107 | 109 | */ |
108 | 110 | public PDDocument overlay(Map<Integer, String> specificPageOverlayFile) throws IOException |
109 | 111 | { |
110 | Map<String, PDDocument> loadedDocuments = new HashMap<String, PDDocument>(); | |
111 | Map<PDDocument, LayoutPage> layouts = new HashMap<PDDocument, LayoutPage>(); | |
112 | Map<String, LayoutPage> layouts = new HashMap<String, LayoutPage>(); | |
113 | String path; | |
112 | 114 | loadPDFs(); |
113 | 115 | for (Map.Entry<Integer, String> e : specificPageOverlayFile.entrySet()) |
114 | 116 | { |
115 | PDDocument doc = loadedDocuments.get(e.getValue()); | |
116 | if (doc == null) | |
117 | path = e.getValue(); | |
118 | LayoutPage layoutPage = layouts.get(path); | |
119 | if (layoutPage == null) | |
117 | 120 | { |
118 | doc = loadPDF(e.getValue()); | |
119 | loadedDocuments.put(e.getValue(), doc); | |
120 | layouts.put(doc, getLayoutPage(doc)); | |
121 | PDDocument doc = loadPDF(path); | |
122 | layouts.put(path, getLayoutPage(doc)); | |
121 | 123 | openDocuments.add(doc); |
122 | 124 | } |
123 | specificPageOverlayPage.put(e.getKey(), layouts.get(doc)); | |
125 | specificPageOverlayPage.put(e.getKey(), layoutPage); | |
124 | 126 | } |
125 | 127 | processPages(inputPDFDocument); |
126 | 128 | return inputPDFDocument; |
284 | 286 | } |
285 | 287 | } |
286 | 288 | |
289 | /** | |
290 | * Create a LayoutPage object from the first page of the given document. | |
291 | * | |
292 | * @param doc | |
293 | * @return | |
294 | * @throws IOException | |
295 | */ | |
287 | 296 | private LayoutPage getLayoutPage(PDDocument doc) throws IOException |
288 | 297 | { |
289 | 298 | return createLayoutPage(doc.getPage(0)); |
290 | 299 | } |
291 | 300 | |
301 | /** | |
302 | * Create a LayoutPage object from given PDPage object. | |
303 | * | |
304 | * @return | |
305 | * @throws IOException | |
306 | */ | |
292 | 307 | private LayoutPage createLayoutPage(PDPage page) throws IOException |
293 | 308 | { |
294 | 309 | COSBase contents = page.getCOSObject().getDictionaryObject(COSName.CONTENTS); |
333 | 348 | // get the content streams as a list |
334 | 349 | private List<COSStream> createContentStreamList(COSBase contents) throws IOException |
335 | 350 | { |
351 | if (contents == null) | |
352 | { | |
353 | return Collections.emptyList(); | |
354 | } | |
355 | if (contents instanceof COSStream) | |
356 | { | |
357 | return Collections.singletonList((COSStream) contents); | |
358 | } | |
359 | ||
336 | 360 | List<COSStream> contentStreams = new ArrayList<COSStream>(); |
337 | if (contents == null) | |
338 | { | |
339 | return contentStreams; | |
340 | } | |
341 | else if (contents instanceof COSStream) | |
342 | { | |
343 | contentStreams.add((COSStream) contents); | |
344 | } | |
345 | else if (contents instanceof COSArray) | |
361 | if (contents instanceof COSArray) | |
346 | 362 | { |
347 | 363 | for (COSBase item : (COSArray) contents) |
348 | 364 | { |
23 | 23 | import java.util.List; |
24 | 24 | import java.util.Map; |
25 | 25 | import java.util.Set; |
26 | import org.apache.commons.logging.Log; | |
27 | import org.apache.commons.logging.LogFactory; | |
26 | 28 | import org.apache.pdfbox.cos.COSArray; |
27 | 29 | import org.apache.pdfbox.cos.COSBase; |
28 | 30 | import org.apache.pdfbox.cos.COSDictionary; |
42 | 44 | */ |
43 | 45 | public class PDFCloneUtility |
44 | 46 | { |
47 | private static final Log LOG = LogFactory.getLog(PDFCloneUtility.class); | |
48 | ||
45 | 49 | private final PDDocument destination; |
46 | 50 | private final Map<Object,COSBase> clonedVersion = new HashMap<Object,COSBase>(); |
47 | 51 | private final Set<COSBase> clonedValues = new HashSet<COSBase>(); |
114 | 118 | for( int i=0; i<array.size(); i++ ) |
115 | 119 | { |
116 | 120 | COSBase value = array.get(i); |
117 | checkForRecursion(base, value); | |
118 | newArray.add(cloneForNewDocument(value)); | |
121 | if (hasSelfReference(base, value)) | |
122 | { | |
123 | newArray.add(newArray); | |
124 | } | |
125 | else | |
126 | { | |
127 | newArray.add(cloneForNewDocument(value)); | |
128 | } | |
119 | 129 | } |
120 | 130 | retval = newArray; |
121 | 131 | } |
132 | 142 | for( Map.Entry<COSName, COSBase> entry : originalStream.entrySet() ) |
133 | 143 | { |
134 | 144 | COSBase value = entry.getValue(); |
135 | checkForRecursion(base, value); | |
136 | stream.setItem(entry.getKey(), cloneForNewDocument(value)); | |
145 | if (hasSelfReference(base, value)) | |
146 | { | |
147 | stream.setItem(entry.getKey(), stream); | |
148 | } | |
149 | else | |
150 | { | |
151 | stream.setItem(entry.getKey(), cloneForNewDocument(value)); | |
152 | } | |
137 | 153 | } |
138 | 154 | retval = stream; |
139 | 155 | } |
145 | 161 | for( Map.Entry<COSName, COSBase> entry : dic.entrySet() ) |
146 | 162 | { |
147 | 163 | COSBase value = entry.getValue(); |
148 | checkForRecursion(base, value); | |
149 | ((COSDictionary) retval).setItem(entry.getKey(), cloneForNewDocument(value)); | |
164 | if (hasSelfReference(base, value)) | |
165 | { | |
166 | ((COSDictionary) retval).setItem(entry.getKey(), retval); | |
167 | } | |
168 | else | |
169 | { | |
170 | ((COSDictionary) retval).setItem(entry.getKey(), cloneForNewDocument(value)); | |
171 | } | |
150 | 172 | } |
151 | 173 | } |
152 | 174 | else |
264 | 286 | * |
265 | 287 | * @param parent COSArray or COSDictionary |
266 | 288 | * @param value an element |
267 | * @throws IOException if value is an object reference to the parent | |
268 | 289 | */ |
269 | private void checkForRecursion(Object parent, COSBase value) throws IOException | |
290 | private boolean hasSelfReference(Object parent, COSBase value) | |
270 | 291 | { |
271 | 292 | if (value instanceof COSObject) |
272 | 293 | { |
273 | 294 | COSBase actual = ((COSObject) value).getObject(); |
274 | 295 | if (actual == parent) |
275 | 296 | { |
276 | throw new IOException("Loop within object " + value); | |
297 | COSObject cosObj = ((COSObject) value); | |
298 | LOG.warn(parent.getClass().getSimpleName() + " object has a reference to itself: " + | |
299 | cosObj.getObjectNumber() + " " + cosObj.getGenerationNumber() + " R"); | |
300 | return true; | |
277 | 301 | } |
278 | 302 | } |
303 | return false; | |
279 | 304 | } |
280 | 305 | } |
49 | 49 | import org.apache.pdfbox.pdmodel.PDDocumentNameDestinationDictionary; |
50 | 50 | import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; |
51 | 51 | import org.apache.pdfbox.pdmodel.PDPage; |
52 | import org.apache.pdfbox.pdmodel.PDPageTree; | |
52 | 53 | import org.apache.pdfbox.pdmodel.PDResources; |
53 | 54 | import org.apache.pdfbox.pdmodel.PDStructureElementNameTreeNode; |
54 | 55 | import org.apache.pdfbox.pdmodel.PageMode; |
357 | 358 | { |
358 | 359 | destination = new PDDocument(memUsageSetting); |
359 | 360 | PDFCloneUtility cloner = new PDFCloneUtility(destination); |
361 | PDPageTree destinationPageTree = destination.getPages(); // cache PageTree | |
360 | 362 | |
361 | 363 | for (Object sourceObject : sources) |
362 | 364 | { |
388 | 390 | { |
389 | 391 | newPage.setResources(new PDResources()); |
390 | 392 | } |
391 | destination.addPage(newPage); | |
393 | destinationPageTree.add(newPage); | |
392 | 394 | } |
393 | 395 | } |
394 | 396 | finally |
794 | 796 | |
795 | 797 | Map<COSDictionary, COSDictionary> objMapping = new HashMap<COSDictionary, COSDictionary>(); |
796 | 798 | int pageIndex = 0; |
799 | PDPageTree destinationPageTree = destination.getPages(); // cache PageTree | |
797 | 800 | for (PDPage page : srcCatalog.getPages()) |
798 | 801 | { |
799 | 802 | PDPage newPage = new PDPage((COSDictionary) cloner.cloneForNewDocument(page.getCOSObject())); |
833 | 836 | } |
834 | 837 | // TODO update mapping for XObjects |
835 | 838 | } |
836 | destination.addPage(newPage); | |
839 | destinationPageTree.add(newPage); | |
837 | 840 | |
838 | 841 | if (pageIndex == pageIndexOpenActionDest) |
839 | 842 | { |
175 | 175 | long objNumber = ((COSInteger) value).longValue(); |
176 | 176 | if (objNumber <= 0) |
177 | 177 | { |
178 | LOG.error("invalid object number value =" + objNumber + " at offset " + numOffset); | |
178 | LOG.warn("invalid object number value =" + objNumber + " at offset " + numOffset); | |
179 | 179 | return COSNull.NULL; |
180 | 180 | } |
181 | 181 | int genNumber = ((COSInteger) generationNumber).intValue(); |
777 | 777 | String string; |
778 | 778 | if (isValidUTF8(bytes)) |
779 | 779 | { |
780 | string = new String(buffer.toByteArray(), Charsets.UTF_8); | |
780 | string = new String(bytes, Charsets.UTF_8); | |
781 | 781 | } |
782 | 782 | else |
783 | 783 | { |
784 | 784 | // some malformed PDFs don't use UTF-8 see PDFBOX-3347 |
785 | string = new String(buffer.toByteArray(), Charsets.WINDOWS_1252); | |
785 | string = new String(bytes, Charsets.WINDOWS_1252); | |
786 | 786 | } |
787 | 787 | return COSName.getPDFName(string); |
788 | 788 | } |
1103 | 1103 | { |
1104 | 1104 | if (seqSource.isEOF()) |
1105 | 1105 | { |
1106 | throw new IOException( "Error: End-of-File, expected line"); | |
1106 | throw new IOException( "Error: End-of-File, expected line at offset " + | |
1107 | seqSource.getPosition()); | |
1107 | 1108 | } |
1108 | 1109 | |
1109 | 1110 | StringBuilder buffer = new StringBuilder( 11 ); |
2472 | 2472 | { |
2473 | 2473 | return false; |
2474 | 2474 | } |
2475 | if (!dictionary.containsKey(COSName.MOD_DATE) && !dictionary.containsKey(COSName.TITLE) | |
2476 | && !dictionary.containsKey(COSName.AUTHOR) | |
2477 | && !dictionary.containsKey(COSName.SUBJECT) | |
2478 | && !dictionary.containsKey(COSName.KEYWORDS) | |
2479 | && !dictionary.containsKey(COSName.CREATOR) | |
2480 | && !dictionary.containsKey(COSName.PRODUCER) | |
2481 | && !dictionary.containsKey(COSName.CREATION_DATE)) | |
2482 | { | |
2483 | return false; | |
2484 | } | |
2485 | return true; | |
2475 | return dictionary.containsKey(COSName.MOD_DATE) || dictionary.containsKey(COSName.TITLE) | |
2476 | || dictionary.containsKey(COSName.AUTHOR) | |
2477 | || dictionary.containsKey(COSName.SUBJECT) | |
2478 | || dictionary.containsKey(COSName.KEYWORDS) | |
2479 | || dictionary.containsKey(COSName.CREATOR) | |
2480 | || dictionary.containsKey(COSName.PRODUCER) | |
2481 | || dictionary.containsKey(COSName.CREATION_DATE); | |
2486 | 2482 | } |
2487 | 2483 | |
2488 | 2484 | /** |
94 | 94 | * Constructor. |
95 | 95 | * |
96 | 96 | * @param bytes the bytes to parse. |
97 | * @throws IOException If there is an error initializing the stream. | |
98 | */ | |
99 | public PDFStreamParser(byte[] bytes) throws IOException | |
97 | */ | |
98 | public PDFStreamParser(byte[] bytes) | |
100 | 99 | { |
101 | 100 | super(new RandomAccessSource(new RandomAccessBuffer(bytes))); |
102 | 101 | } |
403 | 403 | { |
404 | 404 | acroForm.getCOSObject().setItem(COSName.FIELDS, new COSArray()); |
405 | 405 | } |
406 | PDAnnotationWidget firstWidget; | |
406 | 407 | if (signatureField == null) |
407 | 408 | { |
408 | 409 | signatureField = new PDSignatureField(acroForm); |
409 | 410 | // append the signature object |
410 | 411 | signatureField.setValue(sigObject); |
412 | firstWidget = signatureField.getWidgets().get(0); | |
411 | 413 | // backward linking |
412 | signatureField.getWidgets().get(0).setPage(page); | |
414 | firstWidget.setPage(page); | |
413 | 415 | } |
414 | 416 | else |
415 | 417 | { |
418 | firstWidget = signatureField.getWidgets().get(0); | |
416 | 419 | sigObject.getCOSObject().setNeedToBeUpdated(true); |
417 | 420 | } |
418 | 421 | |
422 | 425 | // to conform PDF/A-1 requirement: |
423 | 426 | // The /F key's Print flag bit shall be set to 1 and |
424 | 427 | // its Hidden, Invisible and NoView flag bits shall be set to 0 |
425 | signatureField.getWidgets().get(0).setPrinted(true); | |
428 | firstWidget.setPrinted(true); | |
426 | 429 | // This may be troublesome if several form fields are signed, |
427 | 430 | // see thread from PDFBox users mailing list 17.2.2021 - 19.2.2021 |
428 | 431 | // https://mail-archives.apache.org/mod_mbox/pdfbox-users/202102.mbox/thread |
450 | 453 | // Distinction of case for visual and non-visual signature |
451 | 454 | if (visualSignature == null) |
452 | 455 | { |
453 | prepareNonVisibleSignature(signatureField); | |
456 | prepareNonVisibleSignature(firstWidget); | |
454 | 457 | return; |
455 | 458 | } |
456 | 459 | |
457 | prepareVisibleSignature(signatureField, acroForm, visualSignature); | |
460 | prepareVisibleSignature(firstWidget, acroForm, visualSignature); | |
458 | 461 | |
459 | 462 | // Create Annotation / Field for signature |
460 | 463 | List<PDAnnotation> annotations = page.getAnnotations(); |
466 | 469 | |
467 | 470 | // Get the annotations of the page and append the signature-annotation to it |
468 | 471 | // take care that page and acroforms do not share the same array (if so, we don't need to add it twice) |
469 | if (!(annotations instanceof COSArrayList && | |
472 | if (!(checkFields && | |
473 | annotations instanceof COSArrayList && | |
470 | 474 | acroFormFields instanceof COSArrayList && |
471 | ((COSArrayList<PDAnnotation>) annotations).toList(). | |
472 | equals(((COSArrayList<PDField>) acroFormFields).toList()) && | |
473 | checkFields)) | |
474 | { | |
475 | PDAnnotationWidget widget = signatureField.getWidgets().get(0); | |
475 | ((COSArrayList) annotations).toList(). | |
476 | equals(((COSArrayList) acroFormFields).toList()))) | |
477 | { | |
476 | 478 | // use check to prevent the annotation widget from appearing twice |
477 | if (checkSignatureAnnotation(annotations, widget)) | |
478 | { | |
479 | widget.getCOSObject().setNeedToBeUpdated(true); | |
479 | if (checkSignatureAnnotation(annotations, firstWidget)) | |
480 | { | |
481 | firstWidget.getCOSObject().setNeedToBeUpdated(true); | |
480 | 482 | } |
481 | 483 | else |
482 | 484 | { |
483 | annotations.add(widget); | |
485 | annotations.add(firstWidget); | |
484 | 486 | } |
485 | 487 | } |
486 | 488 | page.getCOSObject().setNeedToBeUpdated(true); |
552 | 554 | return false; |
553 | 555 | } |
554 | 556 | |
555 | private void prepareVisibleSignature(PDSignatureField signatureField, PDAcroForm acroForm, | |
557 | private void prepareVisibleSignature(PDAnnotationWidget firstWidget, PDAcroForm acroForm, | |
556 | 558 | COSDocument visualSignature) |
557 | 559 | { |
558 | 560 | // Obtain visual signature object |
574 | 576 | COSBase type = cosBaseDict.getDictionaryObject(COSName.TYPE); |
575 | 577 | if (annotNotFound && COSName.ANNOT.equals(type)) |
576 | 578 | { |
577 | assignSignatureRectangle(signatureField, cosBaseDict); | |
579 | assignSignatureRectangle(firstWidget, cosBaseDict); | |
578 | 580 | annotNotFound = false; |
579 | 581 | } |
580 | 582 | |
583 | 585 | COSBase apDict = cosBaseDict.getDictionaryObject(COSName.AP); |
584 | 586 | if (sigFieldNotFound && COSName.SIG.equals(fieldType) && apDict instanceof COSDictionary) |
585 | 587 | { |
586 | assignAppearanceDictionary(signatureField, (COSDictionary) apDict); | |
588 | assignAppearanceDictionary(firstWidget, (COSDictionary) apDict); | |
587 | 589 | assignAcroFormDefaultResource(acroForm, cosBaseDict); |
588 | 590 | sigFieldNotFound = false; |
589 | 591 | } |
596 | 598 | } |
597 | 599 | } |
598 | 600 | |
599 | private void assignSignatureRectangle(PDSignatureField signatureField, COSDictionary annotDict) | |
601 | private void assignSignatureRectangle(PDAnnotationWidget firstWidget, COSDictionary annotDict) | |
600 | 602 | { |
601 | 603 | // Read and set the rectangle for visual signature |
602 | PDRectangle existingRectangle = signatureField.getWidgets().get(0).getRectangle(); | |
604 | PDRectangle existingRectangle = firstWidget.getRectangle(); | |
603 | 605 | |
604 | 606 | //in case of an existing field keep the original rect |
605 | 607 | if (existingRectangle == null || existingRectangle.getCOSArray().size() != 4) |
606 | 608 | { |
607 | 609 | COSArray rectArray = (COSArray) annotDict.getDictionaryObject(COSName.RECT); |
608 | 610 | PDRectangle rect = new PDRectangle(rectArray); |
609 | signatureField.getWidgets().get(0).setRectangle(rect); | |
610 | } | |
611 | } | |
612 | ||
613 | private void assignAppearanceDictionary(PDSignatureField signatureField, COSDictionary apDict) | |
611 | firstWidget.setRectangle(rect); | |
612 | } | |
613 | } | |
614 | ||
615 | private void assignAppearanceDictionary(PDAnnotationWidget firstWidget, COSDictionary apDict) | |
614 | 616 | { |
615 | 617 | // read and set Appearance Dictionary |
616 | 618 | PDAppearanceDictionary ap = new PDAppearanceDictionary(apDict); |
617 | 619 | apDict.setDirect(true); |
618 | signatureField.getWidgets().get(0).setAppearance(ap); | |
620 | firstWidget.setAppearance(ap); | |
619 | 621 | } |
620 | 622 | |
621 | 623 | private void assignAcroFormDefaultResource(PDAcroForm acroForm, COSDictionary newDict) |
647 | 649 | } |
648 | 650 | } |
649 | 651 | |
650 | private void prepareNonVisibleSignature(PDSignatureField signatureField) | |
652 | private void prepareNonVisibleSignature(PDAnnotationWidget firstWidget) | |
651 | 653 | { |
652 | 654 | // "Signature fields that are not intended to be visible shall |
653 | 655 | // have an annotation rectangle that has zero height and width." |
654 | 656 | // Set rectangle for non-visual signature to rectangle array [ 0 0 0 0 ] |
655 | signatureField.getWidgets().get(0).setRectangle(new PDRectangle()); | |
657 | firstWidget.setRectangle(new PDRectangle()); | |
656 | 658 | |
657 | 659 | // The visual appearance must also exist for an invisible signature but may be empty. |
658 | 660 | PDAppearanceDictionary appearanceDictionary = new PDAppearanceDictionary(); |
659 | 661 | PDAppearanceStream appearanceStream = new PDAppearanceStream(this); |
660 | 662 | appearanceStream.setBBox(new PDRectangle()); |
661 | 663 | appearanceDictionary.setNormalAppearance(appearanceStream); |
662 | signatureField.getWidgets().get(0).setAppearance(appearanceDictionary); | |
664 | firstWidget.setAppearance(appearanceDictionary); | |
663 | 665 | } |
664 | 666 | |
665 | 667 | /** |
139 | 139 | |
140 | 140 | if (cachedAcroForm == null) |
141 | 141 | { |
142 | COSDictionary dict = (COSDictionary)root.getDictionaryObject(COSName.ACRO_FORM); | |
142 | COSDictionary dict = root.getCOSDictionary(COSName.ACRO_FORM); | |
143 | 143 | cachedAcroForm = dict == null ? null : new PDAcroForm(document, dict); |
144 | 144 | } |
145 | 145 | return cachedAcroForm; |
222 | 222 | array = new COSArray(); |
223 | 223 | root.setItem(COSName.THREADS, array); |
224 | 224 | } |
225 | List<PDThread> pdObjects = new ArrayList<PDThread>(); | |
225 | List<PDThread> pdObjects = new ArrayList<PDThread>(array.size()); | |
226 | 226 | for (int i = 0; i < array.size(); i++) |
227 | 227 | { |
228 | 228 | pdObjects.add(new PDThread((COSDictionary)array.getObject(i))); |
509 | 509 | public PageLayout getPageLayout() |
510 | 510 | { |
511 | 511 | String mode = root.getNameAsString(COSName.PAGE_LAYOUT); |
512 | if (mode != null) | |
513 | { | |
514 | return PageLayout.fromString(mode); | |
515 | } | |
516 | else | |
517 | { | |
518 | return PageLayout.SINGLE_PAGE; | |
519 | } | |
512 | if (mode != null && !mode.isEmpty()) | |
513 | { | |
514 | try | |
515 | { | |
516 | return PageLayout.fromString(mode); | |
517 | } | |
518 | catch (IllegalArgumentException e) | |
519 | { | |
520 | LOG.warn("Invalid PageLayout used '" + mode + "' - returning PageLayout.SINGLE_PAGE", e); | |
521 | } | |
522 | } | |
523 | return PageLayout.SINGLE_PAGE; | |
520 | 524 | } |
521 | 525 | |
522 | 526 | /** |
278 | 278 | { |
279 | 279 | mediaBox = new PDRectangle((COSArray) base); |
280 | 280 | } |
281 | } | |
282 | if (mediaBox == null) | |
283 | { | |
284 | LOG.debug("Can't find MediaBox, will use U.S. Letter"); | |
285 | mediaBox = PDRectangle.LETTER; | |
281 | else | |
282 | { | |
283 | LOG.debug("Can't find MediaBox, will use U.S. Letter"); | |
284 | mediaBox = PDRectangle.LETTER; | |
285 | } | |
286 | 286 | } |
287 | 287 | return mediaBox; |
288 | 288 | } |
466 | 466 | */ |
467 | 467 | private COSObject getIndirect(COSName kind, COSName name) |
468 | 468 | { |
469 | COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind); | |
469 | COSDictionary dict = resources.getCOSDictionary(kind); | |
470 | 470 | if (dict == null) |
471 | 471 | { |
472 | 472 | return null; |
485 | 485 | */ |
486 | 486 | private COSBase get(COSName kind, COSName name) |
487 | 487 | { |
488 | COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind); | |
488 | COSDictionary dict = resources.getCOSDictionary(kind); | |
489 | 489 | if (dict == null) |
490 | 490 | { |
491 | 491 | return null; |
570 | 570 | */ |
571 | 571 | private Iterable<COSName> getNames(COSName kind) |
572 | 572 | { |
573 | COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind); | |
573 | COSDictionary dict = resources.getCOSDictionary(kind); | |
574 | 574 | if (dict == null) |
575 | 575 | { |
576 | 576 | return Collections.emptySet(); |
700 | 700 | private COSName add(COSName kind, String prefix, COSObjectable object) |
701 | 701 | { |
702 | 702 | // return the existing key if the item exists already |
703 | COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind); | |
703 | COSDictionary dict = resources.getCOSDictionary(kind); | |
704 | 704 | if (dict != null && dict.containsValue(object.getCOSObject())) |
705 | 705 | { |
706 | 706 | return dict.getKeyForValue(object.getCOSObject()); |
731 | 731 | */ |
732 | 732 | private COSName createKey(COSName kind, String prefix) |
733 | 733 | { |
734 | COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind); | |
734 | COSDictionary dict = resources.getCOSDictionary(kind); | |
735 | 735 | if (dict == null) |
736 | 736 | { |
737 | 737 | return COSName.getPDFName(prefix + 1); |
754 | 754 | */ |
755 | 755 | private void put(COSName kind, COSName name, COSObjectable object) |
756 | 756 | { |
757 | COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind); | |
757 | COSDictionary dict = resources.getCOSDictionary(kind); | |
758 | 758 | if (dict == null) |
759 | 759 | { |
760 | 760 | dict = new COSDictionary(); |
511 | 511 | { |
512 | 512 | PDMetadata retval = null; |
513 | 513 | COSBase mdStream = stream.getDictionaryObject(COSName.METADATA); |
514 | if (mdStream != null) | |
515 | { | |
516 | if (mdStream instanceof COSStream) | |
517 | { | |
518 | retval = new PDMetadata((COSStream) mdStream); | |
519 | } | |
520 | else if (mdStream instanceof COSNull) | |
521 | { | |
522 | // null is authorized | |
523 | } | |
524 | else | |
525 | { | |
526 | throw new IllegalStateException( | |
527 | "Expected a COSStream but was a " | |
528 | + mdStream.getClass().getSimpleName()); | |
529 | } | |
514 | if (mdStream instanceof COSStream) | |
515 | { | |
516 | retval = new PDMetadata((COSStream) mdStream); | |
517 | } | |
518 | else if (mdStream instanceof COSNull) | |
519 | { | |
520 | // null is authorized | |
521 | } | |
522 | else if (mdStream != null) | |
523 | { | |
524 | throw new IllegalStateException("Expected a COSStream but was a " | |
525 | + mdStream.getClass().getSimpleName()); | |
530 | 526 | } |
531 | 527 | return retval; |
532 | 528 | } |
154 | 154 | for (int i = 0; i < sizeValuesSize; i++) |
155 | 155 | { |
156 | 156 | encode.add(COSInteger.ZERO); |
157 | encode.add(COSInteger.get(sizeValues.getInt(i) - 1)); | |
157 | encode.add(COSInteger.get(sizeValues.getInt(i) - 1L)); | |
158 | 158 | } |
159 | 159 | } |
160 | 160 | } |
112 | 112 | break; |
113 | 113 | } |
114 | 114 | } |
115 | if (function == null) | |
116 | { | |
117 | throw new IOException("partition not found in type 3 function"); | |
118 | } | |
115 | } | |
116 | if (function == null) | |
117 | { | |
118 | throw new IOException("partition not found in type 3 function"); | |
119 | 119 | } |
120 | 120 | float[] functionValues = new float[]{x}; |
121 | 121 | // calculate the output values using the chosen function |
+4
-2
15 | 15 | */ |
16 | 16 | package org.apache.pdfbox.pdmodel.documentinterchange.markedcontent; |
17 | 17 | |
18 | import org.apache.pdfbox.cos.COSBase; | |
18 | 19 | import org.apache.pdfbox.cos.COSDictionary; |
19 | 20 | import org.apache.pdfbox.cos.COSName; |
20 | 21 | import org.apache.pdfbox.pdmodel.common.COSObjectable; |
38 | 39 | */ |
39 | 40 | public static PDPropertyList create(COSDictionary dict) |
40 | 41 | { |
41 | if (COSName.OCG.equals(dict.getItem(COSName.TYPE))) | |
42 | COSBase item = dict.getItem(COSName.TYPE); | |
43 | if (COSName.OCG.equals(item)) | |
42 | 44 | { |
43 | 45 | return new PDOptionalContentGroup(dict); |
44 | 46 | } |
45 | else if (COSName.OCMD.equals(dict.getItem(COSName.TYPE))) | |
47 | else if (COSName.OCMD.equals(item)) | |
46 | 48 | { |
47 | 49 | return new PDOptionalContentMembershipDictionary(dict); |
48 | 50 | } |
56 | 56 | private static final int FILL_IN_FORM_BIT = 9; |
57 | 57 | private static final int EXTRACT_FOR_ACCESSIBILITY_BIT = 10; |
58 | 58 | private static final int ASSEMBLE_DOCUMENT_BIT = 11; |
59 | private static final int DEGRADED_PRINT_BIT = 12; | |
59 | private static final int FAITHFUL_PRINT_BIT = 12; | |
60 | 60 | |
61 | 61 | private int bytes; |
62 | 62 | |
139 | 139 | && this.canModify() |
140 | 140 | && this.canModifyAnnotations() |
141 | 141 | && this.canPrint() |
142 | && this.canPrintDegraded() | |
142 | && this.canPrintFaithful() | |
143 | 143 | ); |
144 | 144 | } |
145 | 145 | |
159 | 159 | ret.setCanModify(true); |
160 | 160 | ret.setCanModifyAnnotations(true); |
161 | 161 | ret.setCanPrint(true); |
162 | ret.setCanPrintDegraded(true); | |
162 | ret.setCanPrintFaithful(true); | |
163 | 163 | return ret; |
164 | 164 | } |
165 | 165 | |
389 | 389 | } |
390 | 390 | |
391 | 391 | /** |
392 | * This will tell if the user can print the document in a degraded format. | |
393 | * | |
394 | * @return true If supplied with the user password they are allowed to print the | |
395 | * document in a degraded format. | |
396 | */ | |
392 | * This will tell if the user can print the document in a faithful format or in a degraded | |
393 | * format (if print is enabled). | |
394 | * | |
395 | * @return true If supplied with the user password they are allowed to print the document in a | |
396 | * faithful format. | |
397 | * | |
398 | * @deprecated use {@link #canPrintFaithful() } | |
399 | */ | |
400 | @Deprecated | |
397 | 401 | public boolean canPrintDegraded() |
398 | 402 | { |
399 | return isPermissionBitOn( DEGRADED_PRINT_BIT ); | |
400 | } | |
401 | ||
402 | /** | |
403 | * Set if the user can print the document in a degraded format. | |
404 | * <p> | |
405 | * This method will have no effect if the object is in read only mode. | |
406 | * | |
407 | * @param canPrintDegraded A boolean determining if the user can print the | |
408 | * document in a degraded format. | |
409 | */ | |
410 | public void setCanPrintDegraded( boolean canPrintDegraded ) | |
411 | { | |
412 | if(!readOnly) | |
413 | { | |
414 | setPermissionBit( DEGRADED_PRINT_BIT, canPrintDegraded ); | |
403 | return isPermissionBitOn(FAITHFUL_PRINT_BIT); | |
404 | } | |
405 | ||
406 | /** | |
407 | * Set if the user can print the document in a faithful format or in a degraded format (if print | |
408 | * is enabled). The PDF version must be 1.5 or higher. | |
409 | * <p> | |
410 | * This method will have no effect if the object is in read only mode. | |
411 | * | |
412 | * @param canPrintFaithful A boolean determining if the user can print the document in a | |
413 | * faithful format. | |
414 | * | |
415 | * @deprecated use {@link #setCanPrintFaithful(boolean)} | |
416 | */ | |
417 | @Deprecated | |
418 | public void setCanPrintDegraded(boolean canPrintFaithful) | |
419 | { | |
420 | if (!readOnly) | |
421 | { | |
422 | setPermissionBit(FAITHFUL_PRINT_BIT, canPrintFaithful); | |
423 | } | |
424 | } | |
425 | ||
426 | /** | |
427 | * This will tell if the user can print the document in a faithful format or in a degraded | |
428 | * format (if print is enabled). | |
429 | * | |
430 | * @return true If supplied with the user password they are allowed to print the document in a | |
431 | * degraded format. | |
432 | */ | |
433 | public boolean canPrintFaithful() | |
434 | { | |
435 | return isPermissionBitOn(FAITHFUL_PRINT_BIT); | |
436 | } | |
437 | ||
438 | /** | |
439 | * Set if the user can print the document in a faithful format or in a degraded format (if print | |
440 | * is enabled). The PDF version must be 1.5 or higher. | |
441 | * <p> | |
442 | * This method will have no effect if the object is in read only mode. | |
443 | * | |
444 | * @param canPrintFaithful A boolean determining if the user can print the document in a | |
445 | * degraded format. | |
446 | */ | |
447 | public void setCanPrintFaithful(boolean canPrintFaithful) | |
448 | { | |
449 | if (!readOnly) | |
450 | { | |
451 | setPermissionBit(FAITHFUL_PRINT_BIT, canPrintFaithful); | |
415 | 452 | } |
416 | 453 | } |
417 | 454 | |
418 | 455 | /** |
419 | 456 | * Locks the access permission read only (ie, the setters will have no effects). |
420 | 457 | * After that, the object cannot be unlocked. |
421 | * This method is used for the currentAccessPermssion of a document to avoid | |
458 | * This method is used for the currentAccessPermission of a document to avoid | |
422 | 459 | * users to change access permission. |
423 | 460 | */ |
424 | 461 | public void setReadOnly() |
456 | 493 | { |
457 | 494 | return true; |
458 | 495 | } |
459 | return canPrintDegraded(); | |
496 | return canPrintFaithful(); | |
460 | 497 | } |
461 | 498 | } |
597 | 597 | * @param objNum The object number. |
598 | 598 | * @param genNum The object generation number. |
599 | 599 | * |
600 | * @throws IOException If an error occurs writing the new string. | |
601 | */ | |
602 | private void decryptString(COSString string, long objNum, long genNum) throws IOException | |
600 | */ | |
601 | private void decryptString(COSString string, long objNum, long genNum) | |
603 | 602 | { |
604 | 603 | // String encrypted with identity filter |
605 | 604 | if (COSName.IDENTITY.equals(stringFilterName)) |
71 | 71 | String symbol = element.getAttribute("symbol"); |
72 | 72 | if (symbol != null && !symbol.isEmpty()) |
73 | 73 | { |
74 | setSymbol(element.getAttribute("symbol")); | |
74 | setSymbol(symbol); | |
75 | 75 | } |
76 | 76 | } |
77 | 77 |
176 | 176 | List<FDFField> retval = null; |
177 | 177 | if (kids != null) |
178 | 178 | { |
179 | List<FDFField> actuals = new ArrayList<FDFField>(); | |
179 | List<FDFField> actuals = new ArrayList<FDFField>(kids.size()); | |
180 | 180 | for (int i = 0; i < kids.size(); i++) |
181 | 181 | { |
182 | 182 | actuals.add(new FDFField((COSDictionary) kids.getObject(i))); |
138 | 138 | */ |
139 | 139 | public Map<String, PDActionJavaScript> getDoc() |
140 | 140 | { |
141 | Map<String, PDActionJavaScript> map = new LinkedHashMap<String, PDActionJavaScript>(); | |
142 | 141 | COSArray array = dictionary.getCOSArray(COSName.DOC); |
143 | 142 | if (array == null) |
144 | 143 | { |
145 | 144 | return null; |
146 | 145 | } |
146 | Map<String, PDActionJavaScript> map = new LinkedHashMap<String, PDActionJavaScript>(); | |
147 | 147 | for (int i = 0; i + 1 < array.size(); i += 2) |
148 | 148 | { |
149 | 149 | String name = array.getName(i); |
20 | 20 | |
21 | 21 | import java.io.IOException; |
22 | 22 | import java.io.InputStream; |
23 | import java.util.Collections; | |
24 | import java.util.HashMap; | |
23 | ||
25 | 24 | import java.util.Map; |
25 | import java.util.concurrent.ConcurrentHashMap; | |
26 | 26 | |
27 | 27 | /** |
28 | 28 | * CMap resource loader and cache. |
29 | 29 | */ |
30 | 30 | final class CMapManager |
31 | 31 | { |
32 | static Map<String, CMap> cMapCache = | |
33 | Collections.synchronizedMap(new HashMap<String, CMap>()); | |
32 | private static final Map<String, CMap> CMAP_CACHE = new ConcurrentHashMap<String, CMap>(); | |
34 | 33 | |
35 | 34 | private CMapManager() |
36 | 35 | { |
45 | 44 | */ |
46 | 45 | public static CMap getPredefinedCMap(String cMapName) throws IOException |
47 | 46 | { |
48 | CMap cmap = cMapCache.get(cMapName); | |
47 | CMap cmap = CMAP_CACHE.get(cMapName); | |
49 | 48 | if (cmap != null) |
50 | 49 | { |
51 | 50 | return cmap; |
54 | 53 | CMap targetCmap = new CMapParser().parsePredefined(cMapName); |
55 | 54 | |
56 | 55 | // limit the cache to predefined CMaps |
57 | cMapCache.put(targetCmap.getName(), targetCmap); | |
56 | CMAP_CACHE.put(targetCmap.getName(), targetCmap); | |
58 | 57 | return targetCmap; |
59 | 58 | } |
60 | 59 |
400 | 400 | private File getDiskCacheFile() |
401 | 401 | { |
402 | 402 | String path = System.getProperty("pdfbox.fontcache"); |
403 | if (path == null || !new File(path).isDirectory() || !new File(path).canWrite()) | |
403 | if (isBadPath(path)) | |
404 | 404 | { |
405 | 405 | path = System.getProperty("user.home"); |
406 | if (path == null || !new File(path).isDirectory() || !new File(path).canWrite()) | |
406 | if (isBadPath(path)) | |
407 | 407 | { |
408 | 408 | path = System.getProperty("java.io.tmpdir"); |
409 | 409 | } |
410 | 410 | } |
411 | 411 | return new File(path, ".pdfbox.cache"); |
412 | } | |
413 | ||
414 | private static boolean isBadPath(String path) | |
415 | { | |
416 | return path == null || !new File(path).isDirectory() || !new File(path).canWrite(); | |
412 | 417 | } |
413 | 418 | |
414 | 419 | /** |
25 | 25 | import java.util.HashSet; |
26 | 26 | import java.util.LinkedHashMap; |
27 | 27 | import java.util.List; |
28 | import java.util.Locale; | |
28 | 29 | import java.util.Map; |
29 | 30 | import java.util.PriorityQueue; |
30 | 31 | import java.util.Set; |
56 | 57 | FontMapperImpl() |
57 | 58 | { |
58 | 59 | // substitutes for standard 14 fonts |
59 | substitutes.put("Courier", | |
60 | addSubstitutes("Courier", | |
60 | 61 | new ArrayList<String>(Arrays.asList("CourierNew", "CourierNewPSMT", "LiberationMono", |
61 | 62 | "NimbusMonL-Regu"))); |
62 | substitutes.put("Courier-Bold", | |
63 | addSubstitutes("Courier-Bold", | |
63 | 64 | new ArrayList<String>(Arrays.asList("CourierNewPS-BoldMT", "CourierNew-Bold", |
64 | 65 | "LiberationMono-Bold", "NimbusMonL-Bold"))); |
65 | substitutes.put("Courier-Oblique", | |
66 | addSubstitutes("Courier-Oblique", | |
66 | 67 | new ArrayList<String>(Arrays.asList("CourierNewPS-ItalicMT","CourierNew-Italic", |
67 | 68 | "LiberationMono-Italic", "NimbusMonL-ReguObli"))); |
68 | substitutes.put("Courier-BoldOblique", | |
69 | new ArrayList<String>(Arrays.asList("CourierNewPS-BoldItalicMT", | |
69 | addSubstitutes("Courier-BoldOblique", | |
70 | new ArrayList<String>(Arrays.asList("CourierNewPS-BoldItalicMT", | |
70 | 71 | "CourierNew-BoldItalic", "LiberationMono-BoldItalic", |
71 | 72 | "NimbusMonL-BoldObli"))); |
72 | substitutes.put("Helvetica", | |
73 | new ArrayList<String>(Arrays.asList("ArialMT", "Arial", "LiberationSans", | |
73 | addSubstitutes("Helvetica", | |
74 | new ArrayList<String>(Arrays.asList("ArialMT", "Arial", "LiberationSans", | |
74 | 75 | "NimbusSanL-Regu"))); |
75 | substitutes.put("Helvetica-Bold", | |
76 | addSubstitutes("Helvetica-Bold", | |
76 | 77 | new ArrayList<String>(Arrays.asList("Arial-BoldMT", "Arial-Bold", |
77 | 78 | "LiberationSans-Bold", "NimbusSanL-Bold"))); |
78 | substitutes.put("Helvetica-Oblique", | |
79 | addSubstitutes("Helvetica-Oblique", | |
79 | 80 | new ArrayList<String>(Arrays.asList("Arial-ItalicMT", "Arial-Italic", |
80 | 81 | "Helvetica-Italic", "LiberationSans-Italic", "NimbusSanL-ReguItal"))); |
81 | substitutes.put("Helvetica-BoldOblique", | |
82 | addSubstitutes("Helvetica-BoldOblique", | |
82 | 83 | new ArrayList<String>(Arrays.asList("Arial-BoldItalicMT", "Helvetica-BoldItalic", |
83 | 84 | "LiberationSans-BoldItalic", "NimbusSanL-BoldItal"))); |
84 | substitutes.put("Times-Roman", | |
85 | addSubstitutes("Times-Roman", | |
85 | 86 | new ArrayList<String>(Arrays.asList("TimesNewRomanPSMT", "TimesNewRoman", |
86 | 87 | "TimesNewRomanPS", "LiberationSerif", "NimbusRomNo9L-Regu"))); |
87 | substitutes.put("Times-Bold", | |
88 | addSubstitutes("Times-Bold", | |
88 | 89 | new ArrayList<String>(Arrays.asList("TimesNewRomanPS-BoldMT", "TimesNewRomanPS-Bold", |
89 | 90 | "TimesNewRoman-Bold", "LiberationSerif-Bold", |
90 | 91 | "NimbusRomNo9L-Medi"))); |
91 | substitutes.put("Times-Italic", | |
92 | new ArrayList<String>(Arrays.asList("TimesNewRomanPS-ItalicMT", | |
92 | addSubstitutes("Times-Italic", | |
93 | new ArrayList<String>(Arrays.asList("TimesNewRomanPS-ItalicMT", | |
93 | 94 | "TimesNewRomanPS-Italic", "TimesNewRoman-Italic", "LiberationSerif-Italic", |
94 | 95 | "NimbusRomNo9L-ReguItal"))); |
95 | substitutes.put("Times-BoldItalic", | |
96 | new ArrayList<String>(Arrays.asList("TimesNewRomanPS-BoldItalicMT", | |
96 | addSubstitutes("Times-BoldItalic", | |
97 | new ArrayList<String>(Arrays.asList("TimesNewRomanPS-BoldItalicMT", | |
97 | 98 | "TimesNewRomanPS-BoldItalic", "TimesNewRoman-BoldItalic", |
98 | 99 | "LiberationSerif-BoldItalic", "NimbusRomNo9L-MediItal"))); |
99 | substitutes.put("Symbol", | |
100 | addSubstitutes("Symbol", | |
100 | 101 | new ArrayList<String>(Arrays.asList("Symbol", "SymbolMT", "StandardSymL"))); |
101 | substitutes.put("ZapfDingbats", new ArrayList<String>( | |
102 | addSubstitutes("ZapfDingbats", new ArrayList<String>( | |
102 | 103 | Arrays.asList("ZapfDingbatsITCbyBT-Regular", "ZapfDingbatsITC", "Dingbats", |
103 | 104 | "MS-Gothic"))); |
104 | 105 | |
106 | 107 | // these include names such as "Arial" and "TimesNewRoman" |
107 | 108 | for (String baseName : Standard14Fonts.getNames()) |
108 | 109 | { |
109 | if (!substitutes.containsKey(baseName)) | |
110 | if (getSubstitutes(baseName).isEmpty()) | |
110 | 111 | { |
111 | 112 | String mappedName = Standard14Fonts.getMappedFontName(baseName); |
112 | substitutes.put(baseName, copySubstitutes(mappedName)); | |
113 | addSubstitutes(baseName, copySubstitutes(mappedName.toLowerCase(Locale.ENGLISH))); | |
113 | 114 | } |
114 | 115 | } |
115 | 116 | |
176 | 177 | { |
177 | 178 | for (String name : getPostScriptNames(info.getPostScriptName())) |
178 | 179 | { |
179 | map.put(name, info); | |
180 | map.put(name.toLowerCase(Locale.ENGLISH), info); | |
180 | 181 | } |
181 | 182 | } |
182 | 183 | return map; |
214 | 215 | */ |
215 | 216 | public void addSubstitute(String match, String replace) |
216 | 217 | { |
217 | if (!substitutes.containsKey(match)) | |
218 | { | |
219 | substitutes.put(match, new ArrayList<String>()); | |
220 | } | |
221 | substitutes.get(match).add(replace); | |
218 | String lowerCaseMatch = match.toLowerCase(Locale.ENGLISH); | |
219 | if (!substitutes.containsKey(lowerCaseMatch)) | |
220 | { | |
221 | substitutes.put(lowerCaseMatch, new ArrayList<String>()); | |
222 | } | |
223 | substitutes.get(lowerCaseMatch).add(replace); | |
224 | } | |
225 | ||
226 | private void addSubstitutes(String match, List<String> replacements) | |
227 | { | |
228 | substitutes.put(match.toLowerCase(Locale.ENGLISH), replacements); | |
222 | 229 | } |
223 | 230 | |
224 | 231 | /** |
226 | 233 | */ |
227 | 234 | private List<String> getSubstitutes(String postScriptName) |
228 | 235 | { |
229 | List<String> subs = substitutes.get(postScriptName.replace(" ", "")); | |
236 | List<String> subs = substitutes.get(postScriptName.replace(" ", "").toLowerCase(Locale.ENGLISH)); | |
230 | 237 | if (subs != null) |
231 | 238 | { |
232 | 239 | return subs; |
475 | 482 | } |
476 | 483 | |
477 | 484 | // look up the PostScript name |
478 | FontInfo info = fontInfoByName.get(postScriptName); | |
485 | FontInfo info = fontInfoByName.get(postScriptName.toLowerCase(Locale.ENGLISH)); | |
479 | 486 | if (info != null && info.getFormat() == format) |
480 | 487 | { |
481 | 488 | if (LOG.isDebugEnabled()) |
79 | 79 | COSArray wArray = (COSArray) wBase; |
80 | 80 | int size = wArray.size(); |
81 | 81 | int counter = 0; |
82 | while (counter < size) | |
82 | while (counter < size - 1) | |
83 | 83 | { |
84 | 84 | COSBase firstCodeBase = wArray.getObject(counter++); |
85 | 85 | if (!(firstCodeBase instanceof COSNumber)) |
110 | 110 | } |
111 | 111 | else |
112 | 112 | { |
113 | if (counter >= size) | |
114 | { | |
115 | LOG.warn("premature end of widths array"); | |
116 | break; | |
117 | } | |
113 | 118 | COSBase secondCodeBase = next; |
114 | 119 | COSBase rangeWidthBase = wArray.getObject(counter++); |
115 | 120 | if (!(secondCodeBase instanceof COSNumber) || !(rangeWidthBase instanceof COSNumber)) |
20 | 20 | import java.io.ByteArrayOutputStream; |
21 | 21 | import java.io.IOException; |
22 | 22 | import java.io.InputStream; |
23 | import java.util.Collections; | |
24 | import java.util.HashMap; | |
25 | 23 | import java.util.List; |
26 | 24 | import java.util.Map; |
27 | 25 | import java.util.Set; |
28 | import java.util.TreeSet; | |
26 | import java.util.TreeMap; | |
29 | 27 | |
30 | 28 | import org.apache.commons.logging.Log; |
31 | 29 | import org.apache.commons.logging.LogFactory; |
104 | 102 | throws IOException |
105 | 103 | { |
106 | 104 | // build CID2GIDMap, because the content stream has been written with the old GIDs |
107 | Map<Integer, Integer> cidToGid = new HashMap<Integer, Integer>(gidToCid.size()); | |
105 | TreeMap<Integer, Integer> cidToGid = new TreeMap<Integer, Integer>(); | |
108 | 106 | for (Map.Entry<Integer, Integer> entry : gidToCid.entrySet()) |
109 | 107 | { |
110 | 108 | int newGID = entry.getKey(); |
235 | 233 | cidFont.setName(COSName.BASE_FONT, newName); |
236 | 234 | } |
237 | 235 | |
238 | private void buildCIDToGIDMap(Map<Integer, Integer> cidToGid) throws IOException | |
239 | { | |
240 | ByteArrayOutputStream out = new ByteArrayOutputStream(); | |
241 | int cidMax = Collections.max(cidToGid.keySet()); | |
236 | private void buildCIDToGIDMap(TreeMap<Integer, Integer> cidToGid) throws IOException | |
237 | { | |
238 | int cidMax = cidToGid.lastKey(); | |
239 | byte[] buffer = new byte[cidMax * 2 + 2]; | |
240 | int bi = 0; | |
242 | 241 | for (int i = 0; i <= cidMax; i++) |
243 | 242 | { |
244 | int gid; | |
245 | if (cidToGid.containsKey(i)) | |
246 | { | |
247 | gid = cidToGid.get(i); | |
248 | } | |
249 | else | |
250 | { | |
251 | gid = 0; | |
252 | } | |
253 | out.write(new byte[] { (byte)(gid >> 8 & 0xff), (byte)(gid & 0xff) }); | |
254 | } | |
255 | ||
256 | InputStream input = new ByteArrayInputStream(out.toByteArray()); | |
243 | Integer gid = cidToGid.get(i); | |
244 | if (gid != null) | |
245 | { | |
246 | buffer[bi] = (byte) (gid >> 8 & 0xff); | |
247 | buffer[bi+1] = (byte) (gid & 0xff); | |
248 | } | |
249 | // else keep 0 initialization | |
250 | bi += 2; | |
251 | } | |
252 | ||
253 | InputStream input = new ByteArrayInputStream(buffer); | |
257 | 254 | PDStream stream = new PDStream(document, input, COSName.FLATE_DECODE); |
258 | 255 | |
259 | 256 | cidFont.setItem(COSName.CID_TO_GID_MAP, stream); |
263 | 260 | * Builds the CIDSet entry, required by PDF/A. This lists all CIDs in the font, including those |
264 | 261 | * that don't have a GID. |
265 | 262 | */ |
266 | private void buildCIDSet(Map<Integer, Integer> cidToGid) throws IOException | |
267 | { | |
268 | int cidMax = Collections.max(cidToGid.keySet()); | |
263 | private void buildCIDSet(TreeMap<Integer, Integer> cidToGid) throws IOException | |
264 | { | |
265 | int cidMax = cidToGid.lastKey(); | |
269 | 266 | byte[] bytes = new byte[cidMax / 8 + 1]; |
270 | 267 | for (int cid = 0; cid <= cidMax; cid++) |
271 | 268 | { |
282 | 279 | /** |
283 | 280 | * Builds widths with a custom CIDToGIDMap (for embedding font subset). |
284 | 281 | */ |
285 | private void buildWidths(Map<Integer, Integer> cidToGid) throws IOException | |
282 | private void buildWidths(TreeMap<Integer, Integer> cidToGid) throws IOException | |
286 | 283 | { |
287 | 284 | float scaling = 1000f / ttf.getHeader().getUnitsPerEm(); |
288 | 285 | |
290 | 287 | COSArray ws = new COSArray(); |
291 | 288 | int prev = Integer.MIN_VALUE; |
292 | 289 | // Use a sorted list to get an optimal width array |
293 | Set<Integer> keys = new TreeSet<Integer>(cidToGid.keySet()); | |
290 | Set<Integer> keys = cidToGid.keySet(); | |
291 | HorizontalMetricsTable horizontalMetricsTable = ttf.getHorizontalMetrics(); | |
294 | 292 | for (int cid : keys) |
295 | 293 | { |
296 | 294 | int gid = cidToGid.get(cid); |
297 | long width = Math.round(ttf.getHorizontalMetrics().getAdvanceWidth(gid) * scaling); | |
295 | long width = Math.round(horizontalMetricsTable.getAdvanceWidth(gid) * scaling); | |
298 | 296 | if (width == 1000) |
299 | 297 | { |
300 | 298 | // skip default width |
339 | 337 | /** |
340 | 338 | * Builds vertical metrics with a custom CIDToGIDMap (for embedding font subset). |
341 | 339 | */ |
342 | private void buildVerticalMetrics(Map<Integer, Integer> cidToGid) throws IOException | |
340 | private void buildVerticalMetrics(TreeMap<Integer, Integer> cidToGid) throws IOException | |
343 | 341 | { |
344 | 342 | // The "vhea" and "vmtx" tables that specify vertical metrics shall never be used by a conforming |
345 | 343 | // reader. The only way to specify vertical metrics in PDF shall be by means of the DW2 and W2 |
364 | 362 | COSArray w2 = new COSArray(); |
365 | 363 | int prev = Integer.MIN_VALUE; |
366 | 364 | // Use a sorted list to get an optimal width array |
367 | Set<Integer> keys = new TreeSet<Integer>(cidToGid.keySet()); | |
365 | Set<Integer> keys = cidToGid.keySet(); | |
368 | 366 | for (int cid : keys) |
369 | 367 | { |
370 | 368 | // Unlike buildWidths, we look up with cid (not gid) here because this is |
404 | 402 | { |
405 | 403 | int cidMax = ttf.getNumberOfGlyphs(); |
406 | 404 | int[] gidwidths = new int[cidMax * 2]; |
405 | HorizontalMetricsTable horizontalMetricsTable = ttf.getHorizontalMetrics(); | |
407 | 406 | for (int cid = 0; cid < cidMax; cid++) |
408 | 407 | { |
409 | 408 | gidwidths[cid * 2] = cid; |
410 | gidwidths[cid * 2 + 1] = ttf.getHorizontalMetrics().getAdvanceWidth(cid); | |
409 | gidwidths[cid * 2 + 1] = horizontalMetricsTable.getAdvanceWidth(cid); | |
411 | 410 | } |
412 | 411 | |
413 | 412 | cidFont.setItem(COSName.W, getWidths(gidwidths)); |
526 | 525 | |
527 | 526 | int cidMax = ttf.getNumberOfGlyphs(); |
528 | 527 | int[] gidMetrics = new int[cidMax * 4]; |
528 | GlyphTable glyphTable = ttf.getGlyph(); | |
529 | VerticalMetricsTable verticalMetricsTable = ttf.getVerticalMetrics(); | |
530 | HorizontalMetricsTable horizontalMetricsTable = ttf.getHorizontalMetrics(); | |
529 | 531 | for (int cid = 0; cid < cidMax; cid++) |
530 | 532 | { |
531 | GlyphData glyph = ttf.getGlyph().getGlyph(cid); | |
533 | GlyphData glyph = glyphTable.getGlyph(cid); | |
532 | 534 | if (glyph == null) |
533 | 535 | { |
534 | 536 | gidMetrics[cid * 4] = Integer.MIN_VALUE; |
536 | 538 | else |
537 | 539 | { |
538 | 540 | gidMetrics[cid * 4] = cid; |
539 | gidMetrics[cid * 4 + 1] = ttf.getVerticalMetrics().getAdvanceHeight(cid); | |
540 | gidMetrics[cid * 4 + 2] = ttf.getHorizontalMetrics().getAdvanceWidth(cid); | |
541 | gidMetrics[cid * 4 + 3] = glyph.getYMaximum() + ttf.getVerticalMetrics().getTopSideBearing(cid); | |
541 | gidMetrics[cid * 4 + 1] = verticalMetricsTable.getAdvanceHeight(cid); | |
542 | gidMetrics[cid * 4 + 2] = horizontalMetricsTable.getAdvanceWidth(cid); | |
543 | gidMetrics[cid * 4 + 3] = glyph.getYMaximum() + verticalMetricsTable.getTopSideBearing(cid); | |
542 | 544 | } |
543 | 545 | } |
544 | 546 |
534 | 534 | { |
535 | 535 | if (fontWidthOfSpace == -1f) |
536 | 536 | { |
537 | COSBase toUnicode = dict.getDictionaryObject(COSName.TO_UNICODE); | |
538 | 537 | try |
539 | 538 | { |
540 | if (toUnicode != null && toUnicodeCMap != null) | |
539 | if (toUnicodeCMap != null && dict.containsKey(COSName.TO_UNICODE)) | |
541 | 540 | { |
542 | 541 | int spaceMapping = toUnicodeCMap.getSpaceMapping(); |
543 | 542 | if (spaceMapping > -1) |
554 | 553 | if (fontWidthOfSpace <= 0) |
555 | 554 | { |
556 | 555 | fontWidthOfSpace = getWidthFromFont(32); |
557 | } | |
558 | // use the average font width as fall back | |
559 | if (fontWidthOfSpace <= 0) | |
560 | { | |
561 | fontWidthOfSpace = getAverageFontWidth(); | |
556 | ||
557 | // use the average font width as fall back | |
558 | if (fontWidthOfSpace <= 0) | |
559 | { | |
560 | fontWidthOfSpace = getAverageFontWidth(); | |
561 | } | |
562 | 562 | } |
563 | 563 | } |
564 | 564 | catch (Exception e) |
46 | 46 | */ |
47 | 47 | public int getFamilyClass() |
48 | 48 | { |
49 | return bytes[0] << 8 | bytes[1]; | |
49 | return bytes[0] << 8 | (bytes[1] & 0xff); | |
50 | 50 | } |
51 | 51 | |
52 | 52 | /** |
33 | 33 | import org.apache.fontbox.util.BoundingBox; |
34 | 34 | import org.apache.pdfbox.cos.COSDictionary; |
35 | 35 | import org.apache.pdfbox.cos.COSName; |
36 | import org.apache.pdfbox.io.IOUtils; | |
36 | 37 | import org.apache.pdfbox.pdmodel.PDDocument; |
37 | 38 | import org.apache.pdfbox.pdmodel.common.PDRectangle; |
38 | 39 | import org.apache.pdfbox.pdmodel.common.PDStream; |
45 | 46 | import org.apache.pdfbox.pdmodel.font.encoding.Type1Encoding; |
46 | 47 | import org.apache.pdfbox.pdmodel.font.encoding.WinAnsiEncoding; |
47 | 48 | |
48 | ||
49 | 49 | import static org.apache.pdfbox.pdmodel.font.UniUtil.getUniNameOfCodePoint; |
50 | 50 | |
51 | 51 | /** |
191 | 191 | PDStream ff2Stream = fd.getFontFile2(); |
192 | 192 | if (ff2Stream != null) |
193 | 193 | { |
194 | InputStream is = null; | |
194 | 195 | try |
195 | 196 | { |
196 | 197 | // embedded |
197 | 198 | TTFParser ttfParser = new TTFParser(true); |
198 | ttfFont = ttfParser.parse(ff2Stream.createInputStream()); | |
199 | is = ff2Stream.createInputStream(); | |
200 | ttfFont = ttfParser.parse(is); | |
199 | 201 | } |
200 | 202 | catch (IOException e) |
201 | 203 | { |
202 | 204 | LOG.warn("Could not read embedded TTF for font " + getBaseFont(), e); |
203 | 205 | fontIsDamaged = true; |
206 | IOUtils.closeQuietly(is); | |
204 | 207 | } |
205 | 208 | } |
206 | 209 | } |
686 | 689 | // PDFBOX-4755 / PDF.js #5501 |
687 | 690 | cmapWinUnicode = cmap; |
688 | 691 | } |
692 | else if (CmapTable.PLATFORM_UNICODE == cmap.getPlatformId() | |
693 | && CmapTable.ENCODING_UNICODE_2_0_BMP == cmap.getPlatformEncodingId()) | |
694 | { | |
695 | // PDFBOX-5484 | |
696 | cmapWinUnicode = cmap; | |
697 | } | |
689 | 698 | } |
690 | 699 | } |
691 | 700 | cmapInitialized = true; |
213 | 213 | * @param closeTTF whether to close the ttf parameter after embedding. Must be true when the ttf |
214 | 214 | * parameter was created in the load() method, false when the ttf parameter was passed to the |
215 | 215 | * load() method. |
216 | * @param vertical | |
216 | * @param vertical whether to enable vertical substitutions. | |
217 | 217 | * @throws IOException |
218 | 218 | */ |
219 | 219 | private PDType0Font(PDDocument document, TrueTypeFont ttf, boolean embedSubset, |
284 | 284 | // predefined CMap |
285 | 285 | COSName encodingName = (COSName) encoding; |
286 | 286 | cMap = CMapManager.getPredefinedCMap(encodingName.getName()); |
287 | if (cMap != null) | |
288 | { | |
289 | isCMapPredefined = true; | |
290 | } | |
291 | else | |
292 | { | |
293 | throw new IOException("Missing required CMap"); | |
294 | } | |
287 | isCMapPredefined = true; | |
295 | 288 | } |
296 | 289 | else if (encoding != null) |
297 | 290 | { |
310 | 303 | PDCIDSystemInfo ros = descendantFont.getCIDSystemInfo(); |
311 | 304 | if (ros != null) |
312 | 305 | { |
306 | String ordering = ros.getOrdering(); | |
313 | 307 | isDescendantCJK = "Adobe".equals(ros.getRegistry()) && |
314 | ("GB1".equals(ros.getOrdering()) || | |
315 | "CNS1".equals(ros.getOrdering()) || | |
316 | "Japan1".equals(ros.getOrdering()) || | |
317 | "Korea1".equals(ros.getOrdering())); | |
308 | ("GB1".equals(ordering) || | |
309 | "CNS1".equals(ordering) || | |
310 | "Japan1".equals(ordering) || | |
311 | "Korea1".equals(ordering)); | |
318 | 312 | } |
319 | 313 | } |
320 | 314 | |
340 | 334 | String strName = null; |
341 | 335 | if (isDescendantCJK) |
342 | 336 | { |
343 | strName = descendantFont.getCIDSystemInfo().getRegistry() + "-" + | |
344 | descendantFont.getCIDSystemInfo().getOrdering() + "-" + | |
345 | descendantFont.getCIDSystemInfo().getSupplement(); | |
337 | PDCIDSystemInfo cidSystemInfo = descendantFont.getCIDSystemInfo(); | |
338 | if (cidSystemInfo != null) | |
339 | { | |
340 | strName = cidSystemInfo.getRegistry() + "-" + | |
341 | cidSystemInfo.getOrdering() + "-" + | |
342 | cidSystemInfo.getSupplement(); | |
343 | } | |
346 | 344 | } |
347 | 345 | else if (name != null) |
348 | 346 | { |
413 | 411 | @Override |
414 | 412 | public boolean isVertical() |
415 | 413 | { |
416 | return cMap.getWMode() == 1; | |
414 | return cMap != null && cMap.getWMode() == 1; | |
417 | 415 | } |
418 | 416 | |
419 | 417 | @Override |
570 | 568 | @Override |
571 | 569 | public int readCode(InputStream in) throws IOException |
572 | 570 | { |
571 | if (cMap == null) | |
572 | { | |
573 | throw new IOException("required cmap is null"); | |
574 | } | |
573 | 575 | return cMap.readCode(in); |
574 | 576 | } |
575 | 577 |
218 | 218 | PDStream fontFile3 = fd.getFontFile3(); |
219 | 219 | if (fontFile3 != null) |
220 | 220 | { |
221 | throw new IOException("/FontFile3 for Type1 font not supported"); | |
221 | LOG.warn("/FontFile3 for Type1 font not supported"); | |
222 | 222 | } |
223 | 223 | |
224 | 224 | // or it may contain a PFB |
120 | 120 | return null; |
121 | 121 | } |
122 | 122 | } |
123 | float x = ((COSNumber) arguments.get(2)).floatValue(); | |
124 | float y = ((COSNumber) arguments.get(3)).floatValue(); | |
123 | 125 | return new PDRectangle( |
124 | ((COSNumber) arguments.get(2)).floatValue(), | |
125 | ((COSNumber) arguments.get(3)).floatValue(), | |
126 | ((COSNumber) arguments.get(4)).floatValue() - ((COSNumber) arguments.get(2)).floatValue(), | |
127 | ((COSNumber) arguments.get(5)).floatValue() - ((COSNumber) arguments.get(3)).floatValue()); | |
126 | x, | |
127 | y, | |
128 | ((COSNumber) arguments.get(4)).floatValue() - x, | |
129 | ((COSNumber) arguments.get(5)).floatValue() - y); | |
128 | 130 | } |
129 | 131 | else |
130 | 132 | { |
194 | 194 | */ |
195 | 195 | private PDFontDescriptor createFontDescriptor(TrueTypeFont ttf) throws IOException |
196 | 196 | { |
197 | PDFontDescriptor fd = new PDFontDescriptor(); | |
198 | fd.setFontName(ttf.getName()); | |
199 | ||
197 | String ttfName = ttf.getName(); | |
200 | 198 | OS2WindowsMetricsTable os2 = ttf.getOS2Windows(); |
201 | 199 | if (os2 == null) |
202 | 200 | { |
203 | throw new IOException("os2 table is missing in font " + ttf.getName()); | |
201 | throw new IOException("os2 table is missing in font " + ttfName); | |
204 | 202 | } |
205 | 203 | PostScriptTable post = ttf.getPostScript(); |
206 | 204 | if (post == null) |
207 | 205 | { |
208 | throw new IOException("post table is missing in font " + ttf.getName()); | |
209 | } | |
206 | throw new IOException("post table is missing in font " + ttfName); | |
207 | } | |
208 | ||
209 | PDFontDescriptor fd = new PDFontDescriptor(); | |
210 | fd.setFontName(ttfName); | |
211 | ||
212 | HorizontalHeaderTable hhea = ttf.getHorizontalHeader(); | |
210 | 213 | |
211 | 214 | // Flags |
212 | fd.setFixedPitch(post.getIsFixedPitch() > 0 || | |
213 | ttf.getHorizontalHeader().getNumberOfHMetrics() == 1); | |
215 | fd.setFixedPitch(post.getIsFixedPitch() > 0 || hhea.getNumberOfHMetrics() == 1); | |
214 | 216 | |
215 | 217 | int fsSelection = os2.getFsSelection(); |
216 | 218 | fd.setItalic(((fsSelection & (ITALIC | OBLIQUE)) != 0)); |
250 | 252 | fd.setFontBoundingBox(rect); |
251 | 253 | |
252 | 254 | // Ascent, Descent |
253 | HorizontalHeaderTable hHeader = ttf.getHorizontalHeader(); | |
254 | fd.setAscent(hHeader.getAscender() * scaling); | |
255 | fd.setDescent(hHeader.getDescender() * scaling); | |
255 | fd.setAscent(hhea.getAscender() * scaling); | |
256 | fd.setDescent(hhea.getDescender() * scaling); | |
256 | 257 | |
257 | 258 | // CapHeight, XHeight |
258 | 259 | if (os2.getVersion() >= 1.2) |
71 | 71 | else if (COSName.FORM.getName().equals(subtype)) |
72 | 72 | { |
73 | 73 | ResourceCache cache = resources != null ? resources.getResourceCache() : null; |
74 | COSDictionary group = (COSDictionary)stream.getDictionaryObject(COSName.GROUP); | |
74 | COSDictionary group = stream.getCOSDictionary(COSName.GROUP); | |
75 | 75 | if (group != null && COSName.TRANSPARENCY.equals(group.getCOSName(COSName.S))) |
76 | 76 | { |
77 | 77 | return new PDTransparencyGroup(stream, cache); |
22 | 22 | import java.io.IOException; |
23 | 23 | import java.util.Arrays; |
24 | 24 | |
25 | import org.apache.commons.logging.Log; | |
26 | import org.apache.commons.logging.LogFactory; | |
27 | import org.apache.pdfbox.cos.COSBase; | |
28 | ||
25 | 29 | /** |
26 | 30 | * A color value, consisting of one or more color components, or for pattern color spaces, |
27 | 31 | * a name and optional color components. |
33 | 37 | */ |
34 | 38 | public final class PDColor |
35 | 39 | { |
40 | private static final Log LOG = LogFactory.getLog(PDColor.class); | |
41 | ||
36 | 42 | private final float[] components; |
37 | 43 | private final COSName patternName; |
38 | 44 | private final PDColorSpace colorSpace; |
48 | 54 | { |
49 | 55 | // color components (optional), for the color of an uncoloured tiling pattern |
50 | 56 | components = new float[array.size() - 1]; |
51 | for (int i = 0; i < components.length; i++) | |
52 | { | |
53 | components[i] = ((COSNumber)array.get(i)).floatValue(); | |
54 | } | |
57 | initComponents(array); | |
55 | 58 | |
56 | 59 | // pattern name (required) |
57 | patternName = (COSName)array.get(array.size() - 1); | |
60 | COSBase base = array.get(array.size() - 1); | |
61 | if (base instanceof COSName) | |
62 | { | |
63 | patternName = (COSName) base; | |
64 | } | |
65 | else | |
66 | { | |
67 | LOG.warn("pattern name in " + array + " isn't a name, ignored"); | |
68 | patternName = COSName.getPDFName("Unknown"); | |
69 | } | |
58 | 70 | } |
59 | 71 | else |
60 | 72 | { |
61 | 73 | // color components only |
62 | 74 | components = new float[array.size()]; |
63 | for (int i = 0; i < array.size(); i++) | |
64 | { | |
65 | components[i] = ((COSNumber)array.get(i)).floatValue(); | |
66 | } | |
75 | initComponents(array); | |
67 | 76 | patternName = null; |
68 | 77 | } |
69 | 78 | this.colorSpace = colorSpace; |
79 | } | |
80 | ||
81 | private void initComponents(COSArray array) | |
82 | { | |
83 | for (int i = 0; i < components.length; i++) | |
84 | { | |
85 | COSBase base = array.get(i); | |
86 | if (base instanceof COSNumber) | |
87 | { | |
88 | components[i] = ((COSNumber) base).floatValue(); | |
89 | } | |
90 | else | |
91 | { | |
92 | LOG.warn("color component " + i + " in " + array + " isn't a number, ignored"); | |
93 | } | |
94 | } | |
70 | 95 | } |
71 | 96 | |
72 | 97 | /** |
+1
-1
131 | 131 | PDDeviceNProcess process = getProcess(); |
132 | 132 | if (process != null) |
133 | 133 | { |
134 | sb.append(getProcess()); | |
134 | sb.append(process); | |
135 | 135 | sb.append(' '); |
136 | 136 | } |
137 | 137 |
77 | 77 | // flip bit to avoid having to set /BlackIs1 |
78 | 78 | mcios.writeBits(~(image.getRGB(x, y) & 1), 1); |
79 | 79 | } |
80 | if (mcios.getBitOffset() != 0) | |
81 | { | |
82 | mcios.writeBits(0, 8 - mcios.getBitOffset()); | |
80 | int bitOffset = mcios.getBitOffset(); | |
81 | if (bitOffset != 0) | |
82 | { | |
83 | mcios.writeBits(0, 8 - bitOffset); | |
83 | 84 | } |
84 | 85 | } |
85 | 86 | mcios.flush(); |
+8
-4
16 | 16 | package org.apache.pdfbox.pdmodel.graphics.optionalcontent; |
17 | 17 | |
18 | 18 | import java.util.ArrayList; |
19 | import java.util.Collections; | |
19 | 20 | import java.util.List; |
20 | 21 | import org.apache.pdfbox.cos.COSArray; |
21 | 22 | import org.apache.pdfbox.cos.COSBase; |
59 | 60 | */ |
60 | 61 | public List<PDPropertyList> getOCGs() |
61 | 62 | { |
62 | List<PDPropertyList> list = new ArrayList<PDPropertyList>(); | |
63 | 63 | COSBase base = dict.getDictionaryObject(COSName.OCGS); |
64 | 64 | if (base instanceof COSDictionary) |
65 | 65 | { |
66 | list.add(PDPropertyList.create((COSDictionary) base)); | |
66 | return Collections.singletonList(PDPropertyList.create((COSDictionary) base)); | |
67 | 67 | } |
68 | else if (base instanceof COSArray) | |
68 | ||
69 | if (base instanceof COSArray) | |
69 | 70 | { |
70 | 71 | COSArray ar = (COSArray) base; |
72 | List<PDPropertyList> list = new ArrayList<PDPropertyList>(); | |
71 | 73 | for (int i = 0; i < ar.size(); ++i) |
72 | 74 | { |
73 | 75 | COSBase elem = ar.getObject(i); |
76 | 78 | list.add(PDPropertyList.create((COSDictionary) elem)); |
77 | 79 | } |
78 | 80 | } |
81 | return list; | |
79 | 82 | } |
80 | return list; | |
83 | ||
84 | return Collections.emptyList(); | |
81 | 85 | } |
82 | 86 | |
83 | 87 | /** |
59 | 59 | private PDLineDashPattern lineDashPattern = new PDLineDashPattern(); |
60 | 60 | private RenderingIntent renderingIntent; |
61 | 61 | private boolean strokeAdjustment = false; |
62 | private BlendMode blendMode = BlendMode.COMPATIBLE; | |
62 | private BlendMode blendMode = BlendMode.NORMAL; | |
63 | 63 | private PDSoftMask softMask; |
64 | 64 | private double alphaConstant = 1.0; |
65 | 65 | private double nonStrokingAlphaConstant = 1.0; |
+4
-3
21 | 21 | import org.apache.pdfbox.cos.COSBase; |
22 | 22 | import org.apache.pdfbox.cos.COSBoolean; |
23 | 23 | import org.apache.pdfbox.cos.COSDictionary; |
24 | import org.apache.pdfbox.cos.COSInteger; | |
24 | 25 | import org.apache.pdfbox.cos.COSName; |
25 | 26 | import org.apache.pdfbox.pdmodel.common.filespecification.PDFileSpecification; |
26 | 27 | import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination; |
86 | 87 | if (destArray.size() >= 1) |
87 | 88 | { |
88 | 89 | COSBase page = destArray.getObject(0); |
89 | if (!(page instanceof COSDictionary)) | |
90 | if (!(page instanceof COSInteger)) | |
90 | 91 | { |
91 | throw new IllegalArgumentException("Destination of a GoToE action must be " | |
92 | + "a page dictionary object"); | |
92 | throw new IllegalArgumentException( | |
93 | "Destination of a GoToE action must be an integer"); | |
93 | 94 | } |
94 | 95 | } |
95 | 96 | } |
85 | 85 | COSBase page = destArray.getObject(0); |
86 | 86 | if (!(page instanceof COSDictionary)) |
87 | 87 | { |
88 | throw new IllegalArgumentException("Destination of a GoTo action must be " | |
89 | + "a page dictionary object"); | |
88 | throw new IllegalArgumentException( | |
89 | "Destination of a GoTo action must be a page dictionary object"); | |
90 | 90 | } |
91 | 91 | } |
92 | 92 | } |
+8
-0
76 | 76 | this.document = document; |
77 | 77 | } |
78 | 78 | |
79 | @Override | |
80 | public void generateAppearanceStreams() | |
81 | { | |
82 | generateNormalAppearance(); | |
83 | generateRolloverAppearance(); | |
84 | generateDownAppearance(); | |
85 | } | |
86 | ||
79 | 87 | PDAnnotation getAnnotation() |
80 | 88 | { |
81 | 89 | return annotation; |
+5
-12
24 | 24 | import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup; |
25 | 25 | import org.apache.pdfbox.pdmodel.PDAppearanceContentStream; |
26 | 26 | import org.apache.pdfbox.pdmodel.PDDocument; |
27 | import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; | |
27 | 28 | import org.apache.pdfbox.util.Matrix; |
28 | 29 | |
29 | 30 | /** |
46 | 47 | } |
47 | 48 | |
48 | 49 | @Override |
49 | public void generateAppearanceStreams() | |
50 | { | |
51 | generateNormalAppearance(); | |
52 | generateRolloverAppearance(); | |
53 | generateDownAppearance(); | |
54 | } | |
55 | ||
56 | @Override | |
57 | 50 | public void generateNormalAppearance() |
58 | 51 | { |
59 | 52 | PDAnnotationMarkup annotation = (PDAnnotationMarkup) getAnnotation(); |
70 | 63 | |
71 | 64 | PDRectangle rect = getRectangle(); |
72 | 65 | PDRectangle bbox = new PDRectangle(rect.getWidth(), rect.getHeight()); |
66 | PDAppearanceStream pdAppearanceStream = annotation.getNormalAppearanceStream(); | |
73 | 67 | if (!annotation.getCOSObject().containsKey(COSName.RD)) |
74 | 68 | { |
75 | 69 | // Adobe creates the /RD entry with a number that is decided |
81 | 75 | float rd = Math.min(rect.getHeight() / 10, 5); |
82 | 76 | annotation.setRectDifferences(rd); |
83 | 77 | bbox = new PDRectangle(-rd, -rd, rect.getWidth() + 2 * rd, rect.getHeight() + 2 * rd); |
84 | Matrix matrix = annotation.getNormalAppearanceStream().getMatrix(); | |
85 | matrix.transformPoint(rd, rd); | |
86 | annotation.getNormalAppearanceStream().setMatrix(matrix.createAffineTransform()); | |
78 | Matrix matrix = pdAppearanceStream.getMatrix(); | |
79 | pdAppearanceStream.setMatrix(matrix.createAffineTransform()); | |
87 | 80 | PDRectangle rect2 = new PDRectangle(rect.getLowerLeftX() - rd, rect.getLowerLeftY() - rd, |
88 | 81 | rect.getWidth() + 2 * rd, rect.getHeight() + 2 * rd); |
89 | 82 | annotation.setRectangle(rect2); |
90 | 83 | } |
91 | annotation.getNormalAppearanceStream().setBBox(bbox); | |
84 | pdAppearanceStream.setBBox(bbox); | |
92 | 85 | |
93 | 86 | float halfX = rect.getWidth() / 2; |
94 | 87 | float halfY = rect.getHeight() / 2; |
+0
-8
49 | 49 | public PDCircleAppearanceHandler(PDAnnotation annotation, PDDocument document) |
50 | 50 | { |
51 | 51 | super(annotation, document); |
52 | } | |
53 | ||
54 | @Override | |
55 | public void generateAppearanceStreams() | |
56 | { | |
57 | generateNormalAppearance(); | |
58 | generateRolloverAppearance(); | |
59 | generateDownAppearance(); | |
60 | 52 | } |
61 | 53 | |
62 | 54 | @Override |
+0
-8
42 | 42 | public PDFileAttachmentAppearanceHandler(PDAnnotation annotation, PDDocument document) |
43 | 43 | { |
44 | 44 | super(annotation, document); |
45 | } | |
46 | ||
47 | @Override | |
48 | public void generateAppearanceStreams() | |
49 | { | |
50 | generateNormalAppearance(); | |
51 | generateRolloverAppearance(); | |
52 | generateDownAppearance(); | |
53 | 45 | } |
54 | 46 | |
55 | 47 | @Override |
+22
-22
46 | 46 | import org.apache.pdfbox.pdmodel.interactive.annotation.layout.AppearanceStyle; |
47 | 47 | import org.apache.pdfbox.pdmodel.interactive.annotation.layout.PlainText; |
48 | 48 | import org.apache.pdfbox.pdmodel.interactive.annotation.layout.PlainTextFormatter; |
49 | import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; | |
49 | 50 | import org.apache.pdfbox.util.Matrix; |
50 | 51 | |
51 | 52 | public class PDFreeTextAppearanceHandler extends PDAbstractAppearanceHandler |
66 | 67 | public PDFreeTextAppearanceHandler(PDAnnotation annotation, PDDocument document) |
67 | 68 | { |
68 | 69 | super(annotation, document); |
69 | } | |
70 | ||
71 | @Override | |
72 | public void generateAppearanceStreams() | |
73 | { | |
74 | generateNormalAppearance(); | |
75 | generateRolloverAppearance(); | |
76 | generateDownAppearance(); | |
77 | 70 | } |
78 | 71 | |
79 | 72 | @Override |
246 | 239 | float clipHeight = rotation == 90 || rotation == 270 ? |
247 | 240 | borderBox.getWidth() - ab.width * 4 : borderBox.getHeight() - ab.width * 4; |
248 | 241 | extractFontDetails(annotation); |
249 | if (document != null && document.getDocumentCatalog().getAcroForm() != null) | |
250 | { | |
251 | // Try to get font from AcroForm default resources | |
252 | // Sample file: https://gitlab.freedesktop.org/poppler/poppler/issues/6 | |
253 | PDResources defaultResources = document.getDocumentCatalog().getAcroForm().getDefaultResources(); | |
254 | if (defaultResources != null) | |
255 | { | |
256 | PDFont defaultResourcesFont = defaultResources.getFont(fontName); | |
257 | if (defaultResourcesFont != null) | |
242 | if (document != null) | |
243 | { | |
244 | PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); | |
245 | if (acroForm != null) | |
246 | { | |
247 | // Try to get font from AcroForm default resources | |
248 | // Sample file: https://gitlab.freedesktop.org/poppler/poppler/issues/6 | |
249 | PDResources defaultResources = acroForm.getDefaultResources(); | |
250 | if (defaultResources != null) | |
258 | 251 | { |
259 | font = defaultResourcesFont; | |
252 | PDFont defaultResourcesFont = defaultResources.getFont(fontName); | |
253 | if (defaultResourcesFont != null) | |
254 | { | |
255 | font = defaultResourcesFont; | |
256 | } | |
260 | 257 | } |
261 | 258 | } |
262 | 259 | } |
440 | 437 | private void extractFontDetails(PDAnnotationMarkup annotation) |
441 | 438 | { |
442 | 439 | String defaultAppearance = annotation.getDefaultAppearance(); |
443 | if (defaultAppearance == null && document != null && | |
444 | document.getDocumentCatalog().getAcroForm() != null) | |
445 | { | |
446 | defaultAppearance = document.getDocumentCatalog().getAcroForm().getDefaultAppearance(); | |
440 | if (defaultAppearance == null && document != null) | |
441 | { | |
442 | PDAcroForm pdAcroForm = document.getDocumentCatalog().getAcroForm(); | |
443 | if (pdAcroForm != null) | |
444 | { | |
445 | defaultAppearance = pdAcroForm.getDefaultAppearance(); | |
446 | } | |
447 | 447 | } |
448 | 448 | if (defaultAppearance == null) |
449 | 449 | { |
+0
-8
52 | 52 | } |
53 | 53 | |
54 | 54 | @Override |
55 | public void generateAppearanceStreams() | |
56 | { | |
57 | generateNormalAppearance(); | |
58 | generateRolloverAppearance(); | |
59 | generateDownAppearance(); | |
60 | } | |
61 | ||
62 | @Override | |
63 | 55 | public void generateNormalAppearance() |
64 | 56 | { |
65 | 57 | PDAnnotationTextMarkup annotation = (PDAnnotationTextMarkup) getAnnotation(); |
+0
-8
42 | 42 | public PDInkAppearanceHandler(PDAnnotation annotation, PDDocument document) |
43 | 43 | { |
44 | 44 | super(annotation, document); |
45 | } | |
46 | ||
47 | @Override | |
48 | public void generateAppearanceStreams() | |
49 | { | |
50 | generateNormalAppearance(); | |
51 | generateRolloverAppearance(); | |
52 | generateDownAppearance(); | |
53 | 45 | } |
54 | 46 | |
55 | 47 | @Override |
+5
-12
49 | 49 | } |
50 | 50 | |
51 | 51 | @Override |
52 | public void generateAppearanceStreams() | |
53 | { | |
54 | generateNormalAppearance(); | |
55 | generateRolloverAppearance(); | |
56 | generateDownAppearance(); | |
57 | } | |
58 | ||
59 | @Override | |
60 | 52 | public void generateNormalAppearance() |
61 | 53 | { |
62 | 54 | PDAnnotationLine annotation = (PDAnnotationLine) getAnnotation(); |
112 | 104 | // arrow length is 9 * width at about 30° => 10 * width seems to be enough |
113 | 105 | // but need to consider /LL, /LLE and /LLO too |
114 | 106 | //TODO find better way to calculate padding |
115 | rect.setLowerLeftX(Math.min(minX - Math.max(lineEndingSize * 10, Math.abs(llo+ll+lle)), rect.getLowerLeftX())); | |
116 | rect.setLowerLeftY(Math.min(minY - Math.max(lineEndingSize * 10, Math.abs(llo+ll+lle)), rect.getLowerLeftY())); | |
117 | rect.setUpperRightX(Math.max(maxX + Math.max(lineEndingSize * 10, Math.abs(llo+ll+lle)), rect.getUpperRightX())); | |
118 | rect.setUpperRightY(Math.max(maxY + Math.max(lineEndingSize * 10, Math.abs(llo+ll+lle)), rect.getUpperRightY())); | |
107 | float max = Math.max(lineEndingSize * 10, Math.abs(llo+ll+lle)); | |
108 | rect.setLowerLeftX(Math.min(minX - max, rect.getLowerLeftX())); | |
109 | rect.setLowerLeftY(Math.min(minY - max, rect.getLowerLeftY())); | |
110 | rect.setUpperRightX(Math.max(maxX + max, rect.getUpperRightX())); | |
111 | rect.setUpperRightY(Math.max(maxY + max, rect.getUpperRightY())); | |
119 | 112 | |
120 | 113 | annotation.setRectangle(rect); |
121 | 114 |
+0
-8
49 | 49 | public PDLinkAppearanceHandler(PDAnnotation annotation, PDDocument document) |
50 | 50 | { |
51 | 51 | super(annotation, document); |
52 | } | |
53 | ||
54 | @Override | |
55 | public void generateAppearanceStreams() | |
56 | { | |
57 | generateNormalAppearance(); | |
58 | generateRolloverAppearance(); | |
59 | generateDownAppearance(); | |
60 | 52 | } |
61 | 53 | |
62 | 54 | @Override |
+0
-8
49 | 49 | public PDPolygonAppearanceHandler(PDAnnotation annotation, PDDocument document) |
50 | 50 | { |
51 | 51 | super(annotation, document); |
52 | } | |
53 | ||
54 | @Override | |
55 | public void generateAppearanceStreams() | |
56 | { | |
57 | generateNormalAppearance(); | |
58 | generateRolloverAppearance(); | |
59 | generateDownAppearance(); | |
60 | 52 | } |
61 | 53 | |
62 | 54 | @Override |
+0
-8
49 | 49 | public PDPolylineAppearanceHandler(PDAnnotation annotation, PDDocument document) |
50 | 50 | { |
51 | 51 | super(annotation, document); |
52 | } | |
53 | ||
54 | @Override | |
55 | public void generateAppearanceStreams() | |
56 | { | |
57 | generateNormalAppearance(); | |
58 | generateRolloverAppearance(); | |
59 | generateDownAppearance(); | |
60 | 52 | } |
61 | 53 | |
62 | 54 | @Override |
+0
-8
30 | 30 | } |
31 | 31 | |
32 | 32 | @Override |
33 | public void generateAppearanceStreams() | |
34 | { | |
35 | generateNormalAppearance(); | |
36 | generateRolloverAppearance(); | |
37 | generateDownAppearance(); | |
38 | } | |
39 | ||
40 | @Override | |
41 | 33 | public void generateNormalAppearance() |
42 | 34 | { |
43 | 35 | // TODO to be implemented |
+0
-8
50 | 50 | public PDSquareAppearanceHandler(PDAnnotation annotation, PDDocument document) |
51 | 51 | { |
52 | 52 | super(annotation, document); |
53 | } | |
54 | ||
55 | @Override | |
56 | public void generateAppearanceStreams() | |
57 | { | |
58 | generateNormalAppearance(); | |
59 | generateRolloverAppearance(); | |
60 | generateDownAppearance(); | |
61 | 53 | } |
62 | 54 | |
63 | 55 | @Override |
+0
-8
55 | 55 | } |
56 | 56 | |
57 | 57 | @Override |
58 | public void generateAppearanceStreams() | |
59 | { | |
60 | generateNormalAppearance(); | |
61 | generateRolloverAppearance(); | |
62 | generateDownAppearance(); | |
63 | } | |
64 | ||
65 | @Override | |
66 | 58 | public void generateNormalAppearance() |
67 | 59 | { |
68 | 60 | PDAnnotationTextMarkup annotation = (PDAnnotationTextMarkup) getAnnotation(); |
+0
-8
41 | 41 | public PDStrikeoutAppearanceHandler(PDAnnotation annotation, PDDocument document) |
42 | 42 | { |
43 | 43 | super(annotation, document); |
44 | } | |
45 | ||
46 | @Override | |
47 | public void generateAppearanceStreams() | |
48 | { | |
49 | generateNormalAppearance(); | |
50 | generateRolloverAppearance(); | |
51 | generateDownAppearance(); | |
52 | 44 | } |
53 | 45 | |
54 | 46 | @Override |
+16
-20
73 | 73 | public PDTextAppearanceHandler(PDAnnotation annotation, PDDocument document) |
74 | 74 | { |
75 | 75 | super(annotation, document); |
76 | } | |
77 | ||
78 | @Override | |
79 | public void generateAppearanceStreams() | |
80 | { | |
81 | generateNormalAppearance(); | |
82 | generateRolloverAppearance(); | |
83 | generateDownAppearance(); | |
84 | 76 | } |
85 | 77 | |
86 | 78 | @Override |
228 | 220 | |
229 | 221 | contentStream.setLineCapStyle(0); |
230 | 222 | contentStream.setLineWidth(0.61f); // value from Adobe |
231 | contentStream.addRect(1, 1, bbox.getWidth() - 2, bbox.getHeight() - 2); | |
232 | contentStream.moveTo(bbox.getWidth() / 4, bbox.getHeight() / 7 * 2); | |
233 | contentStream.lineTo(bbox.getWidth() * 3 / 4 - 1, bbox.getHeight() / 7 * 2); | |
234 | contentStream.moveTo(bbox.getWidth() / 4, bbox.getHeight() / 7 * 3); | |
235 | contentStream.lineTo(bbox.getWidth() * 3 / 4 - 1, bbox.getHeight() / 7 * 3); | |
236 | contentStream.moveTo(bbox.getWidth() / 4, bbox.getHeight() / 7 * 4); | |
237 | contentStream.lineTo(bbox.getWidth() * 3 / 4 - 1, bbox.getHeight() / 7 * 4); | |
238 | contentStream.moveTo(bbox.getWidth() / 4, bbox.getHeight() / 7 * 5); | |
239 | contentStream.lineTo(bbox.getWidth() * 3 / 4 - 1, bbox.getHeight() / 7 * 5); | |
223 | float width = bbox.getWidth(); | |
224 | float height = bbox.getHeight(); | |
225 | contentStream.addRect(1, 1, width - 2, height - 2); | |
226 | contentStream.moveTo(width / 4, height / 7 * 2); | |
227 | contentStream.lineTo(width * 3 / 4 - 1, height / 7 * 2); | |
228 | contentStream.moveTo(width / 4, height / 7 * 3); | |
229 | contentStream.lineTo(width * 3 / 4 - 1, height / 7 * 3); | |
230 | contentStream.moveTo(width / 4, height / 7 * 4); | |
231 | contentStream.lineTo(width * 3 / 4 - 1, height / 7 * 4); | |
232 | contentStream.moveTo(width / 4, height / 7 * 5); | |
233 | contentStream.lineTo(width * 3 / 4 - 1, height / 7 * 5); | |
240 | 234 | contentStream.fillAndStroke(); |
241 | 235 | } |
242 | 236 | |
268 | 262 | gs.setBlendMode(BlendMode.NORMAL); |
269 | 263 | contentStream.setGraphicsStateParameters(gs); |
270 | 264 | contentStream.setNonStrokingColor(1f); |
271 | drawCircle(contentStream, bbox.getWidth() / 2, bbox.getHeight() / 2, smallR); | |
265 | float width = bbox.getWidth() / 2; | |
266 | float height = bbox.getHeight() / 2; | |
267 | drawCircle(contentStream, width, height, smallR); | |
272 | 268 | contentStream.fill(); |
273 | 269 | contentStream.restoreGraphicsState(); |
274 | 270 | |
275 | 271 | contentStream.setLineWidth(0.59f); // value from Adobe |
276 | drawCircle(contentStream, bbox.getWidth() / 2, bbox.getHeight() / 2, smallR); | |
277 | drawCircle2(contentStream, bbox.getWidth() / 2, bbox.getHeight() / 2, largeR); | |
272 | drawCircle(contentStream, width, height, smallR); | |
273 | drawCircle2(contentStream, width, height, largeR); | |
278 | 274 | contentStream.fillAndStroke(); |
279 | 275 | } |
280 | 276 |
+0
-8
41 | 41 | public PDUnderlineAppearanceHandler(PDAnnotation annotation, PDDocument document) |
42 | 42 | { |
43 | 43 | super(annotation, document); |
44 | } | |
45 | ||
46 | @Override | |
47 | public void generateAppearanceStreams() | |
48 | { | |
49 | generateNormalAppearance(); | |
50 | generateRolloverAppearance(); | |
51 | generateDownAppearance(); | |
52 | 44 | } |
53 | 45 | |
54 | 46 | @Override |
39 | 39 | import org.apache.pdfbox.pdmodel.PDPage; |
40 | 40 | import org.apache.pdfbox.pdmodel.PDPageContentStream; |
41 | 41 | import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode; |
42 | import org.apache.pdfbox.pdmodel.PDPageTree; | |
42 | 43 | import org.apache.pdfbox.pdmodel.PDResources; |
43 | 44 | import org.apache.pdfbox.pdmodel.common.COSArrayList; |
44 | 45 | import org.apache.pdfbox.pdmodel.common.COSObjectable; |
61 | 62 | public final class PDAcroForm implements COSObjectable |
62 | 63 | { |
63 | 64 | private static final Log LOG = LogFactory.getLog(PDAcroForm.class); |
64 | ||
65 | ||
65 | 66 | private static final int FLAG_SIGNATURES_EXIST = 1; |
66 | 67 | private static final int FLAG_APPEND_ONLY = 1 << 1; |
67 | 68 | |
68 | 69 | private final PDDocument document; |
69 | 70 | private final COSDictionary dictionary; |
70 | ||
71 | ||
71 | 72 | private Map<String, PDField> fieldCache; |
72 | 73 | |
73 | 74 | private ScriptingHandler scriptingHandler; |
105 | 106 | { |
106 | 107 | return document; |
107 | 108 | } |
108 | ||
109 | ||
109 | 110 | @Override |
110 | 111 | public COSDictionary getCOSObject() |
111 | 112 | { |
150 | 151 | FDFDictionary fdfDict = new FDFDictionary(); |
151 | 152 | catalog.setFDF(fdfDict); |
152 | 153 | |
153 | List<FDFField> fdfFields = new ArrayList<FDFField>(); | |
154 | 154 | List<PDField> fields = getFields(); |
155 | List<FDFField> fdfFields = new ArrayList<FDFField>(fields.size()); | |
155 | 156 | for (PDField field : fields) |
156 | 157 | { |
157 | 158 | fdfFields.add(field.exportFDF()); |
158 | 159 | } |
159 | ||
160 | ||
160 | 161 | fdfDict.setID(document.getDocument().getDocumentID()); |
161 | ||
162 | ||
162 | 163 | if (!fdfFields.isEmpty()) |
163 | 164 | { |
164 | 165 | fdfDict.setFields(fdfFields); |
188 | 189 | LOG.warn("Flatten for a dynamix XFA form is not supported"); |
189 | 190 | return; |
190 | 191 | } |
191 | ||
192 | ||
192 | 193 | List<PDField> fields = new ArrayList<PDField>(); |
193 | 194 | for (PDField field: getFieldTree()) |
194 | 195 | { |
196 | 197 | } |
197 | 198 | flatten(fields, false); |
198 | 199 | } |
199 | ||
200 | ||
200 | ||
201 | ||
201 | 202 | /** |
202 | 203 | * This will flatten the specified form fields. |
203 | 204 | * |
218 | 219 | { |
219 | 220 | return; |
220 | 221 | } |
221 | ||
222 | ||
222 | 223 | if (!refreshAppearances && getNeedAppearances()) |
223 | 224 | { |
224 | 225 | LOG.warn("acroForm.getNeedAppearances() returns true, " + |
234 | 235 | LOG.warn("Flatten for a dynamix XFA form is not supported"); |
235 | 236 | return; |
236 | 237 | } |
237 | ||
238 | ||
238 | 239 | // refresh the appearances if set |
239 | 240 | if (refreshAppearances) |
240 | 241 | { |
242 | 243 | } |
243 | 244 | |
244 | 245 | // get the widgets per page |
245 | Map<COSDictionary,Set<COSDictionary>> pagesWidgetsMap = buildPagesWidgetsMap(fields); | |
246 | ||
246 | PDPageTree pages = document.getPages(); | |
247 | Map<COSDictionary,Set<COSDictionary>> pagesWidgetsMap = buildPagesWidgetsMap(fields, pages); | |
248 | ||
247 | 249 | // preserve all non widget annotations |
248 | for (PDPage page : document.getPages()) | |
250 | for (PDPage page : pages) | |
249 | 251 | { |
250 | 252 | Set<COSDictionary> widgetsForPageMap = pagesWidgetsMap.get(page.getCOSObject()); |
251 | 253 | |
252 | 254 | // indicates if the original content stream |
253 | 255 | // has been wrapped in a q...Q pair. |
254 | 256 | boolean isContentStreamWrapped = false; |
255 | ||
257 | ||
256 | 258 | List<PDAnnotation> annotations = new ArrayList<PDAnnotation>(); |
257 | ||
259 | ||
258 | 260 | for (PDAnnotation annotation: page.getAnnotations()) |
259 | 261 | { |
260 | 262 | if (widgetsForPageMap == null || !widgetsForPageMap.contains(annotation.getCOSObject())) |
261 | 263 | { |
262 | annotations.add(annotation); | |
264 | annotations.add(annotation); | |
263 | 265 | } |
264 | 266 | else if (isVisibleAnnotation(annotation)) |
265 | 267 | { |
292 | 294 | } |
293 | 295 | page.setAnnotations(annotations); |
294 | 296 | } |
295 | ||
297 | ||
296 | 298 | // remove the fields |
297 | 299 | removeFields(fields); |
298 | ||
300 | ||
299 | 301 | // remove XFA for hybrid forms |
300 | 302 | dictionary.removeItem(COSName.XFA); |
301 | 303 | |
355 | 357 | } |
356 | 358 | } |
357 | 359 | } |
358 | ||
359 | ||
360 | ||
361 | ||
360 | 362 | /** |
361 | 363 | * This will return all of the documents root fields. |
362 | 364 | * |
380 | 382 | List<PDField> pdFields = new ArrayList<PDField>(); |
381 | 383 | for (int i = 0; i < cosFields.size(); i++) |
382 | 384 | { |
383 | COSDictionary element = (COSDictionary) cosFields.getObject(i); | |
384 | if (element != null) | |
385 | { | |
386 | PDField field = PDField.fromDictionary(this, element, null); | |
385 | COSBase element = cosFields.getObject(i); | |
386 | if (element instanceof COSDictionary) | |
387 | { | |
388 | PDField field = PDField.fromDictionary(this, (COSDictionary) element, null); | |
387 | 389 | if (field != null) |
388 | 390 | { |
389 | 391 | pdFields.add(field); |
402 | 404 | { |
403 | 405 | dictionary.setItem(COSName.FIELDS, COSArrayList.converterToCOSArray(fields)); |
404 | 406 | } |
405 | ||
407 | ||
406 | 408 | /** |
407 | 409 | * Returns an iterator which walks all fields in the field tree, in order. |
408 | 410 | */ |
417 | 419 | public PDFieldTree getFieldTree() |
418 | 420 | { |
419 | 421 | return new PDFieldTree(this); |
420 | } | |
421 | ||
422 | } | |
423 | ||
422 | 424 | /** |
423 | 425 | * This will tell this form to cache the fields into a Map structure |
424 | 426 | * for fast access via the getField method. The default is false. You would |
476 | 478 | return field; |
477 | 479 | } |
478 | 480 | } |
479 | ||
481 | ||
480 | 482 | return null; |
481 | 483 | } |
482 | 484 | |
521 | 523 | { |
522 | 524 | dictionary.setBoolean(COSName.NEED_APPEARANCES, value); |
523 | 525 | } |
524 | ||
526 | ||
525 | 527 | /** |
526 | 528 | * This will get the default resources for the AcroForm. |
527 | 529 | * |
567 | 569 | { |
568 | 570 | return hasXFA() && getFields().isEmpty(); |
569 | 571 | } |
570 | ||
572 | ||
571 | 573 | /** |
572 | 574 | * Get the XFA resource, the XFA resource is only used for PDF 1.5+ forms. |
573 | 575 | * |
593 | 595 | { |
594 | 596 | dictionary.setItem(COSName.XFA, xfa); |
595 | 597 | } |
596 | ||
598 | ||
597 | 599 | /** |
598 | 600 | * This will get the document-wide default value for the quadding/justification of variable text |
599 | 601 | * fields. |
686 | 688 | { |
687 | 689 | this.scriptingHandler = scriptingHandler; |
688 | 690 | } |
689 | ||
691 | ||
690 | 692 | private Matrix resolveTransformationMatrix(PDAnnotation annotation, PDAppearanceStream appearanceStream) |
691 | 693 | { |
692 | 694 | // 1st step transform appearance stream bbox with appearance stream matrix |
712 | 714 | private Rectangle2D getTransformedAppearanceBBox(PDAppearanceStream appearanceStream) |
713 | 715 | { |
714 | 716 | Matrix appearanceStreamMatrix = appearanceStream.getMatrix(); |
715 | PDRectangle appearanceStreamBBox = appearanceStream.getBBox(); | |
717 | PDRectangle appearanceStreamBBox = appearanceStream.getBBox(); | |
716 | 718 | GeneralPath transformedAppearanceBox = appearanceStreamBBox.transform(appearanceStreamMatrix); |
717 | 719 | return transformedAppearanceBox.getBounds2D(); |
718 | 720 | } |
719 | 721 | |
720 | private Map<COSDictionary,Set<COSDictionary>> buildPagesWidgetsMap(List<PDField> fields) throws IOException | |
722 | private Map<COSDictionary,Set<COSDictionary>> buildPagesWidgetsMap( | |
723 | List<PDField> fields, PDPageTree pages) throws IOException | |
721 | 724 | { |
722 | 725 | Map<COSDictionary,Set<COSDictionary>> pagesAnnotationsMap = |
723 | 726 | new HashMap<COSDictionary, Set<COSDictionary>>(); |
724 | 727 | boolean hasMissingPageRef = false; |
725 | ||
728 | ||
726 | 729 | for (PDField field : fields) |
727 | 730 | { |
728 | 731 | List<PDAnnotationWidget> widgets = field.getWidgets(); |
748 | 751 | // If there is a widget with a missing page reference we need to build the map reverse i.e. |
749 | 752 | // from the annotations to the widget. |
750 | 753 | LOG.warn("There has been a widget with a missing page reference, will check all page annotations"); |
751 | for (PDPage page : document.getPages()) | |
754 | for (PDPage page : pages) | |
752 | 755 | { |
753 | 756 | for (PDAnnotation annotation : page.getAnnotations()) |
754 | 757 | { |
765 | 768 | private void fillPagesAnnotationMap(Map<COSDictionary, Set<COSDictionary>> pagesAnnotationsMap, |
766 | 769 | PDPage page, PDAnnotationWidget widget) |
767 | 770 | { |
768 | if (pagesAnnotationsMap.get(page.getCOSObject()) == null) | |
769 | { | |
770 | Set<COSDictionary> widgetsForPage = new HashSet<COSDictionary>(); | |
771 | Set<COSDictionary> widgetsForPage = pagesAnnotationsMap.get(page.getCOSObject()); | |
772 | if (widgetsForPage == null) | |
773 | { | |
774 | widgetsForPage = new HashSet<COSDictionary>(); | |
771 | 775 | widgetsForPage.add(widget.getCOSObject()); |
772 | 776 | pagesAnnotationsMap.put(page.getCOSObject(), widgetsForPage); |
773 | 777 | } |
774 | 778 | else |
775 | 779 | { |
776 | Set<COSDictionary> widgetsForPage = pagesAnnotationsMap.get(page.getCOSObject()); | |
777 | 780 | widgetsForPage.add(widget.getCOSObject()); |
778 | 781 | } |
779 | 782 | } |
+4
-4
85 | 85 | super.importFDF(fdfField); |
86 | 86 | |
87 | 87 | List<FDFField> fdfKids = fdfField.getKids(); |
88 | if (fdfKids == null) | |
89 | { | |
90 | return; | |
91 | } | |
88 | 92 | List<PDField> children = getChildren(); |
89 | if (fdfKids == null) | |
90 | { | |
91 | return; | |
92 | } | |
93 | 93 | for (int i = 0; i < fdfKids.size(); i++) |
94 | 94 | { |
95 | 95 | for (PDField pdChild : children) |
18 | 18 | import java.io.IOException; |
19 | 19 | import java.util.ArrayList; |
20 | 20 | import java.util.List; |
21 | import java.util.Set; | |
22 | 21 | |
23 | 22 | import org.apache.pdfbox.cos.COSDictionary; |
24 | 23 | import org.apache.pdfbox.cos.COSName; |
123 | 122 | */ |
124 | 123 | public List<String> getSelectedExportValues() throws IOException |
125 | 124 | { |
126 | Set<String> onValues = getOnValues(); | |
127 | 125 | List<String> exportValues = getExportValues(); |
128 | 126 | List<String> selectedExportValues = new ArrayList<String>(); |
129 | 127 | if (exportValues.isEmpty()) |
135 | 133 | { |
136 | 134 | String fieldValue = getValue(); |
137 | 135 | int idx = 0; |
138 | for (String onValue : onValues) | |
136 | for (String onValue : getOnValues()) | |
139 | 137 | { |
140 | 138 | if (onValue.compareTo(fieldValue) == 0) |
141 | 139 | { |
+3
-2
50 | 50 | { |
51 | 51 | super(acroForm); |
52 | 52 | getCOSObject().setItem(COSName.FT, COSName.SIG); |
53 | getWidgets().get(0).setLocked(true); | |
54 | getWidgets().get(0).setPrinted(true); | |
53 | PDAnnotationWidget firstWidget = getWidgets().get(0); | |
54 | firstWidget.setLocked(true); | |
55 | firstWidget.setPrinted(true); | |
55 | 56 | setPartialName(generatePartialName()); |
56 | 57 | } |
57 | 58 |
+2
-2
100 | 100 | { |
101 | 101 | super.importFDF(fdfField); |
102 | 102 | |
103 | Integer f = fdfField.getWidgetFieldFlags(); | |
103 | 104 | for (PDAnnotationWidget widget : getWidgets()) |
104 | 105 | { |
105 | int annotFlags = widget.getAnnotationFlags(); | |
106 | Integer f = fdfField.getWidgetFieldFlags(); | |
107 | 106 | if (f != null) |
108 | 107 | { |
109 | 108 | widget.setAnnotationFlags(f); |
112 | 111 | { |
113 | 112 | // these are supposed to be ignored if the F is set. |
114 | 113 | Integer setF = fdfField.getSetWidgetFieldFlags(); |
114 | int annotFlags = widget.getAnnotationFlags(); | |
115 | 115 | if (setF != null) |
116 | 116 | { |
117 | 117 | annotFlags = annotFlags | setF; |
256 | 256 | } |
257 | 257 | |
258 | 258 | // draw to graphics using PDFRender |
259 | AffineTransform transform = (AffineTransform)graphics2D.getTransform().clone(); | |
259 | AffineTransform transform = graphics2D.getTransform(); | |
260 | 260 | graphics2D.setBackground(Color.WHITE); |
261 | 261 | renderer.setSubsamplingAllowed(subsamplingAllowed); |
262 | 262 | renderer.setRenderingHints(renderingHints); |
527 | 527 | |
528 | 528 | private RenderingHints createDefaultRenderingHints(Graphics2D graphics) |
529 | 529 | { |
530 | boolean isBitonal = isBitonal(graphics); | |
530 | 531 | RenderingHints r = new RenderingHints(null); |
531 | r.put(RenderingHints.KEY_INTERPOLATION, isBitonal(graphics) ? | |
532 | r.put(RenderingHints.KEY_INTERPOLATION, isBitonal ? | |
532 | 533 | RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR : |
533 | 534 | RenderingHints.VALUE_INTERPOLATION_BICUBIC); |
534 | 535 | r.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); |
535 | r.put(RenderingHints.KEY_ANTIALIASING, isBitonal(graphics) ? | |
536 | r.put(RenderingHints.KEY_ANTIALIASING, isBitonal ? | |
536 | 537 | RenderingHints.VALUE_ANTIALIAS_OFF : |
537 | 538 | RenderingHints.VALUE_ANTIALIAS_ON); |
538 | 539 | return r; |
39 | 39 | import java.awt.geom.Point2D; |
40 | 40 | import java.awt.geom.Rectangle2D; |
41 | 41 | import java.awt.image.BufferedImage; |
42 | import java.awt.image.ByteLookupTable; | |
42 | 43 | import java.awt.image.ColorModel; |
43 | 44 | import java.awt.image.ComponentColorModel; |
44 | 45 | import java.awt.image.DataBuffer; |
45 | 46 | import java.awt.image.DataBufferByte; |
47 | import java.awt.image.LookupOp; | |
48 | import java.awt.image.LookupTable; | |
46 | 49 | import java.awt.image.Raster; |
47 | 50 | import java.awt.image.WritableRaster; |
48 | 51 | import java.io.IOException; |
113 | 116 | * If you want to do custom graphics processing rather than Graphics2D rendering, then you should |
114 | 117 | * subclass {@link PDFGraphicsStreamEngine} instead. Subclassing PageDrawer is only suitable for |
115 | 118 | * cases where the goal is to render onto a {@link Graphics2D} surface. In that case you'll also |
116 | * have to subclass {@link PDFRenderer} and modify | |
117 | * {@link PDFRenderer#createPageDrawer(PageDrawerParameters)}. | |
119 | * have to subclass {@link PDFRenderer} and override | |
120 | * {@link PDFRenderer#createPageDrawer(PageDrawerParameters)}. See the <i>OpaquePDFRenderer.java</i> | |
121 | * example in the source code download on how to do this. | |
118 | 122 | * |
119 | 123 | * @author Ben Litchfield |
120 | 124 | */ |
166 | 170 | private final RenderDestination destination; |
167 | 171 | private final RenderingHints renderingHints; |
168 | 172 | private final float imageDownscalingOptimizationThreshold; |
173 | private LookupTable invTable = null; | |
169 | 174 | |
170 | 175 | /** |
171 | 176 | * Default annotations filter, returns all annotations |
488 | 493 | at.concatenate(font.getFontMatrix().createAffineTransform()); |
489 | 494 | |
490 | 495 | Glyph2D glyph2D = createGlyph2D(font); |
491 | drawGlyph2D(glyph2D, font, code, displacement, at); | |
496 | try | |
497 | { | |
498 | drawGlyph2D(glyph2D, font, code, displacement, at); | |
499 | } | |
500 | catch (IOException ex) | |
501 | { | |
502 | LOG.error("Could not draw glyph for code " + code + " at position (" + | |
503 | at.getTranslateX() + "," + at.getTranslateY() + ")", ex); | |
504 | } | |
492 | 505 | } |
493 | 506 | |
494 | 507 | /** |
1153 | 1166 | |
1154 | 1167 | // draw the mask |
1155 | 1168 | BufferedImage mask = pdImage.getImage(); |
1169 | AffineTransform imageTransform = new AffineTransform(at); | |
1170 | imageTransform.scale(1.0 / mask.getWidth(), -1.0 / mask.getHeight()); | |
1171 | imageTransform.translate(0, -mask.getHeight()); | |
1172 | AffineTransform full = new AffineTransform(g.getTransform()); | |
1173 | full.concatenate(imageTransform); | |
1174 | Matrix m = new Matrix(full); | |
1175 | double scaleX = Math.abs(m.getScalingFactorX()); | |
1176 | double scaleY = Math.abs(m.getScalingFactorY()); | |
1177 | ||
1178 | boolean smallMask = mask.getWidth() <= 8 && mask.getHeight() <= 8; | |
1179 | if (!smallMask) | |
1180 | { | |
1181 | // PDFBOX-5403: | |
1182 | // The mask is copied to RGB because this supports a smooth scaling, so we | |
1183 | // get a mask with 255 values instead of just 0 and 255. | |
1184 | // Inverting is done because when we don't do it, the getScaledInstance() call | |
1185 | // produces a black line in many masks. With the inversion we have a white line | |
1186 | // which is neutral. Because of the inversion we don't have to substract from 255 | |
1187 | // in the "apply the mask" segment when rasterPixel[3] is assigned. | |
1188 | ||
1189 | // The inversion is not done for very small ones, because of | |
1190 | // PDFBOX-2171-002-002710-p14.pdf where the "New Harmony Consolidated" and | |
1191 | // "Sailor Springs" patterns became almost invisible. | |
1192 | // (We may have to decide this differently in the future, e.g. on b/w relationship) | |
1193 | BufferedImage tmp = new BufferedImage(mask.getWidth(), mask.getHeight(), BufferedImage.TYPE_INT_RGB); | |
1194 | mask = new LookupOp(getInvLookupTable(), graphics.getRenderingHints()).filter(mask, tmp); | |
1195 | } | |
1196 | ||
1156 | 1197 | BufferedImage renderedMask = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); |
1157 | 1198 | g = (Graphics2D) renderedMask.getGraphics(); |
1158 | 1199 | g.translate(-bounds.getMinX(), -bounds.getMinY()); |
1159 | AffineTransform imageTransform = new AffineTransform(at); | |
1160 | imageTransform.scale(1.0 / mask.getWidth(), -1.0 / mask.getHeight()); | |
1161 | imageTransform.translate(0, -mask.getHeight()); | |
1162 | 1200 | g.setRenderingHints(graphics.getRenderingHints()); |
1163 | g.drawImage(mask, imageTransform, null); | |
1201 | ||
1202 | if (smallMask) | |
1203 | { | |
1204 | g.drawImage(mask, imageTransform, null); | |
1205 | } | |
1206 | else | |
1207 | { | |
1208 | while (scaleX < 0.25) | |
1209 | { | |
1210 | scaleX *= 2.0; | |
1211 | } | |
1212 | while (scaleY < 0.25) | |
1213 | { | |
1214 | scaleY *= 2.0; | |
1215 | } | |
1216 | int w2 = (int) Math.round(mask.getWidth() * scaleX); | |
1217 | int h2 = (int) Math.round(mask.getHeight() * scaleY); | |
1218 | ||
1219 | Image scaledMask = mask.getScaledInstance(w2, h2, Image.SCALE_SMOOTH); | |
1220 | imageTransform.scale(1f / Math.abs(scaleX), 1f / Math.abs(scaleY)); | |
1221 | g.drawImage(scaledMask, imageTransform, null); | |
1222 | } | |
1164 | 1223 | g.dispose(); |
1165 | 1224 | |
1166 | 1225 | // apply the mask |
1167 | final int[] transparent = new int[4]; | |
1168 | 1226 | int[] alphaPixel = null; |
1227 | int[] rasterPixel = null; | |
1169 | 1228 | WritableRaster raster = renderedPaint.getRaster(); |
1170 | 1229 | WritableRaster alpha = renderedMask.getRaster(); |
1171 | 1230 | for (int y = 0; y < h; y++) |
1173 | 1232 | for (int x = 0; x < w; x++) |
1174 | 1233 | { |
1175 | 1234 | alphaPixel = alpha.getPixel(x, y, alphaPixel); |
1176 | if (alphaPixel[0] == 255) | |
1177 | { | |
1178 | raster.setPixel(x, y, transparent); | |
1179 | } | |
1235 | rasterPixel = raster.getPixel(x, y, rasterPixel); | |
1236 | rasterPixel[3] = alphaPixel[0]; | |
1237 | raster.setPixel(x, y, rasterPixel); | |
1180 | 1238 | } |
1181 | 1239 | } |
1182 | 1240 | |
1668 | 1726 | Matrix transform = Matrix.concatenate(ctm, form.getMatrix()); |
1669 | 1727 | |
1670 | 1728 | // transform the bbox |
1671 | GeneralPath transformedBox = form.getBBox().transform(transform); | |
1729 | PDRectangle formBBox = form.getBBox(); | |
1730 | if (formBBox == null) | |
1731 | { | |
1732 | // PDFBOX-5471 | |
1733 | // check done here and not in caller to avoid getBBox() creating rectangle twice | |
1734 | LOG.warn("transparency group ignored because BBox is null"); | |
1735 | formBBox = new PDRectangle(); | |
1736 | } | |
1737 | GeneralPath transformedBox = formBBox.transform(transform); | |
1672 | 1738 | |
1673 | 1739 | // clip the bbox to prevent giant bboxes from consuming all memory |
1674 | 1740 | Area transformed = new Area(transformedBox); |
2074 | 2140 | } |
2075 | 2141 | return true; |
2076 | 2142 | } |
2143 | ||
2144 | private LookupTable getInvLookupTable() | |
2145 | { | |
2146 | if (invTable == null) | |
2147 | { | |
2148 | byte[] inv = new byte[256]; | |
2149 | for (int i = 0; i < inv.length; i++) | |
2150 | { | |
2151 | inv[i] = (byte) (255 - i); | |
2152 | } | |
2153 | invTable = new ByteLookupTable(0, inv); | |
2154 | } | |
2155 | return invTable; | |
2156 | } | |
2077 | 2157 | } |
399 | 399 | c[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7]; |
400 | 400 | c[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8]; |
401 | 401 | } |
402 | ||
402 | 403 | /** |
403 | 404 | * Transforms the given point by this matrix. |
404 | 405 | * |
422 | 423 | * |
423 | 424 | * @param x x-coordinate |
424 | 425 | * @param y y-coordinate |
426 | * | |
427 | * @return the transformed point. | |
425 | 428 | */ |
426 | 429 | public Point2D.Float transformPoint(float x, float y) |
427 | 430 | { |
435 | 438 | } |
436 | 439 | |
437 | 440 | /** |
438 | * Transforms the given point by this matrix. | |
441 | * Transforms the given vector by this matrix. | |
439 | 442 | * |
440 | 443 | * @param vector 2D vector |
444 | * | |
445 | * @return the transformed vector. | |
441 | 446 | */ |
442 | 447 | public Vector transform(Vector vector) |
443 | 448 | { |
112 | 112 | permission1.setCanModify(false); |
113 | 113 | permission1.setCanModifyAnnotations(false); |
114 | 114 | permission1.setCanPrint(false); |
115 | permission1.setCanPrintDegraded(false); | |
115 | permission1.setCanPrintFaithful(false); | |
116 | 116 | |
117 | 117 | permission2 = new AccessPermission(); |
118 | 118 | permission2.setCanAssembleDocument(false); |
122 | 122 | permission2.setCanModify(false); |
123 | 123 | permission2.setCanModifyAnnotations(false); |
124 | 124 | permission2.setCanPrint(true); // it is true now ! |
125 | permission2.setCanPrintDegraded(false); | |
125 | permission2.setCanPrintFaithful(false); | |
126 | 126 | |
127 | 127 | recipient1 = getRecipient("test1.der", permission1); |
128 | 128 | recipient2 = getRecipient("test2.der", permission2); |
214 | 214 | Assert.assertFalse(permission.canModify()); |
215 | 215 | Assert.assertFalse(permission.canModifyAnnotations()); |
216 | 216 | Assert.assertFalse(permission.canPrint()); |
217 | Assert.assertFalse(permission.canPrintDegraded()); | |
217 | Assert.assertFalse(permission.canPrintFaithful()); | |
218 | 218 | } |
219 | 219 | finally |
220 | 220 | { |
250 | 250 | Assert.assertFalse(permission.canModify()); |
251 | 251 | Assert.assertFalse(permission.canModifyAnnotations()); |
252 | 252 | Assert.assertFalse(permission.canPrint()); |
253 | Assert.assertFalse(permission.canPrintDegraded()); | |
253 | Assert.assertFalse(permission.canPrintFaithful()); | |
254 | 254 | } |
255 | 255 | finally |
256 | 256 | { |
269 | 269 | Assert.assertFalse(permission.canModify()); |
270 | 270 | Assert.assertFalse(permission.canModifyAnnotations()); |
271 | 271 | Assert.assertTrue(permission.canPrint()); |
272 | Assert.assertFalse(permission.canPrintDegraded()); | |
272 | Assert.assertFalse(permission.canPrintFaithful()); | |
273 | 273 | } |
274 | 274 | finally |
275 | 275 | { |
96 | 96 | permission.setCanModify(false); |
97 | 97 | permission.setCanModifyAnnotations(false); |
98 | 98 | permission.setCanPrint(true); |
99 | permission.setCanPrintDegraded(false); | |
99 | permission.setCanPrintFaithful(false); | |
100 | 100 | permission.setReadOnly(); |
101 | 101 | } |
102 | 102 | |
132 | 132 | |
133 | 133 | restrAP.setCanAssembleDocument(false); |
134 | 134 | restrAP.setCanExtractForAccessibility(false); |
135 | restrAP.setCanPrintDegraded(false); | |
135 | restrAP.setCanPrintFaithful(false); | |
136 | 136 | |
137 | 137 | inputFileAsByteArray = getFileResourceAsByteArray("PasswordSample-128bit.pdf"); |
138 | 138 | checkPerms(inputFileAsByteArray, "owner", fullAP); |
181 | 181 | assertEquals(expectedPermissions.canModify(), currentAccessPermission.canModify()); |
182 | 182 | assertEquals(expectedPermissions.canModifyAnnotations(), currentAccessPermission.canModifyAnnotations()); |
183 | 183 | assertEquals(expectedPermissions.canPrint(), currentAccessPermission.canPrint()); |
184 | assertEquals(expectedPermissions.canPrintDegraded(), currentAccessPermission.canPrintDegraded()); | |
184 | assertEquals(expectedPermissions.canPrintFaithful(), currentAccessPermission.canPrintFaithful()); | |
185 | 185 | |
186 | 186 | new PDFRenderer(doc).renderImage(0); |
187 | 187 |
16 | 16 | |
17 | 17 | package org.apache.pdfbox.pdmodel.font; |
18 | 18 | |
19 | import java.awt.geom.Area; | |
20 | import java.awt.geom.GeneralPath; | |
19 | 21 | import java.io.ByteArrayOutputStream; |
20 | 22 | import java.io.File; |
21 | 23 | import java.io.FileInputStream; |
28 | 30 | import java.net.URL; |
29 | 31 | import java.util.ArrayList; |
30 | 32 | import java.util.List; |
33 | ||
31 | 34 | import org.apache.fontbox.ttf.TTFParser; |
32 | 35 | import org.apache.fontbox.ttf.TrueTypeCollection; |
33 | 36 | import org.apache.fontbox.ttf.TrueTypeFont; |
40 | 43 | import org.apache.pdfbox.pdmodel.font.encoding.WinAnsiEncoding; |
41 | 44 | import org.apache.pdfbox.rendering.PDFRenderer; |
42 | 45 | import org.apache.pdfbox.text.PDFTextStripper; |
46 | ||
43 | 47 | import org.junit.Assert; |
44 | 48 | import org.junit.Assume; |
45 | 49 | import org.junit.Before; |
440 | 444 | Assert.assertEquals(text + "\n" + text, extractedText.trim()); |
441 | 445 | doc.close(); |
442 | 446 | } |
447 | ||
448 | /** | |
449 | * Test font with an unusual cmap table combination (0, 3). | |
450 | * | |
451 | * @throws IOException | |
452 | */ | |
453 | @Test | |
454 | public void testPDFBox5484() throws IOException | |
455 | { | |
456 | File fontFile = new File("target/fonts", "PDFBOX-5484.ttf"); | |
457 | TrueTypeFont ttf = new TTFParser().parse(fontFile); | |
458 | PDDocument doc = new PDDocument(); | |
459 | PDTrueTypeFont tr = PDTrueTypeFont.load(doc, ttf, WinAnsiEncoding.INSTANCE); | |
460 | GeneralPath path1 = tr.getPath("oslash"); | |
461 | GeneralPath path2 = tr.getPath(248); | |
462 | Assert.assertFalse(path2.getPathIterator(null).isDone()); // not empty | |
463 | Assert.assertTrue(new Area(path1).equals(new Area(path2))); // assertEquals does not test equals() | |
464 | doc.close(); | |
465 | } | |
443 | 466 | } |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.26</version> | |
25 | <version>2.0.27</version> | |
26 | 26 | <relativePath>parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 | |
33 | 33 | |
34 | 34 | <scm> |
35 | 35 | <connection> |
36 | scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/2.0.26 | |
36 | scm:svn:http://svn.apache.org/repos/asf/pdfbox/tags/2.0.27 | |
37 | 37 | </connection> |
38 | 38 | <developerConnection> |
39 | scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/2.0.26 | |
39 | scm:svn:https://svn.apache.org/repos/asf/pdfbox/tags/2.0.27 | |
40 | 40 | </developerConnection> |
41 | <url>http://svn.apache.org/viewvc/pdfbox/tags/2.0.26</url> | |
41 | <url>http://svn.apache.org/viewvc/pdfbox/tags/2.0.27</url> | |
42 | 42 | </scm> |
43 | 43 | |
44 | 44 | <modules> |
134 | 134 | |
135 | 135 | The release candidate is a zip archive of the sources in: |
136 | 136 | |
137 | http://svn.apache.org/repos/asf/pdfbox/tags/${project.version}/ | |
137 | https://svn.apache.org/repos/asf/pdfbox/tags/${project.version}/ | |
138 | 138 | |
139 | 139 | The SHA-512 checksum of the archive is ${checksum}. |
140 | 140 |
25 | 25 | <parent> |
26 | 26 | <groupId>org.apache.pdfbox</groupId> |
27 | 27 | <artifactId>pdfbox-parent</artifactId> |
28 | <version>2.0.26</version> | |
28 | <version>2.0.27</version> | |
29 | 29 | <relativePath>../parent/pom.xml</relativePath> |
30 | 30 | </parent> |
31 | 31 |
+35
-0
122 | 122 | // else Filter entry is optional |
123 | 123 | } |
124 | 124 | |
125 | private boolean readUntilEndOfDictionaryStream(InputStream ra) throws IOException | |
126 | { | |
127 | boolean search = true; | |
128 | boolean maybe = false; | |
129 | do | |
130 | { | |
131 | int c = ra.read(); | |
132 | switch (c) | |
133 | { | |
134 | case '>': | |
135 | if (maybe) | |
136 | { | |
137 | return true; | |
138 | } | |
139 | maybe = true; | |
140 | break; | |
141 | case -1: | |
142 | search = false; | |
143 | break; | |
144 | default: | |
145 | maybe = false; | |
146 | break; | |
147 | } | |
148 | } while (search); | |
149 | return false; | |
150 | } | |
151 | ||
125 | 152 | private boolean readUntilStream(InputStream ra) throws IOException |
126 | 153 | { |
127 | 154 | boolean search = true; |
222 | 249 | return; |
223 | 250 | } |
224 | 251 | skipped += curSkip; |
252 | } | |
253 | ||
254 | // skip (most of) the dictionary to avoid false positives, see PDFBOX-4925 | |
255 | if (!readUntilEndOfDictionaryStream(ra)) | |
256 | { | |
257 | addValidationError(context, new ValidationError(ERROR_SYNTAX_STREAM_DAMAGED, | |
258 | "Unable to find end of dictionary")); | |
259 | return; | |
225 | 260 | } |
226 | 261 | |
227 | 262 | // ---- go to the stream key word |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.26</version> | |
25 | <version>2.0.27</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 |
22 | 22 | <parent> |
23 | 23 | <groupId>org.apache.pdfbox</groupId> |
24 | 24 | <artifactId>pdfbox-parent</artifactId> |
25 | <version>2.0.26</version> | |
25 | <version>2.0.27</version> | |
26 | 26 | <relativePath>../parent/pom.xml</relativePath> |
27 | 27 | </parent> |
28 | 28 |
123 | 123 | { |
124 | 124 | ap.setCanPrint( args[++i].equalsIgnoreCase( "true" ) ); |
125 | 125 | } |
126 | else if( key.equals( "-canPrintDegraded" ) ) | |
127 | { | |
128 | ap.setCanPrintDegraded( args[++i].equalsIgnoreCase( "true" ) ); | |
126 | else if( key.equals( "-canPrintDegraded") || key.equals( "-canPrintFaithful") ) | |
127 | { | |
128 | ap.setCanPrintFaithful( args[++i].equalsIgnoreCase( "true" ) ); | |
129 | 129 | } |
130 | 130 | else if( key.equals( "-certFile" ) ) |
131 | 131 | { |
240 | 240 | + " -canModify <true|false> : Set the modify permission\n" |
241 | 241 | + " -canModifyAnnotations <true|false> : Set the modify annots permission\n" |
242 | 242 | + " -canPrint <true|false> : Set the print permission\n" |
243 | + " -canPrintDegraded <true|false> : Set the print degraded permission\n" | |
243 | + " -canPrintFaithful <true|false> : Set the print faithful permission\n" | |
244 | 244 | + " -keyLength <length> : Key length in bits " |
245 | 245 | + "(valid values: 40, 128 or 256, default is 256)\n" |
246 | 246 | + "\nNote: By default all permissions are set to true!"; |
138 | 138 | { |
139 | 139 | document = PDDocument.load(new File(pdfFile), password); |
140 | 140 | |
141 | int numberOfPages = document.getNumberOfPages(); | |
142 | 141 | boolean startEndPageSet = false; |
143 | 142 | if (startPage != null) |
144 | 143 | { |
146 | 145 | startEndPageSet = true; |
147 | 146 | if (split == null) |
148 | 147 | { |
148 | int numberOfPages = document.getNumberOfPages(); | |
149 | 149 | splitter.setSplitAtPage(numberOfPages); |
150 | 150 | } |
151 | 151 | } |
109 | 109 | */ |
110 | 110 | public void createPDFFromText( PDDocument doc, Reader text ) throws IOException |
111 | 111 | { |
112 | try | |
113 | { | |
114 | ||
115 | final int margin = 40; | |
116 | float height = font.getBoundingBox().getHeight() / FONTSCALE; | |
117 | PDRectangle actualMediaBox = | |
118 | landscape ? new PDRectangle(mediaBox.getHeight(), mediaBox.getWidth()) : mediaBox; | |
119 | ||
120 | //calculate font height and increase by a factor. | |
121 | height = height*fontSize*LINE_HEIGHT_FACTOR; | |
122 | BufferedReader data = new BufferedReader( text ); | |
123 | String nextLine; | |
124 | PDPage page = new PDPage(actualMediaBox); | |
125 | PDPageContentStream contentStream = null; | |
126 | float y = -1; | |
127 | float maxStringLength = page.getMediaBox().getWidth() - 2*margin; | |
128 | ||
129 | // There is a special case of creating a PDF document from an empty string. | |
130 | boolean textIsEmpty = true; | |
131 | ||
132 | StringBuilder nextLineToDraw = new StringBuilder(); | |
133 | ||
134 | while( (nextLine = data.readLine()) != null ) | |
112 | final int margin = 40; | |
113 | float height = font.getBoundingBox().getHeight() / FONTSCALE; | |
114 | PDRectangle actualMediaBox = | |
115 | landscape ? new PDRectangle(mediaBox.getHeight(), mediaBox.getWidth()) : mediaBox; | |
116 | ||
117 | //calculate font height and increase by a factor. | |
118 | height = height * fontSize * LINE_HEIGHT_FACTOR; | |
119 | BufferedReader data = new BufferedReader(text); | |
120 | String nextLine; | |
121 | PDPage page = new PDPage(actualMediaBox); | |
122 | PDPageContentStream contentStream = null; | |
123 | float y = -1; | |
124 | float maxStringLength = page.getMediaBox().getWidth() - 2 * margin; | |
125 | ||
126 | // There is a special case of creating a PDF document from an empty string. | |
127 | boolean textIsEmpty = true; | |
128 | ||
129 | StringBuilder nextLineToDraw = new StringBuilder(); | |
130 | ||
131 | while ((nextLine = data.readLine()) != null) | |
132 | { | |
133 | // The input text is nonEmpty. New pages will be created and added | |
134 | // to the PDF document as they are needed, depending on the length of | |
135 | // the text. | |
136 | textIsEmpty = false; | |
137 | ||
138 | String[] lineWords = nextLine.replaceAll("[\\n\\r]+$", "").split(" ", -1); | |
139 | int lineIndex = 0; | |
140 | while (lineIndex < lineWords.length) | |
135 | 141 | { |
136 | ||
137 | // The input text is nonEmpty. New pages will be created and added | |
138 | // to the PDF document as they are needed, depending on the length of | |
139 | // the text. | |
140 | textIsEmpty = false; | |
141 | ||
142 | String[] lineWords = nextLine.replaceAll("[\\n\\r]+$", "").split(" ", -1); | |
143 | int lineIndex = 0; | |
144 | while( lineIndex < lineWords.length ) | |
142 | nextLineToDraw.setLength(0); | |
143 | boolean addSpace = false; | |
144 | float lengthIfUsingNextWord = 0; | |
145 | boolean ff = false; | |
146 | do | |
145 | 147 | { |
146 | nextLineToDraw.setLength(0); | |
147 | boolean addSpace = false; | |
148 | float lengthIfUsingNextWord = 0; | |
149 | boolean ff = false; | |
150 | do | |
151 | { | |
152 | String word1, word2 = ""; | |
153 | String word = lineWords[lineIndex]; | |
154 | int indexFF = word.indexOf('\f'); | |
155 | if (indexFF == -1) | |
148 | String word1, word2 = ""; | |
149 | String word = lineWords[lineIndex]; | |
150 | int indexFF = word.indexOf('\f'); | |
151 | if (indexFF == -1) | |
152 | { | |
153 | word1 = word; | |
154 | } | |
155 | else | |
156 | { | |
157 | ff = true; | |
158 | word1 = word.substring(0, indexFF); | |
159 | if (indexFF < word.length()) | |
156 | 160 | { |
157 | word1 = word; | |
161 | word2 = word.substring(indexFF + 1); | |
162 | } | |
163 | } | |
164 | // word1 is the part before ff, word2 after | |
165 | // both can be empty | |
166 | // word1 can also be empty without ff, if a line has many spaces | |
167 | if (word1.length() > 0 || !ff) | |
168 | { | |
169 | if (addSpace) | |
170 | { | |
171 | nextLineToDraw.append(" "); | |
158 | 172 | } |
159 | 173 | else |
160 | 174 | { |
161 | ff = true; | |
162 | word1 = word.substring(0, indexFF); | |
163 | if (indexFF < word.length()) | |
164 | { | |
165 | word2 = word.substring(indexFF + 1); | |
166 | } | |
175 | addSpace = true; | |
167 | 176 | } |
168 | // word1 is the part before ff, word2 after | |
169 | // both can be empty | |
170 | // word1 can also be empty without ff, if a line has many spaces | |
171 | if (word1.length() > 0 || !ff) | |
177 | nextLineToDraw.append(word1); | |
178 | } | |
179 | if (!ff || word2.length() == 0) | |
180 | { | |
181 | lineIndex++; | |
182 | } | |
183 | else | |
184 | { | |
185 | lineWords[lineIndex] = word2; | |
186 | } | |
187 | if (ff) | |
188 | { | |
189 | break; | |
190 | } | |
191 | if (lineIndex < lineWords.length) | |
192 | { | |
193 | // need cut off at \f in next word to avoid IllegalArgumentException | |
194 | String nextWord = lineWords[lineIndex]; | |
195 | indexFF = nextWord.indexOf('\f'); | |
196 | if (indexFF != -1) | |
172 | 197 | { |
173 | if (addSpace) | |
174 | { | |
175 | nextLineToDraw.append(" "); | |
176 | } | |
177 | else | |
178 | { | |
179 | addSpace = true; | |
180 | } | |
181 | nextLineToDraw.append(word1); | |
198 | nextWord = nextWord.substring(0, indexFF); | |
182 | 199 | } |
183 | if (!ff || word2.length() == 0) | |
184 | { | |
185 | lineIndex++; | |
186 | } | |
187 | else | |
188 | { | |
189 | lineWords[lineIndex] = word2; | |
190 | } | |
191 | if (ff) | |
192 | { | |
193 | break; | |
194 | } | |
195 | if( lineIndex < lineWords.length ) | |
196 | { | |
197 | // need cut off at \f in next word to avoid IllegalArgumentException | |
198 | String nextWord = lineWords[lineIndex]; | |
199 | indexFF = nextWord.indexOf('\f'); | |
200 | if (indexFF != -1) | |
201 | { | |
202 | nextWord = nextWord.substring(0, indexFF); | |
203 | } | |
204 | ||
205 | String lineWithNextWord = nextLineToDraw + " " + nextWord; | |
206 | lengthIfUsingNextWord = | |
207 | (font.getStringWidth( lineWithNextWord )/FONTSCALE) * fontSize; | |
208 | } | |
209 | } | |
210 | while (lineIndex < lineWords.length && lengthIfUsingNextWord < maxStringLength); | |
211 | ||
212 | if( y < margin ) | |
213 | { | |
214 | // We have crossed the end-of-page boundary and need to extend the | |
215 | // document by another page. | |
216 | page = new PDPage(actualMediaBox); | |
217 | doc.addPage( page ); | |
218 | if( contentStream != null ) | |
219 | { | |
220 | contentStream.endText(); | |
221 | contentStream.close(); | |
222 | } | |
223 | contentStream = new PDPageContentStream(doc, page); | |
224 | contentStream.setFont( font, fontSize ); | |
225 | contentStream.beginText(); | |
226 | y = page.getMediaBox().getHeight() - margin + height; | |
227 | contentStream.newLineAtOffset(margin, y); | |
228 | } | |
229 | ||
230 | if( contentStream == null ) | |
231 | { | |
232 | throw new IOException( "Error:Expected non-null content stream." ); | |
233 | } | |
234 | contentStream.newLineAtOffset(0, -height); | |
235 | y -= height; | |
236 | contentStream.showText(nextLineToDraw.toString()); | |
237 | if (ff) | |
238 | { | |
239 | page = new PDPage(actualMediaBox); | |
240 | doc.addPage(page); | |
200 | ||
201 | String lineWithNextWord = nextLineToDraw + " " + nextWord; | |
202 | lengthIfUsingNextWord | |
203 | = (font.getStringWidth(lineWithNextWord) / FONTSCALE) * fontSize; | |
204 | } | |
205 | } | |
206 | while (lineIndex < lineWords.length && lengthIfUsingNextWord < maxStringLength); | |
207 | ||
208 | if (y < margin) | |
209 | { | |
210 | // We have crossed the end-of-page boundary and need to extend the | |
211 | // document by another page. | |
212 | page = new PDPage(actualMediaBox); | |
213 | doc.addPage(page); | |
214 | if (contentStream != null) | |
215 | { | |
241 | 216 | contentStream.endText(); |
242 | 217 | contentStream.close(); |
243 | contentStream = new PDPageContentStream(doc, page); | |
244 | contentStream.setFont(font, fontSize); | |
245 | contentStream.beginText(); | |
246 | y = page.getMediaBox().getHeight() - margin + height; | |
247 | contentStream.newLineAtOffset(margin, y); | |
248 | } | |
218 | } | |
219 | contentStream = new PDPageContentStream(doc, page); | |
220 | contentStream.setFont(font, fontSize); | |
221 | contentStream.beginText(); | |
222 | y = page.getMediaBox().getHeight() - margin + height; | |
223 | contentStream.newLineAtOffset(margin, y); | |
224 | } | |
225 | ||
226 | if (contentStream == null) | |
227 | { | |
228 | throw new IOException("Error:Expected non-null content stream."); | |
229 | } | |
230 | contentStream.newLineAtOffset(0, -height); | |
231 | y -= height; | |
232 | contentStream.showText(nextLineToDraw.toString()); | |
233 | if (ff) | |
234 | { | |
235 | page = new PDPage(actualMediaBox); | |
236 | doc.addPage(page); | |
237 | contentStream.endText(); | |
238 | contentStream.close(); | |
239 | contentStream = new PDPageContentStream(doc, page); | |
240 | contentStream.setFont(font, fontSize); | |
241 | contentStream.beginText(); | |
242 | y = page.getMediaBox().getHeight() - margin + height; | |
243 | contentStream.newLineAtOffset(margin, y); | |
249 | 244 | } |
250 | 245 | } |
251 | ||
252 | // If the input text was the empty string, then the above while loop will have short-circuited | |
253 | // and we will not have added any PDPages to the document. | |
254 | // So in order to make the resultant PDF document readable by Adobe Reader etc, we'll add an empty page. | |
255 | if (textIsEmpty) | |
256 | { | |
257 | doc.addPage(page); | |
258 | } | |
259 | ||
260 | if( contentStream != null ) | |
261 | { | |
262 | contentStream.endText(); | |
263 | contentStream.close(); | |
264 | } | |
265 | } | |
266 | catch( IOException io ) | |
267 | { | |
268 | if( doc != null ) | |
269 | { | |
270 | doc.close(); | |
271 | } | |
272 | throw io; | |
246 | } | |
247 | ||
248 | // If the input text was the empty string, then the above while loop will have short-circuited | |
249 | // and we will not have added any PDPages to the document. | |
250 | // So in order to make the resultant PDF document readable by Adobe Reader etc, we'll add an empty page. | |
251 | if (textIsEmpty) | |
252 | { | |
253 | doc.addPage(page); | |
254 | } | |
255 | ||
256 | if (contentStream != null) | |
257 | { | |
258 | contentStream.endText(); | |
259 | contentStream.close(); | |
273 | 260 | } |
274 | 261 | } |
275 | 262 |