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