1
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
4
Part of the Processing project - http://processing.org
6
Copyright (c) 2004-07 Ben Fry & Casey Reas
7
Copyright (c) 2001-04 Massachusetts Institute of Technology
9
This library is free software; you can redistribute it and/or
10
modify it under the terms of the GNU Lesser General Public
11
License as published by the Free Software Foundation; either
12
version 2.1 of the License, or (at your option) any later version.
14
This library is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17
Lesser General Public License for more details.
19
You should have received a copy of the GNU Lesser General
20
Public License along with this library; if not, write to the
21
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
22
Boston, MA 02111-1307 USA
25
package processing.core;
28
import java.awt.image.BufferedImage;
29
import java.awt.image.Raster;
31
//import java.lang.reflect.*;
32
import java.util.Arrays;
36
* Grayscale bitmap font class used by Processing.
38
* Awful (and by that, I mean awesome) ascii (non)art for how this works:
41
* | height is the full used height of the image
46
* | XXXX.. } topExtent (top y is baseline - topExtent)
48
* | ..XX.. } dotted areas are where the image data
49
* | ..XX.. } is actually located for the character
50
* +---XXXXXX---- } (it extends to the right and down
51
* | for power of two texture sizes)
52
* ^^^^ leftExtent (amount to move over before drawing the image
54
* ^^^^^^^^^^^^^^ setWidth (width displaced by char)
57
public class PFont implements PConstants {
60
public PImage images[];
63
* Native Java version of the font. If possible, this allows the
64
* PGraphics subclass to just use Java's font rendering stuff
65
* in situations where that's faster.
68
protected boolean fontSearched;
71
* Name of the font as seen by Java when it was created.
72
* If the font is available, the native version will be used.
77
* Postscript name of the font that this bitmap was created from.
81
/** "natural" size of the font (most often 48) */
84
/** true if smoothing was enabled for this font, used for native impl */
85
public boolean smooth;
87
/** next power of 2 over the max image size (usually 64) */
90
/** floating point width (convenience) */
91
protected float fwidth;
93
/** floating point width (convenience) */
94
protected float fheight;
96
/** texture width, same as mbox2, but reserved for future use */
99
/** texture height, same as mbox2, but reserved for future use */
102
public int value[]; // char code
103
public int height[]; // height of the bitmap data
104
public int width[]; // width of bitmap data
105
public int setWidth[]; // width displaced by the char
106
public int topExtent[]; // offset for the top
107
public int leftExtent[]; // offset for the left
112
protected int ascii[]; // quick lookup for the ascii chars
114
// shared by the text() functions to avoid incessant allocation of memory
115
//protected char textBuffer[] = new char[8 * 1024];
116
//protected char widthBuffer[] = new char[8 * 1024];
118
static protected Font[] fonts;
121
public PFont() { } // for subclasses
124
public PFont(InputStream input) throws IOException {
125
DataInputStream is = new DataInputStream(input);
127
// number of character images stored in this font
128
charCount = is.readInt();
130
// bit count is ignored since this is always 8
131
//int numBits = is.readInt();
132
// used to be the bitCount, but now used for version number.
133
// version 8 is any font before 69, so 9 is anything from 83+
134
// 9 was buggy so gonna increment to 10.
135
int version = is.readInt();
137
// this was formerly ignored, now it's the actual font size
138
//mbox = is.readInt();
140
// this was formerly mboxY, the one that was used
141
// this will make new fonts downward compatible
142
//mbox2 = is.readInt();
143
mbox2 = is.readInt();
145
fwidth = size; //mbox;
146
fheight = size; //mbox;
148
// size for image ("texture") is next power of 2
149
// over the font size. for most vlw fonts, the size is 48
150
// so the next power of 2 is 64.
151
// double-check to make sure that mbox2 is a power of 2
152
// there was a bug in the old font generator that broke this
153
//mbox2 = (int) Math.pow(2, Math.ceil(Math.log(mbox2) / Math.log(2)));
154
mbox2 = (int) Math.pow(2, Math.ceil(Math.log(mbox2) / Math.log(2)));
155
// size for the texture is stored in the font
156
twidth = theight = mbox2; //mbox2;
158
ascent = is.readInt(); // formerly baseHt (zero/ignored)
159
descent = is.readInt(); // formerly ignored struct padding
161
// allocate enough space for the character info
162
value = new int[charCount];
163
height = new int[charCount];
164
width = new int[charCount];
165
setWidth = new int[charCount];
166
topExtent = new int[charCount];
167
leftExtent = new int[charCount];
169
ascii = new int[128];
170
for (int i = 0; i < 128; i++) ascii[i] = -1;
172
// read the information about the individual characters
173
for (int i = 0; i < charCount; i++) {
174
value[i] = is.readInt();
175
height[i] = is.readInt();
176
width[i] = is.readInt();
177
setWidth[i] = is.readInt();
178
topExtent[i] = is.readInt();
179
leftExtent[i] = is.readInt();
181
// pointer in the c version, ignored
184
// cache locations of the ascii charset
185
if (value[i] < 128) ascii[value[i]] = i;
187
// the values for getAscent() and getDescent() from FontMetrics
188
// seem to be way too large.. perhaps they're the max?
189
// as such, use a more traditional marker for ascent/descent
190
if (value[i] == 'd') {
191
if (ascent == 0) ascent = topExtent[i];
193
if (value[i] == 'p') {
194
if (descent == 0) descent = -topExtent[i] + height[i];
198
// not a roman font, so throw an error and ask to re-build.
199
// that way can avoid a bunch of error checking hacks in here.
200
if ((ascent == 0) && (descent == 0)) {
201
throw new RuntimeException("Please use \"Create Font\" to " +
202
"re-create this font.");
205
images = new PImage[charCount];
206
for (int i = 0; i < charCount; i++) {
207
images[i] = new PImage(twidth, theight, ALPHA);
208
int bitmapSize = height[i] * width[i];
210
byte temp[] = new byte[bitmapSize];
213
// convert the bitmap to an alpha channel
216
for (int y = 0; y < h; y++) {
217
for (int x = 0; x < w; x++) {
218
int valu = temp[y*w + x] & 0xff;
219
images[i].pixels[y * twidth + x] = valu;
220
//(valu << 24) | 0xFFFFFF; // windows
221
//0xFFFFFF00 | valu; // macosx
223
//System.out.print((images[i].pixels[y*64+x] > 128) ? "*" : ".");
225
//System.out.println();
227
//System.out.println();
230
if (version >= 10) { // includes the font name at the end of the file
232
psname = is.readUTF();
235
smooth = is.readBoolean();
241
* Set the native complement of this font.
243
public void setFont(Font font) {
249
* Return the native java.awt.Font associated with this PFont (if any).
251
public Font getFont() {
252
// if (font == null && !fontSearched) {
253
// font = findFont();
260
* Attempt to find the native version of this font.
261
* (Public so that it can be used by OpenGL or other renderers.)
263
public Font findFont() {
266
// this font may or may not be installed
267
font = new Font(name, Font.PLAIN, size);
268
// if the ps name matches, then we're in fine shape
269
if (!font.getPSName().equals(psname)) {
270
// on osx java 1.4 (not 1.3.. ugh), you can specify the ps name
271
// of the font, so try that in case this .vlw font was created on pc
272
// and the name is different, but the ps name is found on the
273
// java 1.4 mac that's currently running this sketch.
274
font = new Font(psname, Font.PLAIN, size);
276
// check again, and if still bad, screw em
277
if (!font.getPSName().equals(psname)) {
288
* Write this PFont to an OutputStream.
290
* This is used by the Create Font tool, or whatever anyone else dreams
291
* up for messing with fonts themselves.
293
* It is assumed that the calling class will handle closing
294
* the stream when finished.
296
public void save(OutputStream output) throws IOException {
297
DataOutputStream os = new DataOutputStream(output);
299
os.writeInt(charCount);
301
if ((name == null) || (psname == null)) {
305
// formerly numBits, now used for version number
306
//os.writeInt((name != null) ? 11 : 8);
309
os.writeInt(size); // formerly mboxX (was 64, now 48)
310
os.writeInt(mbox2); // formerly mboxY (was 64, still 64)
311
os.writeInt(ascent); // formerly baseHt (was ignored)
312
os.writeInt(descent); // formerly struct padding for c version
314
for (int i = 0; i < charCount; i++) {
315
os.writeInt(value[i]);
316
os.writeInt(height[i]);
317
os.writeInt(width[i]);
318
os.writeInt(setWidth[i]);
319
os.writeInt(topExtent[i]);
320
os.writeInt(leftExtent[i]);
321
os.writeInt(0); // padding
324
for (int i = 0; i < charCount; i++) {
325
for (int y = 0; y < height[i]; y++) {
326
for (int x = 0; x < width[i]; x++) {
327
os.write(images[i].pixels[y * mbox2 + x] & 0xff);
332
//if (name != null) { // version 11
335
os.writeBoolean(smooth);
343
* Get index for the char (convert from unicode to bagel charset).
344
* @return index into arrays or -1 if not found
346
public int index(char c) {
347
// degenerate case, but the find function will have trouble
348
// if there are somehow zero chars in the lookup
349
//if (value.length == 0) return -1;
350
if (charCount == 0) return -1;
352
// quicker lookup for the ascii fellers
353
if (c < 128) return ascii[c];
355
// some other unicode char, hunt it out
356
//return index_hunt(c, 0, value.length-1);
357
return indexHunt(c, 0, charCount-1);
361
protected int indexHunt(int c, int start, int stop) {
362
int pivot = (start + stop) / 2;
364
// if this is the char, then return it
365
if (c == value[pivot]) return pivot;
367
// char doesn't exist, otherwise would have been the pivot
368
//if (start == stop) return -1;
369
if (start >= stop) return -1;
371
// if it's in the lower half, continue searching that
372
if (c < value[pivot]) return indexHunt(c, start, pivot-1);
374
// if it's in the upper half, continue there
375
return indexHunt(c, pivot+1, stop);
380
* Currently un-implemented for .vlw fonts,
381
* but honored for layout in case subclasses use it.
383
public float kern(char a, char b) {
389
* Returns the ascent of this font from the baseline.
390
* The value is based on a font of size 1.
392
public float ascent() {
393
return ((float)ascent / fheight);
398
* Returns how far this font descends from the baseline.
399
* The value is based on a font size of 1.
401
public float descent() {
402
return ((float)descent / fheight);
407
* Width of this character for a font of size 1.
409
public float width(char c) {
410
if (c == 32) return width('i');
413
if (cc == -1) return 0;
415
return ((float)setWidth[cc] / fwidth);
419
//////////////////////////////////////////////////////////////
422
static final char[] EXTRA_CHARS = {
423
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
424
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
425
0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
426
0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
427
0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
428
0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
429
0x00B0, 0x00B1, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00BA,
430
0x00BB, 0x00BF, 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5,
431
0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD,
432
0x00CE, 0x00CF, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6,
433
0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DF,
434
0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
435
0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
436
0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8,
437
0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FF, 0x0102, 0x0103,
438
0x0104, 0x0105, 0x0106, 0x0107, 0x010C, 0x010D, 0x010E, 0x010F,
439
0x0110, 0x0111, 0x0118, 0x0119, 0x011A, 0x011B, 0x0131, 0x0139,
440
0x013A, 0x013D, 0x013E, 0x0141, 0x0142, 0x0143, 0x0144, 0x0147,
441
0x0148, 0x0150, 0x0151, 0x0152, 0x0153, 0x0154, 0x0155, 0x0158,
442
0x0159, 0x015A, 0x015B, 0x015E, 0x015F, 0x0160, 0x0161, 0x0162,
443
0x0163, 0x0164, 0x0165, 0x016E, 0x016F, 0x0170, 0x0171, 0x0178,
444
0x0179, 0x017A, 0x017B, 0x017C, 0x017D, 0x017E, 0x0192, 0x02C6,
445
0x02C7, 0x02D8, 0x02D9, 0x02DA, 0x02DB, 0x02DC, 0x02DD, 0x03A9,
446
0x03C0, 0x2013, 0x2014, 0x2018, 0x2019, 0x201A, 0x201C, 0x201D,
447
0x201E, 0x2020, 0x2021, 0x2022, 0x2026, 0x2030, 0x2039, 0x203A,
448
0x2044, 0x20AC, 0x2122, 0x2202, 0x2206, 0x220F, 0x2211, 0x221A,
449
0x221E, 0x222B, 0x2248, 0x2260, 0x2264, 0x2265, 0x25CA, 0xF8FF,
455
* The default Processing character set.
457
* This is the union of the Mac Roman and Windows ANSI (CP1250)
458
* character sets. ISO 8859-1 Latin 1 is Unicode characters 0x80 -> 0xFF,
459
* and would seem a good standard, but in practice, most P5 users would
460
* rather have characters that they expect from their platform's fonts.
462
* This is more of an interim solution until a much better
463
* font solution can be determined. (i.e. create fonts on
464
* the fly from some sort of vector format).
466
* Not that I expect that to happen.
468
static public char[] DEFAULT_CHARSET;
470
DEFAULT_CHARSET = new char[126-33+1 + EXTRA_CHARS.length];
472
for (int i = 33; i <= 126; i++) {
473
DEFAULT_CHARSET[index++] = (char)i;
475
for (int i = 0; i < EXTRA_CHARS.length; i++) {
476
DEFAULT_CHARSET[index++] = EXTRA_CHARS[i];
482
* Create a new image-based font on the fly.
484
* @param font the font object to create from
485
* @param charset array of all unicode chars that should be included
486
* @param smooth true to enable smoothing/anti-aliasing
488
public PFont(Font font, boolean smooth, char charset[]) {
489
// save this so that we can use the native version
491
this.smooth = smooth;
493
name = font.getName();
494
psname = font.getPSName();
496
// fix regression from sorting (bug #564)
497
if (charset != null) {
498
// charset needs to be sorted to make index lookup run more quickly
499
// http://dev.processing.org/bugs/show_bug.cgi?id=494
500
Arrays.sort(charset);
503
// the count gets reset later based on how many of
504
// the chars are actually found inside the font.
505
this.charCount = (charset == null) ? 65536 : charset.length;
506
this.size = font.getSize();
508
fwidth = fheight = size;
510
PImage bitmaps[] = new PImage[charCount];
512
// allocate enough space for the character info
513
value = new int[charCount];
514
height = new int[charCount];
515
width = new int[charCount];
516
setWidth = new int[charCount];
517
topExtent = new int[charCount];
518
leftExtent = new int[charCount];
520
ascii = new int[128];
521
for (int i = 0; i < 128; i++) ascii[i] = -1;
523
int mbox3 = size * 3;
525
BufferedImage playground =
526
new BufferedImage(mbox3, mbox3, BufferedImage.TYPE_INT_RGB);
528
Graphics2D g = (Graphics2D) playground.getGraphics();
529
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
531
RenderingHints.VALUE_ANTIALIAS_ON :
532
RenderingHints.VALUE_ANTIALIAS_OFF);
535
FontMetrics metrics = g.getFontMetrics();
537
int samples[] = new int[mbox3 * mbox3];
539
int maxWidthHeight = 0;
541
for (int i = 0; i < charCount; i++) {
542
char c = (charset == null) ? (char)i : charset[i];
544
if (!font.canDisplay(c)) { // skip chars not in the font
548
g.setColor(Color.white);
549
g.fillRect(0, 0, mbox3, mbox3);
550
g.setColor(Color.black);
551
g.drawString(String.valueOf(c), size, size * 2);
553
// grabs copy of the current data.. so no updates (do each time)
554
Raster raster = playground.getData();
555
raster.getSamples(0, 0, mbox3, mbox3, 0, samples);
557
int minX = 1000, maxX = 0;
558
int minY = 1000, maxY = 0;
559
boolean pixelFound = false;
561
for (int y = 0; y < mbox3; y++) {
562
for (int x = 0; x < mbox3; x++) {
563
//int sample = raster.getSample(x, y, 0); // maybe?
564
int sample = samples[y * mbox3 + x] & 0xff;
565
// or int samples[] = raster.getPixel(x, y, null);
567
//if (sample == 0) { // or just not white? hmm
569
if (x < minX) minX = x;
570
if (y < minY) minY = y;
571
if (x > maxX) maxX = x;
572
if (y > maxY) maxY = y;
581
// this will create a 1 pixel white (clear) character..
582
// maybe better to set one to -1 so nothing is added?
586
height[index] = (maxY - minY) + 1;
587
width[index] = (maxX - minX) + 1;
588
setWidth[index] = metrics.charWidth(c);
589
//System.out.println((char)c + " " + setWidth[index]);
591
// cache locations of the ascii charset
592
//if (value[i] < 128) ascii[value[i]] = i;
593
if (c < 128) ascii[c] = index;
595
// offset from vertical location of baseline
596
// of where the char was drawn (size*2)
597
topExtent[index] = size*2 - minY;
599
// offset from left of where coord was drawn
600
leftExtent[index] = minX - size;
603
ascent = topExtent[index];
606
descent = -topExtent[index] + height[index];
609
if (width[index] > maxWidthHeight) maxWidthHeight = width[index];
610
if (height[index] > maxWidthHeight) maxWidthHeight = height[index];
612
bitmaps[index] = new PImage(width[index], height[index], ALPHA);
614
for (int y = minY; y <= maxY; y++) {
615
for (int x = minX; x <= maxX; x++) {
616
int val = 255 - (samples[y * mbox3 + x] & 0xff);
617
int pindex = (y - minY) * width[index] + (x - minX);
618
bitmaps[index].pixels[pindex] = val;
625
// foreign font, so just make ascent the max topExtent
626
if ((ascent == 0) && (descent == 0)) {
627
for (int i = 0; i < charCount; i++) {
628
char cc = (char) value[i];
629
if (Character.isWhitespace(cc) ||
630
(cc == '\u00A0') || (cc == '\u2007') || (cc == '\u202F')) {
633
if (topExtent[i] > ascent) {
634
ascent = topExtent[i];
636
int d = -topExtent[i] + height[i];
642
// size for image/texture is next power of 2 over largest char
644
Math.pow(2, Math.ceil(Math.log(maxWidthHeight) / Math.log(2)));
645
twidth = theight = mbox2;
647
// shove the smaller PImage data into textures of next-power-of-2 size,
648
// so that this font can be used immediately by p5.
649
images = new PImage[charCount];
650
for (int i = 0; i < charCount; i++) {
651
images[i] = new PImage(mbox2, mbox2, ALPHA);
652
for (int y = 0; y < height[i]; y++) {
653
System.arraycopy(bitmaps[i].pixels, y*width[i],
654
images[i].pixels, y*mbox2,
663
* Get a list of the fonts installed on the system that can be used
664
* by Java. Not all fonts can be used in Java, in fact it's mostly
665
* only TrueType fonts. OpenType fonts with CFF data such as Adobe's
666
* OpenType fonts seem to have trouble (even though they're sort of
667
* TrueType fonts as well, or may have a .ttf extension). Regular
668
* PostScript fonts seem to work OK, however.
670
* Not recommended for use in applets, but this is implemented
671
* in PFont because the Java methods to access this information
672
* have changed between 1.1 and 1.4, and the 1.4 method is
673
* typical of the sort of undergraduate-level over-abstraction
674
* that the seems to have made its way into the Java API after 1.1.
676
static public String[] list() {
678
String list[] = new String[fonts.length];
679
for (int i = 0; i < list.length; i++) {
680
list[i] = fonts[i].getName();
686
static public void loadFonts() {
688
GraphicsEnvironment ge =
689
GraphicsEnvironment.getLocalGraphicsEnvironment();
690
fonts = ge.getAllFonts();
696
* Starting with Java 1.5, Apple broke the ability to specify most fonts.
697
* This has been filed as bug #4769141 at bugreporter.apple.com. More info at
698
* <a href="http://dev.processing.org/bugs/show_bug.cgi?id=407">Bug 407</a>.
700
static public Font findFont(String name) {
702
if (PApplet.platform == PConstants.MACOSX) {
703
for (int i = 0; i < fonts.length; i++) {
704
if (name.equals(fonts[i].getName())) {
709
return new Font(name, Font.PLAIN, 1);