1    // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
2    package org.xwt;
3    
4    import java.io.*;
5    import java.util.*;
6    import org.xwt.util.*;
7    
8    /** encapsulates a single XWF font */
9    public class XWF {
10   
11       /** all instances of XWF */
12       private static Hash xwtfonts = new Hash();
13   
14       /** the int array comprising the XWF */
15       int[] data = null;
16   
17       /** the width of the XWF's PNG */
18       int w = 0;
19   
20       /** the height of the XWF's PNG */
21       int h = 0;
22   
23       /** the full resource name of this font */
24       String name = null;
25   
26       /** minimum descent of all glyphs */
27       int maxascent = 0;
28   
29       /** maximum descent of all glyphs */
30       int maxdescent = 0;
31   
32       /** hash containing all Picture instances created, keyed on an Integer object containing the argb color of the font */
33       Hash pictures = new Hash();
34   
35       /** each element corresponds to a single glyph; <br>
36        *  metrics[glyphnum][0] == x position of left edge of glyph<br>
37        *  metrics[glyphnum][1] == x position of right edge of glyph<br>
38        *  metrics[glyphnum][2] == y position of top edge of glyph<br>
39        *  metrics[glyphnum][3] == y position of bottom edge of glyph<br>
40        *  metrics[glyphnum][4] == advance amount<br>
41        *  metrics[glyphnum][5] == baseline
42        */
43       int[][] metrics = null;
44   
45       /** drop all cached fonts when the theme mapping changes */
46       public static void flushXWFs() { xwtfonts.clear(); }
47   
48       /** retrieve an XWF instance, creating it if needed */
49       public static XWF getXWF(String resourcename) {
50           XWF ret = (XWF)xwtfonts.get(resourcename);
51           if (ret != null) return ret;
52   
53           String resolved = Resources.resolve(resourcename + ".xwf", null);
54           byte[] bytes = Resources.getResource(resolved);
55           if (bytes != null) {
56               PNG png = PNG.decode(new ByteArrayInputStream(bytes), resourcename);
57               if (png != null) return new XWF(resourcename, png);
58           }
59           return null;
60       }
61   
62       public int getMaxAscent() { return maxascent; }
63       public int getMaxDescent() { return maxdescent; }
64   
65       /** draws <tt>text</tt> on <tt>buf</tt> in this font, with color <tt>argb</tt> */
66       public void drawString(DoubleBuffer buf, String text, int x, int y, int argb) {
67   
68           Integer color = new Integer(argb | 0xFF000000);
69           Picture pg = (Picture)pictures.get(color);
70           if (pg == null) {
71               int[] data2 = new int[data.length];
72               for(int i=0; i<data.length; i++)
73                   data2[i] = (data[i] & 0xFF000000) | (argb & 0x00FFFFFF);
74               pg = Platform.createPicture(data2, w, h);
75               pictures.put(color, pg);
76           }
77   
78           int left = x;
79           for(int i=0; i<text.length(); i++) {
80               int c = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".indexOf(text.charAt(i));
81               if (c == -1 || metrics[c] == null) {
82           left += metrics[64] != null ? metrics[64][4] : 10;
83           continue;
84           }
85               buf.drawPicture(pg,
86                               left,
87                               y - (metrics[c][5] - metrics[c][2]),
88                               left + metrics[c][1] - metrics[c][0],
89                               y - (metrics[c][5] - metrics[c][2]) + metrics[c][3] - metrics[c][2],
90                               metrics[c][0], metrics[c][2], metrics[c][1], metrics[c][3]);
91               
92               left += metrics[c][4];
93           }
94       }
95   
96       /** returns the width of <tt>text</tt> when rendered in this font */
97       public int stringWidth(String text) {
98           int ret = 0;
99           for(int i=0; i<text.length(); i++) {
100              // what a hack...
101              int c = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".indexOf(text.charAt(i));
102              if (c == -1 || metrics[c] == null) { ret += metrics[64][4]; continue; }
103              ret += metrics[c][4];
104          }
105          return ret;
106      }
107  
108      private XWF(String name, PNG png) {
109          this.name = name;
110          xwtfonts.put(name, this);
111  
112          data = png.getData();
113          w = png.getWidth();
114          h = png.getHeight();
115  
116          intintint y1;
117          boolean breakout;
118  
119          intintintintintart_y, end_x, end_y, advance;
120  
121          int[] rows = new int[120];
122  
123          int baseline = 0;
124  
125          metrics = new int[96][];
126          int numglyphs = 0;
127  
128          for(y=0; y<h; y++) {
129  
130              // search for the next non-empty row
131              for(breakout = false; y<h && !breakout; y++)
132                  for(x = 0; x<w; x++)
133                      if ((data[x + y * w] & 0x00FFFFFF) != 0x00FFFFFF) breakout = true;
134  
135              start_y = y - 2;
136  
137              // search for the next empty row
138              for(breakout = false; y<h && !breakout; y++)
139                  for(x = 0, breakout = true; x<w; x++)
140                      if ((data[x + y * w] & 0x00FFFFFF) != 0x00FFFFFF) breakout = false;
141  
142              end_y = y;
143  
144              if (start_y == end_y) continue;
145  
146              for(x=0; x<w; x++) {
147  
148                  // search for the next column with a non-grayscale pixel in it
149                  for(breakout = false; x<w && !breakout; x++)
150                      for(y1 = start_y; y1<end_y; y1++)
151                          if (Math.abs(((data[x + y1 * w] & 0x00FF0000) >> 16) - ((data[x + y1 * w] & 0x0000FF00) >> 8)) > 10 ||
152                              Math.abs(((data[x + y1 * w] & 0x0000FF00) >> 8) - ((data[x + y1 * w] & 0x000000FF))) > 10) {
153                              breakout = true;
154                              baseline = y1;
155                          }
156                  
157                  // search for the next column without a non-grayscale pixel in it
158                  for(breakout = false; x<w && !breakout; x++)
159                      for(y1 = start_y, breakout = true; y1<end_y; y1++)
160                          if (Math.abs(((data[x + y1 * w] & 0x00FF0000) >> 16) - ((data[x + y1 * w] & 0x0000FF00) >> 8)) > 10 ||
161                              Math.abs(((data[x + y1 * w] & 0x0000FF00) >> 8) - ((data[x + y1 * w] & 0x000000FF))) > 10) {
162                              breakout = false;
163                          }
164  
165                  x--;
166                  start_x = x;
167  
168                  // search for the next column with a non-grayscale pixel in it
169                  for(breakout = false; x<w && !breakout; x++)
170                      for(y1 = start_y; y1<end_y; y1++)
171                          if (Math.abs(((data[x + y1 * w] & 0x00FF0000) >> 16) - ((data[x + y1 * w] & 0x0000FF00) >> 8)) > 10 ||
172                              Math.abs(((data[x + y1 * w] & 0x0000FF00) >> 8) - ((data[x + y1 * w] & 0x000000FF))) > 10) {
173                              breakout = true;
174                              baseline = y1;
175                          }
176                  
177                  x--;
178                  advance = x - start_x;
179  
180                  // search for the next column without a grayscale pixel in it
181                  for(breakout = false; x<w && !breakout; x++)
182                      for(y1 = start_y, breakout = true; y1<end_y; y1++)
183                          if (Math.abs(((data[x + y1 * w] & 0x00FF0000) >> 16) - ((data[x + y1 * w] & 0x0000FF00) >> 8)) > 10 ||
184                              Math.abs(((data[x + y1 * w] & 0x0000FF00) >> 8) - ((data[x + y1 * w] & 0x000000FF))) > 10) {
185                              breakout = false;
186                          }
187  
188                  x--;
189                  end_x = x;
190  
191                  if (start_x == end_x) break;
192  
193                  metrics[numglyphs] = new int[6];
194                  metrics[numglyphs][0] = start_x;
195                  metrics[numglyphs][1] = end_x;
196                  metrics[numglyphs][2] = start_y;
197                  metrics[numglyphs][3] = end_y;
198                  metrics[numglyphs][4] = advance;
199                  metrics[numglyphs][5] = baseline;
200                  numglyphs++;
201  
202                  if (numglyphs >= metrics.length) break;
203  
204              }
205  
206              if (numglyphs >= metrics.length) break;
207          }
208  
209          for(int i=0; i<data.length; i++)
210              if (Math.abs(((data[i] & 0x00FF0000) >> 16) - ((data[i] & 0x0000FF00) >> 8)) > 10 ||
211                  Math.abs(((data[i] & 0x0000FF00) >> 8) - ((data[i] & 0x000000FF))) > 10)
212                  data[i] = 0x00;
213              else
214                  data[i] = (0xFF - (data[i] & 0xFF)) << 24;
215  
216          for(int i=33; i<=126; i++)
217              if (metrics[i - 33] != null)
218                  maxascent = Math.max(maxascent, metrics[i - 33][5] - metrics[i - 33][2]);
219  
220          for(int i=33; i<=126; i++)
221              if (metrics[i - 33] != null)
222                  maxdescent = Math.max(maxdescent, metrics[i - 33][3] - metrics[i - 33][5]);
223         
224      }
225  
226  }
227