/
TrueTypeFont.java
1839 lines (1669 loc) · 69.9 KB
/
TrueTypeFont.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.font;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.GraphicsEnvironment;
import java.awt.geom.Point2D;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
import sun.security.action.GetPropertyAction;
/**
* TrueTypeFont is not called SFntFont because it is not expected
* to handle all types that may be housed in a such a font file.
* If additional types are supported later, it may make sense to
* create an SFnt superclass. Eg to handle sfnt-housed postscript fonts.
* OpenType fonts are handled by this class, and possibly should be
* represented by a subclass.
* An instance stores some information from the font file to faciliate
* faster access. File size, the table directory and the names of the font
* are the most important of these. It amounts to approx 400 bytes
* for a typical font. Systems with mutiple locales sometimes have up to 400
* font files, and an app which loads all font files would need around
* 160Kbytes. So storing any more info than this would be expensive.
*/
public class TrueTypeFont extends FileFont {
/* -- Tags for required TrueType tables */
public static final int cmapTag = 0x636D6170; // 'cmap'
public static final int glyfTag = 0x676C7966; // 'glyf'
public static final int headTag = 0x68656164; // 'head'
public static final int hheaTag = 0x68686561; // 'hhea'
public static final int hmtxTag = 0x686D7478; // 'hmtx'
public static final int locaTag = 0x6C6F6361; // 'loca'
public static final int maxpTag = 0x6D617870; // 'maxp'
public static final int nameTag = 0x6E616D65; // 'name'
public static final int postTag = 0x706F7374; // 'post'
public static final int os_2Tag = 0x4F532F32; // 'OS/2'
/* -- Tags for opentype related tables */
public static final int GDEFTag = 0x47444546; // 'GDEF'
public static final int GPOSTag = 0x47504F53; // 'GPOS'
public static final int GSUBTag = 0x47535542; // 'GSUB'
public static final int mortTag = 0x6D6F7274; // 'mort'
public static final int morxTag = 0x6D6F7278; // 'morx'
/* -- Tags for non-standard tables */
public static final int fdscTag = 0x66647363; // 'fdsc' - gxFont descriptor
public static final int fvarTag = 0x66766172; // 'fvar' - gxFont variations
public static final int featTag = 0x66656174; // 'feat' - layout features
public static final int EBLCTag = 0x45424C43; // 'EBLC' - embedded bitmaps
public static final int gaspTag = 0x67617370; // 'gasp' - hint/smooth sizes
/* -- Other tags */
public static final int ttcfTag = 0x74746366; // 'ttcf' - TTC file
public static final int v1ttTag = 0x00010000; // 'v1tt' - Version 1 TT font
public static final int trueTag = 0x74727565; // 'true' - Version 2 TT font
public static final int ottoTag = 0x4f54544f; // 'otto' - OpenType font
/* -- ID's used in the 'name' table */
public static final int MAC_PLATFORM_ID = 1;
public static final int MACROMAN_SPECIFIC_ID = 0;
public static final int MACROMAN_ENGLISH_LANG = 0;
public static final int MS_PLATFORM_ID = 3;
/* MS locale id for US English is the "default" */
public static final short ENGLISH_LOCALE_ID = 0x0409; // 1033 decimal
public static final int FAMILY_NAME_ID = 1;
// public static final int STYLE_WEIGHT_ID = 2; // currently unused.
public static final int FULL_NAME_ID = 4;
public static final int POSTSCRIPT_NAME_ID = 6;
private static final short US_LCID = 0x0409; // US English - default
private static Map<String, Short> lcidMap;
static class DirectoryEntry {
int tag;
int offset;
int length;
}
/* There is a pool which limits the number of fd's that are in
* use. Normally fd's are closed as they are replaced in the pool.
* But if an instance of this class becomes unreferenced, then there
* needs to be a way to close the fd. A finalize() method could do this,
* but using the Disposer class will ensure its called in a more timely
* manner. This is not something which should be relied upon to free
* fd's - its a safeguard.
*/
private static class TTDisposerRecord implements DisposerRecord {
FileChannel channel = null;
public synchronized void dispose() {
try {
if (channel != null) {
channel.close();
}
} catch (IOException e) {
} finally {
channel = null;
}
}
}
TTDisposerRecord disposerRecord = new TTDisposerRecord();
/* > 0 only if this font is a part of a collection */
int fontIndex = 0;
/* Number of fonts in this collection. ==1 if not a collection */
int directoryCount = 1;
/* offset in file of table directory for this font */
int directoryOffset; // 12 if its not a collection.
/* number of table entries in the directory/offsets table */
int numTables;
/* The contents of the directory/offsets table */
DirectoryEntry []tableDirectory;
// protected byte []gposTable = null;
// protected byte []gdefTable = null;
// protected byte []gsubTable = null;
// protected byte []mortTable = null;
// protected boolean hintsTabledChecked = false;
// protected boolean containsHintsTable = false;
/* These fields are set from os/2 table info. */
private boolean supportsJA;
private boolean supportsCJK;
/* These are for faster access to the name of the font as
* typically exposed via API to applications.
*/
private Locale nameLocale;
private String localeFamilyName;
private String localeFullName;
/*
* Used on Windows to validate the font selected by GDI for (sub-pixel
* antialiased) rendering. For 'standalone' fonts it's equal to the font
* file size, for collection (TTC, OTC) members it's the number of bytes in
* the collection file from the start of this font's offset table till the
* end of the file.
*/
int fontDataSize;
public TrueTypeFont(String platname, Object nativeNames, int fIndex,
boolean javaRasterizer)
throws FontFormatException
{
this(platname, nativeNames, fIndex, javaRasterizer, true);
}
/**
* - does basic verification of the file
* - reads the header table for this font (within a collection)
* - reads the names (full, family).
* - determines the style of the font.
* - initializes the CMAP
* @throws FontFormatException if the font can't be opened
* or fails verification, or there's no usable cmap
*/
public TrueTypeFont(String platname, Object nativeNames, int fIndex,
boolean javaRasterizer, boolean useFilePool)
throws FontFormatException {
super(platname, nativeNames);
useJavaRasterizer = javaRasterizer;
fontRank = Font2D.TTF_RANK;
try {
verify(useFilePool);
init(fIndex);
if (!useFilePool) {
close();
}
} catch (Throwable t) {
close();
if (t instanceof FontFormatException) {
throw (FontFormatException)t;
} else {
throw new FontFormatException("Unexpected runtime exception.");
}
}
Disposer.addObjectRecord(this, disposerRecord);
}
private synchronized FileChannel open() throws FontFormatException {
return open(true);
}
/* This is intended to be called, and the returned value used,
* from within a block synchronized on this font object.
* ie the channel returned may be nulled out at any time by "close()"
* unless the caller holds a lock.
* Deadlock warning: FontManager.addToPool(..) acquires a global lock,
* which means nested locks may be in effect.
*/
private synchronized FileChannel open(boolean usePool)
throws FontFormatException {
if (disposerRecord.channel == null) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().info("open TTF: " + platName);
}
try {
RandomAccessFile raf = AccessController.doPrivileged(
new PrivilegedExceptionAction<RandomAccessFile>() {
public RandomAccessFile run() throws FileNotFoundException {
return new RandomAccessFile(platName, "r");
}
});
disposerRecord.channel = raf.getChannel();
fileSize = (int)disposerRecord.channel.size();
if (usePool) {
FontManager fm = FontManagerFactory.getInstance();
if (fm instanceof SunFontManager) {
((SunFontManager) fm).addToPool(this);
}
}
} catch (PrivilegedActionException e) {
close();
Throwable reason = e.getCause();
if (reason == null) {
reason = e;
}
throw new FontFormatException(reason.toString());
} catch (ClosedChannelException e) {
/* NIO I/O is interruptible, recurse to retry operation.
* The call to channel.size() above can throw this exception.
* Clear interrupts before recursing in case NIO didn't.
* Note that close() sets disposerRecord.channel to null.
*/
Thread.interrupted();
close();
open();
} catch (IOException e) {
close();
throw new FontFormatException(e.toString());
}
}
return disposerRecord.channel;
}
protected synchronized void close() {
disposerRecord.dispose();
}
int readBlock(ByteBuffer buffer, int offset, int length) {
int bread = 0;
try {
synchronized (this) {
if (disposerRecord.channel == null) {
open();
}
if (offset + length > fileSize) {
if (offset >= fileSize) {
/* Since the caller ensures that offset is < fileSize
* this condition suggests that fileSize is now
* different than the value we originally provided
* to native when the scaler was created.
* Also fileSize is updated every time we
* open() the file here, but in native the value
* isn't updated. If the file has changed whilst we
* are executing we want to bail, not spin.
*/
if (FontUtilities.isLogging()) {
String msg = "Read offset is " + offset +
" file size is " + fileSize+
" file is " + platName;
FontUtilities.getLogger().severe(msg);
}
return -1;
} else {
length = fileSize - offset;
}
}
buffer.clear();
disposerRecord.channel.position(offset);
while (bread < length) {
int cnt = disposerRecord.channel.read(buffer);
if (cnt == -1) {
String msg = "Unexpected EOF " + this;
int currSize = (int)disposerRecord.channel.size();
if (currSize != fileSize) {
msg += " File size was " + fileSize +
" and now is " + currSize;
}
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().severe(msg);
}
// We could still flip() the buffer here because
// it's possible that we did read some data in
// an earlier loop, and we probably should
// return that to the caller. Although if
// the caller expected 8K of data and we return
// only a few bytes then maybe it's better instead to
// set bread = -1 to indicate failure.
// The following is therefore using arbitrary values
// but is meant to allow cases where enough
// data was read to probably continue.
if (bread > length/2 || bread > 16384) {
buffer.flip();
if (FontUtilities.isLogging()) {
msg = "Returning " + bread +
" bytes instead of " + length;
FontUtilities.getLogger().severe(msg);
}
} else {
bread = -1;
}
throw new IOException(msg);
}
bread += cnt;
}
buffer.flip();
if (bread > length) { // possible if buffer.size() > length
bread = length;
}
}
} catch (FontFormatException e) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().severe(
"While reading " + platName, e);
}
bread = -1; // signal EOF
deregisterFontAndClearStrikeCache();
} catch (ClosedChannelException e) {
/* NIO I/O is interruptible, recurse to retry operation.
* Clear interrupts before recursing in case NIO didn't.
*/
Thread.interrupted();
close();
return readBlock(buffer, offset, length);
} catch (IOException e) {
/* If we did not read any bytes at all and the exception is
* not a recoverable one (ie is not ClosedChannelException) then
* we should indicate that there is no point in re-trying.
* Other than an attempt to read past the end of the file it
* seems unlikely this would occur as problems opening the
* file are handled as a FontFormatException.
*/
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().severe(
"While reading " + platName, e);
}
if (bread == 0) {
bread = -1; // signal EOF
deregisterFontAndClearStrikeCache();
}
}
return bread;
}
ByteBuffer readBlock(int offset, int length) {
ByteBuffer buffer = ByteBuffer.allocate(length);
try {
synchronized (this) {
if (disposerRecord.channel == null) {
open();
}
if (offset + length > fileSize) {
if (offset > fileSize) {
return null; // assert?
} else {
buffer = ByteBuffer.allocate(fileSize-offset);
}
}
disposerRecord.channel.position(offset);
disposerRecord.channel.read(buffer);
buffer.flip();
}
} catch (FontFormatException e) {
return null;
} catch (ClosedChannelException e) {
/* NIO I/O is interruptible, recurse to retry operation.
* Clear interrupts before recursing in case NIO didn't.
*/
Thread.interrupted();
close();
readBlock(buffer, offset, length);
} catch (IOException e) {
return null;
}
return buffer;
}
/* This is used by native code which can't allocate a direct byte
* buffer because of bug 4845371. It, and references to it in native
* code in scalerMethods.c can be removed once that bug is fixed.
* 4845371 is now fixed but we'll keep this around as it doesn't cost
* us anything if its never used/called.
*/
byte[] readBytes(int offset, int length) {
ByteBuffer buffer = readBlock(offset, length);
if (buffer.hasArray()) {
return buffer.array();
} else {
byte[] bufferBytes = new byte[buffer.limit()];
buffer.get(bufferBytes);
return bufferBytes;
}
}
private void verify(boolean usePool) throws FontFormatException {
open(usePool);
}
/* sizes, in bytes, of TT/TTC header records */
private static final int TTCHEADERSIZE = 12;
private static final int DIRECTORYHEADERSIZE = 12;
private static final int DIRECTORYENTRYSIZE = 16;
protected void init(int fIndex) throws FontFormatException {
int headerOffset = 0;
ByteBuffer buffer = readBlock(0, TTCHEADERSIZE);
try {
switch (buffer.getInt()) {
case ttcfTag:
buffer.getInt(); // skip TTC version ID
directoryCount = buffer.getInt();
if (fIndex >= directoryCount) {
throw new FontFormatException("Bad collection index");
}
fontIndex = fIndex;
buffer = readBlock(TTCHEADERSIZE+4*fIndex, 4);
headerOffset = buffer.getInt();
fontDataSize = Math.max(0, fileSize - headerOffset);
break;
case v1ttTag:
case trueTag:
case ottoTag:
fontDataSize = fileSize;
break;
default:
throw new FontFormatException("Unsupported sfnt " +
getPublicFileName());
}
/* Now have the offset of this TT font (possibly within a TTC)
* After the TT version/scaler type field, is the short
* representing the number of tables in the table directory.
* The table directory begins at 12 bytes after the header.
* Each table entry is 16 bytes long (4 32-bit ints)
*/
buffer = readBlock(headerOffset+4, 2);
numTables = buffer.getShort();
directoryOffset = headerOffset+DIRECTORYHEADERSIZE;
ByteBuffer bbuffer = readBlock(directoryOffset,
numTables*DIRECTORYENTRYSIZE);
IntBuffer ibuffer = bbuffer.asIntBuffer();
DirectoryEntry table;
tableDirectory = new DirectoryEntry[numTables];
for (int i=0; i<numTables;i++) {
tableDirectory[i] = table = new DirectoryEntry();
table.tag = ibuffer.get();
/* checksum */ ibuffer.get();
table.offset = ibuffer.get() & 0x7FFFFFFF;
table.length = ibuffer.get() & 0x7FFFFFFF;
if (table.offset + table.length > fileSize) {
throw new FontFormatException("bad table, tag="+table.tag);
}
}
if (getDirectoryEntry(headTag) == null) {
throw new FontFormatException("missing head table");
}
if (getDirectoryEntry(maxpTag) == null) {
throw new FontFormatException("missing maxp table");
}
if (getDirectoryEntry(hmtxTag) != null
&& getDirectoryEntry(hheaTag) == null) {
throw new FontFormatException("missing hhea table");
}
initNames();
} catch (Exception e) {
if (FontUtilities.isLogging()) {
FontUtilities.getLogger().severe(e.toString());
}
if (e instanceof FontFormatException) {
throw (FontFormatException)e;
} else {
throw new FontFormatException(e.toString());
}
}
if (familyName == null || fullName == null) {
throw new FontFormatException("Font name not found");
}
/* The os2_Table is needed to gather some info, but we don't
* want to keep it around (as a field) so obtain it once and
* pass it to the code that needs it.
*/
ByteBuffer os2_Table = getTableBuffer(os_2Tag);
setStyle(os2_Table);
setCJKSupport(os2_Table);
}
/* The array index corresponds to a bit offset in the TrueType
* font's OS/2 compatibility table's code page ranges fields.
* These are two 32 bit unsigned int fields at offsets 78 and 82.
* We are only interested in determining if the font supports
* the windows encodings we expect as the default encoding in
* supported locales, so we only map the first of these fields.
*/
static final String[] encoding_mapping = {
"cp1252", /* 0:Latin 1 */
"cp1250", /* 1:Latin 2 */
"cp1251", /* 2:Cyrillic */
"cp1253", /* 3:Greek */
"cp1254", /* 4:Turkish/Latin 5 */
"cp1255", /* 5:Hebrew */
"cp1256", /* 6:Arabic */
"cp1257", /* 7:Windows Baltic */
"", /* 8:reserved for alternate ANSI */
"", /* 9:reserved for alternate ANSI */
"", /* 10:reserved for alternate ANSI */
"", /* 11:reserved for alternate ANSI */
"", /* 12:reserved for alternate ANSI */
"", /* 13:reserved for alternate ANSI */
"", /* 14:reserved for alternate ANSI */
"", /* 15:reserved for alternate ANSI */
"ms874", /* 16:Thai */
"ms932", /* 17:JIS/Japanese */
"gbk", /* 18:PRC GBK Cp950 */
"ms949", /* 19:Korean Extended Wansung */
"ms950", /* 20:Chinese (Taiwan, Hongkong, Macau) */
"ms1361", /* 21:Korean Johab */
"", /* 22 */
"", /* 23 */
"", /* 24 */
"", /* 25 */
"", /* 26 */
"", /* 27 */
"", /* 28 */
"", /* 29 */
"", /* 30 */
"", /* 31 */
};
/* This maps two letter language codes to a Windows code page.
* Note that eg Cp1252 (the first subarray) is not exactly the same as
* Latin-1 since Windows code pages are do not necessarily correspond.
* There are two codepages for zh and ko so if a font supports
* only one of these ranges then we need to distinguish based on
* country. So far this only seems to matter for zh.
* REMIND: Unicode locales such as Hindi do not have a code page so
* this whole mechanism needs to be revised to map languages to
* the Unicode ranges either when this fails, or as an additional
* validating test. Basing it on Unicode ranges should get us away
* from needing to map to this small and incomplete set of Windows
* code pages which looks odd on non-Windows platforms.
*/
private static final String[][] languages = {
/* cp1252/Latin 1 */
{ "en", "ca", "da", "de", "es", "fi", "fr", "is", "it",
"nl", "no", "pt", "sq", "sv", },
/* cp1250/Latin2 */
{ "cs", "cz", "et", "hr", "hu", "nr", "pl", "ro", "sk",
"sl", "sq", "sr", },
/* cp1251/Cyrillic */
{ "bg", "mk", "ru", "sh", "uk" },
/* cp1253/Greek*/
{ "el" },
/* cp1254/Turkish,Latin 5 */
{ "tr" },
/* cp1255/Hebrew */
{ "he" },
/* cp1256/Arabic */
{ "ar" },
/* cp1257/Windows Baltic */
{ "et", "lt", "lv" },
/* ms874/Thai */
{ "th" },
/* ms932/Japanese */
{ "ja" },
/* gbk/Chinese (PRC GBK Cp950) */
{ "zh", "zh_CN", },
/* ms949/Korean Extended Wansung */
{ "ko" },
/* ms950/Chinese (Taiwan, Hongkong, Macau) */
{ "zh_HK", "zh_TW", },
/* ms1361/Korean Johab */
{ "ko" },
};
private static final String[] codePages = {
"cp1252",
"cp1250",
"cp1251",
"cp1253",
"cp1254",
"cp1255",
"cp1256",
"cp1257",
"ms874",
"ms932",
"gbk",
"ms949",
"ms950",
"ms1361",
};
private static String defaultCodePage = null;
static String getCodePage() {
if (defaultCodePage != null) {
return defaultCodePage;
}
if (FontUtilities.isWindows) {
defaultCodePage =
AccessController.doPrivileged(new GetPropertyAction("file.encoding"));
} else {
if (languages.length != codePages.length) {
throw new InternalError("wrong code pages array length");
}
Locale locale = sun.awt.SunToolkit.getStartupLocale();
String language = locale.getLanguage();
if (language != null) {
if (language.equals("zh")) {
String country = locale.getCountry();
if (country != null) {
language = language + "_" + country;
}
}
for (int i=0; i<languages.length;i++) {
for (int l=0;l<languages[i].length; l++) {
if (language.equals(languages[i][l])) {
defaultCodePage = codePages[i];
return defaultCodePage;
}
}
}
}
}
if (defaultCodePage == null) {
defaultCodePage = "";
}
return defaultCodePage;
}
/* Theoretically, reserved bits must not be set, include symbol bits */
public static final int reserved_bits1 = 0x80000000;
public static final int reserved_bits2 = 0x0000ffff;
@Override
boolean supportsEncoding(String encoding) {
if (encoding == null) {
encoding = getCodePage();
}
if ("".equals(encoding)) {
return false;
}
encoding = encoding.toLowerCase();
/* java_props_md.c has a couple of special cases
* if language packs are installed. In these encodings the
* fontconfig files pick up different fonts :
* SimSun-18030 and MingLiU_HKSCS. Since these fonts will
* indicate they support the base encoding, we need to rewrite
* these encodings here before checking the map/array.
*/
if (encoding.equals("gb18030")) {
encoding = "gbk";
} else if (encoding.equals("ms950_hkscs")) {
encoding = "ms950";
}
ByteBuffer buffer = getTableBuffer(os_2Tag);
/* required info is at offsets 78 and 82 */
if (buffer == null || buffer.capacity() < 86) {
return false;
}
int range1 = buffer.getInt(78); /* ulCodePageRange1 */
// int range2 = buffer.getInt(82); /* ulCodePageRange2 */
/* This test is too stringent for Arial on Solaris (and perhaps
* other fonts). Arial has at least one reserved bit set for an
* unknown reason.
*/
// if (((range1 & reserved_bits1) | (range2 & reserved_bits2)) != 0) {
// return false;
// }
for (int em=0; em<encoding_mapping.length; em++) {
if (encoding_mapping[em].equals(encoding)) {
if (((1 << em) & range1) != 0) {
return true;
}
}
}
return false;
}
/* Use info in the os_2Table to test CJK support */
private void setCJKSupport(ByteBuffer os2Table) {
/* required info is in ulong at offset 46 */
if (os2Table == null || os2Table.capacity() < 50) {
return;
}
int range2 = os2Table.getInt(46); /* ulUnicodeRange2 */
/* Any of these bits set in the 32-63 range indicate a font with
* support for a CJK range. We aren't looking at some other bits
* in the 64-69 range such as half width forms as its unlikely a font
* would include those and none of these.
*/
supportsCJK = ((range2 & 0x29bf0000) != 0);
/* This should be generalised, but for now just need to know if
* Hiragana or Katakana ranges are supported by the font.
* In the 4 longs representing unicode ranges supported
* bits 49 & 50 indicate hiragana and katakana
* This is bits 17 & 18 in the 2nd ulong. If either is supported
* we presume this is a JA font.
*/
supportsJA = ((range2 & 0x60000) != 0);
}
boolean supportsJA() {
return supportsJA;
}
ByteBuffer getTableBuffer(int tag) {
DirectoryEntry entry = null;
for (int i=0;i<numTables;i++) {
if (tableDirectory[i].tag == tag) {
entry = tableDirectory[i];
break;
}
}
if (entry == null || entry.length == 0 ||
entry.offset+entry.length > fileSize) {
return null;
}
int bread = 0;
ByteBuffer buffer = ByteBuffer.allocate(entry.length);
synchronized (this) {
try {
if (disposerRecord.channel == null) {
open();
}
disposerRecord.channel.position(entry.offset);
bread = disposerRecord.channel.read(buffer);
buffer.flip();
} catch (ClosedChannelException e) {
/* NIO I/O is interruptible, recurse to retry operation.
* Clear interrupts before recursing in case NIO didn't.
*/
Thread.interrupted();
close();
return getTableBuffer(tag);
} catch (IOException e) {
return null;
} catch (FontFormatException e) {
return null;
}
if (bread < entry.length) {
return null;
} else {
return buffer;
}
}
}
@Override
protected byte[] getTableBytes(int tag) {
ByteBuffer buffer = getTableBuffer(tag);
if (buffer == null) {
return null;
} else if (buffer.hasArray()) {
try {
return buffer.array();
} catch (Exception re) {
}
}
byte []data = new byte[getTableSize(tag)];
buffer.get(data);
return data;
}
int getTableSize(int tag) {
for (int i=0;i<numTables;i++) {
if (tableDirectory[i].tag == tag) {
return tableDirectory[i].length;
}
}
return 0;
}
int getTableOffset(int tag) {
for (int i=0;i<numTables;i++) {
if (tableDirectory[i].tag == tag) {
return tableDirectory[i].offset;
}
}
return 0;
}
DirectoryEntry getDirectoryEntry(int tag) {
for (int i=0;i<numTables;i++) {
if (tableDirectory[i].tag == tag) {
return tableDirectory[i];
}
}
return null;
}
/* Used to determine if this size has embedded bitmaps, which
* for CJK fonts should be used in preference to LCD glyphs.
*/
boolean useEmbeddedBitmapsForSize(int ptSize) {
if (!supportsCJK) {
return false;
}
if (getDirectoryEntry(EBLCTag) == null) {
return false;
}
ByteBuffer eblcTable = getTableBuffer(EBLCTag);
int numSizes = eblcTable.getInt(4);
/* The bitmapSizeTable's start at offset of 8.
* Each bitmapSizeTable entry is 48 bytes.
* The offset of ppemY in the entry is 45.
*/
for (int i=0;i<numSizes;i++) {
int ppemY = eblcTable.get(8+(i*48)+45) &0xff;
if (ppemY == ptSize) {
return true;
}
}
return false;
}
public String getFullName() {
return fullName;
}
/* This probably won't get called but is there to support the
* contract() of setStyle() defined in the superclass.
*/
@Override
protected void setStyle() {
setStyle(getTableBuffer(os_2Tag));
}
private int fontWidth = 0;
@Override
public int getWidth() {
return (fontWidth > 0) ? fontWidth : super.getWidth();
}
private int fontWeight = 0;
@Override
public int getWeight() {
return (fontWeight > 0) ? fontWeight : super.getWeight();
}
/* TrueTypeFont can use the fsSelection fields of OS/2 table
* to determine the style. In the unlikely case that doesn't exist,
* can use macStyle in the 'head' table but simpler to
* fall back to super class algorithm of looking for well known string.
* A very few fonts don't specify this information, but I only
* came across one: Lucida Sans Thai Typewriter Oblique in
* /usr/openwin/lib/locale/th_TH/X11/fonts/TrueType/lucidai.ttf
* that explicitly specified the wrong value. It says its regular.
* I didn't find any fonts that were inconsistent (ie regular plus some
* other value).
*/
private static final int fsSelectionItalicBit = 0x00001;
private static final int fsSelectionBoldBit = 0x00020;
private static final int fsSelectionRegularBit = 0x00040;
private void setStyle(ByteBuffer os_2Table) {
if (os_2Table == null) {
return;
}
if (os_2Table.capacity() >= 8) {
fontWeight = os_2Table.getChar(4) & 0xffff;
fontWidth = os_2Table.getChar(6) & 0xffff;
}
/* fsSelection is unsigned short at buffer offset 62 */
if (os_2Table.capacity() < 64) {
super.setStyle();
return;
}
int fsSelection = os_2Table.getChar(62) & 0xffff;
int italic = fsSelection & fsSelectionItalicBit;
int bold = fsSelection & fsSelectionBoldBit;
int regular = fsSelection & fsSelectionRegularBit;
// System.out.println("platname="+platName+" font="+fullName+
// " family="+familyName+
// " R="+regular+" I="+italic+" B="+bold);
if (regular!=0 && ((italic|bold)!=0)) {
/* This is inconsistent. Try using the font name algorithm */
super.setStyle();
return;
} else if ((regular|italic|bold) == 0) {
/* No style specified. Try using the font name algorithm */
super.setStyle();
return;
}
switch (bold|italic) {
case fsSelectionItalicBit:
style = Font.ITALIC;
break;
case fsSelectionBoldBit:
style = Font.BOLD;
break;
case fsSelectionBoldBit|fsSelectionItalicBit:
style = Font.BOLD|Font.ITALIC;
}
}
private float stSize, stPos, ulSize, ulPos;
private void setStrikethroughMetrics(ByteBuffer os_2Table, int upem) {
if (os_2Table == null || os_2Table.capacity() < 30 || upem < 0) {
stSize = .05f;
stPos = -.4f;
return;
}
ShortBuffer sb = os_2Table.asShortBuffer();
stSize = sb.get(13) / (float)upem;
stPos = -sb.get(14) / (float)upem;
}
private void setUnderlineMetrics(ByteBuffer postTable, int upem) {
if (postTable == null || postTable.capacity() < 12 || upem < 0) {
ulSize = .05f;
ulPos = .1f;
return;
}
ShortBuffer sb = postTable.asShortBuffer();