2
|
1 |
/*
|
|
2 |
* Copyright 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
|
|
3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
4 |
*
|
|
5 |
* This code is free software; you can redistribute it and/or modify it
|
|
6 |
* under the terms of the GNU General Public License version 2 only, as
|
|
7 |
* published by the Free Software Foundation. Sun designates this
|
|
8 |
* particular file as subject to the "Classpath" exception as provided
|
|
9 |
* by Sun in the LICENSE file that accompanied this code.
|
|
10 |
*
|
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that
|
|
15 |
* accompanied this code).
|
|
16 |
*
|
|
17 |
* You should have received a copy of the GNU General Public License version
|
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
20 |
*
|
|
21 |
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
|
|
22 |
* CA 95054 USA or visit www.sun.com if you need additional information or
|
|
23 |
* have any questions.
|
|
24 |
*/
|
|
25 |
|
|
26 |
package sun.font;
|
|
27 |
|
|
28 |
import java.lang.ref.WeakReference;
|
|
29 |
import java.awt.FontFormatException;
|
|
30 |
import java.io.FileNotFoundException;
|
|
31 |
import java.io.IOException;
|
|
32 |
import java.io.RandomAccessFile;
|
|
33 |
import java.io.UnsupportedEncodingException;
|
|
34 |
import java.lang.ref.WeakReference;
|
|
35 |
import java.nio.ByteBuffer;
|
|
36 |
import java.nio.ByteOrder;
|
|
37 |
import java.nio.MappedByteBuffer;
|
|
38 |
import java.nio.BufferUnderflowException;
|
|
39 |
import java.nio.channels.ClosedChannelException;
|
|
40 |
import java.nio.channels.FileChannel;
|
|
41 |
import sun.java2d.Disposer;
|
|
42 |
import java.util.HashSet;
|
|
43 |
import java.util.HashMap;
|
|
44 |
import java.awt.Font;
|
|
45 |
|
|
46 |
/*
|
|
47 |
* Adobe Technical Note 5040 details the format of PFB files.
|
|
48 |
* the file is divided into ascii and binary sections. Each section
|
|
49 |
* starts with a header
|
|
50 |
* 0x8001 - start of binary data, is followed by 4 bytes length, then data
|
|
51 |
* 0x8002 - start of ascii data, is followed by 4 bytes length, then data
|
|
52 |
* 0x8003 - end of data segment
|
|
53 |
* The length is organised as LSB->MSB.
|
|
54 |
*
|
|
55 |
* Note: I experimented with using a MappedByteBuffer and
|
|
56 |
* there were two problems/questions.
|
|
57 |
* 1. If a global buffer is used rather than one allocated in the calling
|
|
58 |
* context, then we need to synchronize on all uses of that data, which
|
|
59 |
* means more code would beed to be synchronized with probable repercussions
|
|
60 |
* elsewhere.
|
|
61 |
* 2. It is not clear whether to free the buffer when the file is closed.
|
|
62 |
* If we have the contents in memory then why keep open files around?
|
|
63 |
* The mmapped buffer doesn't need it.
|
|
64 |
* Also regular GC is what frees the buffer. So closing the file and nulling
|
|
65 |
* out the reference still needs to wait for the buffer to be GC'd to
|
|
66 |
* reclaim the storage.
|
|
67 |
* If the contents of the buffer are persistent there's no need
|
|
68 |
* to worry about synchronization.
|
|
69 |
* Perhaps could use a WeakReference, and when its referent is gone, and
|
|
70 |
* need it can just reopen the file.
|
|
71 |
* Type1 fonts thus don't use up file descriptor references, but can
|
|
72 |
* use memory footprint in a way that's managed by the host O/S.
|
|
73 |
* The main "pain" may be the different model means code needs to be written
|
|
74 |
* without assumptions as to how this is handled by the different subclasses
|
|
75 |
* of FileFont.
|
|
76 |
*/
|
|
77 |
public class Type1Font extends FileFont {
|
|
78 |
|
|
79 |
WeakReference bufferRef = new WeakReference(null);
|
|
80 |
|
|
81 |
private String psName = null;
|
|
82 |
|
|
83 |
static private HashMap styleAbbreviationsMapping;
|
|
84 |
static private HashSet styleNameTokes;
|
|
85 |
|
|
86 |
static {
|
|
87 |
styleAbbreviationsMapping = new HashMap();
|
|
88 |
styleNameTokes = new HashSet();
|
|
89 |
|
|
90 |
/* These abbreviation rules are taken from Appendix 1 of Adobe Technical Note #5088 */
|
|
91 |
/* NB: this list is not complete - we did not include abbreviations which contain
|
|
92 |
several capital letters because current expansion algorithm do not support this.
|
|
93 |
(namely we have omited MM aka "Multiple Master", OsF aka "Oldstyle figures",
|
|
94 |
OS aka "Oldstyle", SC aka "Small caps" and DS aka "Display" */
|
|
95 |
String nm[] = {"Black", "Bold", "Book", "Demi", "Heavy", "Light",
|
|
96 |
"Meduium", "Nord", "Poster", "Regular", "Super", "Thin",
|
|
97 |
"Compressed", "Condensed", "Compact", "Extended", "Narrow",
|
|
98 |
"Inclined", "Italic", "Kursiv", "Oblique", "Upright", "Sloped",
|
|
99 |
"Semi", "Ultra", "Extra",
|
|
100 |
"Alternate", "Alternate", "Deutsche Fraktur", "Expert", "Inline", "Ornaments",
|
|
101 |
"Outline", "Roman", "Rounded", "Script", "Shaded", "Swash", "Titling", "Typewriter"};
|
|
102 |
String abbrv[] = {"Blk", "Bd", "Bk", "Dm", "Hv", "Lt",
|
|
103 |
"Md", "Nd", "Po", "Rg", "Su", "Th",
|
|
104 |
"Cm", "Cn", "Ct", "Ex", "Nr",
|
|
105 |
"Ic", "It", "Ks", "Obl", "Up", "Sl",
|
|
106 |
"Sm", "Ult", "X",
|
|
107 |
"A", "Alt", "Dfr", "Exp", "In", "Or",
|
|
108 |
"Ou", "Rm", "Rd", "Scr", "Sh", "Sw", "Ti", "Typ"};
|
|
109 |
/* This is only subset of names from nm[] because we want to distinguish things
|
|
110 |
like "Lucida Sans TypeWriter Bold" and "Lucida Sans Bold".
|
|
111 |
Names from "Design and/or special purpose" group are omitted. */
|
|
112 |
String styleTokens[] = {"Black", "Bold", "Book", "Demi", "Heavy", "Light",
|
|
113 |
"Medium", "Nord", "Poster", "Regular", "Super", "Thin",
|
|
114 |
"Compressed", "Condensed", "Compact", "Extended", "Narrow",
|
|
115 |
"Inclined", "Italic", "Kursiv", "Oblique", "Upright", "Sloped", "Slanted",
|
|
116 |
"Semi", "Ultra", "Extra"};
|
|
117 |
|
|
118 |
for(int i=0; i<nm.length; i++) {
|
|
119 |
styleAbbreviationsMapping.put(abbrv[i], nm[i]);
|
|
120 |
}
|
|
121 |
for(int i=0; i<styleTokens.length; i++) {
|
|
122 |
styleNameTokes.add(styleTokens[i]);
|
|
123 |
}
|
|
124 |
}
|
|
125 |
|
|
126 |
|
|
127 |
/**
|
|
128 |
* - does basic verification of the file
|
|
129 |
* - reads the names (full, family).
|
|
130 |
* - determines the style of the font.
|
|
131 |
* @throws FontFormatException - if the font can't be opened
|
|
132 |
* or fails verification, or there's no usable cmap
|
|
133 |
*/
|
|
134 |
public Type1Font(String platname, Object nativeNames)
|
|
135 |
throws FontFormatException {
|
|
136 |
super(platname, nativeNames);
|
|
137 |
fontRank = Font2D.TYPE1_RANK;
|
|
138 |
checkedNatives = true;
|
|
139 |
verify();
|
|
140 |
}
|
|
141 |
|
|
142 |
private synchronized ByteBuffer getBuffer() throws FontFormatException {
|
|
143 |
MappedByteBuffer mapBuf = (MappedByteBuffer)bufferRef.get();
|
|
144 |
if (mapBuf == null) {
|
|
145 |
//System.out.println("open T1 " + platName);
|
|
146 |
try {
|
|
147 |
RandomAccessFile raf = (RandomAccessFile)
|
|
148 |
java.security.AccessController.doPrivileged(
|
|
149 |
new java.security.PrivilegedAction() {
|
|
150 |
public Object run() {
|
|
151 |
try {
|
|
152 |
return new RandomAccessFile(platName, "r");
|
|
153 |
} catch (FileNotFoundException ffne) {
|
|
154 |
}
|
|
155 |
return null;
|
|
156 |
}
|
|
157 |
});
|
|
158 |
FileChannel fc = raf.getChannel();
|
|
159 |
fileSize = (int)fc.size();
|
|
160 |
mapBuf = fc.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
|
|
161 |
mapBuf.position(0);
|
|
162 |
bufferRef = new WeakReference(mapBuf);
|
|
163 |
fc.close();
|
|
164 |
} catch (NullPointerException e) {
|
|
165 |
throw new FontFormatException(e.toString());
|
|
166 |
} catch (ClosedChannelException e) {
|
|
167 |
/* NIO I/O is interruptible, recurse to retry operation.
|
|
168 |
* Clear interrupts before recursing in case NIO didn't.
|
|
169 |
*/
|
|
170 |
Thread.interrupted();
|
|
171 |
return getBuffer();
|
|
172 |
} catch (IOException e) {
|
|
173 |
throw new FontFormatException(e.toString());
|
|
174 |
}
|
|
175 |
}
|
|
176 |
return mapBuf;
|
|
177 |
}
|
|
178 |
|
|
179 |
protected void close() {
|
|
180 |
}
|
|
181 |
|
|
182 |
/* called from native code to read file into a direct byte buffer */
|
|
183 |
void readFile(ByteBuffer buffer) {
|
|
184 |
RandomAccessFile raf = null;
|
|
185 |
FileChannel fc;
|
|
186 |
try {
|
|
187 |
raf = (RandomAccessFile)
|
|
188 |
java.security.AccessController.doPrivileged(
|
|
189 |
new java.security.PrivilegedAction() {
|
|
190 |
public Object run() {
|
|
191 |
try {
|
|
192 |
return new RandomAccessFile(platName, "r");
|
|
193 |
} catch (FileNotFoundException fnfe) {
|
|
194 |
}
|
|
195 |
return null;
|
|
196 |
}
|
|
197 |
});
|
|
198 |
fc = raf.getChannel();
|
|
199 |
while (buffer.remaining() > 0 && fc.read(buffer) != -1) {}
|
|
200 |
} catch (NullPointerException npe) {
|
|
201 |
} catch (ClosedChannelException e) {
|
|
202 |
try {
|
|
203 |
if (raf != null) {
|
|
204 |
raf.close();
|
|
205 |
raf = null;
|
|
206 |
}
|
|
207 |
} catch (IOException ioe) {
|
|
208 |
}
|
|
209 |
/* NIO I/O is interruptible, recurse to retry operation.
|
|
210 |
* Clear interrupts before recursing in case NIO didn't.
|
|
211 |
*/
|
|
212 |
Thread.interrupted();
|
|
213 |
readFile(buffer);
|
|
214 |
} catch (IOException e) {
|
|
215 |
} finally {
|
|
216 |
if (raf != null) {
|
|
217 |
try {
|
|
218 |
raf.close();
|
|
219 |
} catch (IOException e) {
|
|
220 |
}
|
|
221 |
}
|
|
222 |
}
|
|
223 |
}
|
|
224 |
|
|
225 |
public synchronized ByteBuffer readBlock(int offset, int length) {
|
|
226 |
ByteBuffer mappedBuf = null;
|
|
227 |
try {
|
|
228 |
mappedBuf = getBuffer();
|
|
229 |
if (offset > fileSize) {
|
|
230 |
offset = fileSize;
|
|
231 |
}
|
|
232 |
mappedBuf.position(offset);
|
|
233 |
return mappedBuf.slice();
|
|
234 |
} catch (FontFormatException e) {
|
|
235 |
return null;
|
|
236 |
}
|
|
237 |
}
|
|
238 |
|
|
239 |
private void verify() throws FontFormatException {
|
|
240 |
/* Normal usage should not call getBuffer(), as its state
|
|
241 |
* ie endianness, position etc, are shared. verify() can do
|
|
242 |
* this as its called only from within the constructor before
|
|
243 |
* there are other users of this object.
|
|
244 |
*/
|
|
245 |
ByteBuffer bb = getBuffer();
|
|
246 |
if (bb.capacity() < 6) {
|
|
247 |
throw new FontFormatException("short file");
|
|
248 |
}
|
|
249 |
int val = bb.get(0) & 0xff;
|
|
250 |
if ((bb.get(0) & 0xff) == 0x80) {
|
|
251 |
verifyPFB(bb);
|
|
252 |
bb.position(6);
|
|
253 |
} else {
|
|
254 |
verifyPFA(bb);
|
|
255 |
bb.position(0);
|
|
256 |
}
|
|
257 |
initNames(bb);
|
|
258 |
if (familyName == null || fullName == null) {
|
|
259 |
throw new FontFormatException("Font name not found");
|
|
260 |
}
|
|
261 |
setStyle();
|
|
262 |
}
|
|
263 |
|
|
264 |
public int getFileSize() {
|
|
265 |
if (fileSize == 0) {
|
|
266 |
try {
|
|
267 |
getBuffer();
|
|
268 |
} catch (FontFormatException e) {
|
|
269 |
}
|
|
270 |
}
|
|
271 |
return fileSize;
|
|
272 |
}
|
|
273 |
|
|
274 |
private void verifyPFA(ByteBuffer bb) throws FontFormatException {
|
|
275 |
if (bb.getShort() != 0x2521) { // 0x2521 is %!
|
|
276 |
throw new FontFormatException("bad pfa font");
|
|
277 |
}
|
|
278 |
// remind - additional verification needed?
|
|
279 |
}
|
|
280 |
|
|
281 |
private void verifyPFB(ByteBuffer bb) throws FontFormatException {
|
|
282 |
|
|
283 |
int pos = 0;
|
|
284 |
while (true) {
|
|
285 |
try {
|
|
286 |
int segType = bb.getShort(pos) & 0xffff;
|
|
287 |
if (segType == 0x8001 || segType == 0x8002) {
|
|
288 |
bb.order(ByteOrder.LITTLE_ENDIAN);
|
|
289 |
int segLen = bb.getInt(pos+2);
|
|
290 |
bb.order(ByteOrder.BIG_ENDIAN);
|
|
291 |
if (segLen <= 0) {
|
|
292 |
throw new FontFormatException("bad segment length");
|
|
293 |
}
|
|
294 |
pos += segLen+6;
|
|
295 |
} else if (segType == 0x8003) {
|
|
296 |
return;
|
|
297 |
} else {
|
|
298 |
throw new FontFormatException("bad pfb file");
|
|
299 |
}
|
|
300 |
} catch (BufferUnderflowException bue) {
|
|
301 |
throw new FontFormatException(bue.toString());
|
|
302 |
} catch (Exception e) {
|
|
303 |
throw new FontFormatException(e.toString());
|
|
304 |
}
|
|
305 |
}
|
|
306 |
}
|
|
307 |
|
|
308 |
private static final int PSEOFTOKEN = 0;
|
|
309 |
private static final int PSNAMETOKEN = 1;
|
|
310 |
private static final int PSSTRINGTOKEN = 2;
|
|
311 |
|
|
312 |
/* Need to parse the ascii contents of the Type1 font file,
|
|
313 |
* looking for FullName, FamilyName and FontName.
|
|
314 |
* If explicit names are not found then extract them from first text line.
|
|
315 |
* Operating on bytes so can't use Java String utilities, which
|
|
316 |
* is a large part of why this is a hack.
|
|
317 |
*
|
|
318 |
* Also check for mandatory FontType and verify if it is supported.
|
|
319 |
*/
|
|
320 |
private void initNames(ByteBuffer bb) throws FontFormatException {
|
|
321 |
boolean eof = false;
|
|
322 |
String fontType = null;
|
|
323 |
try {
|
|
324 |
//Parse font looking for explicit FullName, FamilyName and FontName
|
|
325 |
// (acording to Type1 spec they are optional)
|
|
326 |
while ((fullName == null || familyName == null || psName == null || fontType == null) && !eof) {
|
|
327 |
int tokenType = nextTokenType(bb);
|
|
328 |
if (tokenType == PSNAMETOKEN) {
|
|
329 |
int pos = bb.position();
|
|
330 |
if (bb.get(pos) == 'F') {
|
|
331 |
String s = getSimpleToken(bb);
|
|
332 |
if ("FullName".equals(s)) {
|
|
333 |
if (nextTokenType(bb)==PSSTRINGTOKEN) {
|
|
334 |
fullName = getString(bb);
|
|
335 |
}
|
|
336 |
} else if ("FamilyName".equals(s)) {
|
|
337 |
if (nextTokenType(bb)==PSSTRINGTOKEN) {
|
|
338 |
familyName = getString(bb);
|
|
339 |
}
|
|
340 |
} else if ("FontName".equals(s)) {
|
|
341 |
if (nextTokenType(bb)==PSNAMETOKEN) {
|
|
342 |
psName = getSimpleToken(bb);
|
|
343 |
}
|
|
344 |
} else if ("FontType".equals(s)) {
|
|
345 |
/* look for
|
|
346 |
/FontType id def
|
|
347 |
*/
|
|
348 |
String token = getSimpleToken(bb);
|
|
349 |
if ("def".equals(getSimpleToken(bb))) {
|
|
350 |
fontType = token;
|
|
351 |
}
|
|
352 |
}
|
|
353 |
} else {
|
|
354 |
while (bb.get() > ' '); // skip token
|
|
355 |
}
|
|
356 |
} else if (tokenType == PSEOFTOKEN) {
|
|
357 |
eof = true;
|
|
358 |
}
|
|
359 |
}
|
|
360 |
} catch (Exception e) {
|
|
361 |
throw new FontFormatException(e.toString());
|
|
362 |
}
|
|
363 |
|
|
364 |
/* Ignore all fonts besides Type1 (e.g. Type3 fonts) */
|
|
365 |
if (!"1".equals(fontType)) {
|
|
366 |
throw new FontFormatException("Unsupported font type");
|
|
367 |
}
|
|
368 |
|
|
369 |
if (psName == null) { //no explicit FontName
|
|
370 |
// Try to extract font name from the first text line.
|
|
371 |
// According to Type1 spec first line consist of
|
|
372 |
// "%!FontType1-SpecVersion: FontName FontVersion"
|
|
373 |
// or
|
|
374 |
// "%!PS-AdobeFont-1.0: FontName version"
|
|
375 |
bb.position(0);
|
|
376 |
if (bb.getShort() != 0x2521) { //if pfb (do not start with "%!")
|
|
377 |
//skip segment header and "%!"
|
|
378 |
bb.position(8);
|
|
379 |
//NB: assume that first segment is ASCII one
|
|
380 |
// (is it possible to have valid Type1 font with first binary segment?)
|
|
381 |
}
|
|
382 |
String formatType = getSimpleToken(bb);
|
|
383 |
if (!formatType.startsWith("FontType1-") && !formatType.startsWith("PS-AdobeFont-")) {
|
|
384 |
throw new FontFormatException("Unsupported font format [" + formatType + "]");
|
|
385 |
}
|
|
386 |
psName = getSimpleToken(bb);
|
|
387 |
}
|
|
388 |
|
|
389 |
//if we got to the end of file then we did not find at least one of FullName or FamilyName
|
|
390 |
//Try to deduce missing names from present ones
|
|
391 |
//NB: At least psName must be already initialized by this moment
|
|
392 |
if (eof) {
|
|
393 |
//if we find fullName or familyName then use it as another name too
|
|
394 |
if (fullName != null) {
|
|
395 |
familyName = fullName2FamilyName(fullName);
|
|
396 |
} else if (familyName != null) {
|
|
397 |
fullName = familyName;
|
|
398 |
} else { //fallback - use postscript font name to deduce full and family names
|
|
399 |
fullName = psName2FullName(psName);
|
|
400 |
familyName = psName2FamilyName(psName);
|
|
401 |
}
|
|
402 |
}
|
|
403 |
}
|
|
404 |
|
|
405 |
private String fullName2FamilyName(String name) {
|
|
406 |
String res, token;
|
|
407 |
int len, start, end; //length of family name part
|
|
408 |
|
|
409 |
//FamilyName is truncated version of FullName
|
|
410 |
//Truncated tail must contain only style modifiers
|
|
411 |
|
|
412 |
end = name.length();
|
|
413 |
|
|
414 |
while (end > 0) {
|
|
415 |
start = end - 1;
|
|
416 |
while (start > 0 && name.charAt(start) != ' ')
|
|
417 |
start--;
|
|
418 |
//as soon as we meet first non style token truncate
|
|
419 |
// current tail and return
|
|
420 |
if (!isStyleToken(name.substring(start+1, end))) {
|
|
421 |
return name.substring(0, end);
|
|
422 |
}
|
|
423 |
end = start;
|
|
424 |
}
|
|
425 |
|
|
426 |
return name; //should not happen
|
|
427 |
}
|
|
428 |
|
|
429 |
private String expandAbbreviation(String abbr) {
|
|
430 |
if (styleAbbreviationsMapping.containsKey(abbr))
|
|
431 |
return (String) styleAbbreviationsMapping.get(abbr);
|
|
432 |
return abbr;
|
|
433 |
}
|
|
434 |
|
|
435 |
private boolean isStyleToken(String token) {
|
|
436 |
return styleNameTokes.contains(token);
|
|
437 |
}
|
|
438 |
|
|
439 |
private String psName2FullName(String name) {
|
|
440 |
String res;
|
|
441 |
int pos;
|
|
442 |
|
|
443 |
//According to Adobe technical note #5088 psName (aka FontName) has form
|
|
444 |
// <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
|
|
445 |
//where spaces are not allowed.
|
|
446 |
|
|
447 |
//Conversion: Expand abbreviations in style portion (everything after '-'),
|
|
448 |
// replace '-' with space and insert missing spaces
|
|
449 |
pos = name.indexOf("-");
|
|
450 |
if (pos >= 0) {
|
|
451 |
res = expandName(name.substring(0, pos), false);
|
|
452 |
res += " " + expandName(name.substring(pos+1), true);
|
|
453 |
} else {
|
|
454 |
res = expandName(name, false);
|
|
455 |
}
|
|
456 |
|
|
457 |
return res;
|
|
458 |
}
|
|
459 |
|
|
460 |
private String psName2FamilyName(String name) {
|
|
461 |
String tmp = name;
|
|
462 |
|
|
463 |
//According to Adobe technical note #5088 psName (aka FontName) has form
|
|
464 |
// <Family Name><VendorID>-<Weight><Width><Slant><Character Set>
|
|
465 |
//where spaces are not allowed.
|
|
466 |
|
|
467 |
//Conversion: Truncate style portion (everything after '-')
|
|
468 |
// and insert missing spaces
|
|
469 |
|
|
470 |
if (tmp.indexOf("-") > 0) {
|
|
471 |
tmp = tmp.substring(0, tmp.indexOf("-"));
|
|
472 |
}
|
|
473 |
|
|
474 |
return expandName(tmp, false);
|
|
475 |
}
|
|
476 |
|
|
477 |
private int nextCapitalLetter(String s, int off) {
|
|
478 |
for (; (off >=0) && off < s.length(); off++) {
|
|
479 |
if (s.charAt(off) >= 'A' && s.charAt(off) <= 'Z')
|
|
480 |
return off;
|
|
481 |
}
|
|
482 |
return -1;
|
|
483 |
}
|
|
484 |
|
|
485 |
private String expandName(String s, boolean tryExpandAbbreviations) {
|
|
486 |
StringBuffer res = new StringBuffer(s.length() + 10);
|
|
487 |
int start=0, end;
|
|
488 |
|
|
489 |
while(start < s.length()) {
|
|
490 |
end = nextCapitalLetter(s, start + 1);
|
|
491 |
if (end < 0) {
|
|
492 |
end = s.length();
|
|
493 |
}
|
|
494 |
|
|
495 |
if (start != 0) {
|
|
496 |
res.append(" ");
|
|
497 |
}
|
|
498 |
|
|
499 |
if (tryExpandAbbreviations) {
|
|
500 |
res.append(expandAbbreviation(s.substring(start, end)));
|
|
501 |
} else {
|
|
502 |
res.append(s.substring(start, end));
|
|
503 |
}
|
|
504 |
start = end;
|
|
505 |
}
|
|
506 |
|
|
507 |
return res.toString();
|
|
508 |
}
|
|
509 |
|
|
510 |
/* skip lines beginning with "%" and leading white space on a line */
|
|
511 |
private byte skip(ByteBuffer bb) {
|
|
512 |
byte b = bb.get();
|
|
513 |
while (b == '%') {
|
|
514 |
while (true) {
|
|
515 |
b = bb.get();
|
|
516 |
if (b == '\r' || b == '\n') {
|
|
517 |
break;
|
|
518 |
}
|
|
519 |
}
|
|
520 |
}
|
|
521 |
while (b <= ' ') {
|
|
522 |
b = bb.get();
|
|
523 |
}
|
|
524 |
return b;
|
|
525 |
}
|
|
526 |
|
|
527 |
/*
|
|
528 |
* Token types:
|
|
529 |
* PSNAMETOKEN - /
|
|
530 |
* PSSTRINGTOKEN - literal text string
|
|
531 |
*/
|
|
532 |
private int nextTokenType(ByteBuffer bb) {
|
|
533 |
|
|
534 |
try {
|
|
535 |
byte b = skip(bb);
|
|
536 |
|
|
537 |
while (true) {
|
|
538 |
if (b == (byte)'/') { // PS defined name follows.
|
|
539 |
return PSNAMETOKEN;
|
|
540 |
} else if (b == (byte)'(') { // PS string follows
|
|
541 |
return PSSTRINGTOKEN;
|
|
542 |
} else if ((b == (byte)'\r') || (b == (byte)'\n')) {
|
|
543 |
b = skip(bb);
|
|
544 |
} else {
|
|
545 |
b = bb.get();
|
|
546 |
}
|
|
547 |
}
|
|
548 |
} catch (BufferUnderflowException e) {
|
|
549 |
return PSEOFTOKEN;
|
|
550 |
}
|
|
551 |
}
|
|
552 |
|
|
553 |
/* Read simple token (sequence of non-whitespace characters)
|
|
554 |
starting from the current position.
|
|
555 |
Skip leading whitespaces (if any). */
|
|
556 |
private String getSimpleToken(ByteBuffer bb) {
|
|
557 |
while (bb.get() <= ' ');
|
|
558 |
int pos1 = bb.position()-1;
|
|
559 |
while (bb.get() > ' ');
|
|
560 |
int pos2 = bb.position();
|
|
561 |
byte[] nameBytes = new byte[pos2-pos1-1];
|
|
562 |
bb.position(pos1);
|
|
563 |
bb.get(nameBytes);
|
|
564 |
try {
|
|
565 |
return new String(nameBytes, "US-ASCII");
|
|
566 |
} catch (UnsupportedEncodingException e) {
|
|
567 |
return new String(nameBytes);
|
|
568 |
}
|
|
569 |
}
|
|
570 |
|
|
571 |
private String getString(ByteBuffer bb) {
|
|
572 |
int pos1 = bb.position();
|
|
573 |
while (bb.get() != ')');
|
|
574 |
int pos2 = bb.position();
|
|
575 |
byte[] nameBytes = new byte[pos2-pos1-1];
|
|
576 |
bb.position(pos1);
|
|
577 |
bb.get(nameBytes);
|
|
578 |
try {
|
|
579 |
return new String(nameBytes, "US-ASCII");
|
|
580 |
} catch (UnsupportedEncodingException e) {
|
|
581 |
return new String(nameBytes);
|
|
582 |
}
|
|
583 |
}
|
|
584 |
|
|
585 |
|
|
586 |
public String getPostscriptName() {
|
|
587 |
return psName;
|
|
588 |
}
|
|
589 |
|
|
590 |
protected synchronized FontScaler getScaler() {
|
|
591 |
if (scaler == null) {
|
|
592 |
return FontManager.getScaler(this, 0, false, fileSize);
|
|
593 |
}
|
|
594 |
|
|
595 |
return scaler;
|
|
596 |
}
|
|
597 |
|
|
598 |
CharToGlyphMapper getMapper() {
|
|
599 |
if (mapper == null) {
|
|
600 |
mapper = new Type1GlyphMapper(this);
|
|
601 |
}
|
|
602 |
return mapper;
|
|
603 |
}
|
|
604 |
|
|
605 |
public int getNumGlyphs() {
|
|
606 |
try {
|
|
607 |
return getScaler().getNumGlyphs();
|
|
608 |
} catch (FontScalerException e) {
|
|
609 |
scaler = FontManager.getNullScaler();
|
|
610 |
return getNumGlyphs();
|
|
611 |
}
|
|
612 |
}
|
|
613 |
|
|
614 |
public int getMissingGlyphCode() {
|
|
615 |
try {
|
|
616 |
return getScaler().getMissingGlyphCode();
|
|
617 |
} catch (FontScalerException e) {
|
|
618 |
scaler = FontManager.getNullScaler();
|
|
619 |
return getMissingGlyphCode();
|
|
620 |
}
|
|
621 |
}
|
|
622 |
|
|
623 |
public int getGlyphCode(char charCode) {
|
|
624 |
try {
|
|
625 |
return getScaler().getGlyphCode(charCode);
|
|
626 |
} catch (FontScalerException e) {
|
|
627 |
scaler = FontManager.getNullScaler();
|
|
628 |
return getGlyphCode(charCode);
|
|
629 |
}
|
|
630 |
}
|
|
631 |
|
|
632 |
public String toString() {
|
|
633 |
return "** Type1 Font: Family="+familyName+ " Name="+fullName+
|
|
634 |
" style="+style+" fileName="+platName;
|
|
635 |
}
|
|
636 |
|
|
637 |
}
|