]> git.parisson.com Git - pdf.js.git/commitdiff
Selection working
authorArtur Adib <arturadib@gmail.com>
Fri, 28 Oct 2011 21:37:55 +0000 (17:37 -0400)
committerArtur Adib <arturadib@gmail.com>
Fri, 28 Oct 2011 21:37:55 +0000 (17:37 -0400)
src/canvas.js
src/core.js
web/viewer.css
web/viewer.js

index 70dd65e3df25efd9d7999d2e0b5e6b4eb6b41c5b..d771fa15e8622dda1443e390c329f12257f3dabb 100644 (file)
@@ -60,7 +60,7 @@ var CanvasGraphics = (function canvasGraphics() {
   // if we execute longer then `kExecutionTime`.
   var kExecutionTimeCheck = 500;
 
-  function constructor(canvasCtx, objs) {
+  function constructor(canvasCtx, objs, textLayer, textScale) {
     this.ctx = canvasCtx;
     this.current = new CanvasExtraState();
     this.stateStack = [];
@@ -69,6 +69,8 @@ var CanvasGraphics = (function canvasGraphics() {
     this.xobjs = null;
     this.ScratchCanvas = ScratchCanvas;
     this.objs = objs;
+    this.textLayer = textLayer;
+    this.textScale = textScale;
   }
 
   var LINE_CAP_STYLES = ['butt', 'round', 'square'];
@@ -95,6 +97,7 @@ var CanvasGraphics = (function canvasGraphics() {
           break;
       }
       this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
+      this.textDivs = [];
     },
 
     executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR,
@@ -150,6 +153,17 @@ var CanvasGraphics = (function canvasGraphics() {
 
     endDrawing: function canvasGraphicsEndDrawing() {
       this.ctx.restore();
+
+      // Text selection-specific
+      var textLayer = this.textLayer;
+      var textDivs = this.textDivs;
+      for (var i = 0, length = textDivs.length; i < length; ++i) {
+        if (textDivs[i].dataset.textLength>1) { // avoid div by zero
+          textLayer.appendChild(textDivs[i]);
+          // Adjust div width to match canvas text width
+          textDivs[i].style.letterSpacing = ((textDivs[i].dataset.canvasWidth - textDivs[i].offsetWidth)/(textDivs[i].dataset.textLength-1)) + 'px';
+        }
+      }
     },
 
     // Graphics state
@@ -414,6 +428,12 @@ var CanvasGraphics = (function canvasGraphics() {
       this.moveText(0, this.current.leading);
     },
     showText: function canvasGraphicsShowText(text) {
+      function unicodeToChar(unicode) {
+        return (unicode >= 0x10000) ?
+          String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
+          0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
+      };
+
       var ctx = this.ctx;
       var current = this.current;
       var font = current.font;
@@ -423,6 +443,8 @@ var CanvasGraphics = (function canvasGraphics() {
       var wordSpacing = current.wordSpacing;
       var textHScale = current.textHScale;
       var glyphsLength = glyphs.length;
+      var text = { chars:'', width:0 };
+      
       if (font.coded) {
         ctx.save();
         ctx.transform.apply(ctx, current.textMatrix);
@@ -446,11 +468,12 @@ var CanvasGraphics = (function canvasGraphics() {
           this.restore();
 
           var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
-          var width = transformed[0] * fontSize + charSpacing;
-
-          ctx.translate(width, 0);
-          current.x += width;
+          var charWidth = transformed[0] * fontSize + charSpacing;
+          ctx.translate(charWidth, 0);
+          current.x += charWidth;
 
+          text.chars += unicodeToChar(glyph.unicode);
+          text.width += charWidth;
         }
         ctx.restore();
       } else {
@@ -459,7 +482,6 @@ var CanvasGraphics = (function canvasGraphics() {
         ctx.scale(1, -1);
         ctx.translate(current.x, -1 * current.y);
         ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX);
-
         ctx.scale(1 / textHScale, 1);
 
         var width = 0;
@@ -471,36 +493,100 @@ var CanvasGraphics = (function canvasGraphics() {
             continue;
           }
 
-          var unicode = glyph.unicode;
-          var char = (unicode >= 0x10000) ?
-            String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
-            0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
-
+          var char = unicodeToChar(glyph.unicode);
+          var charWidth = glyph.width * fontSize * 0.001 + charSpacing;
           ctx.fillText(char, width, 0);
-          width += glyph.width * fontSize * 0.001 + charSpacing;
+          width += charWidth;
+          
+          text.chars += char;
+          text.width += charWidth;
         }
         current.x += width;
-
         ctx.restore();
       }
+      return text;
     },
-
     showSpacedText: function canvasGraphicsShowSpacedText(arr) {
       var ctx = this.ctx;
       var current = this.current;
       var fontSize = current.fontSize;
       var textHScale = current.textHScale;
       var arrLength = arr.length;
+      var textLayer = this.textLayer;
+      var font = current.font;
+      var text = {str:'', length:0, canvasWidth:0, spaceWidth:0, geom:{}};
+
+      // Text selection-specific
+      text.spaceWidth = this.current.font.charsToGlyphs(' ')[0].width;
+      if (!text.spaceWidth>0) {
+        // Hack (space is sometimes not encoded)
+        text.spaceWidth = this.current.font.charsToGlyphs('i')[0].width;
+      }
+
+      // Compute text.geom
+      // TODO: refactor the series of transformations below, and share it with showText()
+      ctx.save();
+      ctx.transform.apply(ctx, current.textMatrix);
+      ctx.scale(1, -1);
+      ctx.translate(current.x, -1 * current.y);
+      ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX);
+      ctx.scale(1 / textHScale, 1);
+      var inv = ctx.mozCurrentTransform;
+      if (inv) {
+        var bl = Util.applyTransform([0, 0], inv);
+        var tr = Util.applyTransform([1, 1], inv);
+        text.geom.x = bl[0];
+        text.geom.y = bl[1];
+        text.geom.xFactor = tr[0] - bl[0];
+        text.geom.yFactor = tr[1] - bl[1];
+      }
+      ctx.restore();
+      
       for (var i = 0; i < arrLength; ++i) {
         var e = arr[i];
         if (isNum(e)) {
-          current.x -= e * 0.001 * fontSize * textHScale;
+          var spacingLength = -e * 0.001 * fontSize * textHScale;
+          current.x += spacingLength;
+
+          // Text selection-specific
+          // Emulate arbitrary spacing via HTML spaces
+          text.canvasWidth += spacingLength;
+          if (e<0 && text.spaceWidth>0) { // avoid div by zero
+            var numFakeSpaces = Math.round(-e / text.spaceWidth);
+            for (var j = 0; j < numFakeSpaces; ++j)
+              text.str += '&nbsp;';
+            text.length += numFakeSpaces>0 ? 1 : 0;
+          }
         } else if (isString(e)) {
-          this.showText(e);
+          var shownText = this.showText(e);
+
+          // Text selection-specific
+          if (shownText.chars === ' ') {
+            text.str += '&nbsp;';        
+          } else {
+            text.str += shownText.chars;
+          }
+          text.canvasWidth += shownText.width;
+          text.length += e.length;
         } else {
           malformed('TJ array element ' + e + ' is not string or num');
         }
       }
+      
+      if (textLayer) {
+        var div = document.createElement('div');
+        var fontHeight = text.geom.yFactor * fontSize;
+        div.style.fontSize = fontHeight + 'px';
+        // TODO: family should be '= font.loadedName', but some fonts don't 
+        // have spacing info (cf. fonts.js > Font > fields > htmx)
+        div.style.fontFamily = 'serif'; 
+        div.style.left = text.geom.x + 'px';
+        div.style.top = (text.geom.y - fontHeight) + 'px';
+        div.innerHTML = text.str;
+        div.dataset.canvasWidth = text.canvasWidth * text.geom.xFactor;
+        div.dataset.textLength = text.length;
+        this.textDivs.push(div);                
+      }      
     },
     nextLineShowText: function canvasGraphicsNextLineShowText(text) {
       this.nextLine();
index 4b411cff5c93fd566fe877df30ca249a5ee97468..7e7bb6ea88ad5060eb467c9911816f21fd0a3739 100644 (file)
@@ -157,7 +157,7 @@ var Page = (function pagePage() {
                                                 IRQueue, fonts) {
       var self = this;
       this.IRQueue = IRQueue;
-      var gfx = new CanvasGraphics(this.ctx, this.objs);
+      var gfx = new CanvasGraphics(this.ctx, this.objs, this.textLayer, this.textScale);
       var startTime = Date.now();
 
       var displayContinuation = function pageDisplayContinuation() {
@@ -243,6 +243,7 @@ var Page = (function pagePage() {
         startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
         if (startIdx == length) {
           self.stats.render = Date.now();
+          gfx.endDrawing();
           if (callback) callback();
         }
       }
@@ -305,9 +306,11 @@ var Page = (function pagePage() {
       }
       return links;
     },
-    startRendering: function(ctx, callback)  {
+    startRendering: function(ctx, callback, textLayer, textScale)  {
       this.ctx = ctx;
       this.callback = callback;
+      this.textLayer = textLayer;
+      this.textScale = textScale;
 
       this.startRenderingTime = Date.now();
       this.pdf.startRendering(this);
index 52852d591fe41a56511d2ddd421d74c6f9f02489..19895ac27a59d8f4714e9128f9c8aeb3f2f33070 100644 (file)
@@ -246,6 +246,12 @@ canvas {
   line-height:1.3;
 }
 
+::selection { background:rgba(0,0,255,0.3); }
+::-moz-selection { background:rgba(0,0,255,0.3); }
+/* TODO: file FF bug to support ::-moz-selection:window-inactive
+   so we can override the opaque grey background when the window is inactive;
+   see also http://css-tricks.com/9288-window-inactive-styling */
+
 #viewer {
   margin: 44px 0px 0px;
   padding: 8px 0px;
index 63215a6d8830cc67f624cbc2a2e4470e62248b9e..523b7dc56820999fe71f1839e1cb8d412a254f1a 100644 (file)
@@ -475,9 +475,9 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
     canvas.mozOpaque = true;
     div.appendChild(canvas);
 
-    var textDiv = document.createElement('div');
-    textDiv.className = 'textLayer';
-    div.appendChild(textDiv);
+    var textLayer = document.createElement('div');
+    textLayer.className = 'textLayer';
+    div.appendChild(textLayer);
 
     var scale = this.scale;
     canvas.width = pageWidth * scale;
@@ -491,7 +491,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
     ctx.translate(-this.x * scale, -this.y * scale);
 
     stats.begin = Date.now();
-    this.content.startRendering(ctx, this.updateStats, textDiv, scale);
+    this.content.startRendering(ctx, this.updateStats, textLayer, scale);
 
     setupLinks(this.content, this.scale);
     div.setAttribute('data-loaded', true);