diff --git a/.gitignore b/.gitignore
index 95de9fb8e..9e2d0f211 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+*~
 pdf.pdf
 intelisa.pdf
 openweb_tm-PRINT.pdf
+local.mk
diff --git a/Makefile b/Makefile
index c6971d025..9b2817fc4 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,9 @@ BUILD_DIR := build
 DEFAULT_BROWSERS := test/resources/browser_manifests/browser_manifest.json
 DEFAULT_TESTS := test/test_manifest.json
 
+# Let folks define custom rules for their clones.
+-include local.mk
+
 # JS files needed for pdf.js.
 # This list doesn't account for the 'worker' directory.
 PDF_JS_FILES = \
@@ -156,5 +159,5 @@ clean:
 help:
 	@echo "Read the comments in the Makefile for guidance.";
 
-.PHONY: all test browser-test font-test shell-test \
+.PHONY:: all test browser-test font-test shell-test \
 	shell-msg lint clean web compiler help server
diff --git a/fonts.js b/fonts.js
index 9b3b01dc0..19ea747b4 100755
--- a/fonts.js
+++ b/fonts.js
@@ -2,7 +2,6 @@
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
 'use strict';
-
 var isWorker = (typeof window == 'undefined');
 
 /**
@@ -15,20 +14,6 @@ var kMaxFontFileSize = 40000;
  */
 var kMaxWaitForFontFace = 1000;
 
