001: /*
002: * Copyright (c) 2007, intarsys consulting GmbH
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * - Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * - Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * - Neither the name of intarsys nor the names of its contributors may be used
015: * to endorse or promote products derived from this software without specific
016: * prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
028: * POSSIBILITY OF SUCH DAMAGE.
029: */
030: package de.intarsys.pdf.font;
031:
032: import java.util.ArrayList;
033: import java.util.HashMap;
034: import java.util.HashSet;
035: import java.util.Iterator;
036: import java.util.List;
037: import java.util.Map;
038: import java.util.Set;
039:
040: import de.intarsys.pdf.cds.CDSRectangle;
041: import de.intarsys.pdf.content.CSContent;
042: import de.intarsys.pdf.content.CSError;
043: import de.intarsys.pdf.content.CSException;
044: import de.intarsys.pdf.content.CSInterpreter;
045: import de.intarsys.pdf.content.CSOperation;
046: import de.intarsys.pdf.content.CSWarning;
047: import de.intarsys.pdf.cos.COSDictionary;
048: import de.intarsys.pdf.cos.COSInteger;
049: import de.intarsys.pdf.cos.COSName;
050: import de.intarsys.pdf.cos.COSObject;
051: import de.intarsys.pdf.cos.COSObjectWalkerDeep;
052: import de.intarsys.pdf.cos.COSStream;
053: import de.intarsys.pdf.cos.COSTools;
054: import de.intarsys.pdf.cos.COSVisitorException;
055: import de.intarsys.pdf.cos.ICOSObjectVisitor;
056: import de.intarsys.pdf.encoding.WinAnsiEncoding;
057: import de.intarsys.pdf.font.outlet.IFontOutlet;
058: import de.intarsys.pdf.pd.PDDocument;
059: import de.intarsys.pdf.pd.PDForm;
060: import de.intarsys.pdf.pd.PDObject;
061: import de.intarsys.pdf.pd.PDPage;
062: import de.intarsys.pdf.pd.PDPattern;
063: import de.intarsys.pdf.pd.PDResources;
064:
065: /**
066: * Tool class for handling PDF fonts.
067: * <p>
068: * For more sophisticated help, see {@link IFontOutlet}.
069: *
070: */
071: public class PDFontTools {
072: private static final Object ATTR_CREATEDFONTS = new Object();
073:
074: /**
075: * A builtin font created "on the fly"
076: * <p>
077: * A font was requested in a content stream that can't be looked up in the
078: * associated resources. Some PDF engines just omit the font object when
079: * dealing with builtins - try to interpret as such.
080: *
081: * @param document
082: * The document requesting the font.
083: * @param name
084: * The fonts name
085: * @return A builtin font created "on the fly"
086: */
087: protected static PDFont createLazyFont(PDDocument document,
088: COSName name) {
089: Map fonts;
090: PDFont font;
091:
092: fonts = null;
093: font = null;
094: if (document != null) {
095: fonts = (Map) document.getAttribute(ATTR_CREATEDFONTS);
096: if (fonts == null) {
097: fonts = new HashMap();
098: document.setAttribute(ATTR_CREATEDFONTS, fonts);
099: }
100: font = (PDFont) fonts.get(name.stringValue());
101: }
102: if (font != null) {
103: return font;
104: }
105: font = PDFontType1.createNew(name.stringValue());
106: /*
107: * when not setting encoding an AFM encoding will be used which does not
108: * handle umlauts and other special characters; use WinAnsiEcoding as
109: * found in other places where fonts are created
110: */
111: font.setEncoding(WinAnsiEncoding.UNIQUE);
112: if (document != null) {
113: fonts.put(name.stringValue(), font);
114: }
115: return font;
116: }
117:
118: /**
119: * The font <code>name</code>, looked up in <code>resources</code>.
120: * <p>
121: * When no matching resource is found, a builtin font is created on the fly.
122: *
123: * @param document
124: * @param resources
125: * @param name
126: * @return The font <code>name</code>, looked up in
127: * <code>resources</code>.
128: */
129: public static PDFont getFont(PDDocument document,
130: PDResources resources, COSName name) {
131: if (resources != null) {
132: PDFont font = resources.getFontResource(name);
133: if (font != null) {
134: return font;
135: }
136: }
137: // bad PDF but try to be nice; treat as builtin font anyway
138: return createLazyFont(document, name);
139: }
140:
141: /**
142: * The font <code>name</code>, looked up in <code>resources</code>.
143: *
144: * @param resources
145: * @param name
146: * @return The font <code>name</code>, looked up in
147: * <code>resources</code>.
148: */
149: public static PDFont getFont(PDResources resources, COSName name) {
150: return getFont(resources.getDoc(), resources, name);
151: }
152:
153: /**
154: * Tries to determine which fonts are really used within the document
155: *
156: * The following criteria are used to determine usage of a font.
157: * <ul>
158: * Any glyph of the font is referenced in
159: * <li>the Contents stream of a page object</li>
160: * <li>the stream of a Form XObject</li>
161: * <li>the appearance stream of an annotation, including form fields</li>
162: * <li>the content stream of a Type 3 font glyph</li>
163: * <li>the stream of a tiling pattern</li>
164: * </ul>
165: *
166: * @param doc
167: * The PDDocument to parse
168: * @param considerTR
169: * If true, considers font references with Text rendering mode 3
170: * as unused
171: * @return Set of all used fonts
172: */
173: public static List getUsedFonts(PDDocument doc, boolean considerTR) {
174: Set fonts = new HashSet();
175:
176: // get the fonts from the page objects / contentstream
177: PDPage page = doc.getPageTree().getFirstPage();
178: while (true) {
179: if (page == null) {
180: break;
181: }
182: CSContent contentstream = page.getContentStream();
183: if (contentstream != null) {
184: collectFonts(fonts, contentstream, page.getResources(),
185: considerTR);
186: }
187: page = page.getNextPage();
188: }
189:
190: // get the fonts from all XObjects, also from tiling patterns
191: for (Iterator it = doc.cosGetDoc().objects(); it.hasNext();) {
192: COSObject object = (COSObject) it.next();
193: COSDictionary dict = COSTools.toDictionary(object);
194: if (dict == null) {
195: continue;
196: }
197: // Form XObjects
198: if (dict.get(PDObject.DK_Subtype).equals(
199: PDForm.CN_Subtype_Form)) {
200: PDForm form = (PDForm) PDForm.META
201: .createFromCos(object);
202: if (form != null) {
203: collectFonts(fonts, form.getContentStream(), form
204: .getResources(), considerTR);
205: }
206: }
207:
208: // Pattern tiling
209: if (dict.get(PDPattern.DK_PatternType).equals(
210: COSInteger.create(PDPattern.PATTERN_TYPE_TILING))) {
211: CSContent patternCS = CSContent
212: .createFromCos((COSStream) object);
213: COSDictionary r = dict.get(PDForm.DK_Resources)
214: .asDictionary();
215: PDResources resources = (PDResources) PDResources.META
216: .createFromCos(r);
217: collectFonts(fonts, patternCS, resources, considerTR);
218: }
219: }
220:
221: return new ArrayList(fonts);
222: }
223:
224: static protected void collectFonts(Set fonts, CSContent content,
225: PDResources resources, final boolean considerTR) {
226: final Set fontNames = new HashSet();
227: CSInterpreter collector = new CSInterpreter(new HashMap()) {
228:
229: private COSName fontName;
230:
231: private int renderingMode = 0;
232:
233: protected void handleWarning(CSWarning warning)
234: throws CSException {
235: //
236: }
237:
238: protected void handleError(CSError error)
239: throws CSException {
240: //
241: }
242:
243: protected void notSupported(CSOperation operation)
244: throws CSException {
245: //
246: }
247:
248: protected void render_Tr(CSOperation operation)
249: throws CSException {
250: COSInteger mode = (COSInteger) operation.getOperand(0);
251: renderingMode = mode.intValue();
252: }
253:
254: protected void render_Tf(CSOperation operation)
255: throws CSException {
256: if (operation.operandSize() == 2) {
257: fontName = operation.getOperand(0).asName();
258: }
259: }
260:
261: protected void handleText() {
262: if (renderingMode != 3 || !considerTR) {
263: fontNames.add(fontName);
264: }
265: }
266:
267: protected void render_Tj(CSOperation operation)
268: throws CSException {
269: handleText();
270: }
271:
272: protected void render_TJ(CSOperation operation)
273: throws CSException {
274: handleText();
275: }
276:
277: protected void render_Quote(CSOperation operation)
278: throws CSException {
279: handleText();
280: }
281:
282: protected void render_DoubleQuote(CSOperation operation)
283: throws CSException {
284: handleText();
285: }
286: };
287: collector.process(content, resources);
288: for (Iterator it = fontNames.iterator(); it.hasNext();) {
289: COSName fontName = (COSName) it.next();
290: fonts.add(resources.getFontResource(fontName));
291: }
292: }
293:
294: /**
295: * The scaled font height in user space coordinates.
296: *
297: * @param font
298: * The font to be used.
299: * @param size
300: * The font size
301: * @return The scaled font height in user space coordinates.
302: */
303: public static float getGlyphHeightScaled(PDFont font, float size) {
304: CDSRectangle rect = font.getFontDescriptor().getFontBB();
305: float scaledHeight = rect.getUpperRightY()
306: - rect.getLowerLeftY();
307: scaledHeight = (size * scaledHeight) / 1000f;
308: return scaledHeight;
309: }
310:
311: /**
312: * The font height in user space coordinates.
313: *
314: * @param font
315: * The font to be used.
316: * @param size
317: * The font size
318: * @return The scaled font height in user space coordinates.
319: */
320: public static float getGlyphHeight(PDFont font) {
321: CDSRectangle rect = font.getFontDescriptor().getFontBB();
322: float scaledHeight = rect.getUpperRightY()
323: - rect.getLowerLeftY();
324: scaledHeight = scaledHeight / 1000f;
325: return scaledHeight;
326: }
327:
328: /**
329: * The sum of the length of all glyphs referenced by <code>length</code>
330: * bytes from <code>codepoints</code> starting at <code>offset</code>.
331: *
332: * @param font
333: * @param codepoints
334: * @param offset
335: * @param length
336: * @return The sum of the length of all glyphs referenced by
337: * <code>length</code> bytes from <code>codepoints</code>
338: * starting at <code>offset</code>.
339: */
340: public static int getGlyphWidth(PDFont font, byte[] codepoints,
341: int offset, int length) {
342: int result = 0;
343: int stop = offset + length;
344: int[] widths = font.getGlyphWidths();
345: for (int i = offset; i < stop; i++) {
346: result = result + widths[codepoints[i] & 0xFF];
347: }
348: return result;
349: }
350:
351: /**
352: * The scaled sum of the length of all glyphs referenced by
353: * <code>length</code> bytes from <code>codepoints</code> starting at
354: * <code>offset</code>.
355: *
356: * @param font
357: * @param size
358: * @param codepoints
359: * @param offset
360: * @param length
361: * @return The scaled sum of the length of all glyphs referenced by
362: * <code>length</code> bytes from <code>codepoints</code>
363: * starting at <code>offset</code>.
364: */
365: public static float getGlyphWidthScaled(PDFont font, float size,
366: byte[] codepoints, int offset, int length) {
367: float width = getGlyphWidth(font, codepoints, offset, length);
368: return (size * width) / 1000f;
369: }
370:
371: /**
372: * The scaled width of the glyphs referenced by <code>codepoint</code> .
373: *
374: * @param font
375: * @param size
376: * @param codepoint
377: * @return The scaled sum of the length of all glyphs referenced by
378: * <code>length</code> bytes from <code>codepoints</code>
379: * starting at <code>offset</code>.
380: */
381: public static float getGlyphWidthScaled(PDFont font, float size,
382: int codepoint) {
383: float width = font.getGlyphWidth(codepoint);
384: return (size * width) / 1000f;
385: }
386:
387: /**
388: * Determine the fonts contained as objects within the document.
389: *
390: * @param doc
391: * The PDDocument to parse
392: * @return Collection of all {@link PDFont} objects in the document.
393: */
394: public static List getFonts(PDDocument doc) {
395: final List result = new ArrayList();
396: try {
397: COSDictionary trailer = doc.cosGetDoc().stGetDoc()
398: .cosGetTrailer();
399: ICOSObjectVisitor visitor = new COSObjectWalkerDeep() {
400: public Object visitFromDictionary(COSDictionary dict)
401: throws COSVisitorException {
402: try {
403: PDFont font = (PDFont) PDFont.META
404: .createFromCos(dict);
405: if (font != null) {
406: result.add(font);
407: return null;
408: }
409: } catch (Exception e) {
410: //
411: }
412: return super .visitFromDictionary(dict);
413: }
414: };
415: trailer.accept(visitor);
416: } catch (COSVisitorException e) {
417: // ignore
418: }
419: return result;
420: }
421: }
|