001: /*
002: * Copyright 1996-2007 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.awt;
027:
028: import java.awt.GraphicsEnvironment;
029: import java.awt.peer.FontPeer;
030: import java.util.Locale;
031: import java.util.Vector;
032: import sun.java2d.FontSupport;
033: import java.nio.CharBuffer;
034: import java.nio.ByteBuffer;
035:
036: public abstract class PlatformFont implements FontPeer {
037:
038: static {
039: NativeLibLoader.loadLibraries();
040: initIDs();
041: }
042:
043: protected FontDescriptor[] componentFonts;
044: protected char defaultChar;
045: protected FontConfiguration fontConfig;
046:
047: protected FontDescriptor defaultFont;
048:
049: protected String familyName;
050:
051: private Object[] fontCache;
052:
053: // Maybe this should be a property that is set based
054: // on the locale?
055: protected static int FONTCACHESIZE = 256;
056: protected static int FONTCACHEMASK = PlatformFont.FONTCACHESIZE - 1;
057: protected static String osVersion;
058:
059: public PlatformFont(String name, int style) {
060: GraphicsEnvironment ge = GraphicsEnvironment
061: .getLocalGraphicsEnvironment();
062: if (ge instanceof FontSupport) {
063: fontConfig = ((FontSupport) ge).getFontConfiguration();
064: }
065: if (fontConfig == null) {
066: return;
067: }
068:
069: // map given font name to a valid logical font family name
070: familyName = name.toLowerCase(Locale.ENGLISH);
071: if (!FontConfiguration.isLogicalFontFamilyName(familyName)) {
072: familyName = fontConfig.getFallbackFamilyName(familyName,
073: "sansserif");
074: }
075:
076: componentFonts = fontConfig.getFontDescriptors(familyName,
077: style);
078:
079: // search default character
080: //
081: char missingGlyphCharacter = getMissingGlyphCharacter();
082:
083: defaultChar = '?';
084: if (componentFonts.length > 0)
085: defaultFont = componentFonts[0];
086:
087: for (int i = 0; i < componentFonts.length; i++) {
088: if (componentFonts[i].isExcluded(missingGlyphCharacter)) {
089: continue;
090: }
091:
092: if (componentFonts[i].encoder
093: .canEncode(missingGlyphCharacter)) {
094: defaultFont = componentFonts[i];
095: defaultChar = missingGlyphCharacter;
096: break;
097: }
098: }
099: }
100:
101: /**
102: * Returns the character that should be rendered when a glyph
103: * is missing.
104: */
105: protected abstract char getMissingGlyphCharacter();
106:
107: /**
108: * make a array of CharsetString with given String.
109: */
110: public CharsetString[] makeMultiCharsetString(String str) {
111: return makeMultiCharsetString(str.toCharArray(), 0, str
112: .length(), true);
113: }
114:
115: /**
116: * make a array of CharsetString with given String.
117: */
118: public CharsetString[] makeMultiCharsetString(String str,
119: boolean allowdefault) {
120: return makeMultiCharsetString(str.toCharArray(), 0, str
121: .length(), allowdefault);
122: }
123:
124: /**
125: * make a array of CharsetString with given char array.
126: * @param str The char array to convert.
127: * @param offset offset of first character of interest
128: * @param len number of characters to convert
129: */
130: public CharsetString[] makeMultiCharsetString(char str[],
131: int offset, int len) {
132: return makeMultiCharsetString(str, offset, len, true);
133: }
134:
135: /**
136: * make a array of CharsetString with given char array.
137: * @param str The char array to convert.
138: * @param offset offset of first character of interest
139: * @param len number of characters to convert
140: * @param allowDefault whether to allow the default char.
141: * Setting this to true overloads the meaning of this method to
142: * return non-null only if all chars can be converted.
143: * @return array of CharsetString or if allowDefault is false and any
144: * of the returned chars would have been converted to a default char,
145: * then return null.
146: * This is used to choose alternative means of displaying the text.
147: */
148: public CharsetString[] makeMultiCharsetString(char str[],
149: int offset, int len, boolean allowDefault) {
150:
151: if (len < 1) {
152: return new CharsetString[0];
153: }
154: Vector mcs = null;
155: char[] tmpStr = new char[len];
156: char tmpChar = defaultChar;
157: boolean encoded = false;
158:
159: FontDescriptor currentFont = defaultFont;
160:
161: for (int i = 0; i < componentFonts.length; i++) {
162: if (componentFonts[i].isExcluded(str[offset])) {
163: continue;
164: }
165:
166: /* Need "encoded" variable to distinguish the case when
167: * the default char is the same as the encoded char.
168: * The defaultChar on Linux is '?' so it is needed there.
169: */
170: if (componentFonts[i].encoder.canEncode(str[offset])) {
171: currentFont = componentFonts[i];
172: tmpChar = str[offset];
173: encoded = true;
174: break;
175: }
176: }
177: if (!allowDefault && !encoded) {
178: return null;
179: } else {
180: tmpStr[0] = tmpChar;
181: }
182:
183: int lastIndex = 0;
184: for (int i = 1; i < len; i++) {
185: char ch = str[offset + i];
186: FontDescriptor fd = defaultFont;
187: tmpChar = defaultChar;
188: encoded = false;
189: for (int j = 0; j < componentFonts.length; j++) {
190: if (componentFonts[j].isExcluded(ch)) {
191: continue;
192: }
193:
194: if (componentFonts[j].encoder.canEncode(ch)) {
195: fd = componentFonts[j];
196: tmpChar = ch;
197: encoded = true;
198: break;
199: }
200: }
201: if (!allowDefault && !encoded) {
202: return null;
203: } else {
204: tmpStr[i] = tmpChar;
205: }
206: if (currentFont != fd) {
207: if (mcs == null) {
208: mcs = new Vector(3);
209: }
210: mcs.addElement(new CharsetString(tmpStr, lastIndex, i
211: - lastIndex, currentFont));
212: currentFont = fd;
213: fd = defaultFont;
214: lastIndex = i;
215: }
216: }
217: CharsetString[] result;
218: CharsetString cs = new CharsetString(tmpStr, lastIndex, len
219: - lastIndex, currentFont);
220: if (mcs == null) {
221: result = new CharsetString[1];
222: result[0] = cs;
223: } else {
224: mcs.addElement(cs);
225: result = new CharsetString[mcs.size()];
226: for (int i = 0; i < mcs.size(); i++) {
227: result[i] = (CharsetString) mcs.elementAt(i);
228: }
229: }
230: return result;
231: }
232:
233: /**
234: * Is it possible that this font's metrics require the multi-font calls?
235: * This might be true, for example, if the font supports kerning.
236: **/
237: public boolean mightHaveMultiFontMetrics() {
238: return fontConfig != null;
239: }
240:
241: /**
242: * Specialized fast path string conversion for AWT.
243: */
244: public Object[] makeConvertedMultiFontString(String str) {
245: return makeConvertedMultiFontChars(str.toCharArray(), 0, str
246: .length());
247: }
248:
249: public Object[] makeConvertedMultiFontChars(char[] data, int start,
250: int len) {
251: Object[] result = new Object[2];
252: Object[] workingCache;
253: byte[] convertedData = null;
254: int stringIndex = start;
255: int convertedDataIndex = 0;
256: int resultIndex = 0;
257: int cacheIndex;
258: FontDescriptor currentFontDescriptor = null;
259: FontDescriptor lastFontDescriptor = null;
260: char currentDefaultChar;
261: PlatformFontCache theChar;
262:
263: // Simple bounds check
264: int end = start + len;
265: if (start < 0 || end > data.length) {
266: throw new ArrayIndexOutOfBoundsException();
267: }
268:
269: if (stringIndex >= end) {
270: return null;
271: }
272:
273: // coversion loop
274: while (stringIndex < end) {
275: currentDefaultChar = data[stringIndex];
276:
277: // Note that cache sizes must be a power of two!
278: cacheIndex = (int) (currentDefaultChar & this .FONTCACHEMASK);
279:
280: theChar = (PlatformFontCache) getFontCache()[cacheIndex];
281:
282: // Is the unicode char we want cached?
283: if (theChar == null
284: || theChar.uniChar != currentDefaultChar) {
285: /* find a converter that can convert the current character */
286: currentFontDescriptor = defaultFont;
287: currentDefaultChar = defaultChar;
288: char ch = (char) data[stringIndex];
289: int componentCount = componentFonts.length;
290:
291: for (int j = 0; j < componentCount; j++) {
292: FontDescriptor fontDescriptor = componentFonts[j];
293:
294: fontDescriptor.encoder.reset();
295: //fontDescriptor.encoder.onUnmappleCharacterAction(...);
296:
297: if (fontDescriptor.isExcluded(ch)) {
298: continue;
299: }
300: if (fontDescriptor.encoder.canEncode(ch)) {
301: currentFontDescriptor = fontDescriptor;
302: currentDefaultChar = ch;
303: break;
304: }
305: }
306: try {
307: char[] input = new char[1];
308: input[0] = currentDefaultChar;
309:
310: theChar = new PlatformFontCache();
311: if (currentFontDescriptor.useUnicode()) {
312: /*
313: currentFontDescriptor.unicodeEncoder.encode(CharBuffer.wrap(input),
314: theChar.bb,
315: true);
316: */
317: if (currentFontDescriptor.isLE) {
318: theChar.bb.put((byte) (input[0] & 0xff));
319: theChar.bb.put((byte) (input[0] >> 8));
320: } else {
321: theChar.bb.put((byte) (input[0] >> 8));
322: theChar.bb.put((byte) (input[0] & 0xff));
323: }
324: } else {
325: currentFontDescriptor.encoder.encode(CharBuffer
326: .wrap(input), theChar.bb, true);
327: }
328: theChar.fontDescriptor = currentFontDescriptor;
329: theChar.uniChar = data[stringIndex];
330: getFontCache()[cacheIndex] = theChar;
331: } catch (Exception e) {
332: // Should never happen!
333: System.err.println(e);
334: e.printStackTrace();
335: return null;
336: }
337: }
338:
339: // Check to see if we've changed fonts.
340: if (lastFontDescriptor != theChar.fontDescriptor) {
341: if (lastFontDescriptor != null) {
342: result[resultIndex++] = lastFontDescriptor;
343: result[resultIndex++] = convertedData;
344: // Add the size to the converted data field.
345: if (convertedData != null) {
346: convertedDataIndex -= 4;
347: convertedData[0] = (byte) (convertedDataIndex >> 24);
348: convertedData[1] = (byte) (convertedDataIndex >> 16);
349: convertedData[2] = (byte) (convertedDataIndex >> 8);
350: convertedData[3] = (byte) convertedDataIndex;
351: }
352:
353: if (resultIndex >= result.length) {
354: Object[] newResult = new Object[result.length * 2];
355:
356: System.arraycopy(result, 0, newResult, 0,
357: result.length);
358: result = newResult;
359: }
360: }
361:
362: if (theChar.fontDescriptor.useUnicode()) {
363: convertedData = new byte[(end - stringIndex + 1)
364: * (int) theChar.fontDescriptor.unicodeEncoder
365: .maxBytesPerChar() + 4];
366: } else {
367: convertedData = new byte[(end - stringIndex + 1)
368: * (int) theChar.fontDescriptor.encoder
369: .maxBytesPerChar() + 4];
370: }
371:
372: convertedDataIndex = 4;
373:
374: lastFontDescriptor = theChar.fontDescriptor;
375: }
376:
377: byte[] ba = theChar.bb.array();
378: int size = theChar.bb.position();
379: if (size == 1) {
380: convertedData[convertedDataIndex++] = ba[0];
381: } else if (size == 2) {
382: convertedData[convertedDataIndex++] = ba[0];
383: convertedData[convertedDataIndex++] = ba[1];
384: } else if (size == 3) {
385: convertedData[convertedDataIndex++] = ba[0];
386: convertedData[convertedDataIndex++] = ba[1];
387: convertedData[convertedDataIndex++] = ba[2];
388: } else if (size == 4) {
389: convertedData[convertedDataIndex++] = ba[0];
390: convertedData[convertedDataIndex++] = ba[1];
391: convertedData[convertedDataIndex++] = ba[2];
392: convertedData[convertedDataIndex++] = ba[3];
393: }
394: stringIndex++;
395: }
396:
397: result[resultIndex++] = lastFontDescriptor;
398: result[resultIndex] = convertedData;
399:
400: // Add the size to the converted data field.
401: if (convertedData != null) {
402: convertedDataIndex -= 4;
403: convertedData[0] = (byte) (convertedDataIndex >> 24);
404: convertedData[1] = (byte) (convertedDataIndex >> 16);
405: convertedData[2] = (byte) (convertedDataIndex >> 8);
406: convertedData[3] = (byte) convertedDataIndex;
407: }
408: return result;
409: }
410:
411: /*
412: * Create fontCache on demand instead of during construction to
413: * reduce overall memory consumption.
414: *
415: * This method is declared final so that its code can be inlined
416: * by the compiler.
417: */
418: protected final Object[] getFontCache() {
419: // This method is not MT-safe by design. Since this is just a
420: // cache anyways, it's okay if we occasionally allocate the array
421: // twice or return an array which will be dereferenced and gced
422: // right away.
423: if (fontCache == null) {
424: fontCache = new Object[this .FONTCACHESIZE];
425: }
426:
427: return fontCache;
428: }
429:
430: /**
431: * Initialize JNI field and method IDs
432: */
433: private static native void initIDs();
434:
435: class PlatformFontCache {
436: char uniChar;
437: FontDescriptor fontDescriptor;
438: ByteBuffer bb = ByteBuffer.allocate(4);
439: }
440: }
|