import org.xwt.util.*;
import org.xwt.js.*;
import java.util.*;
+import java.io.*;
public class Font {
if (g == null) glyphsToBeRendered.prepend(g = new Glyph(c));
if (g.p == null) {
glyphsToBeRendered.prepend(g);
- Scheduler.add(glyphRenderingTask);
encounteredUnrenderedGlyph = true;
} else if (!encounteredUnrenderedGlyph) {
if (pb != null && g.p != null)
}});
if (!used) for(int i=32; i<128; i++) glyphsToBeRendered.append(glyphs[i] = new Glyph((char)i));
+ if (!used || encounteredUnrenderedGlyph) { System.out.println("foo!"); Scheduler.add(glyphRenderingTask); }
used = true;
return ((long)width << 16) | (long)height;
}
static final Queue glyphsToBeRendered = new Queue(255);
static final Scheduler.Task glyphRenderingTask = new Scheduler.Task() { public void perform() {
Glyph g = (Glyph)glyphsToBeRendered.remove(false);
- if (g == null || g.p != null) return;
+ if (g == null) return;
+ if (g.p != null) { perform(); return; }
Log.log(Glyph.class, "rendering glyph " + g.c);
- freetype.renderGlyph(g);
+ try {
+ freetype.renderGlyph(g);
+ } catch (IOException e) {
+ Log.log(Freetype.class, e);
+ }
Scheduler.add(this);
} };
}
/** base class for XWT resources */
public abstract class Res extends JS {
- public String getDescriptiveName() { return ""; }
+ public abstract String getDescriptiveName();
public String typeName() { return "resource"; }
/** cache of subresources so that the equality operator works on them */
}
/** subclass from this if you want a CachedInputStream for each path */
- public static abstract class CachedRes extends Res {
+ public static class CachedRes extends Res {
+ private Res parent;
+ private boolean disk = false;
+
+ // FIXME: security concern here
+ private String subdir = null;
+
+ public String getDescriptiveName() { return parent.getDescriptiveName(); }
private Hash cachedInputStreams = new Hash();
- abstract InputStream _getInputStream(String path) throws IOException;
- public final InputStream getInputStream(String path) throws IOException {
+ public CachedRes(Res parent, String subdir, boolean disk) {
+ this.parent = parent; this.disk = disk; this.subdir = subdir;
+ }
+ public InputStream getInputStream(String path) throws IOException {
CachedInputStream cis = (CachedInputStream)cachedInputStreams.get(path);
if (cis == null) {
- cis = new CachedInputStream(_getInputStream(path));
+ java.io.File f = null;
+ if (disk) {
+ // FIXME ugly
+ // FIXME need separate hash for disk/nondisk
+ f = new java.io.File(System.getProperty("user.home") +
+ java.io.File.separatorChar + ".xwt" +
+ java.io.File.separatorChar + "caches" +
+ java.io.File.separatorChar + subdir +
+ java.io.File.separatorChar +
+ new String(Base64.encode(parent.getDescriptiveName().getBytes())));
+ Log.log(this, "caching resource in " + f);
+ new java.io.File(f.getParent()).mkdirs();
+ if (f.exists()) return new FileInputStream(f);
+ }
+ cis = new CachedInputStream(parent.getInputStream(path), f);
cachedInputStreams.put(path, cis);
}
return cis.getInputStream();
}
/** HTTP or HTTPS resource */
- public static class HTTP extends CachedRes {
+ public static class HTTP extends Res {
private String url;
HTTP(String url) { this.url = url; }
public String getDescriptiveName() { return url; }
- public InputStream _getInputStream(String path) throws IOException {
+ public InputStream getInputStream(String path) throws IOException {
return new org.xwt.HTTP(url + path).GET(); }
}
if (checkOnly) return Boolean.TRUE;
return new Res.Cab((Res)args.elementAt(0));
+ } else if (method.equals("cache")) {
+ if (checkOnly) return Boolean.TRUE;
+ return new Res.CachedRes((Res)args.elementAt(0), "resources", true);
+
} else if (method.equals("watchProgress")) {
if (checkOnly) return Boolean.TRUE;
return new Res.ProgressWatcher((Res)args.elementAt(0), (Function)args.elementAt(1));
}
}
+ // FIXME
public static Object sleep(final int i) {
final JS.Context jsthread = JS.Context.current();
final long currentTime = System.currentTimeMillis();
- final Scheduler.Task task = new Scheduler.Task() { public void perform() {
- // FIXME: don't busy-wait
- if (System.currentTimeMillis() - currentTime < i) Scheduler.add(this);
- else jsthread.resume();
- } };
- Scheduler.add(task);
+ new Thread() { public void run() {
+ try { Thread.sleep(i); } catch (InterruptedException e) { }
+ Scheduler.add(new Scheduler.Task() { public void perform() {
+ jsthread.resume();
+ } }); } }.start();
return JS.Context.pause;
}
xwt.thread = function() {
// $text.font = xwt.fonts.vera["Vera.ttf"];
- $text.font = xwt.uncab(xwt.load("http://master.dist.xwt.org/msfonts/arial32.exe"))["Arial.TTF"];
+ $text.font = xwt.cache(xwt.uncab(xwt.load("http://master.dist.xwt.org/msfonts/arial32.exe"))["Arial.TTF"]);
$text.fontsize = 18;
$text.text = "downloading...";
fill = xwt.org.xwt.builtin["splash.png"];
var new_xwt = xwt.clone(new_rr);
xwt.thread = function() {
+xwt.sleep(1000); // let the fonts get pulled in
for(var i=0; 100>i; i++) {
progress(i, 100);
xwt.yield();
import java.io.*;
import java.util.zip.*;
import java.util.*;
+import org.bouncycastle.util.encoders.Base64;
// FEATURE: use streams, not memoryfont's
// FEATURE: kerning pairs
}
}
- public synchronized void renderGlyph(Font.Glyph glyph) {
- try {
+ public synchronized void renderGlyph(Font.Glyph glyph) throws IOException {
+ int width = 0;
+ int height = 0;
+ byte[] data = null;
+ String key = glyph.font.res.getDescriptiveName() + ":" + glyph.c;
+ key = new String(Base64.encode(key.getBytes()));
+ File cacheFile = new java.io.File(System.getProperty("user.home") +
+ java.io.File.separatorChar + ".xwt" +
+ java.io.File.separatorChar + "caches" +
+ java.io.File.separatorChar + "glyphs" +
+ java.io.File.separatorChar +
+ key);
+ new java.io.File(cacheFile.getParent()).mkdirs();
+
+ if (cacheFile.exists()) {
+ DataInputStream dis = new DataInputStream(new FileInputStream(cacheFile));
+ width = dis.readInt();
+ height = dis.readInt();
+ glyph.font.max_ascent = dis.readInt();
+ glyph.font.max_descent = dis.readInt();
+ glyph.baseline = dis.readInt();
+ glyph.advance = dis.readInt();
+ data = new byte[width * height];
+ if (width != 0 && height != 0) dis.readFully(data);
+
+ } else try {
+ System.out.println("cache miss!");
if (loadedStream != glyph.font.res) loadFontByteStream(glyph.font.res);
vm.setUserInfo(2, (int)glyph.c);
vm.setUserInfo(3, (int)glyph.c);
glyph.baseline = vm.getUserInfo(10);
glyph.advance = vm.getUserInfo(11);
- int width = vm.getUserInfo(6);
- int height = vm.getUserInfo(7);
- if (width == 0 || height == 0) {
- Log.log(Freetype.class, "warning glyph has zero width/height");
- glyph.p = Platform.createAlphaOnlyPicture(new byte[] { }, 0, 0);
-
- } else {
- byte[] data = new byte[width * height];
- int addr = vm.getUserInfo(5);
- vm.copyin(addr,data,width*height);
- glyph.p = Platform.createAlphaOnlyPicture(data, width, height);
- }
+ width = vm.getUserInfo(6);
+ height = vm.getUserInfo(7);
+
+ data = new byte[width * height];
+ int addr = vm.getUserInfo(5);
+ vm.copyin(addr,data,width*height);
+ File tmp = new File(cacheFile.getCanonicalPath() + ".tmp");
+ DataOutputStream dis = new DataOutputStream(new FileOutputStream(tmp));
+ dis.writeInt(width);
+ dis.writeInt(height);
+ dis.writeInt(glyph.font.max_ascent);
+ dis.writeInt(glyph.font.max_descent);
+ dis.writeInt(glyph.baseline);
+ dis.writeInt(glyph.advance);
+ if (width != 0 && height != 0) dis.write(data, 0, data.length);
+ dis.close();
+ tmp.renameTo(cacheFile);
} catch (Exception e) {
Log.log(this, e);
}
+
+ if (width == 0 || height == 0) Log.log(Freetype.class, "warning glyph has zero width/height");
+ glyph.p = Platform.createAlphaOnlyPicture(data, width, height);
}
}
package org.xwt.util;
import java.io.*;
+// FEATURE: don't use a byte[] if we have a diskCache file
/**
* Wraps around an InputStream, caching the stream in a byte[] as it
* is read and permitting multiple simultaneous readers
byte[] cache = new byte[1024 * 128];
int size = 0;
final InputStream is;
+ File diskCache;
- public CachedInputStream(InputStream is) { this.is = is; }
- public InputStream getInputStream() { return new SubStream(); }
+ public CachedInputStream(InputStream is) { this(is, null); }
+ public CachedInputStream(InputStream is, File diskCache) {
+ this.is = is;
+ this.diskCache = diskCache;
+ }
+ public InputStream getInputStream() throws IOException {
+ System.out.println("diskCache == " + diskCache);
+ System.out.println("diskCache.exists() == " + diskCache.exists());
+ if (diskCache != null && diskCache.exists()) return new FileInputStream(diskCache);
+ return new SubStream();
+ }
public void grow(int newLength) {
if (newLength < cache.length) return;
filling = true;
grow(size + howMuch);
int ret = is.read(cache, size, howMuch);
- if (ret == -1) eof = true;
+ if (ret == -1) {
+ eof = true;
+ // FIXME: probably a race here
+ if (diskCache != null && !diskCache.exists())
+ try {
+ File cacheFile = new File(diskCache + ".tmp");
+ FileOutputStream cacheFileStream = new FileOutputStream(cacheFile);
+ cacheFileStream.write(cache, 0, size);
+ cacheFileStream.close();
+ cacheFile.renameTo(diskCache);
+ } catch (IOException e) {
+ Log.log(this, "exception thrown while writing disk cache");
+ Log.log(this, e);
+ }
+ }
else size += ret;
filling = false;
notifyAll();