From ee179c3ff98348acc2f7b5695440ac06a00bc475 Mon Sep 17 00:00:00 2001 From: megacz Date: Fri, 30 Jan 2004 07:40:57 +0000 Subject: [PATCH] 2003/11/03 07:36:40 darcs-hash:20040130074057-2ba56-1e5cb4e2a75d08acda2fda2188280515422417a9.gz --- src/org/xwt/Font.java | 12 ++++-- src/org/xwt/Res.java | 37 ++++++++++++++---- src/org/xwt/XWT.java | 16 +++++--- src/org/xwt/builtin/splash.xwt | 3 +- src/org/xwt/translators/Freetype.java | 62 ++++++++++++++++++++++++------- src/org/xwt/util/CachedInputStream.java | 31 ++++++++++++++-- 6 files changed, 127 insertions(+), 34 deletions(-) diff --git a/src/org/xwt/Font.java b/src/org/xwt/Font.java index b1d9230..75bd9c8 100644 --- a/src/org/xwt/Font.java +++ b/src/org/xwt/Font.java @@ -4,6 +4,7 @@ import org.xwt.translators.*; import org.xwt.util.*; import org.xwt.js.*; import java.util.*; +import java.io.*; public class Font { @@ -46,7 +47,6 @@ 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) @@ -67,6 +67,7 @@ public class Font { }}); 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; } @@ -85,9 +86,14 @@ public class Font { 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); } }; } diff --git a/src/org/xwt/Res.java b/src/org/xwt/Res.java index 954f290..3bf88ee 100644 --- a/src/org/xwt/Res.java +++ b/src/org/xwt/Res.java @@ -11,7 +11,7 @@ import org.bouncycastle.util.encoders.Base64; /** 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 */ @@ -87,13 +87,36 @@ public abstract class Res extends JS { } /** 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(); @@ -101,11 +124,11 @@ public abstract class Res extends JS { } /** 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(); } } diff --git a/src/org/xwt/XWT.java b/src/org/xwt/XWT.java index 0c01e67..53514ed 100644 --- a/src/org/xwt/XWT.java +++ b/src/org/xwt/XWT.java @@ -88,6 +88,10 @@ public final class XWT extends JS.Obj { 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)); @@ -195,15 +199,15 @@ public final class XWT extends JS.Obj { } } + // 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; } diff --git a/src/org/xwt/builtin/splash.xwt b/src/org/xwt/builtin/splash.xwt index 6294636..a99bb00 100644 --- a/src/org/xwt/builtin/splash.xwt +++ b/src/org/xwt/builtin/splash.xwt @@ -11,7 +11,7 @@ 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"]; @@ -28,6 +28,7 @@ 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(); diff --git a/src/org/xwt/translators/Freetype.java b/src/org/xwt/translators/Freetype.java index de5e0c0..18934c9 100644 --- a/src/org/xwt/translators/Freetype.java +++ b/src/org/xwt/translators/Freetype.java @@ -4,6 +4,7 @@ import org.xwt.util.*; 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 @@ -37,8 +38,33 @@ public class Freetype { } } - 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); @@ -50,21 +76,29 @@ public class Freetype { 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); } } diff --git a/src/org/xwt/util/CachedInputStream.java b/src/org/xwt/util/CachedInputStream.java index d46569e..a599459 100644 --- a/src/org/xwt/util/CachedInputStream.java +++ b/src/org/xwt/util/CachedInputStream.java @@ -1,6 +1,7 @@ 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 @@ -12,9 +13,19 @@ public class CachedInputStream { 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; @@ -28,7 +39,21 @@ public class CachedInputStream { 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(); -- 1.7.10.4