Skip to content
This repository
Browse code

Swap over to binary protocol instead of JSON

  • Loading branch information...
commit 1b3d6d96bfa7445fb2e2a34e6d81a2a7fa94c7c8 1 parent 39deabf
Chris Smith authored August 30, 2011
13  src/App.hs
@@ -10,6 +10,8 @@ import           Data.Map (Map)
10 10
 import qualified Data.Map as M
11 11
 
12 12
 import Data.ByteString (ByteString)
  13
+import qualified Data.ByteString.Char8 as BC
  14
+import qualified Data.ByteString.Base64 as B64
13 15
 
14 16
 import GlossAdapters
15 17
 import ClientManager
@@ -37,3 +39,14 @@ newApp heist = do
37 39
     csim        <- newMVar M.empty
38 40
     return (App heist animMgr simMgr cpic canim csim)
39 41
 
  42
+
  43
+{-|
  44
+    Base64 encodes a ByteString, and forms a filename from it.  Since this is
  45
+    a file name, we need to use '-' intead of a slash.  This is used in several
  46
+    places, so it's in App so that it is easy to import anywhere.
  47
+-}
  48
+base64FileName :: ByteString -> FilePath
  49
+base64FileName str = map slashToDash $ BC.unpack $ B64.encode str
  50
+    where slashToDash '/' = '-'
  51
+          slashToDash c   = c
  52
