From 9d588907a938418008fcefb1a5b6c10c03ca4ea2 Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Fri, 10 May 2013 22:50:14 -0500
Subject: [PATCH] Outlines type3 glyph

---
 src/canvas.js | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 179 insertions(+)

diff --git a/src/canvas.js b/src/canvas.js
index 07b95ec8d..468bcbe62 100644
--- a/src/canvas.js
+++ b/src/canvas.js
@@ -38,6 +38,8 @@ var TextRenderingMode = {
 // Minimal font size that would be used during canvas fillText operations.
 var MIN_FONT_SIZE = 16;
 
+var COMPILE_TYPE3_GLYPHS = true;
+
 function createScratchCanvas(width, height) {
   var canvas = document.createElement('canvas');
   canvas.width = width;
@@ -164,6 +166,157 @@ function addContextCurrentTransform(ctx) {
   }
 }
 
+function compileType3Glyph(imgData) {
+  var POINT_TO_PROCESS_LIMIT = 1000;
+
+  var width = imgData.width, height = imgData.height;
+  var i, j;
+  // we need sparse arrays
+  var points = [];
+  for (i = 0; i <= height; i++) {
+    points.push([]);
+  }
+
+  // finding iteresting points: every point is located between mask pixels,
+  // so there will be points of the (width + 1)x(height + 1) grid. Every point
+  // will have flags assigned based on neighboring mask pixels:
+  //   4 | 8
+  //   --P--
+  //   2 | 1
+  // We are interested only in points with the flags:
+  //   - outside corners: 1, 2, 4, 8;
+  //   - inside corners: 7, 11, 13, 14;
+  //   - and, intersections: 5, 10.
+  var pos = 3, data = imgData.data, lineSize = width * 4, count = 0;
+  if (data[3] !== 0) {
+    points[0][0] = 1;
+    ++count;
+  }
+  for (j = 1; j < width; j++) {
+    if (data[pos] !== data[pos + 4]) {
+      points[0][j] = data[pos] ? 2 : 1;
+      ++count;
+    }
+    pos += 4;
+  }
+  if (data[pos] !== 0) {
+    points[0][j] = 2;
+    ++count;
+  }
+  pos += 4;
+  for (i = 1; i < height; i++) {
+    if (data[pos - lineSize] !== data[pos]) {
+      points[i][0] = data[pos] ? 1 : 8;
+      ++count;
+    }
+    for (j = 1; j < width; j++) {
+      var f1 = data[pos + 4] ? 1 : 0;
+      var f2 = data[pos] ? 1 : 0;
+      var f4 = data[pos - lineSize] ? 1 : 0;
+      var f8 = data[pos - lineSize + 4] ? 1 : 0;
+      var fSum = f1 + f2 + f4 + f8;
+      if (fSum === 1 || fSum === 3 || (fSum === 2 && f1 === f4)) {
+        points[i][j] = f1 | (f2 << 1) | (f4 << 2) | (f8 << 3);
+        ++count;
+      }
+      pos += 4;
+    }
+    if (data[pos - lineSize] !== data[pos]) {
+      points[i][j] = data[pos] ? 2 : 4;
+      ++count;
+    }
+    pos += 4;
+
+    if (count > POINT_TO_PROCESS_LIMIT) {
+      return null;
+    }
+  }
+  pos -= lineSize;
+  if (data[pos] !== 0) {
+    points[i][0] = 8;
+    ++count;
+  }
+  for (j = 1; j < width; j++) {
+    if (data[pos] !== data[pos + 4]) {
+      points[i][j] = data[pos] ? 4 : 8;
+      ++count;
+    }
+    pos += 4;
+  }
+  if (data[pos] !== 0) {
+    points[i][j] = 4;
+    ++count;
+  }
+  if (count > POINT_TO_PROCESS_LIMIT) {
+    return null;
+  }
+
+  // building outlines
+  var outline = [];
+  outline.push('c.save();');
+  // the path shall be painted in [0..1]x[0..1] space
+  outline.push('c.scale(' + (1 / width) + ',' +  (-1 / height) + ');');
+  outline.push('c.translate(0,-' + height + ');');
+  outline.push('c.beginPath();');
+  for (i = 0; i <= height; i++) {
+    if (points[i].length === 0) {
+      continue;
+    }
+    var js = null;
+    for (js in points[i]) {
+      break;
+    }
+    if (js === null) {
+      continue;
+    }
+    var i0 = i, j0 = (j = +js);
+
+    outline.push('c.moveTo(' + j + ',' + i + ');');
+    var type = points[i][j], d = 0;
+    do {
+      if (type === 5 || type === 10) {
+        // line crossed: following dirrection we followed
+        points[i0][j0] = type | (15 ^ d); // changing direction for "future hit"
+        type |= d;
+      }
+
+      switch (type) {
+      case 1:
+      case 13:
+        do { i0++; } while (!points[i0][j0]);
+        d = 9;
+        break;
+      case 4:
+      case 7:
+        do { i0--; } while (!points[i0][j0]);
+        d = 6;
+        break;
+      case 8:
+      case 14:
+        do { j0++; } while (!points[i0][j0]);
+        d = 12;
+        break;
+      case 2:
+      case 11:
+        do { j0--; } while (!points[i0][j0]);
+        d = 3;
+        break;
+      }
+      outline.push('c.lineTo(' + j0 + ',' + i0 + ');');
+
+      type = points[i0][j0];
+      delete points[i0][j0];
+    } while (j0 !== j || i0 !== i);
+    --i;
+  }
+  outline.push('c.fill();');
+  outline.push('c.beginPath();');
+  outline.push('c.restore();');
+
+  /*jshint -W054 */
+  return new Function('c', outline.join('\n'));
+}
+
 var CanvasExtraState = (function CanvasExtraStateClosure() {
   function CanvasExtraState(old) {
     // Are soft masks and alpha values shapes or opacities?
@@ -234,6 +387,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
     this.textLayer = textLayer;
     this.imageLayer = imageLayer;
     this.groupStack = [];
+    this.processingType3 = null;
     if (canvasCtx) {
       addContextCurrentTransform(canvasCtx);
     }
@@ -1002,6 +1156,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
             continue;
           }
 
+          this.processingType3 = glyph;
           this.save();
           ctx.scale(fontSize, fontSize);
           ctx.transform.apply(ctx, fontMatrix);
@@ -1018,6 +1173,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
           canvasWidth += width;
         }
         ctx.restore();
+        this.processingType3 = null;
       } else {
         ctx.save();
         this.applyTextTransforms();
@@ -1566,6 +1722,29 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
     paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(
                              imgArray, inverseDecode, width, height) {
       var ctx = this.ctx;
+      var glyph = this.processingType3;
+
+      if (COMPILE_TYPE3_GLYPHS && glyph && !('compiled' in glyph)) {
+        var MAX_SIZE_TO_COMPILE = 1000;
+        if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
+          var pixels = new Uint8Array(width * height * 4);
+          for (var i = 3, ii = pixels.length; i < ii; i += 4) {
+            pixels[i] = 255;
+          }
+          applyStencilMask(imgArray, width, height, inverseDecode, pixels);
+          glyph.compiled =
+            compileType3Glyph({data: pixels, width: width, height: height});
+        } else {
+          glyph.compiled = null;
+        }
+      }
+
+      if (glyph && glyph.compiled) {
+        glyph.compiled(ctx);
+        return;
+      }
+
+
       var tmpCanvas = createScratchCanvas(width, height);
       var tmpCtx = tmpCanvas.getContext('2d');