Selection working
This commit is contained in:
parent
6c5d2ac88b
commit
e7d08e3a98
118
src/canvas.js
118
src/canvas.js
@ -60,7 +60,7 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||||||
// if we execute longer then `kExecutionTime`.
|
// if we execute longer then `kExecutionTime`.
|
||||||
var kExecutionTimeCheck = 500;
|
var kExecutionTimeCheck = 500;
|
||||||
|
|
||||||
function constructor(canvasCtx, objs) {
|
function constructor(canvasCtx, objs, textLayer, textScale) {
|
||||||
this.ctx = canvasCtx;
|
this.ctx = canvasCtx;
|
||||||
this.current = new CanvasExtraState();
|
this.current = new CanvasExtraState();
|
||||||
this.stateStack = [];
|
this.stateStack = [];
|
||||||
@ -69,6 +69,8 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||||||
this.xobjs = null;
|
this.xobjs = null;
|
||||||
this.ScratchCanvas = ScratchCanvas;
|
this.ScratchCanvas = ScratchCanvas;
|
||||||
this.objs = objs;
|
this.objs = objs;
|
||||||
|
this.textLayer = textLayer;
|
||||||
|
this.textScale = textScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
var LINE_CAP_STYLES = ['butt', 'round', 'square'];
|
var LINE_CAP_STYLES = ['butt', 'round', 'square'];
|
||||||
@ -95,6 +97,7 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
|
this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
|
||||||
|
this.textDivs = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR,
|
executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR,
|
||||||
@ -150,6 +153,17 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||||||
|
|
||||||
endDrawing: function canvasGraphicsEndDrawing() {
|
endDrawing: function canvasGraphicsEndDrawing() {
|
||||||
this.ctx.restore();
|
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
|
// Graphics state
|
||||||
@ -414,6 +428,12 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||||||
this.moveText(0, this.current.leading);
|
this.moveText(0, this.current.leading);
|
||||||
},
|
},
|
||||||
showText: function canvasGraphicsShowText(text) {
|
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 ctx = this.ctx;
|
||||||
var current = this.current;
|
var current = this.current;
|
||||||
var font = current.font;
|
var font = current.font;
|
||||||
@ -423,6 +443,8 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||||||
var wordSpacing = current.wordSpacing;
|
var wordSpacing = current.wordSpacing;
|
||||||
var textHScale = current.textHScale;
|
var textHScale = current.textHScale;
|
||||||
var glyphsLength = glyphs.length;
|
var glyphsLength = glyphs.length;
|
||||||
|
var text = { chars:'', width:0 };
|
||||||
|
|
||||||
if (font.coded) {
|
if (font.coded) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.transform.apply(ctx, current.textMatrix);
|
ctx.transform.apply(ctx, current.textMatrix);
|
||||||
@ -446,11 +468,12 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||||||
this.restore();
|
this.restore();
|
||||||
|
|
||||||
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
|
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
|
||||||
var width = transformed[0] * fontSize + charSpacing;
|
var charWidth = transformed[0] * fontSize + charSpacing;
|
||||||
|
ctx.translate(charWidth, 0);
|
||||||
ctx.translate(width, 0);
|
current.x += charWidth;
|
||||||
current.x += width;
|
|
||||||
|
|
||||||
|
text.chars += unicodeToChar(glyph.unicode);
|
||||||
|
text.width += charWidth;
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
} else {
|
} else {
|
||||||
@ -459,7 +482,6 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||||||
ctx.scale(1, -1);
|
ctx.scale(1, -1);
|
||||||
ctx.translate(current.x, -1 * current.y);
|
ctx.translate(current.x, -1 * current.y);
|
||||||
ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX);
|
ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX);
|
||||||
|
|
||||||
ctx.scale(1 / textHScale, 1);
|
ctx.scale(1 / textHScale, 1);
|
||||||
|
|
||||||
var width = 0;
|
var width = 0;
|
||||||
@ -471,36 +493,100 @@ var CanvasGraphics = (function canvasGraphics() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var unicode = glyph.unicode;
|
var char = unicodeToChar(glyph.unicode);
|
||||||
var char = (unicode >= 0x10000) ?
|
var charWidth = glyph.width * fontSize * 0.001 + charSpacing;
|
||||||
String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
|
|
||||||
0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
|
|
||||||
|
|
||||||
ctx.fillText(char, width, 0);
|
ctx.fillText(char, width, 0);
|
||||||
width += glyph.width * fontSize * 0.001 + charSpacing;
|
width += charWidth;
|
||||||
|
|
||||||
|
text.chars += char;
|
||||||
|
text.width += charWidth;
|
||||||
}
|
}
|
||||||
current.x += width;
|
current.x += width;
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
return text;
|
||||||
},
|
},
|
||||||
|
|
||||||
showSpacedText: function canvasGraphicsShowSpacedText(arr) {
|
showSpacedText: function canvasGraphicsShowSpacedText(arr) {
|
||||||
var ctx = this.ctx;
|
var ctx = this.ctx;
|
||||||
var current = this.current;
|
var current = this.current;
|
||||||
var fontSize = current.fontSize;
|
var fontSize = current.fontSize;
|
||||||
var textHScale = current.textHScale;
|
var textHScale = current.textHScale;
|
||||||
var arrLength = arr.length;
|
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) {
|
for (var i = 0; i < arrLength; ++i) {
|
||||||
var e = arr[i];
|
var e = arr[i];
|
||||||
if (isNum(e)) {
|
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 += ' ';
|
||||||
|
text.length += numFakeSpaces>0 ? 1 : 0;
|
||||||
|
}
|
||||||
} else if (isString(e)) {
|
} else if (isString(e)) {
|
||||||
this.showText(e);
|
var shownText = this.showText(e);
|
||||||
|
|
||||||
|
// Text selection-specific
|
||||||
|
if (shownText.chars === ' ') {
|
||||||
|
text.str += ' ';
|
||||||
|
} else {
|
||||||
|
text.str += shownText.chars;
|
||||||
|
}
|
||||||
|
text.canvasWidth += shownText.width;
|
||||||
|
text.length += e.length;
|
||||||
} else {
|
} else {
|
||||||
malformed('TJ array element ' + e + ' is not string or num');
|
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) {
|
nextLineShowText: function canvasGraphicsNextLineShowText(text) {
|
||||||
this.nextLine();
|
this.nextLine();
|
||||||
|
@ -157,7 +157,7 @@ var Page = (function pagePage() {
|
|||||||
IRQueue, fonts) {
|
IRQueue, fonts) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.IRQueue = IRQueue;
|
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 startTime = Date.now();
|
||||||
|
|
||||||
var displayContinuation = function pageDisplayContinuation() {
|
var displayContinuation = function pageDisplayContinuation() {
|
||||||
@ -243,6 +243,7 @@ var Page = (function pagePage() {
|
|||||||
startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
|
startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
|
||||||
if (startIdx == length) {
|
if (startIdx == length) {
|
||||||
self.stats.render = Date.now();
|
self.stats.render = Date.now();
|
||||||
|
gfx.endDrawing();
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,9 +306,11 @@ var Page = (function pagePage() {
|
|||||||
}
|
}
|
||||||
return links;
|
return links;
|
||||||
},
|
},
|
||||||
startRendering: function(ctx, callback) {
|
startRendering: function(ctx, callback, textLayer, textScale) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
this.textLayer = textLayer;
|
||||||
|
this.textScale = textScale;
|
||||||
|
|
||||||
this.startRenderingTime = Date.now();
|
this.startRenderingTime = Date.now();
|
||||||
this.pdf.startRendering(this);
|
this.pdf.startRendering(this);
|
||||||
|
@ -246,6 +246,12 @@ canvas {
|
|||||||
line-height:1.3;
|
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 {
|
#viewer {
|
||||||
margin: 44px 0px 0px;
|
margin: 44px 0px 0px;
|
||||||
padding: 8px 0px;
|
padding: 8px 0px;
|
||||||
|
@ -475,9 +475,9 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
|
|||||||
canvas.mozOpaque = true;
|
canvas.mozOpaque = true;
|
||||||
div.appendChild(canvas);
|
div.appendChild(canvas);
|
||||||
|
|
||||||
var textDiv = document.createElement('div');
|
var textLayer = document.createElement('div');
|
||||||
textDiv.className = 'textLayer';
|
textLayer.className = 'textLayer';
|
||||||
div.appendChild(textDiv);
|
div.appendChild(textLayer);
|
||||||
|
|
||||||
var scale = this.scale;
|
var scale = this.scale;
|
||||||
canvas.width = pageWidth * 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);
|
ctx.translate(-this.x * scale, -this.y * scale);
|
||||||
|
|
||||||
stats.begin = Date.now();
|
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);
|
setupLinks(this.content, this.scale);
|
||||||
div.setAttribute('data-loaded', true);
|
div.setAttribute('data-loaded', true);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user