001: /*
002: * $Id: TTFFont.java,v 1.4 2007/12/20 18:33:31 rbair Exp $
003: *
004: * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005: * Santa Clara, California 95054, U.S.A. All rights reserved.
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020: */
021:
022: package com.sun.pdfview.font;
023:
024: import java.awt.geom.AffineTransform;
025: import java.awt.geom.GeneralPath;
026: import java.io.IOException;
027:
028: import com.sun.pdfview.PDFObject;
029: import com.sun.pdfview.font.ttf.CMap;
030: import com.sun.pdfview.font.ttf.CmapTable;
031: import com.sun.pdfview.font.ttf.Glyf;
032: import com.sun.pdfview.font.ttf.GlyfCompound;
033: import com.sun.pdfview.font.ttf.GlyfSimple;
034: import com.sun.pdfview.font.ttf.GlyfTable;
035: import com.sun.pdfview.font.ttf.HeadTable;
036: import com.sun.pdfview.font.ttf.HmtxTable;
037: import com.sun.pdfview.font.ttf.PostTable;
038: import com.sun.pdfview.font.ttf.TrueTypeFont;
039:
040: /**
041: * A true-type font
042: */
043: public class TTFFont extends OutlineFont {
044:
045: /** the truetype font itself */
046: private TrueTypeFont font;
047: /** the number of units per em in the font */
048: private float unitsPerEm;
049:
050: /**
051: * create a new TrueTypeFont object based on a description of the
052: * font from the PDF file. If the description happens to contain
053: * an in-line true-type font file (under key "FontFile2"), use the
054: * true type font. Otherwise, parse the description for key information
055: * and use that to generate an appropriate font.
056: */
057: public TTFFont(String baseFont, PDFObject fontObj,
058: PDFFontDescriptor descriptor) throws IOException {
059: super (baseFont, fontObj, descriptor);
060:
061: String fontName = descriptor.getFontName();
062: PDFObject ttfObj = descriptor.getFontFile2();
063:
064: // try {
065: // byte[] fontData = ttfObj.getStream();
066: // java.io.FileOutputStream fis = new java.io.FileOutputStream("/tmp/" + fontName + ".ttf");
067: // fis.write(fontData);
068: // fis.flush();
069: // fis.close();
070: // } catch (Exception ex) {
071: // ex.printStackTrace();
072: // }
073: if (ttfObj != null) {
074: font = TrueTypeFont.parseFont(ttfObj.getStreamBuffer());
075: } else {
076: font = null;
077: }
078: // System.out.println ("TTFFont: ttfObj: " + ttfObj + ", fontName: " + fontName);
079:
080: // read the units per em from the head table
081: HeadTable head = (HeadTable) font.getTable("head");
082: unitsPerEm = head.getUnitsPerEm();
083: }
084:
085: /**
086: * Get the outline of a character given the character code
087: */
088: protected synchronized GeneralPath getOutline(char src, float width) {
089: // find the cmaps
090: CmapTable cmap = (CmapTable) font.getTable("cmap");
091:
092: // if there are no cmaps, this is (hopefully) a cid-mapped font,
093: // so just trust the value we were given for src
094: if (cmap == null) {
095: return getOutline((int) src, width);
096: }
097:
098: CMap[] maps = cmap.getCMaps();
099:
100: // try the maps in order
101: for (int i = 0; i < maps.length; i++) {
102: int idx = maps[i].map(src);
103: if (idx != 0) {
104: return getOutline(idx, width);
105: }
106: }
107:
108: // not found, return the empty glyph
109: return getOutline(0, width);
110: }
111:
112: /**
113: * Get the outline of a character given the character name
114: */
115: protected synchronized GeneralPath getOutline(String name,
116: float width) {
117: PostTable post = (PostTable) font.getTable("post");
118: if (post == null) {
119: // no post table means no outline
120: return null;
121: }
122:
123: int idx = post.getGlyphNameIndex(name);
124: if (idx == 0) {
125: // not found
126: return null;
127: }
128:
129: return getOutline(idx, width);
130: }
131:
132: /**
133: * Get the outline of a character given the glyph id
134: */
135: protected synchronized GeneralPath getOutline(int glyphId,
136: float width) {
137: // find the glyph itself
138: GlyfTable glyf = (GlyfTable) font.getTable("glyf");
139: Glyf g = glyf.getGlyph(glyphId);
140:
141: GeneralPath gp = null;
142: if (g instanceof GlyfSimple) {
143: gp = renderSimpleGlyph((GlyfSimple) g);
144: } else if (g instanceof GlyfCompound) {
145: gp = renderCompoundGlyph(glyf, (GlyfCompound) g);
146: } else {
147: gp = new GeneralPath();
148: }
149:
150: // calculate the advance
151: HmtxTable hmtx = (HmtxTable) font.getTable("hmtx");
152: float advance = (float) hmtx.getAdvance(glyphId)
153: / (float) unitsPerEm;
154:
155: // scale the glyph to match the desired advance
156: float widthfactor = width / advance;
157:
158: // the base transform scales the glyph to 1x1
159: AffineTransform at = AffineTransform.getScaleInstance(
160: 1 / unitsPerEm, 1 / unitsPerEm);
161: at
162: .concatenate(AffineTransform.getScaleInstance(
163: widthfactor, 1));
164:
165: gp.transform(at);
166:
167: return gp;
168: }
169:
170: /**
171: * Render a simple glyf
172: */
173: protected GeneralPath renderSimpleGlyph(GlyfSimple g) {
174: // the current contour
175: int curContour = 0;
176:
177: // the render state
178: RenderState rs = new RenderState();
179: rs.gp = new GeneralPath();
180:
181: for (int i = 0; i < g.getNumPoints(); i++) {
182: PointRec rec = new PointRec(g, i);
183:
184: if (rec.onCurve) {
185: addOnCurvePoint(rec, rs);
186: } else {
187: addOffCurvePoint(rec, rs);
188: }
189:
190: // see if we just ended a contour
191: if (i == g.getContourEndPoint(curContour)) {
192: curContour++;
193:
194: if (rs.firstOff != null) {
195: addOffCurvePoint(rs.firstOff, rs);
196: }
197:
198: if (rs.firstOn != null) {
199: addOnCurvePoint(rs.firstOn, rs);
200: }
201:
202: rs.firstOn = null;
203: rs.firstOff = null;
204: rs.prevOff = null;
205: }
206: }
207:
208: return rs.gp;
209: }
210:
211: /**
212: * Render a compound glyf
213: */
214: protected GeneralPath renderCompoundGlyph(GlyfTable glyf,
215: GlyfCompound g) {
216: GeneralPath gp = new GeneralPath();
217:
218: for (int i = 0; i < g.getNumComponents(); i++) {
219: // find and render the component glyf
220: GlyfSimple gs = (GlyfSimple) glyf.getGlyph(g
221: .getGlyphIndex(i));
222: GeneralPath path = renderSimpleGlyph(gs);
223:
224: // multiply the translations by units per em
225: double[] matrix = g.getTransform(i);
226:
227: // transform the path
228: path.transform(new AffineTransform(matrix));
229:
230: // add it to the global path
231: gp.append(path, false);
232: }
233:
234: return gp;
235: }
236:
237: /** add a point on the curve */
238: private void addOnCurvePoint(PointRec rec, RenderState rs) {
239: // if the point is on the curve, either move to it,
240: // or draw a line from the previous point
241: if (rs.firstOn == null) {
242: rs.firstOn = rec;
243: rs.gp.moveTo(rec.x, rec.y);
244: } else if (rs.prevOff != null) {
245: rs.gp.quadTo(rs.prevOff.x, rs.prevOff.y, rec.x, rec.y);
246: rs.prevOff = null;
247: } else {
248: rs.gp.lineTo(rec.x, rec.y);
249: }
250: }
251:
252: /** add a point off the curve */
253: private void addOffCurvePoint(PointRec rec, RenderState rs) {
254: if (rs.prevOff != null) {
255: PointRec oc = new PointRec((rec.x + rs.prevOff.x) / 2,
256: (rec.y + rs.prevOff.y) / 2, true);
257: addOnCurvePoint(oc, rs);
258: } else if (rs.firstOn == null) {
259: rs.firstOff = rec;
260: }
261:
262: rs.prevOff = rec;
263: }
264:
265: class RenderState {
266: // the shape itself
267: GeneralPath gp;
268: // the first off and on-curve points in the current segment
269: PointRec firstOn;
270: PointRec firstOff;
271: // the previous off and on-curve points in the current segment
272: PointRec prevOff;
273: }
274:
275: /** a point on the stack of points */
276: class PointRec {
277:
278: int x;
279: int y;
280: boolean onCurve;
281:
282: public PointRec(int x, int y, boolean onCurve) {
283: this .x = x;
284: this .y = y;
285: this .onCurve = onCurve;
286: }
287:
288: public PointRec(GlyfSimple g, int idx) {
289: x = g.getXCoord(idx);
290: y = g.getYCoord(idx);
291: onCurve = g.onCurve(idx);
292: }
293: }
294: }
|