+
137  src/Instances.hs
... ...
@@ -1,137 +0,0 @@
1  
-{-# LANGUAGE OverloadedStrings    #-}
2  
-{-# LANGUAGE StandaloneDeriving   #-}
3  
-{-# LANGUAGE TypeSynonymInstances #-}
4  
-{-# LANGUAGE FlexibleInstances    #-}
5  
-{-# LANGUAGE DeriveDataTypeable   #-}
6  
-
7  
-module Instances where
8  
-
9  
-import Blaze.ByteString.Builder
10  
-import Codec.Compression.Zlib
11  
-import Control.Monad
12  
-import Crypto.Hash.MD5
13  
-import Data.Aeson hiding (Array)
14  
-import Data.Array
15  
-import Data.Bits
16  
-import Data.Ix
17  
-import Data.List
18  
-import Data.Monoid
19  
-import Data.Typeable
20  
-import Data.Word
21  
-import Graphics.Gloss
22  
-import System.IO.Unsafe
23  
-import System.Directory
24  
-
25  
-import Data.Text (Text)
26  
-import qualified Data.Text as T
27  
-import qualified Data.Text.Encoding as T
28  
-
29  
-import Data.ByteString (ByteString)
30  
-import qualified Data.ByteString as B
31  
-import qualified Data.ByteString.Lazy as LB
32  
-import qualified Data.ByteString.Char8 as BC
33  
-import qualified Data.ByteString.Base64 as B64
34  
-
35  
-
36  
-deriving instance Typeable Picture
37  
-
38  
-crcTable :: Array Word8 Word32
39  
-crcTable = array (0,255) [ (i, crcFor i) | i <- [0..255] ]
40  
-    where crcFor n = foldl' step (fromIntegral n) [0..7]
41  
-          step c _ | odd c     = 0xedb88320 `xor` (c `shiftR` 1)
42  
-                   | otherwise = c `shiftR` 1
43  
-
44  
-crc :: LB.ByteString -> Word32
45  
-crc x = LB.foldl' step 0xffffffff x `xor` 0xffffffff
46  
-  where step c x = (crcTable ! (fromIntegral c `xor` x)) `xor` (c `shiftR` 8)
47  
-
48  
-
49  
-chunk :: ByteString -> LB.ByteString -> Builder
50  
-chunk nm dt = mconcat [
51  
-    fromWord32be (fromIntegral (LB.length dt)),
52  
-    fromByteString nm,
53  
-    fromLazyByteString dt,
54  
-    fromWord32be (crc dt)
55  
-    ]
56  
-
57  
-ifilter :: Int -> ByteString -> LB.ByteString
58  
-ifilter w bs = LB.fromChunks (flist bs)
59  
-  where
60  
-    flist x | B.null x  = []
61  
-            | otherwise = let (s, r) = B.splitAt (4*w) x
62  
-                          in  B.singleton 0 : s : flist r
63  
-
64  
-
65  
-bmpToPng :: Int -> Int -> ByteString -> Builder
66  
-bmpToPng w h bs = mconcat [
67  
-    signature,
68  
-    ihdrChunk,
69  
-    idatChunk,
70  
-    iendChunk
71  
-    ]
72  
-  where
73  
-    signature = fromByteString (B.pack [ 137, 80, 78, 71, 13, 10, 26, 10 ])
74  
-    ihdrChunk = chunk "IHDR" $ toLazyByteString $ mconcat [
75  
-        fromWord32be (fromIntegral w),
76  
-        fromWord32be (fromIntegral h),
77  
-        fromWord8 8,
78  
-        fromWord8 6,
79  
-        fromWord8 0,
80  
-        fromWord8 0,
81  
-        fromWord8 0
82  
-        ]
83  
-    idatChunk = chunk "IDAT" (compress (ifilter w bs))
84  
-    iendChunk = chunk "IEND" LB.empty
85  
-
86  
-
87  
-cachedBmpToPng :: Int -> Int -> ByteString -> IO LB.ByteString
88  
-cachedBmpToPng w h bs = do
89  
-    let digest = BC.unpack $ B64.encode $ hash bs
90  
-    let fn     = "tmp/" ++ digest ++ ".png"
91  
-    e <- doesFileExist fn
92  
-    case e of
93  
-        True  -> LB.readFile fn
94  
-        False -> do let res = toLazyByteString (bmpToPng w h bs)
95  
-                    LB.writeFile fn res
96  
-                    return res
97  
-
98  
-
99  
-toBitmap :: Int -> Int -> ByteString -> Text
100  
-toBitmap w h bs = unsafePerformIO $ do
101  
-    png <- cachedBmpToPng w h bs
102  
-    let spng = B.concat (LB.toChunks png)
103  
-    return ("data:image/png;base64," `T.append` T.decodeUtf8 (B64.encode spng))
104  
-
105  
-
106  
-instance ToJSON Picture where
107  
-    toJSON Blank             = Null
108  
-    toJSON (Polygon path)    = object [ "t" .= ("p" :: Text), "p" .= path ]
109  
-    toJSON (Line path)       = object [ "t" .= ("l" :: Text), "p" .= path ]
110  
-    toJSON (Circle r)        = object [ "t" .= ("c" :: Text), "r" .= r ]
111  
-    toJSON (ThickCircle r w) = object [ "t" .= ("h" :: Text), "w" .= w, "r" .= r ]
112  
-    toJSON (Text str)        = object [ "t" .= ("t" :: Text), "c" .= T.pack str ]
113  
-    toJSON (Bitmap w h bmp)  = object [ "t" .= ("b" :: Text), "c" .= toBitmap w h bmp ]
114  
-    toJSON (Color c p)       = let (r,g,b,a) = rgbaOfColor c
115  
-                               in  object [ "t" .= ("z" :: Text)
116  
-                                          , "r" .= (round (255 * r) :: Int)
117  
-                                          , "g" .= (round (255 * g) :: Int)
118  
-                                          , "b" .= (round (255 * b) :: Int)
119  
-                                          , "a" .= (round (255 * a) :: Int)
120  
-                                          , "p" .= p ]
121  
-    toJSON (Translate x y p) = object [ "t" .= ("x" :: Text)
122  
-                                      , "x" .= x
123  
-                                      , "y" .= y
124  
-                                      , "p" .= p ]
125  
-    toJSON (Rotate r p)      = object [ "t" .= ("r" :: Text)
126  
-                                      , "r" .= r
127  
-                                      , "p" .= p ]
128  
-    toJSON (Scale x y p)     = object [ "t" .= ("s" :: Text)
129  
-                                      , "x" .= x
130  
-                                      , "y" .= y
131  
-                                      , "p" .= p ]
132  
-    toJSON (Pictures ps)     = toJSON ps
133  
-
134  
-
135  
-instance ToJSON Point where
136  
-    toJSON (x,y) = object [ "x" .= x, "y" .= y ]
137  
-
13  src/Main.hs
... ...
@@ -1,6 +1,7 @@
1 1
 {-# LANGUAGE OverloadedStrings #-}
2 2
 module Main where
3 3
 
  4
+import Blaze.ByteString.Builder
4 5
 import Control.Applicative
5 6
 import Control.Concurrent
6 7
 import Control.Monad
@@ -29,7 +30,7 @@ import App
29 30
 import EventStream
30 31
 import ClientManager
31 32
 import Source
32  
-import Instances
  33
+import Serialize
33 34
 import GlossAdapters
34 35
 
35 36
 
@@ -152,9 +153,9 @@ display app pic = do
152 153
     writeBuilder b
153 154
   where
154 155
     scrSplice pic = return [ Element "script" [("type", "text/javascript")] [
155  
-        TextNode "picture = ",
156  
-        TextNode $ T.decodeUtf8 $ B.concat $ LB.toChunks $ encode pic,
157  
-        TextNode ";"
  156
+        TextNode "picture = '",
  157
+        TextNode $ T.decodeUtf8 $ toByteString $ base64 $ fromPicture pic,
  158
+        TextNode "';"
158 159
         ]]
159 160
 
160 161
 
@@ -202,7 +203,7 @@ animateStream app = do
202 203
         t1 <- getCurrentTime
203 204
         writeIORef tv t1
204 205
         let t = realToFrac (t1 `diffUTCTime` t0)
205  
-        return $ ServerEvent Nothing Nothing [ fromValue $ toJSON $ f t ]
  206
+        return $ ServerEvent Nothing Nothing [ base64 $ fromPicture $ f t ]
206 207
   where
207 208
     targetInterval = 0.1
208 209
 
@@ -250,7 +251,7 @@ simulateStream app = do
250 251
         let t = realToFrac (t1 `diffUTCTime` t0)
251 252
         let sim' = advanceSimulation t sim
252 253
         let pic  = simulationToPicture sim'
253  
-        return ((t1, sim'), ServerEvent Nothing Nothing [ fromValue $ toJSON pic ])
  254
+        return ((t1, sim'), ServerEvent Nothing Nothing [ base64 $ fromPicture pic ])
254 255
   where
255 256
     targetInterval = 0.1
256 257
 
181  src/Serialize.hs
... ...
@@ -0,0 +1,181 @@
  1
+{-# LANGUAGE MagicHash            #-}
  2
+{-# LANGUAGE OverloadedStrings    #-}
  3
+{-# LANGUAGE StandaloneDeriving   #-}
  4
+{-# LANGUAGE TypeSynonymInstances #-}
  5
+{-# LANGUAGE FlexibleInstances    #-}
  6
+
  7
+module Serialize (fromPicture, base64) where
  8
+
  9
+import Blaze.ByteString.Builder
  10
+import Codec.Compression.Zlib
  11
+import Control.Monad
  12
+import Crypto.Hash.MD5
  13
+import Data.Aeson hiding (Array)
  14
+import Data.Array
  15
+import Data.Bits
  16
+import Data.Ix
  17
+import Data.List
  18
+import Data.Monoid
  19
+import Data.Word
  20
+import Foreign hiding (unsafePerformIO)
  21
+import GHC.Exts
  22
+import GHC.Word
  23
+import Graphics.Gloss
  24
+import System.IO.Unsafe
  25
+import System.Directory
  26
+
  27
+import Data.Text (Text)
  28
+import qualified Data.Text as T
  29
+import qualified Data.Text.Encoding as T
  30
+
  31
+import Data.ByteString (ByteString)
  32
+import qualified Data.ByteString as B
  33
+import qualified Data.ByteString.Lazy as LB
  34
+import qualified Data.ByteString.Char8 as BC
  35
+import qualified Data.ByteString.Base64 as B64
  36
+
  37
+import App
  38
+
  39
+
  40
+{-|
  41
+    Encodes the contents of a Builder in base64.  For now, this results in
  42
+    creating it all in memory, since we need a strict ByteString for the
  43
+    base64 encoding.
  44
+-}
  45
+base64 :: Builder -> Builder
  46
+base64 = fromByteString . B64.encode . toByteString
  47
+
  48
+
  49
+{-|
  50
+    Serializes a picture to a packed binary format that's intended to be fast
  51
+    to render and parse.  Each picture type has a 8-bit tag, followed by
  52
+    type-specific data.  
  53
+-}
  54
+fromPicture :: Picture -> Builder
  55
+fromPicture Blank
  56
+    = fromWord8  1
  57
+fromPicture (Polygon path)
  58
+    = fromWord8  2 `mappend` fromPath path
  59
+fromPicture (Line path)
  60
+    = fromWord8  3 `mappend` fromPath path
  61
+fromPicture (Circle r)
  62
+    = fromWord8  4 `mappend` fromFloat r
  63
+fromPicture (ThickCircle r w)
  64
+    = fromWord8  5 `mappend` fromFloat r `mappend` fromFloat w
  65
+fromPicture (Text str)
  66
+    = fromWord8  6 `mappend` fromWord32be (fromIntegral (B.length t))
  67
+      `mappend` fromByteString t
  68
+  where t = T.encodeUtf8 (T.pack str)
  69
+fromPicture (Bitmap w h bmp)
  70
+    = fromWord8  7 `mappend` fromWord32be (fromIntegral (B.length b))
  71
+      `mappend` fromByteString b
  72
+  where b = toPng w h bmp
  73
+fromPicture (Color c p)
  74
+    = fromWord8  8 `mappend` fromColor c `mappend` fromPicture p
  75
+fromPicture (Translate x y p)
  76
+    = fromWord8  9 `mappend` fromFloat x `mappend` fromFloat y `mappend` fromPicture p
  77
+fromPicture (Rotate r p)
  78
+    = fromWord8 10 `mappend` fromFloat r `mappend` fromPicture p
  79
+fromPicture (Scale x y p)
  80
+    = fromWord8 11 `mappend` fromFloat x `mappend` fromFloat y `mappend` fromPicture p
  81
+fromPicture (Pictures ps)
  82
+    = fromWord8 12 `mappend` mconcat (map fromPicture ps) `mappend` fromWord8 0
  83
+
  84
+fromColor c
  85
+    = let (r,g,b,a) = rgbaOfColor c
  86
+      in  fromWord8 (clamp r) `mappend` fromWord8 (clamp g) `mappend`
  87
+          fromWord8 (clamp b) `mappend` fromWord8 (clamp a)
  88
+  where
  89
+    clamp f | f < 0     = 0
  90
+            | f > 1     = 255
  91
+            | otherwise = round (255 * f)
  92
+
  93
+fromPath ps = fromWord32be (fromIntegral (length ps)) `mappend` mconcat (map fromPoint ps)
  94
+
  95
+fromPoint (x,y) = fromFloat x `mappend` fromFloat y
  96
+
  97
+
  98
+{-|
  99
+    Treat a single-precision floating-point number as a Word32.  This is
  100
+    rather hackish, but also accepted as the right way to do the job.
  101
+-}
  102
+castFloat :: Float -> Word32
  103
+castFloat f = unsafePerformIO $ alloca $ \buf -> do
  104
+    poke (castPtr buf) f
  105
+    peek buf
  106
+
  107
+fromFloat :: Float -> Builder
  108
+fromFloat = fromWord32be . castFloat
  109
+
  110
+
  111
+{-|
  112
+    Encoding a bitmap is tricky: since we don't want to draw it pixel by
  113
+    pixel, we actually build a file in PNG format, then encode that into a
  114
+    data scheme URL.  This does mean that the actual PNG ends up base64
  115
+    encoded twice (after the entire picture is encoded), but that's the
  116
+    cleanest way to do it.
  117
+-}
  118
+crcTable :: Array Word8 Word32
  119
+crcTable = array (0,255) [ (i, crcFor i) | i <- [0..255] ]
  120
+    where crcFor n = foldl' step (fromIntegral n) [0..7]
  121
+          step c _ | odd c     = 0xedb88320 `xor` (c `shiftR` 1)
  122
+                   | otherwise = c `shiftR` 1
  123
+
  124
+crc :: LB.ByteString -> Word32
  125
+crc x = LB.foldl' step 0xffffffff x `xor` 0xffffffff
  126
+  where step c x = (crcTable ! (fromIntegral c `xor` x)) `xor` (c `shiftR` 8)
  127
+
  128
+
  129
+chunk :: ByteString -> LB.ByteString -> Builder
  130
+chunk nm dt = mconcat [
  131
+    fromWord32be (fromIntegral (LB.length dt)),
  132
+    fromByteString nm,
  133
+    fromLazyByteString dt,
  134
+    fromWord32be (crc dt)
  135
+    ]
  136
+
  137
+ifilter :: Int -> ByteString -> LB.ByteString
  138
+ifilter w bs = LB.fromChunks (flist bs)
  139
+  where
  140
+    flist x | B.null x  = []
  141
+            | otherwise = let (s, r) = B.splitAt (4*w) x
  142
+                          in  B.singleton 0 : s : flist r
  143
+
  144
+
  145
+bmpToPng :: Int -> Int -> ByteString -> Builder
  146
+bmpToPng w h bs = mconcat [
  147
+    signature,
  148
+    ihdrChunk,
  149
+    idatChunk,
  150
+    iendChunk
  151
+    ]
  152
+  where
  153
+    signature = fromByteString (B.pack [ 137, 80, 78, 71, 13, 10, 26, 10 ])
  154
+    ihdrChunk = chunk "IHDR" $ toLazyByteString $ mconcat [
  155
+        fromWord32be (fromIntegral w),
  156
+        fromWord32be (fromIntegral h),
  157
+        fromWord8 8,
  158
+        fromWord8 6,
  159
+        fromWord8 0,
  160
+        fromWord8 0,
  161
+        fromWord8 0
  162
+        ]
  163
+    idatChunk = chunk "IDAT" (compress (ifilter w bs))
  164
+    iendChunk = chunk "IEND" LB.empty
  165
+
  166
+
  167
+cachedBmpToPng :: Int -> Int -> ByteString -> IO Builder
  168
+cachedBmpToPng w h bs = do
  169
+    let digest = base64FileName (hash bs)
  170
+    let fn     = "tmp/" ++ digest ++ ".png"
  171
+    e <- doesFileExist fn
  172
+    case e of
  173
+        True  -> fmap fromLazyByteString (LB.readFile fn)
  174
+        False -> do let res = bmpToPng w h bs
  175
+                    LB.writeFile fn (toLazyByteString res)
  176
+                    return res
  177
+
  178
+
  179
+toPng :: Int -> Int -> ByteString -> ByteString
  180
+toPng w h bs = unsafePerformIO $ fmap toByteString $ cachedBmpToPng w h bs
  181
+
9  src/Source.hs
@@ -78,15 +78,6 @@ getSimulation app src = do
78 78
 
79 79
 #endif
80 80
 
81  
-{-|
82  
-    Base64 encodes a ByteString, and forms a filename from it.  Since this is
83  
-    a file name, we need to use '-' intead of '/'.
84  
--}
85  
-base64FileName :: ByteString -> FilePath
86  
-base64FileName str = map slashToDash $ BC.unpack $ B64.encode str
87  
-    where slashToDash '/' = '-'
88  
-          slashToDash c   = c
89  
-
90 81
 
91 82
 {-|
92 83
     Returns a possibly cached compile result.  The map in the first parameter
3  web/display.tpl
@@ -28,8 +28,7 @@
28 28
                 {
29 29
                     var eventSource = new EventSource(eventURI);
30 30
                     eventSource.onmessage = function(event) {
31  
-                        var pic = JSON.parse(event.data);
32  
-                        displayInCanvas(canvas, pic);
  31
+                        displayInCanvas(canvas, event.data);
33 32
                     }
34 33
                 }
35 34
             }
253  web/draw.js
... ...
@@ -1,7 +1,97 @@
  1
+function Stream(b)
  2
+{
  3
+    var index = 0;
  4
+
  5
+    this.popByte = function()
  6
+    {
  7
+        return b.charCodeAt(index++);
  8
+    }
  9
+
  10
+    this.peekByte = function()
  11
+    {
  12
+        return b.charCodeAt(index);
  13
+    }
  14
+
  15
+    this.popWord32 = function()
  16
+    {
  17
+        var a = this.popByte();
  18
+        var b = this.popByte();
  19
+        var c = this.popByte();
  20
+        var d = this.popByte();
  21
+
  22
+        return (a << 24) | (b << 16) | (c << 8) | (d);
  23
+    }
  24
+
  25
+    this.popFloat = function()
  26
+    {
  27
+        var i = this.popWord32();
  28
+        var sign = ((i >> 31) & 0x00000001) ? -1 : 1;
  29
+        var expt = (i >> 23) & 0x000000ff;
  30
+        var mant = (i      ) & 0x007fffff;
  31
+
  32
+        if (expt == 0)
  33
+        {
  34
+            return sign * Math.pow(2, -126) * (mant / 0x800000);
  35
+        }
  36
+        else if (expt == 0xff)
  37
+        {
  38
+            if (mant != 0) return Number.NaN;
  39
+            else return sign * Infinity;
  40
+        }
  41
+        else
  42
+        {
  43
+            return sign * Math.pow(2, expt - 127) * (1 + mant / 0x800000);
  44
+        }
  45
+    }
  46
+
  47
+    this.popBytes = function(len)
  48
+    {
  49
+        var i = index;
  50
+        index += len;
  51
+        return b.slice(i, i + len);
  52
+    }
  53
+
  54
+    this.popUtf8 = function(len)
  55
+    {
  56
+        var utftext = this.popBytes(len);
  57
+    	var string = "";
  58
+    	var i = 0;
  59
+    	var c = c1 = c2 = 0;
  60
+
  61
+    	while ( i < utftext.length )
  62
+    	{
  63
+    		c = utftext.charCodeAt(i);
  64
+
  65
+    		if (c < 128)
  66
+    		{
  67
+    			string += String.fromCharCode(c);
  68
+	    		i++;
  69
+	    	}
  70
+    		else if ((c > 191) && (c < 224))
  71
+    		{
  72
+    			c2 = utftext.charCodeAt(i+1);
  73
+	    		string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
  74
+	    		i += 2;
  75
+	    	}
  76
+    		else
  77
+    		{
  78
+    			c2 = utftext.charCodeAt(i+1);
  79
+	    		c3 = utftext.charCodeAt(i+2);
  80
+	    		string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
  81
+	    		i += 3;
  82
+	    	}
  83
+        }
  84
+
  85
+    	return string;
  86
+	}
  87
+}
  88
+
1 89
 var ctx = null;
2 90
 
3 91
 function displayInCanvas(c, pic)
4 92
 {
  93
+    var decoded = atob(pic);
  94
+
5 95
     if (ctx == null) ctx = c.getContext("2d");
6 96
     ctx.clearRect(0,0,500,500);
7 97
     ctx.save();
@@ -11,136 +101,167 @@ function displayInCanvas(c, pic)
11 101
     ctx.textBaseline = "alphabetic";
12 102
     ctx.lineWidth = 0;
13 103
     ctx.font = "100px Times Roman";
14  
-    display(ctx, pic, 1, 0, 0, 1, 0, 0);
  104
+    display(ctx, new Stream(decoded), 1, 0, 0, 1, 0, 0);
15 105
     ctx.restore();
16 106
 }
17 107
 
18  
-function display(ctx, pic, a, b, c, d, e, f)
  108
+function display(ctx, p, a, b, c, d, e, f)
19 109
 {
20  
-    if (pic == null)
21  
-    {
22  
-        // do nothing
23  
-    }
24  
-    else if (pic instanceof Array)
25  
-    {
26  
-        var i;
27  
-        for (i = 0; i < pic.length; i++)
28  
-        {
29  
-            display(ctx, pic[i], a, b, c, d, e, f);
30  
-        }
31  
-    }
32  
-    else if (pic.t == "c")
  110
+    var tag = p.popByte();
  111
+
  112
+    if (tag == 1)
33 113
     {
34  
-        ctx.save();
35  
-        ctx.transform(a, b, c, d, e, f);
36  
-        ctx.beginPath();
37  
-        ctx.arc(0, 0, pic.r, 0, 2 * Math.PI);
38  
-        ctx.restore();
39  
-        ctx.stroke();
  114
+        // Blank
40 115
     }
41  
-    else if (pic.t == "p")
  116
+    else if (tag == 2)
42 117
     {
43  
-        if (pic.p.length > 0)
  118
+        // Polygon
  119
+        var len = p.popWord32();
  120
+        if (len > 0)
44 121
         {
45 122
             ctx.save();
46 123
             ctx.transform(a, b, c, d, e, f);
47 124
             ctx.beginPath();
48  
-            ctx.moveTo(pic.p[0].x, pic.p[0].y);
  125
+            ctx.moveTo(p.popFloat(), p.popFloat());
49 126
             var i;
50  
-            for (i = 1; i < pic.p.length; i++)
  127
+            for (i = 1; i < len; i++)
51 128
             {
52  
-                ctx.lineTo(pic.p[i].x, pic.p[i].y);
  129
+                ctx.lineTo(p.popFloat(), p.popFloat());
53 130
             }
54 131
             ctx.restore();
55 132
             ctx.fill();
56 133
         }
57 134
     }
58  
-    else if (pic.t == "l")
  135
+    else if (tag == 3)
59 136
     {
60  
-        if (pic.p.length > 0)
  137
+        // Line
  138
+        var len = p.popWord32();
  139
+        if (len > 0)
61 140
         {
62 141
             ctx.save();
63 142
             ctx.transform(a, b, c, d, e, f);
64 143
             ctx.beginPath();
65  
-            ctx.moveTo(pic.p[0].x, pic.p[0].y);
  144
+            ctx.moveTo(p.popFloat(), p.popFloat());
66 145
             var i;
67  
-            for (i = 1; i < pic.p.length; i++)
  146
+            for (i = 1; i < len; i++)
68 147
             {
69  
-                ctx.lineTo(pic.p[i].x, pic.p[i].y);
  148
+                ctx.lineTo(p.popFloat(), p.popFloat());
70 149
             }
71 150
             ctx.restore();
72 151
             ctx.stroke();
73 152
         }
74 153
     }
75  
-    else if (pic.t == 'h')
  154
+    else if (tag == 4)
76 155
     {
77  
-        if (pic.w == 0)
  156
+        // Circle
  157
+        var r = p.popFloat();
  158
+        ctx.save();
  159
+        ctx.transform(a, b, c, d, e, f);
  160
+        ctx.beginPath();
  161
+        ctx.arc(0, 0, r, 0, 2 * Math.PI);
  162
+        ctx.restore();
  163
+        ctx.stroke();
  164
+    }
  165
+    else if (tag == 5)
  166
+    {
  167
+        // ThickCircle
  168
+        var r = p.popFloat();
  169
+        var w = p.popFloat();
  170
+        ctx.save();
  171
+        ctx.transform(a, b, c, d, e, f);
  172
+        if (w > 0) ctx.lineWidth = w;
  173
+        ctx.beginPath();
  174
+        ctx.arc(0, 0, r, 0, 2 * Math.PI);
  175
+        if (w > 0)
78 176
         {
79  
-            pic.t = 'c';
80  
-            display(ctx, pic, a, b, c, d, e, f);
  177
+            ctx.stroke();
  178
+            ctx.restore();
81 179
         }
82 180
         else
83 181
         {
84  
-            ctx.save();
85  
-            ctx.transform(a, b, c, d, e, f);
86  
-            ctx.lineWidth = pic.w;
87  
-            ctx.beginPath();
88  
-            ctx.arc(0, 0, pic.r, 0, 2 * Math.PI);
89  
-            ctx.stroke();
90 182
             ctx.restore();
  183
+            ctx.stroke();
91 184
         }
92 185
     }
93  
-    else if (pic.t == 't')
  186
+    else if (tag == 6)
94 187
     {
  188
+        // Text
  189
+        var len = p.popWord32();
  190
+        var txt = p.popUtf8(len);
  191
+
95 192
         ctx.save();
96 193
         ctx.transform(a, b, c, d, e, f);
97 194
         ctx.scale(1,-1);
98  
-        ctx.fillText(pic.c, 0, 0);
  195
+        ctx.fillText(txt, 0, 0);
99 196
         ctx.restore();
100 197
     }
101  
-    else if (pic.t == 'z')
  198
+    else if (tag == 7)
102 199
     {
103  
-        var str = "rgba("
104  
-            + pic.r + "," + pic.g + "," + pic.b + "," + pic.a + ")";
  200
+        // Bitmap
  201
+        var len = p.popWord32();
  202
+        var dat = "data:image/png;base64," + atob(p.popBytes(len));
  203
+        ctx.save();
  204
+        ctx.transform(a, b, c, d, e, f);
  205
+        var img = new Image(dat);
  206
+        ctx.drawImage(img,
  207
+            -img.naturalWidth / 2, -img.naturalHeight / 2,
  208
+            img.naturalWidth, img.naturalHeight);
  209
+        ctx.restore();
  210
+    }
  211
+    else if (tag == 8)
  212
+    {
  213
+        // Color
  214
+        var cr = p.popByte();
  215
+        var cg = p.popByte();
  216
+        var cb = p.popByte();
  217
+        var ca = p.popByte();
  218
+        var str = "rgba(" + cr + "," + cg + "," + cb + "," + ca + ")";
105 219
         ctx.save();
106 220
         ctx.strokeStyle = str;
107 221
         ctx.fillStyle = str;
108  
-        display(ctx, pic.p, a, b, c, d, e, f);
  222
+        display(ctx, p, a, b, c, d, e, f);
109 223
         ctx.restore();
110 224
     }
111  
-    else if (pic.t == 'x')
  225
+    else if (tag == 9)
112 226
     {
113  
-        display(ctx, pic.p,
  227
+        // Translate
  228
+        var x = p.popFloat();
  229
+        var y = p.popFloat();
  230
+        display(ctx, p,
114 231
             a, b, c, d,
115  
-            a * pic.x + c * pic.y + e,
116  
-            b * pic.x + d * pic.y + f);
  232
+            a * x + c * y + e,
  233
+            b * x + d * y + f);
117 234
     }
118  
-    else if (pic.t == 'r')
  235
+    else if (tag == 10)
119 236
     {
120  
-        var th = Math.PI * pic.r / 180;
121  
-        display(ctx, pic.p,
  237
+        // Rotate
  238
+        var r = p.popFloat();
  239
+        var th = Math.PI * r / 180;
  240
+        display(ctx, p,
122 241
              a * Math.cos(th) + c * Math.sin(th),
123 242
              b * Math.cos(th) + d * Math.sin(th),
124 243
             -a * Math.sin(th) + c * Math.cos(th),
125 244
             -b * Math.sin(th) + d * Math.cos(th),
126 245
             e, f);
127 246
     }
128  
-    else if (pic.t == 's')
  247
+    else if (tag == 11)
129 248
     {
130  
-        display(ctx, pic.p,
131  
-            pic.x * a, pic.x * b,
132  
-            pic.y * c, pic.y * d,
  249
+        // Scale
  250
+        var x = p.popFloat();
  251
+        var y = p.popFloat();
  252
+        display(ctx, p,
  253
+            x * a, x * b,
  254
+            y * c, y * d,
133 255
             e, f);
134 256
     }
135  
-    else if (pic.t == 'b')
  257
+    else if (tag == 12)
136 258
     {
137  
-        ctx.save();
138  
-        ctx.transform(a, b, c, d, e, f);
139  
-        var img = new Image(pic.c);
140  
-        ctx.drawImage(img,
141  
-            -img.naturalWidth / 2, -pic.naturalHeight / 2,
142  
-            pic.naturalWidth, pic.naturalHeight);
143  
-        ctx.restore();
  259
+        while (p.peekByte() != 0)
  260
+        {
  261
+            display(ctx, p, a, b, c, d, e, f);
  262
+        }
  263
+
  264
+        p.popByte(); // Discard trailing zero
144 265
     }
145 266
 }
146 267
 

0 notes on commit 1b3d6d9

Please sign in to comment.
Something went wrong with that request. Please try again.