-/**
- * Useful for debugging when you want to certains operations depending on how
- * many fonts are loaded.
- */
-var fontCount = 0;
-var fontName = '';
-
-/**
- * If for some reason one want to debug without fonts activated, it just need
- * to turn this pref to true/false.
- */
-var kDisableFonts = false;
-
-
 /**
  * Hold a map of decoded fonts and of the standard fourteen Type1 fonts and
  * their acronyms.
@@ -37,91 +22,33 @@ var kDisableFonts = false;
  */
 
 var Fonts = (function Fonts() {
-  var kScalePrecision = 40;
-  var fonts = Object.create(null);
-
-  if (!isWorker) {
-    var ctx = document.createElement('canvas').getContext('2d');
-    ctx.scale(1 / kScalePrecision, 1);
-  }
-
-  function Font(name, data, properties) {
+  var fonts = [];
+  var fontCount = 0;
+  
+  function FontInfo(name, data, properties) {
     this.name = name;
     this.data = data;
     this.properties = properties;
+    this.id = fontCount++;
     this.loading = true;
-    this.charsCache = Object.create(null);
     this.sizes = [];
   }
 
   var current;
-  var charsCache;
-  var measureCache;
 
   return {
     registerFont: function fonts_registerFont(fontName, data, properties) {
-      fonts[fontName] = new Font(fontName, data, properties);
+      var font = new FontInfo(fontName, data, properties);
+      fonts.push(font);
+      return font.id;
     },
     blacklistFont: function fonts_blacklistFont(fontName) {
-      registerFont(fontName, null, {});
+      var id = registerFont(fontName, null, {});
       markLoaded(fontName);
+      return id;
     },
-    lookup: function fonts_lookup(fontName) {
-      return fonts[fontName];
-    },
-    setActive: function fonts_setActive(fontName, size) {
-      // |current| can be null is fontName is a built-in font
-      // (e.g. "sans-serif")
-      if ((current = fonts[fontName])) {
-        charsCache = current.charsCache;
-        var sizes = current.sizes;
-        if (!(measureCache = sizes[size]))
-          measureCache = sizes[size] = Object.create(null);
-      }
-      ctx.font = (size * kScalePrecision) + 'px "' + fontName + '"';
-    },
-    charsToUnicode: function fonts_chars2Unicode(chars) {
-      if (!charsCache)
-        return chars;
-
-      // if we translated this string before, just grab it from the cache
-      var str = charsCache[chars];
-      if (str)
-        return str;
-
-      // translate the string using the font's encoding
-      var encoding = current ? current.properties.encoding : null;
-      if (!encoding)
-        return chars;
-
-      str = '';
-      for (var i = 0; i < chars.length; ++i) {
-        var charcode = chars.charCodeAt(i);
-        var unicode = encoding[charcode];
-
-        // Check if the glyph has already been converted
-        if (!IsNum(unicode))
-          unicode = encoding[unicode] = GlyphsUnicode[unicode.name];
-
-        // Handle surrogate pairs
-        if (unicode > 0xFFFF) {
-          str += String.fromCharCode(unicode & 0xFFFF);
-          unicode >>= 16;
-        }
-        str += String.fromCharCode(unicode);
-      }
-
-      // Enter the translated string into the cache
-      return charsCache[chars] = str;
-    },
-    measureText: function fonts_measureText(text) {
-      var width;
-      if (measureCache && (width = measureCache[text]))
-        return width;
-      width = ctx.measureText(text).width / kScalePrecision;
-      if (measureCache)
-        measureCache[text] = width;
-      return width;
+    lookupById: function fonts_lookupById(id) {
+      return fonts[id];
     }
   };
 })();
@@ -131,9 +58,9 @@ var FontLoader = {
 
   bind: function(fonts, callback) {
     function checkFontsLoaded() {
-      for (var i = 0; i < fonts.length; i++) {
-        var font = fonts[i];
-        if (Fonts.lookup(font.name).loading) {
+      for (var i = 0; i < allIds.length; i++) {
+        var id = allIds[i];
+        if (Fonts.lookupById(id).loading) {
           return false;
         }
       }
@@ -145,30 +72,35 @@ var FontLoader = {
       return true;
     }
 
-    var rules = [], names = [];
+    var allIds = [];
+    var rules = [], names = [], ids = [];
+
     for (var i = 0; i < fonts.length; i++) {
       var font = fonts[i];
-      if (!Fonts.lookup(font.name)) {
-        var obj = new Font(font.name, font.file, font.properties);
-
-        var str = '';
-        var data = Fonts.lookup(font.name).data;
-        var length = data.length;
-        for (var j = 0; j < length; j++)
-          str += String.fromCharCode(data[j]);
-
-        var rule = isWorker ? obj.bindWorker(str) : obj.bindDOM(str);
-        if (rule) {
-          rules.push(rule);
-          names.push(font.name);
-        }
+
+      var obj = new Font(font.name, font.file, font.properties);
+      font.fontDict.fontObj = obj;
+      allIds.push(obj.id);
+
+      var str = '';
+      var data = Fonts.lookupById(obj.id).data;
+      var length = data.length;
+      for (var j = 0; j < length; j++)
+        str += String.fromCharCode(data[j]);
+
+      var rule = isWorker ? obj.bindWorker(str) : obj.bindDOM(str);
+      if (rule) {
+        rules.push(rule);
+        names.push(obj.loadedName);
+        ids.push(obj.id);
       }
     }
 
+    this.listeningForFontLoad = false;
     if (!isWorker && rules.length) {
-      FontLoader.prepareFontLoadEvent(rules, names);
+      FontLoader.prepareFontLoadEvent(rules, names, ids);
     }
-
+    
     if (!checkFontsLoaded()) {
       document.documentElement.addEventListener(
         'pdfjsFontLoad', checkFontsLoaded, false);
@@ -181,7 +113,7 @@ var FontLoader = {
   // loaded in a subdocument.  It's expected that the load of |rules|
   // has already started in this (outer) document, so that they should
   // be ordered before the load in the subdocument.
-  prepareFontLoadEvent: function(rules, names) {
+  prepareFontLoadEvent: function(rules, names, ids) {
       /** Hack begin */
       // There's no event when a font has finished downloading so the
       // following code is a dirty hack to 'guess' when a font is
@@ -217,13 +149,13 @@ var FontLoader = {
       div.innerHTML = html;
       document.body.appendChild(div);
 
-      if (!this.listeneningForFontLoad) {
+      if (!this.listeningForFontLoad) {
         window.addEventListener(
           'message',
           function(e) {
             var fontNames = JSON.parse(e.data);
             for (var i = 0; i < fontNames.length; ++i) {
-              var font = Fonts.lookup(fontNames[i]);
+              var font = Fonts.lookupById(fontNames[i].substring(7) | 0);
               font.loading = false;
             }
             var evt = document.createEvent('Events');
@@ -231,7 +163,7 @@ var FontLoader = {
             document.documentElement.dispatchEvent(evt);
           },
           false);
-        this.listeneningForFontLoad = true;
+        this.listeningForFontLoad = true;
       }
 
       // XXX we should have a time-out here too, and maybe fire
@@ -253,7 +185,7 @@ var FontLoader = {
       src += '  }';
       src += '</script></head><body>';
       for (var i = 0; i < names.length; ++i) {
-        src += '<p style="font-family:\'' + fontName + '\'">Hi</p>';
+        src += '<p style="font-family:\'' + names[i] + '\'">Hi</p>';
       }
       src += '</body></html>';
       var frame = document.createElement('iframe');
@@ -413,20 +345,14 @@ function getUnicodeRangeFor(value) {
 var Font = (function() {
   var constructor = function font_constructor(name, file, properties) {
     this.name = name;
+    this.textMatrix = properties.textMatrix || IDENTITY_MATRIX;
     this.encoding = properties.encoding;
 
-    // If the font has already been decoded simply return it
-    if (Fonts.lookup(name)) {
-      this.font = Fonts.lookup(name).data;
-      return;
-    }
-    fontCount++;
-    fontName = name;
-
     // If the font is to be ignored, register it like an already loaded font
     // to avoid the cost of waiting for it be be loaded by the platform.
-    if (properties.ignore || kDisableFonts) {
-      Fonts.blacklistFont(name);
+    if (properties.ignore) {
+      this.id = Fonts.blacklistFont(name);
+      this.loadedName = 'pdfFont' + this.id;
       return;
     }
 
@@ -453,7 +379,9 @@ var Font = (function() {
         break;
     }
     this.data = data;
-    Fonts.registerFont(name, data, properties);
+
+    this.id = Fonts.registerFont(name, data, properties);
+    this.loadedName = 'pdfFont' + this.id;
   };
 
   function stringToArray(str) {
@@ -1128,14 +1056,14 @@ var Font = (function() {
         action: 'font',
         data: {
           raw: data,
-          fontName: this.name,
+          fontName: this.loadedName,
           mimetype: this.mimetype
         }
       });
     },
 
     bindDOM: function font_bindDom(data) {
-      var fontName = this.name;
+      var fontName = this.loadedName;
 
       // Add the font-face rule to the document
       var url = ('url(data:' + this.mimetype + ';base64,' +
@@ -1145,6 +1073,46 @@ var Font = (function() {
       styleSheet.insertRule(rule, styleSheet.cssRules.length);
 
       return rule;
+    },
+
+    charsToUnicode: function fonts_charsToUnicode(chars) {
+      var charsCache = this.charsCache;
+
+      // if we translated this string before, just grab it from the cache
+      if (charsCache) {
+        var str = charsCache[chars];
+        if (str)
+          return str;
+      }
+
+      // translate the string using the font's encoding
+      var encoding = this.encoding;
+      if (!encoding)
+        return chars;
+
+      // lazily create the translation cache
+      if (!charsCache)
+        charsCache = this.charsCache = Object.create(null);
+
+      str = '';
+      for (var i = 0; i < chars.length; ++i) {
+        var charcode = chars.charCodeAt(i);
+        var unicode = encoding[charcode];
+
+        // Check if the glyph has already been converted
+        if (!IsNum(unicode))
+          unicode = encoding[unicode] = GlyphsUnicode[unicode.name];
+
+        // Handle surrogate pairs
+        if (unicode > 0xFFFF) {
+          str += String.fromCharCode(unicode & 0xFFFF);
+          unicode >>= 16;
+        }
+        str += String.fromCharCode(unicode);
+      }
+
+      // Enter the translated string into the cache
+      return charsCache[chars] = str;
     }
   };
 
@@ -1676,7 +1644,7 @@ CFF.prototype = {
       var unicode = GlyphsUnicode[glyph.glyph];
       if (!unicode) {
         if (glyph.glyph != '.notdef') {
-          warn(glyph +
+          warn(glyph.glyph +
                ' does not have an entry in the glyphs unicode dictionary');
         }
       } else {
diff --git a/pdf.js b/pdf.js
index 209027537..674341f7d 100644
--- a/pdf.js
+++ b/pdf.js
@@ -904,6 +904,74 @@ var Ascii85Stream = (function() {
   return constructor;
 })();
 
+var AsciiHexStream = (function() {
+  function constructor(str) {
+    this.str = str;
+    this.dict = str.dict;
+    
+    DecodeStream.call(this);
+  }
+  
+  var hexvalueMap = {
+      9: -1, // \t
+      32: -1, // space
+      48: 0,
+      49: 1,
+      50: 2,
+      51: 3,
+      52: 4,
+      53: 5,
+      54: 6,
+      55: 7,
+      56: 8,
+      57: 9,
+      65: 10,
+      66: 11,
+      67: 12,
+      68: 13,
+      69: 14,
+      70: 15,
+      97: 10,
+      98: 11,
+      99: 12,
+      100: 13,
+      101: 14,
+      102: 15
+  };
+
+  constructor.prototype = Object.create(DecodeStream.prototype);
+  
+  constructor.prototype.readBlock = function() {
+    var gtCode = '>'.charCodeAt(0), bytes = this.str.getBytes(), c, n, 
+        decodeLength, buffer, bufferLength, i, length;
+    
+    decodeLength = (bytes.length + 1) >> 1;
+    buffer = this.ensureBuffer(this.bufferLength + decodeLength);
+    bufferLength = this.bufferLength;
+    
+    for(i = 0, length = bytes.length; i < length; i++) {
+      c = hexvalueMap[bytes[i]];
+      while (c == -1 && (i+1) < length) {
+        c = hexvalueMap[bytes[++i]];
+      }
+      
+      if((i+1) < length && (bytes[i+1] !== gtCode)) {
+        n = hexvalueMap[bytes[++i]];
+        buffer[bufferLength++] = c*16+n;
+      } else {
+        if(bytes[i] !== gtCode) { // EOD marker at an odd number, behave as if a 0 followed the last digit.
+          buffer[bufferLength++] = c*16;
+        }
+      }
+    }
+    
+    this.bufferLength = bufferLength;
+    this.eof = true;
+  };
+  
+  return constructor;
+})();
+
 var CCITTFaxStream = (function() {
 
   var ccittEOL = -2;
@@ -1613,7 +1681,7 @@ var CCITTFaxStream = (function() {
       }
 
       if (this.byteAlign)
-        inputBits &= ~7;
+        this.inputBits &= ~7;
 
       var gotEOL = false;
 
@@ -1921,28 +1989,33 @@ var Dict = (function() {
   }
 
   constructor.prototype = {
-    get: function(key) {
-      if (key in this.map)
-        return this.map[key];
-      return null;
-    },
-    get2: function(key1, key2) {
-      return this.get(key1) || this.get(key2);
+    get: function(key1, key2, key3) {
+      var value;
+      if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map || typeof key2 == 'undefined') {
+        return value;
+      }
+      if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map || typeof key3 == 'undefined') {
+        return value;
+      }
+
+      return this.map[key3] || null;
     },
-    get3: function(key1, key2, key3) {
-      return this.get(key1) || this.get(key2) || this.get(key3);
+
+    set: function(key, value) {
+      this.map[key] = value;
     },
+
     has: function(key) {
       return key in this.map;
     },
-    set: function(key, value) {
-      this.map[key] = value;
-    },
-    forEach: function(aCallback) {
-      for (var key in this.map)
-        aCallback(key, this.map[key]);
+
+    forEach: function(callback) {
+      for (var key in this.map) {
+        callback(key, this.map[key]);
+      }
     }
   };
+
   return constructor;
 })();
 
@@ -2459,8 +2532,8 @@ var Parser = (function() {
       return stream;
     },
     filter: function(stream, dict, length) {
-      var filter = dict.get2('Filter', 'F');
-      var params = dict.get2('DecodeParms', 'DP');
+      var filter = dict.get('Filter', 'F');
+      var params = dict.get('DecodeParms', 'DP');
       if (IsName(filter))
         return this.makeFilter(stream, filter.name, length, params);
       if (IsArray(filter)) {
@@ -2491,6 +2564,8 @@ var Parser = (function() {
         return new JpegStream(bytes, stream.dict);
       } else if (name == 'ASCII85Decode') {
         return new Ascii85Stream(stream);
+      } else if (name == 'ASCIIHexDecode') {
+        return new AsciiHexStream(stream);
       } else if (name == 'CCITTFaxDecode') {
         TODO('implement fax stream');
         return new CCITTFaxStream(stream, params);
@@ -3472,7 +3547,7 @@ var CanvasGraphics = (function() {
       assertWellFormed(IsName(fontName), 'invalid font name');
       fontName = fontName.name.replace('+', '_');
 
-      var fontFile = descriptor.get3('FontFile', 'FontFile2', 'FontFile3');
+      var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3');
       if (!fontFile)
         error('FontFile not found for font: ' + fontName);
       fontFile = xref.fetchIfRef(fontFile);
@@ -3482,7 +3557,19 @@ var CanvasGraphics = (function() {
       if (fontDict.has('Encoding')) {
         var encoding = xref.fetchIfRef(fontDict.get('Encoding'));
         if (IsDict(encoding)) {
-          // Build a map between codes and glyphs
+          // Build a map of between codes and glyphs
+          // Load the base encoding
+          var baseName = encoding.get('BaseEncoding');
+          if (baseName) {
+            var base = Encodings[baseName.name];
+            var index = 0;
+            for (var j = 0, end = base.length; j < end; j++)
+              encodingMap[index++] = GlyphsUnicode[base[j]];
+          } else {
+            TODO('need to load default encoding');
+          }
+
+          // Load the differences between the base and original
           var differences = encoding.get('Differences');
           var index = 0;
           for (var j = 0; j < differences.length; j++) {
@@ -3605,9 +3692,10 @@ var CanvasGraphics = (function() {
 
       return {
         name: fontName,
-          file: fontFile,
-          properties: properties
-          };
+        fontDict: fontDict, 
+        file: fontFile,
+        properties: properties
+      };
     },
 
     beginDrawing: function(mediaBox) {
@@ -3868,25 +3956,22 @@ var CanvasGraphics = (function() {
         return;
 
       var fontName = '';
-      var fontDescriptor = font.get('FontDescriptor');
-      if (fontDescriptor && fontDescriptor.num) {
-        var fontDescriptor = this.xref.fetchIfRef(fontDescriptor);
-        fontName = fontDescriptor.get('FontName').name.replace('+', '_');
-      }
+      var fontObj = font.fontObj;
+      if (fontObj)
+        fontName = fontObj.loadedName;
 
       if (!fontName) {
         // TODO: fontDescriptor is not available, fallback to default font
         fontName = 'sans-serif';
       }
 
-      this.current.fontName = fontName;
+      this.current.font = fontObj;
       this.current.fontSize = size;
 
       if (this.ctx.$setFont) {
         this.ctx.$setFont(fontName, size);
       } else {
         this.ctx.font = size + 'px "' + fontName + '"';
-        Fonts.setActive(fontName, size);
       }
     },
     setTextRenderingMode: function(mode) {
@@ -3921,25 +4006,28 @@ var CanvasGraphics = (function() {
     showText: function(text) {
       // TODO: apply charSpacing, wordSpacing, textHScale
 
-      this.ctx.save();
-      this.ctx.transform.apply(this.ctx, this.current.textMatrix);
-      this.ctx.scale(1, -1);
+      var ctx = this.ctx;
+      var current = this.current;
 
       if (this.ctx.$showText) {
-        this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text));
+        ctx.$showText(current.y, text);
       } else {
-        text = Fonts.charsToUnicode(text);
-        this.ctx.translate(this.current.x, -1 * this.current.y);
+        ctx.save();
 
-        var font = Fonts.lookup(this.current.fontName);
-        if (font && font.properties.textMatrix)
-          this.ctx.transform.apply(this.ctx, font.properties.textMatrix);
+        ctx.transform.apply(ctx, current.textMatrix);
+        ctx.scale(1, -1);
+        ctx.translate(current.x, -current.y);
 
-        this.ctx.fillText(text, 0, 0);
-        this.current.x += Fonts.measureText(text);
-      }
+        var font = current.font;
+        ctx.transform.apply(ctx, font.textMatrix);
 
-      this.ctx.restore();
+        text = font.charsToUnicode(text);
+
+        ctx.fillText(text, 0, 0);
+        current.x += ctx.measureText(text).width;
+
+        ctx.restore();
+      }
     },
     showSpacedText: function(arr) {
       for (var i = 0; i < arr.length; ++i) {
@@ -4003,14 +4091,28 @@ var CanvasGraphics = (function() {
       var cs = this.getFillColorSpace();
 
       if (cs.name == 'Pattern') {
-        var patternName = arguments[0];
-        this.setFillPattern(patternName);
+        var length = arguments.length;
+        var base = cs.base;
+        if (base) {
+          var baseComps = base.numComps;
+
+          if (baseComps != length - 1)
+            error("invalid base color for pattern colorspace");
+
+          var color = [];
+          for (var i = 0; i < baseComps; ++i)
+            color.push(arguments[i]);
+
+          color = base.getRgb(color);
+        }
+        var patternName = arguments[length - 1];
+        this.setFillPattern(patternName, base, color);
       } else {
         // TODO real impl
         this.setFillColor.apply(this, arguments);
       }
     },
-    setFillPattern: function(patternName) {
+    setFillPattern: function(patternName, baseCS, color) {
       if (!IsName(patternName))
         error("Bad args to getPattern");
 
@@ -4028,7 +4130,7 @@ var CanvasGraphics = (function() {
       var patternFn = types[typeNum];
       if (!patternFn)
         error("Unhandled pattern type");
-      patternFn.call(this, pattern, dict);
+      patternFn.call(this, pattern, dict, baseCS, color);
     },
     setShadingPattern: function(pattern, dict) {
       var matrix = dict.get("Matrix");
@@ -4051,7 +4153,7 @@ var CanvasGraphics = (function() {
       this.ctx.fillRect(0,0,0,0);
       this.transform.apply(this, inv);
     },
-    setTilingPattern: function(pattern, dict) {
+    setTilingPattern: function(pattern, dict, baseCS, color) {
       function multiply(m, tm) {
         var a = m[0] * tm[0] + m[1] * tm[2];
         var b = m[0] * tm[1] + m[1] * tm[3];
@@ -4065,17 +4167,6 @@ var CanvasGraphics = (function() {
       this.save();
       var ctx = this.ctx;
 
-      var paintType = dict.get('PaintType');
-      switch (paintType) {
-      case PAINT_TYPE_COLORED:
-        // should go to default for color space
-        ctx.fillStyle = this.makeCssRgb(1, 1, 1);
-        ctx.strokeStyle = this.makeCssRgb(0, 0, 0);
-        break;
-      case PAINT_TYPE_UNCOLORED:
-      default:
-        error('Unsupported paint type');
-      }
 
       TODO('TilingType');
 
@@ -4106,6 +4197,21 @@ var CanvasGraphics = (function() {
       var savedCtx = ctx;
       this.ctx = tmpCtx;
 
+      var paintType = dict.get('PaintType');
+      switch (paintType) {
+      case PAINT_TYPE_COLORED:
+        // should go to default for color space
+        tmpCtx.fillStyle = this.makeCssRgb(1, 1, 1);
+        tmpCtx.strokeStyle = this.makeCssRgb(0, 0, 0);
+        break;
+      case PAINT_TYPE_UNCOLORED:
+        tmpCtx.fillStyle = this.makeCssRgb.apply(this, baseCS.getRgb(color));
+        tmpCtx.strokeStyle = this.makeCssRgb.apply(this, baseCS.getRgb(color));
+        break;
+      default:
+        error('Unsupported paint type');
+      }
+
       // normalize transform matrix so each step
       // takes up the entire tmpCanvas (need to remove white borders)
       if (matrix[1] === 0 && matrix[2] === 0) {
@@ -4219,7 +4325,7 @@ var CanvasGraphics = (function() {
       if (background)
         TODO('handle background colors');
 
-      var cs = shading.get2('ColorSpace', 'CS');
+      var cs = shading.get('ColorSpace', 'CS');
       cs = ColorSpace.parse(cs, this.xref, this.res);
 
       var types = [null,
@@ -4229,8 +4335,12 @@ var CanvasGraphics = (function() {
 
       var typeNum = shading.get('ShadingType');
       var shadingFn = types[typeNum];
+
+      // Most likely we will not implement other types of shading
+      // unless the browser supports them
       if (!shadingFn)
-        error("Unknown or NYI type of shading '"+ typeNum +"'");
+        TODO("Unknown or NYI type of shading '"+ typeNum +"'");
+
       return shadingFn.call(this, shading, cs);
                 },
     getAxialShading: function(sh, cs) {
@@ -4383,8 +4493,8 @@ var CanvasGraphics = (function() {
 
       var ctx = this.ctx;
       var dict = image.dict;
-      var w = dict.get2('Width', 'W');
-      var h = dict.get2('Height', 'H');
+      var w = dict.get('Width', 'W');
+      var h = dict.get('Height', 'H');
       // scale the image to the unit square
       ctx.scale(1 / w, -1 / h);
 
@@ -4520,10 +4630,24 @@ var CanvasGraphics = (function() {
 })();
 
 var ColorSpace = (function() {
+  // Constructor should define this.numComps, this.defaultColor, this.name
   function constructor() {
     error('should not call ColorSpace constructor');
   };
 
+  constructor.prototype = {
+    // Input: array of size numComps representing color component values
+    // Output: array of rgb values, each value ranging from [0.1]
+    getRgb: function cs_getRgb(color) {
+      error('Should not call ColorSpace.getRgb');
+    },
+    // Input: Uint8Array of component values, each value scaled to [0,255]
+    // Output: Uint8Array of rgb values, each value scaled to [0,255]
+    getRgbBuffer: function cs_getRgbBuffer(input) {
+      error('Should not call ColorSpace.getRgbBuffer');
+    }
+  };
+
   constructor.parse = function colorspace_parse(cs, xref, res) {
     if (IsName(cs)) {
       var colorSpaces = res.get('ColorSpace');
@@ -4594,15 +4718,24 @@ var ColorSpace = (function() {
           return new DeviceCmykCS();
         break;
       case 'Pattern':
-        return new PatternCS();
+        var baseCS = cs[1];
+        if (baseCS)
+          baseCS = ColorSpace.parse(baseCS, xref, res);
+        return new PatternCS(baseCS);
         break;
       case 'Indexed':
         var base = ColorSpace.parse(cs[1], xref, res);
         var hiVal = cs[2] + 1;
         var lookup = xref.fetchIfRef(cs[3]);
         return new IndexedCS(base, hiVal, lookup);
+        break;
+      case 'Separation':
+        var name = cs[1];
+        var alt = ColorSpace.parse(cs[2], xref, res);
+        var tintFn = new PDFFunction(xref, xref.fetchIfRef(cs[3]));
+        return new SeparationCS(alt, tintFn);
+        break;
       case 'Lab':
-      case 'Seperation':
       case 'DeviceN':
       default:
         error("unrecognized color space object '" + mode + "'");
@@ -4615,9 +4748,48 @@ var ColorSpace = (function() {
   return constructor;
 })();
 
+var SeparationCS = (function() {
+  function constructor(base, tintFn) {
+    this.name = "Separation";
+    this.numComps = 1;
+    this.defaultColor = [1];
+
+    this.base = base;
+    this.tintFn = tintFn;
+  }
+
+  constructor.prototype = {
+    getRgb: function sepcs_getRgb(color) {
+      var tinted = this.tintFn.func(color);
+      return this.base.getRgb(tinted);
+    },
+    getRgbBuffer: function sepcs_getRgbBuffer(input) {
+      var tintFn = this.tintFn;
+      var base = this.base;
+
+      var length = 3 * input.length;
+      var pos = 0;
+
+      var numComps = base.numComps;
+      var baseBuf = new Uint8Array(numComps * input.length);
+      for (var i = 0, ii = input.length; i < ii; ++i) {
+        var scaled = input[i] / 255;
+        var tinted = tintFn.func([scaled]);
+        for (var j = 0; j < numComps; ++j)
+          baseBuf[pos++] = 255 * tinted[j];
+      }
+      return base.getRgbBuffer(baseBuf);
+
+    }
+  };
+
+  return constructor;
+})();
+
 var PatternCS = (function() {
-  function constructor() {
+  function constructor(baseCS) {
     this.name = 'Pattern';
+    this.base = baseCS;
   }
   constructor.prototype = {};
 
@@ -4649,17 +4821,18 @@ var IndexedCS = (function() {
   }
 
   constructor.prototype = {
-    getRgb: function graycs_getRgb(color) {
-      var lookup = this.lookup;
-      var base = this.base;
+    getRgb: function indexcs_getRgb(color) {
       var numComps = base.numComps;
 
+      var start = color[0] * numComps;
       var c = [];
-      for (var i = 0; i < numComps; ++i)
-        c.push(lookup[i]);
+
+      for (var i = start, ii = start + numComps; i < ii; ++i)
+        c.push(this.lookup[i]);
+
       return this.base.getRgb(c);
     },
-    getRgbBuffer: function graycs_getRgbBuffer(input) {
+    getRgbBuffer: function indexcs_getRgbBuffer(input) {
       var base = this.base;
       var numComps = base.numComps;
       var lookup = this.lookup;
@@ -4668,7 +4841,7 @@ var IndexedCS = (function() {
       var baseBuf = new Uint8Array(length * numComps);
       var baseBufPos = 0;
       for (var i = 0; i < length; ++i) {
-        var lookupPos = input[i];
+        var lookupPos = input[i] * numComps;
         for (var j = 0; j < numComps; ++j) {
           baseBuf[baseBufPos++] = lookup[lookupPos + j];
         }
@@ -4693,7 +4866,7 @@ var DeviceGrayCS = (function() {
       return [c, c, c];
     },
     getRgbBuffer: function graycs_getRgbBuffer(input) {
-      var length = input.length;
+      var length = input.length * 3;
       var rgbBuf = new Uint8Array(length);
       for (var i = 0, j = 0; i < length; ++i) {
         var c = input[i];
@@ -4786,8 +4959,22 @@ var DeviceCmykCS = (function() {
       return [r, g, b];
     },
     getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf) {
-      error('conversion from rgb to cmyk not implemented for images');
-      return colorBuf;
+      var length = colorBuf.length / 4;
+      var rgbBuf = new Uint8Array(length * 3);
+      var rgbBufPos = 0;
+      var colorBufPos = 0;
+
+      for (var i = 0; i < length; i++) {
+        var cmyk = [];
+        for (var j = 0; j < 4; ++j)
+          cmyk.push(colorBuf[colorBufPos++]/255);
+
+        var rgb = this.getRgb(cmyk);
+        for (var j = 0; j < 3; ++j)
+          rgbBuf[rgbBufPos++] = Math.round(rgb[j] * 255);
+      }
+
+      return rgbBuf;
     }
   };
   return constructor;
@@ -4806,18 +4993,18 @@ var PDFImage = (function() {
     // TODO cache rendered images?
 
     var dict = image.dict;
-    this.width = dict.get2('Width', 'W');
-    this.height = dict.get2('Height', 'H');
+    this.width = dict.get('Width', 'W');
+    this.height = dict.get('Height', 'H');
 
     if (this.width < 1 || this.height < 1)
       error('Invalid image width or height');
 
-    this.interpolate = dict.get2('Interpolate', 'I') || false;
-    this.imageMask = dict.get2('ImageMask', 'IM') || false;
+    this.interpolate = dict.get('Interpolate', 'I') || false;
+    this.imageMask = dict.get('ImageMask', 'IM') || false;
 
     var bitsPerComponent = image.bitsPerComponent;
     if (!bitsPerComponent) {
-      bitsPerComponent = dict.get2('BitsPerComponent', 'BPC');
+      bitsPerComponent = dict.get('BitsPerComponent', 'BPC');
       if (!bitsPerComponent) {
         if (this.imageMask)
           bitsPerComponent = 1;
@@ -4827,11 +5014,11 @@ var PDFImage = (function() {
     }
     this.bpc = bitsPerComponent;
 
-    var colorSpace = dict.get2('ColorSpace', 'CS');
+    var colorSpace = dict.get('ColorSpace', 'CS');
     this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
 
     this.numComps = this.colorSpace.numComps;
-    this.decode = dict.get2('Decode', 'D');
+    this.decode = dict.get('Decode', 'D');
 
     var mask = xref.fetchIfRef(image.dict.get('Mask'));
     var smask = xref.fetchIfRef(image.dict.get('SMask'));
@@ -4904,7 +5091,7 @@ var PDFImage = (function() {
           output[i] = Math.round(255 * ret / ((1 << bpc) - 1));
         }
       }
-      return this.colorSpace.getRbaBuffer(output);
+      return output;
     },
     getOpacity: function getOpacity() {
       var smask = this.smask;
@@ -4936,32 +5123,17 @@ var PDFImage = (function() {
       var rowBytes = (width * numComps * bpc + 7) >> 3;
       var imgArray = this.image.getBytes(height * rowBytes);
 
-      var comps = this.getComponents(imgArray);
+      var comps = this.colorSpace.getRgbBuffer(this.getComponents(imgArray));
       var compsPos = 0;
       var opacity = this.getOpacity();
       var opacityPos = 0;
       var length = width * height * 4;
 
-      switch (numComps) {
-      case 1:
-        for (var i = 0; i < length; i += 4) {
-          var p = comps[compsPos++];
-          buffer[i] = p;
-          buffer[i + 1] = p;
-          buffer[i + 2] = p;
-          buffer[i + 3] = opacity[opacityPos++];
-        }
-        break;
-      case 3:
-        for (var i = 0; i < length; i += 4) {
-          buffer[i] = comps[compsPos++];
-          buffer[i + 1] = comps[compsPos++];
-          buffer[i + 2] = comps[compsPos++];
-          buffer[i + 3] = opacity[opacityPos++];
-        }
-        break;
-      default:
-        TODO('Images with ' + numComps + ' components per pixel');
+      for (var i = 0; i < length; i += 4) {
+        buffer[i] = comps[compsPos++];
+        buffer[i + 1] = comps[compsPos++];
+        buffer[i + 2] = comps[compsPos++];
+        buffer[i + 3] = opacity[opacityPos++];
       }
     },
     fillGrayBuffer: function fillGrayBuffer(buffer) {
@@ -5004,7 +5176,7 @@ var PDFFunction = (function() {
     if (!typeFn)
       error('Unknown type of function');
 
-    typeFn.apply(this, [fn, dict]);
+    typeFn.call(this, fn, dict);
   };
 
   constructor.prototype = {
diff --git a/test/pdfs/asciihexdecode.pdf b/test/pdfs/asciihexdecode.pdf
new file mode 100644
index 000000000..f3bd457ec
--- /dev/null
+++ b/test/pdfs/asciihexdecode.pdf
@@ -0,0 +1,55 @@
+%PDF-1.0
+1 0 obj
+<<
+	/Pages 2 0 R
+	/Type /Catalog
+>>
+endobj
+2 0 obj
+<<
+	/Count 1
+	/Kids [ 3 0 R ]
+	/Type /Pages
+>>
+endobj
+3 0 obj
+<<
+	/MediaBox [ 0 0 795 842 ]
+	/Parent 2 0 R
+	/Contents 4 0 R
+	/Resources <<
+		/Font <<
+			/F1 <<
+				/Name /F1
+				/BaseFont /Helvetica
+				/Subtype /Type1
+				/Type /Font
+			>>
+		>>
+	>>
+	/Type /Page
+>>
+endobj
+4 0 obj
+<<
+	/Filter /ASCIIHexDecode
+	/Length 111
+>>stream
+42540A2F46312033302054660A333530203735302054640A323020544C0A312054720A2848656C6C6F20776F726C642920546A0A45540A>
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f
+0000000010 00000 n
+0000000067 00000 n
+0000000136 00000 n
+0000000373 00000 n
+trailer
+<<
+	/Root 1 0 R
+	/Size 5
+>>
+startxref
+568
+%%EOF