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.io.ByteArrayOutputStream;
033: import java.io.FileOutputStream;
034: import java.io.IOException;
035: import java.io.InputStream;
036: import java.util.Arrays;
037: import java.util.HashMap;
038: import java.util.Map;
039: import java.util.StringTokenizer;
040: import de.intarsys.font.FontStyle;
041: import de.intarsys.font.IFont;
042: import de.intarsys.font.afm.AFM;
043: import de.intarsys.font.afm.AFMChar;
044: import de.intarsys.font.afm.AFMRegistry;
045: import de.intarsys.pdf.cds.CDSRectangle;
046: import de.intarsys.pdf.cos.COSBasedObject;
047: import de.intarsys.pdf.cos.COSDictionary;
048: import de.intarsys.pdf.cos.COSName;
049: import de.intarsys.pdf.cos.COSObject;
050: import de.intarsys.pdf.encoding.Encoding;
051: import de.intarsys.pdf.encoding.FontSpecificEncoding;
052: import de.intarsys.tools.stream.StreamTools;
053:
054: /**
055: * basic implementation for type 1 support
056: */
057: public class PDFontType1 extends PDFont implements IFont {
058: /**
059: * The meta class implementation
060: */
061: static public class MetaClass extends PDFont.MetaClass {
062: protected MetaClass(Class instanceClass) {
063: super (instanceClass);
064: }
065:
066: protected COSBasedObject doCreateCOSBasedObject(COSObject object) {
067: return new PDFontType1(object);
068: }
069: }
070:
071: /** Map of known alternative names for the builtin fonts */
072: public static final Map FontAlternatives;
073:
074: static public String FONT_Courier = "Courier"; //$NON-NLS-1$
075:
076: static public String FONT_Courier_Bold = "Courier-Bold"; //$NON-NLS-1$
077:
078: static public String FONT_Courier_BoldOblique = "Courier-BoldOblique"; //$NON-NLS-1$
079:
080: static public String FONT_Courier_Oblique = "Courier-Oblique"; //$NON-NLS-1$
081:
082: static public String FONT_Helvetica = "Helvetica"; //$NON-NLS-1$
083:
084: static public String FONT_Helvetica_Bold = "Helvetica-Bold"; //$NON-NLS-1$
085:
086: static public String FONT_Helvetica_BoldOblique = "Helvetica-BoldOblique"; //$NON-NLS-1$
087:
088: static public String FONT_Helvetica_Oblique = "Helvetica-Oblique"; //$NON-NLS-1$
089:
090: static public String FONT_Symbol = "Symbol"; //$NON-NLS-1$
091:
092: static public String FONT_Times_Bold = "Times-Bold"; //$NON-NLS-1$
093:
094: static public String FONT_Times_BoldItalic = "Times-BoldItalic"; //$NON-NLS-1$
095:
096: static public String FONT_Times_Italic = "Times-Italic"; //$NON-NLS-1$
097:
098: static public String FONT_Times_Roman = "Times-Roman"; //$NON-NLS-1$
099:
100: static public String FONT_ZapfDingbats = "ZapfDingbats"; //$NON-NLS-1$
101:
102: public static final String[] FONTS_STANDARD = new String[] {
103: FONT_Courier, FONT_Courier_Bold, FONT_Courier_BoldOblique,
104: FONT_Courier_Oblique, FONT_Helvetica, FONT_Helvetica_Bold,
105: FONT_Helvetica_BoldOblique, FONT_Helvetica_Oblique,
106: FONT_Symbol, FONT_Times_Bold, FONT_Times_BoldItalic,
107: FONT_Times_Italic, FONT_Times_Roman, FONT_ZapfDingbats };
108:
109: /** The meta class instance */
110: static public final MetaClass META = new MetaClass(MetaClass.class
111: .getDeclaringClass());
112:
113: static {
114: FontAlternatives = new HashMap();
115: // abbreviations used in pdfmarks
116: FontAlternatives.put("Cour", FONT_Courier);
117: FontAlternatives.put("CoBo", FONT_Courier_Bold);
118: FontAlternatives.put("CoOb", FONT_Courier_Oblique);
119: FontAlternatives.put("CoBO", FONT_Courier_BoldOblique);
120: FontAlternatives.put("Helv", FONT_Helvetica);
121: FontAlternatives.put("HeBo", FONT_Helvetica_Bold);
122: FontAlternatives.put("HeOb", FONT_Helvetica_Oblique);
123: FontAlternatives.put("HeBO", FONT_Helvetica_BoldOblique);
124: FontAlternatives.put("TiRo", FONT_Times_Roman);
125: FontAlternatives.put("TiBo", FONT_Times_Bold);
126: FontAlternatives.put("TiIt", FONT_Times_Italic);
127: FontAlternatives.put("TiBI", FONT_Times_BoldItalic);
128: FontAlternatives.put("ZaDb", FONT_ZapfDingbats);
129: FontAlternatives.put("Symb", FONT_Symbol);
130: // alternatives given in pdf reference 1.4
131: FontAlternatives.put("TimesNewRoman", FONT_Times_Roman);
132: FontAlternatives.put("TimesNewRomanPS", FONT_Times_Roman);
133: FontAlternatives.put("TimesNewRomanPSMT", FONT_Times_Roman);
134: FontAlternatives.put("Arial", FONT_Helvetica);
135: FontAlternatives.put("ArialMT", FONT_Helvetica);
136: FontAlternatives.put("CourierNew", FONT_Courier);
137: FontAlternatives.put("CourierNewPSMT", FONT_Courier);
138: FontAlternatives.put("TimesNewRoman-Bold", FONT_Times_Roman);
139: FontAlternatives.put("TimesNewRoman,Bold", FONT_Times_Roman);
140: FontAlternatives.put("TimesNewRomanPS-Bold", FONT_Times_Roman);
141: FontAlternatives
142: .put("TimesNewRomanPS-BoldMT", FONT_Times_Roman);
143: FontAlternatives.put("Helvetica,Bold", FONT_Helvetica_Bold);
144: FontAlternatives.put("Arial-Bold", FONT_Helvetica_Bold);
145: FontAlternatives.put("Arial,Bold", FONT_Helvetica_Bold);
146: FontAlternatives.put("Arial-BoldMT", FONT_Helvetica_Bold);
147: FontAlternatives.put("Courier,Bold", FONT_Courier_Bold);
148: FontAlternatives.put("CourierNew-Bold", FONT_Courier_Bold);
149: FontAlternatives.put("CourierNew,Bold", FONT_Courier_Bold);
150: FontAlternatives.put("CourierNewPS-BoldMT", FONT_Courier_Bold);
151: FontAlternatives.put("TimesNewRoman-Italic", FONT_Times_Italic);
152: FontAlternatives.put("TimesNewRoman,Italic", FONT_Times_Italic);
153: FontAlternatives.put("TimesNewRomanPS-Italic",
154: FONT_Times_Italic);
155: FontAlternatives.put("TimesNewRomanPS-ItalicMT",
156: FONT_Times_Italic);
157: FontAlternatives
158: .put("Helvetica-Italic", FONT_Helvetica_Oblique);
159: FontAlternatives
160: .put("Helvetica,Italic", FONT_Helvetica_Oblique);
161: FontAlternatives.put("Arial-Italic", FONT_Helvetica_Oblique);
162: FontAlternatives.put("Arial,Italic", FONT_Helvetica_Oblique);
163: FontAlternatives.put("Arial-ItalicMT", FONT_Helvetica_Oblique);
164: FontAlternatives.put("Courier,Italic", FONT_Courier_Oblique);
165: FontAlternatives.put("CourierNew-Italic", FONT_Courier_Oblique);
166: FontAlternatives.put("CourierNew,Italic", FONT_Courier_Oblique);
167: FontAlternatives.put("CourierNewPS-ItalicMT",
168: FONT_Courier_Oblique);
169: FontAlternatives.put("TimesNewRoman-BoldItalic",
170: FONT_Times_BoldItalic);
171: FontAlternatives.put("TimesNewRoman,BoldItalic",
172: FONT_Times_BoldItalic);
173: FontAlternatives.put("TimesNewRomanPS-BoldItalic",
174: FONT_Times_BoldItalic);
175: FontAlternatives.put("TimesNewRomanPS-BoldItalicMT",
176: FONT_Times_BoldItalic);
177: FontAlternatives.put("Helvetica-BoldItalic",
178: FONT_Helvetica_BoldOblique);
179: FontAlternatives.put("Helvetica,BoldItalic",
180: FONT_Helvetica_BoldOblique);
181: FontAlternatives.put("Arial-BoldItalic",
182: FONT_Helvetica_BoldOblique);
183: FontAlternatives.put("Arial,BoldItalic",
184: FONT_Helvetica_BoldOblique);
185: FontAlternatives.put("Arial-BoldItalicMT",
186: FONT_Helvetica_BoldOblique);
187: FontAlternatives.put("Courier,BoldItalic",
188: FONT_Courier_BoldOblique);
189: FontAlternatives.put("CourierNew-BoldItalic",
190: FONT_Courier_BoldOblique);
191: FontAlternatives.put("CourierNew,BoldItalic",
192: FONT_Courier_BoldOblique);
193: FontAlternatives.put("CourierNewPS-BoldItalicMT",
194: FONT_Courier_BoldOblique);
195: }
196:
197: /**
198: * Create a Type1 font from an existing AFM definition.
199: *
200: * @param afm
201: * the AFM font definition structure.
202: *
203: * @return the new font created
204: */
205: static public PDFontType1 createNew(AFM afm) {
206: PDFontType1 font = (PDFontType1) PDFontType1.META.createNew();
207: String name = afm.getFontName();
208: font.setName(name);
209: font.setAfm(afm);
210: font.initializeFromAFM();
211: return font;
212: }
213:
214: /**
215: * create a Type1 font object to be used in the pdf document
216: *
217: * @param name
218: * the name of the font to use
219: *
220: * @return the new font created
221: */
222: static public PDFontType1 createNew(String name) {
223: PDFontType1 font = (PDFontType1) PDFontType1.META.createNew();
224: font.setName(name);
225: return font;
226: }
227:
228: // a reference to the parsed adobe font metric (if used)
229: private AFM afm;
230:
231: //
232: private String name;
233:
234: /**
235: * Create the receiver class from an already defined {@link COSDictionary}.
236: * NEVER use the constructor directly.
237: *
238: * @param object
239: * the PDDocument containing the new object
240: */
241: protected PDFontType1(COSObject object) {
242: super (object);
243: }
244:
245: /*
246: * (non-Javadoc)
247: *
248: * @see de.intarsys.pdf.font.PDFont#createBuiltinFontDescriptor()
249: */
250: protected PDFontDescriptor createBuiltinFontDescriptor() {
251: return new PDFontDescriptorType1(this );
252: }
253:
254: /*
255: * (non-Javadoc)
256: *
257: * @see de.intarsys.pdf.font.PDFont#createBuiltInWidths(int[])
258: */
259: protected int[] createBuiltInWidths(int[] result) {
260: if (getAfm() == null) {
261: return result;
262: }
263: for (int i = 0; i < 256; i++) {
264: String glyphName = getEncoding().getGlyphName(i);
265: AFMChar afmChar = getAfm().getCharByName(glyphName);
266: if (afmChar != null) {
267: result[i] = afmChar.getWidth();
268: }
269: }
270: return result;
271: }
272:
273: /*
274: * (non-Javadoc)
275: *
276: * @see de.intarsys.pdf.font.PDFont#createFontEncoding()
277: */
278: protected Encoding createDefaultEncoding() {
279: if (getAfm() == null) {
280: if ((getFontDescriptor() != null)
281: && getFontDescriptor().isSymbolic()) {
282: return FontSpecificEncoding.UNIQUE;
283: }
284: return super .createDefaultEncoding();
285: }
286: if (getFontDescriptor().isSymbolic()) {
287: return new AFMSymbolicEncoding(getAfm());
288: }
289: return new AFMEncoding(getAfm());
290: }
291:
292: protected int createFirstChar() {
293: // check if there is some undefined code
294: Encoding encoding = getEncoding();
295: for (int i = 0; i <= 255; i++) {
296: if (encoding.getUnicode(i) != -1) {
297: return i;
298: }
299: }
300:
301: // ??
302: return 0;
303: }
304:
305: protected int createLastChar() {
306: // check if there is some undefined code
307: Encoding encoding = getEncoding();
308: for (int i = 255; i >= 0; i--) {
309: if (encoding.getUnicode(i) != -1) {
310: return i;
311: }
312: }
313:
314: // ??
315: return 0;
316: }
317:
318: /*
319: * (non-Javadoc)
320: *
321: * @see de.intarsys.pdf.font.PDFont#dump()
322: */
323: protected void dump() {
324: PDFontDescriptorEmbedded fd = (PDFontDescriptorEmbedded) getFontDescriptor();
325: if (fd == null) {
326: return;
327: }
328: byte[] data = fd.getFontFile();
329: if (data == null) {
330: return;
331: }
332: try {
333: String filename = getBaseFont().stringValue();
334: filename = filename.replace('+', '_');
335: filename = filename.replace(',', '_');
336: FileOutputStream os = new FileOutputStream("fontdump_"
337: + filename + ".pfb");
338: os.write(data);
339: os.close();
340: } catch (Exception e) {
341: // ignore and use fallback
342: }
343: }
344:
345: public void embedFontProgram(InputStream is) {
346: ByteArrayOutputStream bos = new ByteArrayOutputStream();
347: try {
348: StreamTools.copyStream(is, bos);
349: } catch (IOException e) {
350: // TODO was "ignore" originally intended?
351: }
352:
353: ((PDFontDescriptorEmbedded) getFontDescriptor())
354: .setFontFile(bos.toByteArray());
355: }
356:
357: public AFM getAfm() {
358: return afm;
359: }
360:
361: public String getFontFamilyName() {
362: if (getAfm() == null) {
363: return PDFont
364: .getFontFamilyName(getBaseFont().stringValue());
365: }
366: return getAfm().getFontFamilyName();
367: }
368:
369: public String getFontName() {
370: if (getAfm() == null) {
371: return PDFont.getFontName(getBaseFont().stringValue());
372: }
373: return getAfm().getFontName();
374: }
375:
376: public FontStyle getFontStyle() {
377: if (getAfm() == null) {
378: return PDFont.getFontStyle(getBaseFont().stringValue());
379: }
380: return getAfm().getFontStyle();
381: }
382:
383: /*
384: * (non-Javadoc)
385: *
386: * @see de.intarsys.pdf.font.PDFont#getNextCID(byte[], int)
387: */
388: public CID getNextCID(byte[] bytes, int offset) {
389: return new CIDSelectorCode((int) (bytes[offset] & 0xff));
390: }
391:
392: /*
393: * (non-Javadoc)
394: *
395: * @see de.intarsys.pdf.pd.PDObject#cosGetExpectedSubtype()
396: */
397: protected COSName cosGetExpectedSubtype() {
398: return CN_Subtype_Type1;
399: }
400:
401: public float getUnderlinePosition() {
402: float result = -100;
403: if (getAfm() != null) {
404: String uPos = getAfm().getAttribute("UnderlinePosition");
405: if (uPos != null) {
406: result = Integer.parseInt(uPos);
407: }
408: }
409: return result;
410: }
411:
412: public int getUnderlineThickness() {
413: int result = 100;
414: if (getAfm() != null) {
415: String uThick = getAfm().getAttribute("UnderlineThickness");
416: if (uThick != null) {
417: result = Integer.parseInt(uThick);
418: }
419: }
420: return result;
421: }
422:
423: /*
424: * (non-Javadoc)
425: *
426: * @see de.intarsys.pdf.pd.PDObject#initializeFromScratch()
427: */
428: protected void initializeFromAFM() {
429: super .initializeFromScratch();
430: setBaseFont(name);
431: }
432:
433: /*
434: * (non-Javadoc)
435: *
436: * @see de.intarsys.pdf.pd.PDObject#initializeFromCos()
437: */
438: protected void initializeFromCos() {
439: super .initializeFromCos();
440:
441: AFM localAfm;
442:
443: localAfm = AFMRegistry.instance().getFont(
444: getBaseFont().stringValue());
445: if (localAfm == null) {
446: String alternativeName;
447:
448: // try to find the name in our list of known alternative names;
449: // maybe we can still get metrics
450: alternativeName = (String) FontAlternatives
451: .get(getBaseFont().stringValue());
452: localAfm = AFMRegistry.instance().getFont(alternativeName);
453: }
454:
455: // TODO 3 what happens if it is still null?
456: setAfm(localAfm);
457: }
458:
459: public void initializeFromScratchFontDescriptor() {
460: if (getAfm() == null) {
461: return;
462: }
463:
464: PDFontDescriptorEmbedded result = (PDFontDescriptorEmbedded) PDFontDescriptorEmbedded.META
465: .createNew();
466: String fontBBox = getAfm().getAttribute("FontBBox");
467: if (fontBBox == null) {
468: fontBBox = "0 0 0 0";
469: }
470: String italicAngle = getAfm().getAttribute("ItalicAngle");
471: if (italicAngle == null) {
472: italicAngle = "0";
473: }
474: String ascent = getAfm().getAttribute("Ascender");
475: if (ascent == null) {
476: ascent = "0";
477: }
478: String descend = getAfm().getAttribute("Descender");
479: if (descend == null) {
480: descend = "0";
481: }
482: String capheight = getAfm().getAttribute("CapHeight");
483: if (capheight == null) {
484: capheight = "0";
485: }
486:
487: // unsure here
488: String stemV = getAfm().getAttribute("StdHW");
489: if (stemV == null) {
490: stemV = "0";
491: }
492:
493: StringTokenizer tokens = new StringTokenizer(fontBBox);
494: String a = tokens.nextToken();
495: String b = tokens.nextToken();
496: String c = tokens.nextToken();
497: String d = tokens.nextToken();
498:
499: result.setFontBB(new CDSRectangle(
500: Integer.valueOf(a).intValue(), Integer.valueOf(b)
501: .intValue(), Integer.valueOf(c).intValue(),
502: Integer.valueOf(d).intValue()));
503: result.setItalicAngle(Float.valueOf(italicAngle).floatValue());
504: result.setAscent(Integer.valueOf(ascent).intValue());
505: result.setDescent(Integer.valueOf(descend).intValue());
506: result.setCapHeight(Integer.valueOf(capheight).intValue());
507: result.setStemV(Integer.valueOf(stemV).intValue());
508:
509: // Flags
510: result.setFlags(0);
511: result.getFlags()
512: .setSymbolic(
513: getAfm().getAttribute("CharacterSet").equals(
514: "Special"));
515:
516: setFontDescriptor(result);
517: }
518:
519: public boolean isStandardFont() {
520: if (cosGetField(DK_FontDescriptor).isNull()) {
521: return true;
522: }
523: if (Arrays.asList(FONTS_STANDARD).contains(
524: cosGetField(DK_BaseFont))) {
525: return true;
526: }
527: return false;
528: }
529:
530: public void setAfm(AFM afm) {
531: this .afm = afm;
532: }
533:
534: protected void setName(String paramName) {
535: String baseFont;
536:
537: name = paramName;
538: baseFont = paramName;
539: if (!Arrays.asList(FONTS_STANDARD).contains(baseFont)) {
540: String properName;
541:
542: properName = (String) FontAlternatives.get(paramName);
543: if (properName != null) {
544: baseFont = properName;
545: }
546: }
547: setBaseFont(baseFont);
548:
549: // TODO 3 what happens if it is null?
550: setAfm(AFMRegistry.instance().getFont(baseFont));
551: }
552: }
|