make core compile with new js stuff and Task replacement class
[org.ibex.core.git] / src / org / ibex / graphics / Font.java
1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the GNU General Public License version 2 ("the License").
3 // You may not use this file except in compliance with the License.
4
5 package org.ibex.graphics;
6 import org.ibex.util.*;
7 import java.io.*;
8 import java.util.Hashtable;
9 import org.ibex.js.*;
10 import org.ibex.core.*;
11 import org.ibex.nestedvm.*;
12 import org.ibex.plat.*;
13 import org.ibex.nestedvm.Runtime;
14
15 // FEATURE: this could be cleaner
16 /** encapsulates a single font (a set of Glyphs) */
17 public class Font {
18
19     private Font(JS stream, int pointsize) { this.stream = stream; this.pointsize = pointsize; }
20
21     private static boolean glyphRenderingTaskIsScheduled = false;
22
23     public final int pointsize;                 ///< the size of the font
24     public final JS stream;                 ///< the stream from which this font was loaded
25     public int max_ascent;                      ///< the maximum ascent, in pixels
26     public int max_descent;                     ///< the maximum descent, in pixels
27     boolean latinCharsPreloaded = false;        ///< true if a request to preload ASCII 32-127 has begun
28     public Glyph[] glyphs = new Glyph[65535];          ///< the glyphs that comprise this font
29
30     public abstract static class Glyph {
31         protected Glyph(Font font, char c) { this.font = font; this.c = c; }
32         public final Font font;
33         public final char c;
34         public int baseline;                    ///< within the alphamask, this is the y-coordinate of the baseline
35         public int advance;                     ///< amount to increment the x-coordinate
36         public boolean isLoaded = false;        ///< true iff the glyph is loaded
37         public int width = -1;                  ///< the width of the glyph
38         public int height = -1;                 ///< the height of the glyph
39         public byte[] data = null;              ///< the alpha channel samples for this font
40         public String path = null; // FIXME this should be shared across point sizes
41         void render() {
42             if (!isLoaded) try { renderGlyph(this); } catch (IOException e) { Log.info(Font.class, e); }
43             isLoaded = true;
44         }
45     }
46
47
48     // Statics //////////////////////////////////////////////////////////////////////
49
50     static final Hashtable glyphsToBeCached = new Hashtable();
51     static final Hashtable glyphsToBeDisplayed = new Hashtable();
52     // HACK: replace with Cache<JS, int>
53     private static Basket.Map fonts = new Basket.HashMap();
54     public static Font getFont(JS stream, int pointsize) {
55         Basket.Map m = (Basket.Map)fonts.get(stream);
56         Font ret = null;
57         if (m != null) ret = (Font)m.get(new Integer(pointsize));
58         else fonts.put(stream, m = new Basket.HashMap());
59         if (ret == null) m.put(new Integer(pointsize), ret = new Font(stream, pointsize));
60         return ret;
61     }
62
63
64     // Methods //////////////////////////////////////////////////////////////////////
65
66     /**
67      *  Rasterize the glyphs of <code>text</code>.
68      *  @returns <code>(width&lt;&lt;32)|height</code>
69      */
70     public long rasterizeGlyphs(String text, PixelBuffer pb, int textcolor, int x, int y, int cx1, int cy1, int cx2, int cy2) {
71         int width = 0, height = 0;
72         if (!latinCharsPreloaded) {       // preload the Latin-1 charset with low priority (we'll probably want it)
73             for(int i=48; i<57; i++) if (glyphs[i]==null) glyphsToBeCached.put(glyphs[i]=Platform.createGlyph(this, (char)i),"");
74             for(int i=32; i<47; i++) if (glyphs[i]==null) glyphsToBeCached.put(glyphs[i]=Platform.createGlyph(this, (char)i),"");
75             for(int i=57; i<128; i++) if (glyphs[i]==null) glyphsToBeCached.put(glyphs[i]=Platform.createGlyph(this, (char)i),"");
76             if (!glyphRenderingTaskIsScheduled) {
77                 Scheduler.add(glyphRenderingTask);
78                 glyphRenderingTaskIsScheduled = true;
79             }
80             latinCharsPreloaded = true;
81         }
82         for(int i=0; i<text.length(); i++) {
83             final char c = text.charAt(i);
84             Glyph g = glyphs[c];
85             if (g == null) glyphs[c] = g = Platform.createGlyph(this, c);
86             g.render();
87             if (pb != null) pb.drawGlyph(g, x + width, y + g.font.max_ascent - g.baseline, cx1, cy1, cx2, cy2, textcolor);
88             width += g.advance;
89             height = java.lang.Math.max(height, max_ascent + max_descent);
90         }
91         return ((((long)width) << 32) | (long)(height & 0xffffffffL));
92     }
93
94     // FEATURE do we really need to be caching sizes?
95     private static Cache sizeCache = new Cache(1000, true);
96     public int textwidth(String s) { return (int)((textsize(s) >>> 32) & 0xffffffff); }
97     public int textheight(String s) { return (int)(textsize(s) & 0xffffffffL); }
98     public long textsize(String s) {
99         Long l = (Long)sizeCache.get(s);
100         if (l != null) return ((Long)l).longValue();
101         long ret = rasterizeGlyphs(s, null, 0, 0, 0, 0, 0, 0, 0);
102         sizeCache.put(s, new Long(ret));
103         return ret;
104     }
105
106     static final Callable glyphRenderingTask = new Callable() { public Object run(Object o) {
107         // FIXME: this should be a low-priority task
108         glyphRenderingTaskIsScheduled = false;
109         if (glyphsToBeCached.isEmpty()) return null;
110         Glyph g = (Glyph)glyphsToBeCached.keys().nextElement();
111         if (g == null) return null;
112         glyphsToBeCached.remove(g);
113         Log.debug(Font.class, "glyphRenderingTask rendering " + g.c + " of " + g.font);
114         g.render();
115         Log.debug(Glyph.class, "   done rendering glyph " + g.c);
116         glyphRenderingTaskIsScheduled = true;
117         Scheduler.add(this);
118         return null;
119     } };
120
121
122     // FreeType Interface //////////////////////////////////////////////////////////////////////////////
123
124     // FEATURE: use streams, not memoryfont's
125     // FEATURE: kerning pairs
126     private static final Runtime rt;
127     private static JS loadedStream;
128     private static int loadedStreamAddr;
129
130     static {
131         try {
132             rt = (Runtime)Class.forName("org.ibex.util.MIPSApps").newInstance();
133         } catch(Exception e) {
134             throw new Error("Error instantiating freetype: " + e);
135         }
136         rt.start(new String[]{"freetype"});
137         rt.execute();
138         rtCheck();
139     }
140     
141     private static void rtCheck() { if(rt.getState() != Runtime.PAUSED) throw new Error("Freetype exited " + rt.exitStatus()); }
142     
143     private static void loadFontByteStream(JS res) {
144         if(loadedStream == res) return;
145         
146         try {
147             Log.info(Font.class, "loading font " + JSU.str(res));
148             InputStream is = res.getInputStream();
149             byte[] fontstream = InputStreamToByteArray.convert(is);
150             rt.free(loadedStreamAddr);
151             loadedStreamAddr = rt.xmalloc(fontstream.length);
152             rt.copyout(fontstream, loadedStreamAddr, fontstream.length);
153             if(rt.call("load_font", loadedStreamAddr, fontstream.length) != 0)
154                 throw new RuntimeException("load_font failed"); // FEATURE: better error
155             rtCheck();
156             loadedStream = res;
157         } catch (Exception e) {
158             // FEATURE: Better error reporting (thow an exception?)
159             Log.info(Font.class, e);
160         }
161     }
162
163     private static synchronized void renderGlyph(Font.Glyph glyph) throws IOException {
164         try {
165             Log.debug(Font.class, "rasterizing glyph " + glyph.c + " of font " + glyph.font);
166             if (loadedStream != glyph.font.stream) loadFontByteStream(glyph.font.stream);
167             
168             long start = System.currentTimeMillis();
169             
170             rt.call("render",glyph.c,glyph.font.pointsize);
171             rtCheck();
172             
173             glyph.font.max_ascent = rt.getUserInfo(3);
174             glyph.font.max_descent = rt.getUserInfo(4);
175             glyph.baseline = rt.getUserInfo(5);
176             glyph.advance = rt.getUserInfo(6);
177             
178             glyph.width = rt.getUserInfo(1);
179             glyph.height = rt.getUserInfo(2);
180
181             glyph.data = new byte[glyph.width * glyph.height];
182             int addr = rt.getUserInfo(0);
183             rt.copyin(addr, glyph.data, glyph.width * glyph.height);
184             
185             byte[] path = new byte[rt.getUserInfo(8)];
186             rt.copyin(rt.getUserInfo(7), path, rt.getUserInfo(8));
187             glyph.path = new String(path);
188             glyph.path += " m " + (64 * glyph.advance) + " 0 "; 
189
190             glyph.isLoaded = true;
191         } catch (Exception e) {
192             // FEATURE: Better error reporting (thow an exception?)
193             Log.info(Font.class, e);
194         }
195     }
196 }