Browse Source

Build the text layer geometry on the worker.

Brendan Dahl 11 years ago
parent
commit
5bd8a83c9b
  1. 9
      src/core/core.js
  2. 465
      src/core/evaluator.js
  3. 17
      src/core/fonts.js
  4. 35
      src/display/api.js
  5. 109
      src/display/canvas.js
  6. 39
      test/driver.js
  7. 25
      web/page_view.js
  8. 7
      web/pdf_find_controller.js
  9. 97
      web/text_layer_builder.js
  10. 1
      web/viewer.css

9
src/core/core.js

@ -233,8 +233,6 @@ var Page = (function PageClosure() {
var self = this; var self = this;
var textContentPromise = new LegacyPromise();
var pdfManager = this.pdfManager; var pdfManager = this.pdfManager;
var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', var contentStreamPromise = pdfManager.ensure(this, 'getContentStream',
[]); []);
@ -247,7 +245,7 @@ var Page = (function PageClosure() {
var dataPromises = Promise.all([contentStreamPromise, var dataPromises = Promise.all([contentStreamPromise,
resourcesPromise]); resourcesPromise]);
dataPromises.then(function(data) { return dataPromises.then(function(data) {
var contentStream = data[0]; var contentStream = data[0];
var partialEvaluator = new PartialEvaluator(pdfManager, self.xref, var partialEvaluator = new PartialEvaluator(pdfManager, self.xref,
handler, self.pageIndex, handler, self.pageIndex,
@ -255,12 +253,9 @@ var Page = (function PageClosure() {
self.idCounters, self.idCounters,
self.fontCache); self.fontCache);
var bidiTexts = partialEvaluator.getTextContent(contentStream, return partialEvaluator.getTextContent(contentStream,
self.resources); self.resources);
textContentPromise.resolve(bidiTexts);
}); });
return textContentPromise;
}, },
getAnnotationsData: function Page_getAnnotationsData() { getAnnotationsData: function Page_getAnnotationsData() {

465
src/core/evaluator.js

@ -21,16 +21,14 @@
MurmurHash3_64, Name, Parser, Pattern, PDFImage, PDFJS, serifFonts, MurmurHash3_64, Name, Parser, Pattern, PDFImage, PDFJS, serifFonts,
stdFontMap, symbolsFonts, getTilingPatternIR, warn, Util, Promise, stdFontMap, symbolsFonts, getTilingPatternIR, warn, Util, Promise,
LegacyPromise, RefSetCache, isRef, TextRenderingMode, CMapFactory, LegacyPromise, RefSetCache, isRef, TextRenderingMode, CMapFactory,
OPS, UNSUPPORTED_FEATURES, UnsupportedManager */ OPS, UNSUPPORTED_FEATURES, UnsupportedManager, NormalizedUnicodes,
IDENTITY_MATRIX, reverseIfRtl */
'use strict'; 'use strict';
var PartialEvaluator = (function PartialEvaluatorClosure() { var PartialEvaluator = (function PartialEvaluatorClosure() {
function PartialEvaluator(pdfManager, xref, handler, pageIndex, function PartialEvaluator(pdfManager, xref, handler, pageIndex,
uniquePrefix, idCounters, fontCache) { uniquePrefix, idCounters, fontCache) {
this.state = new EvalState();
this.stateStack = [];
this.pdfManager = pdfManager; this.pdfManager = pdfManager;
this.xref = xref; this.xref = xref;
this.handler = handler; this.handler = handler;
@ -96,7 +94,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
buildFormXObject: function PartialEvaluator_buildFormXObject(resources, buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
xobj, smask, xobj, smask,
operatorList, operatorList,
state) { initialState) {
var matrix = xobj.dict.get('Matrix'); var matrix = xobj.dict.get('Matrix');
var bbox = xobj.dict.get('BBox'); var bbox = xobj.dict.get('BBox');
var group = xobj.dict.get('Group'); var group = xobj.dict.get('Group');
@ -123,7 +121,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]); operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
this.getOperatorList(xobj, (xobj.dict.get('Resources') || resources), this.getOperatorList(xobj, (xobj.dict.get('Resources') || resources),
operatorList, state); operatorList, initialState);
operatorList.addOp(OPS.paintFormXObjectEnd, []); operatorList.addOp(OPS.paintFormXObjectEnd, []);
if (group) { if (group) {
@ -221,15 +219,15 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}, },
handleSMask: function PartialEvaluator_handleSmask(smask, resources, handleSMask: function PartialEvaluator_handleSmask(smask, resources,
operatorList) { operatorList,
stateManager) {
var smaskContent = smask.get('G'); var smaskContent = smask.get('G');
var smaskOptions = { var smaskOptions = {
subtype: smask.get('S').name, subtype: smask.get('S').name,
backdrop: smask.get('BC') backdrop: smask.get('BC')
}; };
this.buildFormXObject(resources, smaskContent, smaskOptions, this.buildFormXObject(resources, smaskContent, smaskOptions,
operatorList); operatorList, stateManager.state.clone());
}, },
handleTilingType: handleTilingType:
@ -250,7 +248,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
handleSetFont: handleSetFont:
function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef, function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef,
operatorList) { operatorList, state) {
// TODO(mack): Not needed? // TODO(mack): Not needed?
var fontName; var fontName;
if (fontArgs) { if (fontArgs) {
@ -260,7 +258,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var self = this; var self = this;
var font = this.loadFont(fontName, fontRef, this.xref, resources, var font = this.loadFont(fontName, fontRef, this.xref, resources,
operatorList); operatorList);
this.state.font = font; state.font = font;
var loadedName = font.loadedName; var loadedName = font.loadedName;
if (!font.sent) { if (!font.sent) {
var fontData = font.translated.exportData(); var fontData = font.translated.exportData();
@ -276,10 +274,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
return loadedName; return loadedName;
}, },
handleText: function PartialEvaluator_handleText(chars) { handleText: function PartialEvaluator_handleText(chars, state) {
var font = this.state.font.translated; var font = state.font.translated;
var glyphs = font.charsToGlyphs(chars); var glyphs = font.charsToGlyphs(chars);
var isAddToPathSet = !!(this.state.textRenderingMode & var isAddToPathSet = !!(state.textRenderingMode &
TextRenderingMode.ADD_TO_PATH_FLAG); TextRenderingMode.ADD_TO_PATH_FLAG);
if (font.data && (isAddToPathSet || PDFJS.disableFontFace)) { if (font.data && (isAddToPathSet || PDFJS.disableFontFace)) {
for (var i = 0; i < glyphs.length; i++) { for (var i = 0; i < glyphs.length; i++) {
@ -302,7 +300,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}, },
setGState: function PartialEvaluator_setGState(resources, gState, setGState: function PartialEvaluator_setGState(resources, gState,
operatorList, xref) { operatorList, xref,
stateManager) {
var self = this; var self = this;
// TODO(mack): This should be rewritten so that this function returns // TODO(mack): This should be rewritten so that this function returns
@ -324,7 +323,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
break; break;
case 'Font': case 'Font':
var loadedName = self.handleSetFont(resources, null, value[0], var loadedName = self.handleSetFont(resources, null, value[0],
operatorList); operatorList,
stateManager.state);
operatorList.addDependency(loadedName); operatorList.addDependency(loadedName);
gStateObj.push([key, [loadedName, value[1]]]); gStateObj.push([key, [loadedName, value[1]]]);
break; break;
@ -338,7 +338,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
} }
var dict = xref.fetchIfRef(value); var dict = xref.fetchIfRef(value);
if (isDict(dict)) { if (isDict(dict)) {
self.handleSMask(dict, resources, operatorList); self.handleSMask(dict, resources, operatorList, stateManager);
gStateObj.push([key, true]); gStateObj.push([key, true]);
} else { } else {
warn('Unsupported SMask type'); warn('Unsupported SMask type');
@ -495,7 +495,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
getOperatorList: function PartialEvaluator_getOperatorList(stream, getOperatorList: function PartialEvaluator_getOperatorList(stream,
resources, resources,
operatorList, operatorList,
evaluatorState) { initialState) {
var self = this; var self = this;
var xref = this.xref; var xref = this.xref;
@ -507,10 +507,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
resources = (resources || Dict.empty); resources = (resources || Dict.empty);
var xobjs = (resources.get('XObject') || Dict.empty); var xobjs = (resources.get('XObject') || Dict.empty);
var patterns = (resources.get('Pattern') || Dict.empty); var patterns = (resources.get('Pattern') || Dict.empty);
var preprocessor = new EvaluatorPreprocessor(stream, xref); var stateManager = new StateManager(initialState || new EvalState());
if (evaluatorState) { var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
preprocessor.setState(evaluatorState);
}
var promise = new LegacyPromise(); var promise = new LegacyPromise();
var operation; var operation;
@ -570,9 +568,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
'XObject should have a Name subtype'); 'XObject should have a Name subtype');
if ('Form' == type.name) { if ('Form' == type.name) {
stateManager.save();
self.buildFormXObject(resources, xobj, null, operatorList, self.buildFormXObject(resources, xobj, null, operatorList,
preprocessor.getState()); stateManager.state.clone());
args = []; args = [];
stateManager.restore();
continue; continue;
} else if ('Image' == type.name) { } else if ('Image' == type.name) {
self.buildPaintImageXObject(resources, xobj, false, self.buildPaintImageXObject(resources, xobj, false,
@ -587,7 +587,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
case OPS.setFont: case OPS.setFont:
// eagerly collect all fonts // eagerly collect all fonts
var loadedName = self.handleSetFont(resources, args, null, var loadedName = self.handleSetFont(resources, args, null,
operatorList); operatorList,
stateManager.state);
operatorList.addDependency(loadedName); operatorList.addDependency(loadedName);
args[0] = loadedName; args[0] = loadedName;
break; break;
@ -602,37 +603,26 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList, cacheKey, imageCache); operatorList, cacheKey, imageCache);
args = []; args = [];
continue; continue;
case OPS.save:
var old = this.state;
this.stateStack.push(this.state);
this.state = old.clone();
break;
case OPS.restore:
var prev = this.stateStack.pop();
if (prev) {
this.state = prev;
}
break;
case OPS.showText: case OPS.showText:
args[0] = this.handleText(args[0]); args[0] = this.handleText(args[0], stateManager.state);
break; break;
case OPS.showSpacedText: case OPS.showSpacedText:
var arr = args[0]; var arr = args[0];
var arrLength = arr.length; var arrLength = arr.length;
for (var i = 0; i < arrLength; ++i) { for (var i = 0; i < arrLength; ++i) {
if (isString(arr[i])) { if (isString(arr[i])) {
arr[i] = this.handleText(arr[i]); arr[i] = this.handleText(arr[i], stateManager.state);
} }
} }
break; break;
case OPS.nextLineShowText: case OPS.nextLineShowText:
args[0] = this.handleText(args[0]); args[0] = this.handleText(args[0], stateManager.state);
break; break;
case OPS.nextLineSetSpacingShowText: case OPS.nextLineSetSpacingShowText:
args[2] = this.handleText(args[2]); args[2] = this.handleText(args[2], stateManager.state);
break; break;
case OPS.setTextRenderingMode: case OPS.setTextRenderingMode:
this.state.textRenderingMode = args[0]; stateManager.state.textRenderingMode = args[0];
break; break;
// Parse the ColorSpace data to a raw format. // Parse the ColorSpace data to a raw format.
case OPS.setFillColorSpace: case OPS.setFillColorSpace:
@ -665,7 +655,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
} }
var gState = extGState.get(dictName.name); var gState = extGState.get(dictName.name);
self.setGState(resources, gState, operatorList, xref); self.setGState(resources, gState, operatorList, xref,
stateManager);
args = []; args = [];
continue; continue;
} }
@ -682,40 +673,165 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}, },
getTextContent: function PartialEvaluator_getTextContent(stream, resources, getTextContent: function PartialEvaluator_getTextContent(stream, resources,
textState) { stateManager) {
textState = (textState || new TextState()); stateManager = (stateManager || new StateManager(new TextState()));
var bidiTexts = []; var textContent = {
items: [],
styles: Object.create(null)
};
var bidiTexts = textContent.items;
var SPACE_FACTOR = 0.35; var SPACE_FACTOR = 0.35;
var MULTI_SPACE_FACTOR = 1.5; var MULTI_SPACE_FACTOR = 1.5;
var self = this; var self = this;
var xref = this.xref; var xref = this.xref;
function handleSetFont(fontName, fontRef) {
return self.loadFont(fontName, fontRef, xref, resources, null);
}
resources = (xref.fetchIfRef(resources) || Dict.empty); resources = (xref.fetchIfRef(resources) || Dict.empty);
// The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd. // The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
var xobjs = null; var xobjs = null;
var xobjsCache = {}; var xobjsCache = {};
var preprocessor = new EvaluatorPreprocessor(stream, xref); var preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
var res = resources; var res = resources;
var chunkBuf = [];
var font = null;
var charSpace = 0, wordSpace = 0;
var operation; var operation;
var textState;
function newTextChunk() {
var font = textState.font;
if (!(font.loadedName in textContent.styles)) {
textContent.styles[font.loadedName] = {
fontFamily: font.fallbackName,
ascent: font.ascent,
descent: font.descent,
vertical: font.vertical
};
}
return {
str: '',
dir: null,
width: 0,
height: 0,
transform: null,
fontName: font.loadedName
};
}
function runBidi(textChunk) {
var bidiResult = PDFJS.bidi(textChunk.str, -1, textState.font.vertical);
textChunk.str = bidiResult.str;
textChunk.dir = bidiResult.dir;
return textChunk;
}
function handleSetFont(fontName, fontRef) {
var font = textState.font = self.loadFont(fontName, fontRef, xref,
resources, null).translated;
textState.fontMatrix = font.fontMatrix ? font.fontMatrix :
FONT_IDENTITY_MATRIX;
}
function buildTextGeometry(chars, textChunk) {
var font = textState.font;
textChunk = textChunk || newTextChunk();
if (!textChunk.transform) {
// 9.4.4 Text Space Details
var tsm = [textState.fontSize * textState.textHScale, 0,
0, textState.fontSize,
0, textState.textRise];
var trm = textChunk.transform = Util.transform(textState.ctm,
Util.transform(textState.textMatrix, tsm));
if (!font.vertical) {
textChunk.height = Math.sqrt(trm[2] * trm[2] + trm[3] * trm[3]);
} else {
textChunk.width = Math.sqrt(trm[0] * trm[0] + trm[1] * trm[1]);
}
}
var width = 0;
var height = 0;
var glyphs = font.charsToGlyphs(chars);
var defaultVMetrics = font.defaultVMetrics;
for (var i = 0; i < glyphs.length; i++) {
var glyph = glyphs[i];
if (!glyph) { // Previous glyph was a space.
continue;
}
var vMetricX = null;
var vMetricY = null;
var glyphWidth = null;
if (font.vertical) {
if (glyph.vmetric) {
glyphWidth = glyph.vmetric[0];
vMetricX = glyph.vmetric[1];
vMetricY = glyph.vmetric[2];
} else {
glyphWidth = glyph.width;
vMetricX = glyph.width * 0.5;
vMetricY = defaultVMetrics[2];
}
} else {
glyphWidth = glyph.width;
}
var glyphUnicode = glyph.unicode;
if (glyphUnicode in NormalizedUnicodes) {
glyphUnicode = NormalizedUnicodes[glyphUnicode];
}
glyphUnicode = reverseIfRtl(glyphUnicode);
// The following will calculate the x and y of the individual glyphs.
// if (font.vertical) {
// tsm[4] -= vMetricX * Math.abs(textState.fontSize) *
// textState.fontMatrix[0];
// tsm[5] -= vMetricY * textState.fontSize *
// textState.fontMatrix[0];
// }
// var trm = Util.transform(textState.textMatrix, tsm);
// var pt = Util.applyTransform([trm[4], trm[5]], textState.ctm);
// var x = pt[0];
// var y = pt[1];
var tx = 0;
var ty = 0;
if (!font.vertical) {
var w0 = glyphWidth * textState.fontMatrix[0];
tx = (w0 * textState.fontSize + textState.charSpacing) *
textState.textHScale;
width += tx;
} else {
var w1 = glyphWidth * textState.fontMatrix[0];
ty = w1 * textState.fontSize + textState.charSpacing;
height += ty;
}
textState.translateTextMatrix(tx, ty);
textChunk.str += glyphUnicode;
}
var a = textState.textLineMatrix[0];
var b = textState.textLineMatrix[1];
var scaleLineX = Math.sqrt(a * a + b * b);
a = textState.ctm[0];
b = textState.ctm[1];
var scaleCtmX = Math.sqrt(a * a + b * b);
if (!font.vertical) {
textChunk.width += width * scaleCtmX * scaleLineX;
} else {
textChunk.height += Math.abs(height * scaleCtmX * scaleLineX);
}
return textChunk;
}
while ((operation = preprocessor.read())) { while ((operation = preprocessor.read())) {
textState = stateManager.state;
var fn = operation.fn; var fn = operation.fn;
var args = operation.args; var args = operation.args;
switch (fn) { switch (fn) {
// TODO: Add support for SAVE/RESTORE and XFORM here.
case OPS.setFont: case OPS.setFont:
font = handleSetFont(args[0].name).translated; handleSetFont(args[0].name);
textState.fontSize = args[1]; textState.fontSize = args[1];
break; break;
case OPS.setTextRise: case OPS.setTextRise:
@ -728,64 +844,79 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
textState.leading = args[0]; textState.leading = args[0];
break; break;
case OPS.moveText: case OPS.moveText:
textState.translateTextMatrix(args[0], args[1]); textState.translateTextLineMatrix(args[0], args[1]);
textState.textMatrix = textState.textLineMatrix.slice();
break; break;
case OPS.setLeadingMoveText: case OPS.setLeadingMoveText:
textState.leading = -args[1]; textState.leading = -args[1];
textState.translateTextMatrix(args[0], args[1]); textState.translateTextLineMatrix(args[0], args[1]);
textState.textMatrix = textState.textLineMatrix.slice();
break; break;
case OPS.nextLine: case OPS.nextLine:
textState.translateTextMatrix(0, -textState.leading); textState.carriageReturn();
break; break;
case OPS.setTextMatrix: case OPS.setTextMatrix:
textState.setTextMatrix(args[0], args[1], textState.setTextMatrix(args[0], args[1], args[2], args[3],
args[2], args[3], args[4], args[5]); args[4], args[5]);
textState.setTextLineMatrix(args[0], args[1], args[2], args[3],
args[4], args[5]);
break; break;
case OPS.setCharSpacing: case OPS.setCharSpacing:
charSpace = args[0]; textState.charSpace = args[0];
break; break;
case OPS.setWordSpacing: case OPS.setWordSpacing:
wordSpace = args[0]; textState.wordSpace = args[0];
break; break;
case OPS.beginText: case OPS.beginText:
textState.initialiseTextObj(); textState.textMatrix = IDENTITY_MATRIX.slice();
textState.textLineMatrix = IDENTITY_MATRIX.slice();
break; break;
case OPS.showSpacedText: case OPS.showSpacedText:
var items = args[0]; var items = args[0];
var textChunk = newTextChunk();
for (var j = 0, jj = items.length; j < jj; j++) { for (var j = 0, jj = items.length; j < jj; j++) {
if (typeof items[j] === 'string') { if (typeof items[j] === 'string') {
chunkBuf.push(fontCharsToUnicode(items[j], font)); buildTextGeometry(items[j], textChunk);
} else if (items[j] < 0 && font.spaceWidth > 0) { } else {
var fakeSpaces = -items[j] / font.spaceWidth; var val = items[j] / 1000;
if (!textState.font.vertical) {
var offset = -val * textState.fontSize * textState.textHScale;
textState.translateTextMatrix(offset, 0);
textChunk.width += offset;
} else {
var offset = -val * textState.fontSize;
textState.translateTextMatrix(0, offset);
textChunk.height += offset;
}
if (items[j] < 0 && textState.font.spaceWidth > 0) {
var fakeSpaces = -items[j] / textState.font.spaceWidth;
if (fakeSpaces > MULTI_SPACE_FACTOR) { if (fakeSpaces > MULTI_SPACE_FACTOR) {
fakeSpaces = Math.round(fakeSpaces); fakeSpaces = Math.round(fakeSpaces);
while (fakeSpaces--) { while (fakeSpaces--) {
chunkBuf.push(' '); textChunk.str += ' ';
} }
} else if (fakeSpaces > SPACE_FACTOR) { } else if (fakeSpaces > SPACE_FACTOR) {
chunkBuf.push(' '); textChunk.str += ' ';
}
} }
} }
} }
bidiTexts.push(runBidi(textChunk));
break; break;
case OPS.showText: case OPS.showText:
chunkBuf.push(fontCharsToUnicode(args[0], font)); bidiTexts.push(runBidi(buildTextGeometry(args[0])));
break; break;
case OPS.nextLineShowText: case OPS.nextLineShowText:
// For search, adding a extra white space for line breaks would be textState.carriageReturn();
// better here, but that causes too much spaces in the bidiTexts.push(runBidi(buildTextGeometry(args[0])));
// text-selection divs.
chunkBuf.push(fontCharsToUnicode(args[0], font));
break; break;
case OPS.nextLineSetSpacingShowText: case OPS.nextLineSetSpacingShowText:
// Note comment in "'" textState.wordSpacing = args[0];
chunkBuf.push(fontCharsToUnicode(args[2], font)); textState.charSpacing = args[1];
textState.carriageReturn();
bidiTexts.push(runBidi(buildTextGeometry(args[2])));
break; break;
case OPS.paintXObject: case OPS.paintXObject:
// Set the chunk such that the following if won't add something
// to the state.
chunkBuf.length = 0;
if (args[0].code) { if (args[0].code) {
break; break;
} }
@ -797,7 +928,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var name = args[0].name; var name = args[0].name;
if (xobjsCache.key === name) { if (xobjsCache.key === name) {
if (xobjsCache.texts) { if (xobjsCache.texts) {
Util.concatenateToArray(bidiTexts, xobjsCache.texts); Util.concatenateToArray(bidiTexts, xobjsCache.texts.items);
Util.extendObj(textContent.styles, xobjsCache.texts.styles);
} }
break; break;
} }
@ -818,11 +950,23 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
break; break;
} }
var formTexts = this.getTextContent(xobj, stateManager.save();
(xobj.dict.get('Resources') || resources), textState); var matrix = xobj.dict.get('Matrix');
if (isArray(matrix) && matrix.length === 6) {
stateManager.transform(matrix);
}
var formTextContent = this.getTextContent(
xobj,
xobj.dict.get('Resources') || resources,
stateManager
);
Util.concatenateToArray(bidiTexts, formTextContent.items);
Util.extendObj(textContent.styles, formTextContent.styles);
stateManager.restore();
xobjsCache.key = name; xobjsCache.key = name;
xobjsCache.texts = formTexts; xobjsCache.texts = formTextContent;
Util.concatenateToArray(bidiTexts, formTexts);
break; break;
case OPS.setGState: case OPS.setGState:
var dictName = args[0]; var dictName = args[0];
@ -836,36 +980,14 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
for (var i = 0; i < gsState.length; i++) { for (var i = 0; i < gsState.length; i++) {
if (gsState[i] === 'Font') { if (gsState[i] === 'Font') {
font = handleSetFont(args[0].name).translated; handleSetFont(args[0].name);
} }
} }
break; break;
} } // switch
} // while
if (chunkBuf.length > 0) { return textContent;
var chunk = chunkBuf.join('');
var bidiText = PDFJS.bidi(chunk, -1, font.vertical);
var renderParams = textState.calcRenderParams(preprocessor.ctm);
var fontHeight = textState.fontSize * renderParams.vScale;
var fontAscent = (font.ascent ? (font.ascent * fontHeight) :
(font.descent ? ((1 + font.descent) * fontHeight) : fontHeight));
bidiText.x = renderParams.renderMatrix[4] - (fontAscent *
Math.sin(renderParams.angle));
bidiText.y = renderParams.renderMatrix[5] + (fontAscent *
Math.cos(renderParams.angle));
if (bidiText.dir == 'ttb') {
bidiText.x += renderParams.vScale / 2;
bidiText.y -= renderParams.vScale;
}
bidiText.angle = renderParams.angle;
bidiText.size = fontHeight;
bidiTexts.push(bidiText);
chunkBuf.length = 0;
}
}
return bidiTexts;
}, },
extractDataStructures: function extractDataStructures: function
@ -1467,64 +1589,89 @@ var OperatorList = (function OperatorListClosure() {
return OperatorList; return OperatorList;
})(); })();
var StateManager = (function StateManagerClosure() {
function StateManager(initialState) {
this.state = initialState;
this.stateStack = [];
}
StateManager.prototype = {
save: function () {
var old = this.state;
this.stateStack.push(this.state);
this.state = old.clone();
},
restore: function () {
var prev = this.stateStack.pop();
if (prev) {
this.state = prev;
}
},
transform: function (args) {
this.state.ctm = Util.transform(this.state.ctm, args);
}
};
return StateManager;
})();
var TextState = (function TextStateClosure() { var TextState = (function TextStateClosure() {
function TextState() { function TextState() {
this.ctm = new Float32Array(IDENTITY_MATRIX);
this.fontSize = 0; this.fontSize = 0;
this.textMatrix = [1, 0, 0, 1, 0, 0]; this.font = null;
this.stateStack = []; this.fontMatrix = FONT_IDENTITY_MATRIX;
//textState variables this.textMatrix = IDENTITY_MATRIX.slice();
this.textLineMatrix = IDENTITY_MATRIX.slice();
this.charSpacing = 0;
this.wordSpacing = 0;
this.leading = 0; this.leading = 0;
this.textHScale = 1; this.textHScale = 1;
this.textRise = 0; this.textRise = 0;
} }
TextState.prototype = { TextState.prototype = {
initialiseTextObj: function TextState_initialiseTextObj() {
var m = this.textMatrix;
m[0] = 1; m[1] = 0; m[2] = 0; m[3] = 1; m[4] = 0; m[5] = 0;
},
setTextMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) { setTextMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) {
var m = this.textMatrix; var m = this.textMatrix;
m[0] = a; m[1] = b; m[2] = c; m[3] = d; m[4] = e; m[5] = f; m[0] = a; m[1] = b; m[2] = c; m[3] = d; m[4] = e; m[5] = f;
}, },
setTextLineMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) {
var m = this.textLineMatrix;
m[0] = a; m[1] = b; m[2] = c; m[3] = d; m[4] = e; m[5] = f;
},
translateTextMatrix: function TextState_translateTextMatrix(x, y) { translateTextMatrix: function TextState_translateTextMatrix(x, y) {
var m = this.textMatrix; var m = this.textMatrix;
m[4] = m[0] * x + m[2] * y + m[4]; m[4] = m[0] * x + m[2] * y + m[4];
m[5] = m[1] * x + m[3] * y + m[5]; m[5] = m[1] * x + m[3] * y + m[5];
}, },
calcRenderParams: function TextState_calcRenderingParams(cm) { translateTextLineMatrix: function TextState_translateTextMatrix(x, y) {
var tm = this.textMatrix; var m = this.textLineMatrix;
var a = this.fontSize; m[4] = m[0] * x + m[2] * y + m[4];
var b = a * this.textHScale; m[5] = m[1] * x + m[3] * y + m[5];
var c = this.textRise; },
var vScale = Math.sqrt((tm[2] * tm[2]) + (tm[3] * tm[3])); calcRenderMatrix: function TextState_calcRendeMatrix(ctm) {
var angle = Math.atan2(tm[1], tm[0]); // 9.4.4 Text Space Details
var m0 = tm[0] * cm[0] + tm[1] * cm[2]; var tsm = [this.fontSize * this.textHScale, 0,
var m1 = tm[0] * cm[1] + tm[1] * cm[3]; 0, this.fontSize,
var m2 = tm[2] * cm[0] + tm[3] * cm[2]; 0, this.textRise];
var m3 = tm[2] * cm[1] + tm[3] * cm[3]; return Util.transform(ctm, Util.transform(this.textMatrix, tsm));
var m4 = tm[4] * cm[0] + tm[5] * cm[2] + cm[4];
var m5 = tm[4] * cm[1] + tm[5] * cm[3] + cm[5];
var renderMatrix = [
b * m0,
b * m1,
a * m2,
a * m3,
c * m2 + m4,
c * m3 + m5
];
return {
renderMatrix: renderMatrix,
vScale: vScale,
angle: angle
};
}, },
carriageReturn: function TextState_carriageReturn() {
this.translateTextLineMatrix(0, -this.leading);
this.textMatrix = this.textLineMatrix.slice();
},
clone: function TextState_clone() {
var clone = Object.create(this);
clone.textMatrix = this.textMatrix.slice();
clone.textLineMatrix = this.textLineMatrix.slice();
clone.fontMatrix = this.fontMatrix.slice();
return clone;
}
}; };
return TextState; return TextState;
})(); })();
var EvalState = (function EvalStateClosure() { var EvalState = (function EvalStateClosure() {
function EvalState() { function EvalState() {
this.ctm = new Float32Array(IDENTITY_MATRIX);
this.font = null; this.font = null;
this.textRenderingMode = TextRenderingMode.FILL; this.textRenderingMode = TextRenderingMode.FILL;
} }
@ -1650,17 +1797,16 @@ var EvaluatorPreprocessor = (function EvaluatorPreprocessor() {
'null': null 'null': null
}; };
function EvaluatorPreprocessor(stream, xref) { function EvaluatorPreprocessor(stream, xref, stateManager) {
// TODO(mduan): pass array of knownCommands rather than OP_MAP // TODO(mduan): pass array of knownCommands rather than OP_MAP
// dictionary // dictionary
this.parser = new Parser(new Lexer(stream, OP_MAP), false, xref); this.parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
this.ctm = new Float32Array([1, 0, 0, 1, 0, 0]); this.stateManager = stateManager;
this.savedStates = [];
} }
EvaluatorPreprocessor.prototype = { EvaluatorPreprocessor.prototype = {
get savedStatesDepth() { get savedStatesDepth() {
return this.savedStates.length; return this.stateManager.stateStack.length;
}, },
read: function EvaluatorPreprocessor_read() { read: function EvaluatorPreprocessor_read() {
@ -1717,38 +1863,17 @@ var EvaluatorPreprocessor = (function EvaluatorPreprocessor() {
} }
}, },
getState: function EvaluatorPreprocessor_getState() {
return {
ctm: this.ctm
};
},
setState: function EvaluatorPreprocessor_setState(state) {
this.ctm = state.ctm;
},
preprocessCommand: preprocessCommand:
function EvaluatorPreprocessor_preprocessCommand(fn, args) { function EvaluatorPreprocessor_preprocessCommand(fn, args) {
switch (fn | 0) { switch (fn | 0) {
case OPS.save: case OPS.save:
this.savedStates.push(this.getState()); this.stateManager.save();
break; break;
case OPS.restore: case OPS.restore:
var previousState = this.savedStates.pop(); this.stateManager.restore();
if (previousState) {
this.setState(previousState);
}
break; break;
case OPS.transform: case OPS.transform:
var ctm = this.ctm; this.stateManager.transform(args);
var m = new Float32Array(6);
m[0] = ctm[0] * args[0] + ctm[2] * args[1];
m[1] = ctm[1] * args[0] + ctm[3] * args[1];
m[2] = ctm[0] * args[2] + ctm[2] * args[3];
m[3] = ctm[1] * args[2] + ctm[3] * args[3];
m[4] = ctm[0] * args[4] + ctm[2] * args[5] + ctm[4];
m[5] = ctm[1] * args[4] + ctm[3] * args[5] + ctm[5];
this.ctm = m;
break; break;
} }
} }

17
src/core/fonts.js

@ -2102,23 +2102,6 @@ function reverseIfRtl(chars) {
return s; return s;
} }
function fontCharsToUnicode(charCodes, font) {
var glyphs = font.charsToGlyphs(charCodes);
var result = '';
for (var i = 0, ii = glyphs.length; i < ii; i++) {
var glyph = glyphs[i];
if (!glyph) {
continue;
}
var glyphUnicode = glyph.unicode;
if (glyphUnicode in NormalizedUnicodes) {
glyphUnicode = NormalizedUnicodes[glyphUnicode];
}
result += reverseIfRtl(glyphUnicode);
}
return result;
}
function adjustWidths(properties) { function adjustWidths(properties) {
if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) { if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) {
return; return;

35
src/display/api.js

@ -339,16 +339,34 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
return PDFDocumentProxy; return PDFDocumentProxy;
})(); })();
/**
* Page text content.
*
* @typedef {Object} TextContent
* @property {array} items - array of {@link TextItem}
* @property {Object} styles - {@link TextStyles} objects, indexed by font
* name.
*/
/** /**
* Page text content part. * Page text content part.
* *
* @typedef {Object} BidiText * @typedef {Object} TextItem
* @property {string} str - text content. * @property {string} str - text content.
* @property {string} dir - text direction: 'ttb', 'ltr' or 'rtl'. * @property {string} dir - text direction: 'ttb', 'ltr' or 'rtl'.
* @property {number} x - x position of the text on the page. * @property {array} transform - transformation matrix.
* @property {number} y - y position of the text on the page. * @property {number} width - width in device space.
* @property {number} angle - text rotation. * @property {number} height - height in device space.
* @property {number} size - font size. * @property {string} fontName - font name used by pdf.js for converted font.
*/
/**
* Text style
* @typedef {Object} TextStyle
* @property {number} ascent - font ascent.
* @property {number} descent - font descent.
* @property {boolean} vertical - text is in vertical mode.
* @property {string} fontFamily - possible font family
*/ */
/** /**
@ -522,8 +540,8 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
return renderTask; return renderTask;
}, },
/** /**
* @return {Promise} That is resolved with the array of {@link BidiText} * @return {Promise} That is resolved a {@link TextContent}
* objects that represent the page text content. * object that represent the page text content.
*/ */
getTextContent: function PDFPageProxy_getTextContent() { getTextContent: function PDFPageProxy_getTextContent() {
var promise = new PDFJS.LegacyPromise(); var promise = new PDFJS.LegacyPromise();
@ -1210,8 +1228,7 @@ var InternalRenderTask = (function InternalRenderTaskClosure() {
var params = this.params; var params = this.params;
this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs, this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs,
this.objs, params.textLayer, this.objs, params.imageLayer);
params.imageLayer);
this.gfx.beginDrawing(params.viewport, transparency); this.gfx.beginDrawing(params.viewport, transparency);
this.operatorListIdx = 0; this.operatorListIdx = 0;

109
src/display/canvas.js

@ -402,7 +402,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// before it stops and shedules a continue of execution. // before it stops and shedules a continue of execution.
var EXECUTION_TIME = 15; var EXECUTION_TIME = 15;
function CanvasGraphics(canvasCtx, commonObjs, objs, textLayer, imageLayer) { function CanvasGraphics(canvasCtx, commonObjs, objs, imageLayer) {
this.ctx = canvasCtx; this.ctx = canvasCtx;
this.current = new CanvasExtraState(); this.current = new CanvasExtraState();
this.stateStack = []; this.stateStack = [];
@ -412,7 +412,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.xobjs = null; this.xobjs = null;
this.commonObjs = commonObjs; this.commonObjs = commonObjs;
this.objs = objs; this.objs = objs;
this.textLayer = textLayer;
this.imageLayer = imageLayer; this.imageLayer = imageLayer;
this.groupStack = []; this.groupStack = [];
this.processingType3 = null; this.processingType3 = null;
@ -718,9 +717,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.baseTransform = this.ctx.mozCurrentTransform.slice(); this.baseTransform = this.ctx.mozCurrentTransform.slice();
if (this.textLayer) {
this.textLayer.beginLayout();
}
if (this.imageLayer) { if (this.imageLayer) {
this.imageLayer.beginLayout(); this.imageLayer.beginLayout();
} }
@ -802,9 +798,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
CachedCanvases.clear(); CachedCanvases.clear();
WebGLUtils.clear(); WebGLUtils.clear();
if (this.textLayer) {
this.textLayer.endLayout();
}
if (this.imageLayer) { if (this.imageLayer) {
this.imageLayer.endLayout(); this.imageLayer.endLayout();
} }
@ -1234,33 +1227,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
ctx.scale(-current.textHScale, 1); ctx.scale(-current.textHScale, 1);
} }
}, },
createTextGeometry: function CanvasGraphics_createTextGeometry() {
var geometry = {};
var ctx = this.ctx;
var font = this.current.font;
var ctxMatrix = ctx.mozCurrentTransform;
var a = ctxMatrix[0], b = ctxMatrix[1], c = ctxMatrix[2];
var d = ctxMatrix[3], e = ctxMatrix[4], f = ctxMatrix[5];
var sx = (a >= 0) ?
Math.sqrt((a * a) + (b * b)) : -Math.sqrt((a * a) + (b * b));
var sy = (d >= 0) ?
Math.sqrt((c * c) + (d * d)) : -Math.sqrt((c * c) + (d * d));
var angle = Math.atan2(b, a);
var x = e;
var y = f;
geometry.x = x;
geometry.y = y;
geometry.hScale = sx;
geometry.vScale = sy;
geometry.angle = angle;
geometry.spaceWidth = font.spaceWidth;
geometry.fontName = font.loadedName;
geometry.fontFamily = font.fallbackName;
geometry.fontSize = this.current.fontSize;
geometry.ascent = font.ascent;
geometry.descent = font.descent;
return geometry;
},
paintChar: function (character, x, y) { paintChar: function (character, x, y) {
var ctx = this.ctx; var ctx = this.ctx;
@ -1332,7 +1298,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
return shadow(this, 'isFontSubpixelAAEnabled', enabled); return shadow(this, 'isFontSubpixelAAEnabled', enabled);
}, },
showText: function CanvasGraphics_showText(glyphs, skipTextSelection) { showText: function CanvasGraphics_showText(glyphs) {
var ctx = this.ctx; var ctx = this.ctx;
var current = this.current; var current = this.current;
var font = current.font; var font = current.font;
@ -1343,24 +1309,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var textHScale = current.textHScale * current.fontDirection; var textHScale = current.textHScale * current.fontDirection;
var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
var glyphsLength = glyphs.length; var glyphsLength = glyphs.length;
var textLayer = this.textLayer;
var geom;
var textSelection = textLayer && !skipTextSelection ? true : false;
var canvasWidth = 0.0;
var vertical = font.vertical; var vertical = font.vertical;
var defaultVMetrics = font.defaultVMetrics; var defaultVMetrics = font.defaultVMetrics;
if (fontSize === 0) { if (fontSize === 0) {
if (textSelection) { return;
geom = this.createTextGeometry();
geom.canvasWidth = canvasWidth;
if (vertical) {
var VERTICAL_TEXT_ROTATION = Math.PI / 2;
geom.angle += VERTICAL_TEXT_ROTATION;
}
this.textLayer.appendText(geom);
}
return canvasWidth;
} }
// Type3 fonts - each glyph is a "mini-PDF" // Type3 fonts - each glyph is a "mini-PDF"
@ -1371,12 +1324,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
ctx.scale(textHScale, 1); ctx.scale(textHScale, 1);
if (textSelection) {
this.save();
ctx.scale(1, -1);
geom = this.createTextGeometry();
this.restore();
}
for (var i = 0; i < glyphsLength; ++i) { for (var i = 0; i < glyphsLength; ++i) {
var glyph = glyphs[i]; var glyph = glyphs[i];
@ -1400,8 +1347,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
ctx.translate(width, 0); ctx.translate(width, 0);
current.x += width * textHScale; current.x += width * textHScale;
canvasWidth += width;
} }
ctx.restore(); ctx.restore();
this.processingType3 = null; this.processingType3 = null;
@ -1418,10 +1363,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
lineWidth /= scale; lineWidth /= scale;
} }
if (textSelection) {
geom = this.createTextGeometry();
}
if (fontSizeScale != 1.0) { if (fontSizeScale != 1.0) {
ctx.scale(fontSizeScale, fontSizeScale); ctx.scale(fontSizeScale, fontSizeScale);
lineWidth /= fontSizeScale; lineWidth /= fontSizeScale;
@ -1485,8 +1426,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
x += charWidth; x += charWidth;
canvasWidth += charWidth;
if (restoreNeeded) { if (restoreNeeded) {
ctx.restore(); ctx.restore();
} }
@ -1498,17 +1437,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
} }
ctx.restore(); ctx.restore();
} }
if (textSelection) {
geom.canvasWidth = canvasWidth;
if (vertical) {
var VERTICAL_TEXT_ROTATION = Math.PI / 2;
geom.angle += VERTICAL_TEXT_ROTATION;
}
this.textLayer.appendText(geom);
}
return canvasWidth;
}, },
showSpacedText: function CanvasGraphics_showSpacedText(arr) { showSpacedText: function CanvasGraphics_showSpacedText(arr) {
var ctx = this.ctx; var ctx = this.ctx;
@ -1518,19 +1446,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// TJ array's number is independent from fontMatrix // TJ array's number is independent from fontMatrix
var textHScale = current.textHScale * 0.001 * current.fontDirection; var textHScale = current.textHScale * 0.001 * current.fontDirection;
var arrLength = arr.length; var arrLength = arr.length;
var textLayer = this.textLayer;
var geom;
var canvasWidth = 0.0;
var textSelection = textLayer ? true : false;
var vertical = font.vertical; var vertical = font.vertical;
var spacingAccumulator = 0;
if (textSelection) {
ctx.save();
this.applyTextTransforms();
geom = this.createTextGeometry();
ctx.restore();
}
for (var i = 0; i < arrLength; ++i) { for (var i = 0; i < arrLength; ++i) {
var e = arr[i]; var e = arr[i];
@ -1542,26 +1458,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
current.x += spacingLength; current.x += spacingLength;
} }
if (textSelection) {
spacingAccumulator += spacingLength;
}
} else { } else {
var shownCanvasWidth = this.showText(e, true); this.showText(e);
if (textSelection) {
canvasWidth += spacingAccumulator + shownCanvasWidth;
spacingAccumulator = 0;
}
}
}
if (textSelection) {
geom.canvasWidth = canvasWidth;
if (vertical) {
var VERTICAL_TEXT_ROTATION = Math.PI / 2;
geom.angle += VERTICAL_TEXT_ROTATION;
} }
this.textLayer.appendText(geom);
} }
}, },
nextLineShowText: function CanvasGraphics_nextLineShowText(text) { nextLineShowText: function CanvasGraphics_nextLineShowText(text) {

39
test/driver.js

@ -203,38 +203,39 @@ function SimpleTextLayerBuilder(ctx, viewport) {
this.textCounter = 0; this.textCounter = 0;
} }
SimpleTextLayerBuilder.prototype = { SimpleTextLayerBuilder.prototype = {
beginLayout: function SimpleTextLayerBuilder_BeginLayout() { appendText: function SimpleTextLayerBuilder_AppendText(geom, styles) {
this.ctx.save(); var style = styles[geom.fontName];
},
endLayout: function SimpleTextLayerBuilder_EndLayout() {
this.ctx.restore();
},
appendText: function SimpleTextLayerBuilder_AppendText(geom) {
var ctx = this.ctx, viewport = this.viewport; var ctx = this.ctx, viewport = this.viewport;
// vScale and hScale already contain the scaling to pixel units var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform);
var fontHeight = geom.fontSize * Math.abs(geom.vScale); var angle = Math.atan2(tx[1], tx[0]);
var fontAscent = (geom.ascent ? geom.ascent * fontHeight : var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
(geom.descent ? (1 + geom.descent) * fontHeight : fontHeight)); var fontAscent = (style.ascent ? style.ascent * fontHeight :
(style.descent ? (1 + style.descent) * fontHeight : fontHeight));
ctx.save(); ctx.save();
ctx.beginPath(); ctx.beginPath();
ctx.strokeStyle = 'red'; ctx.strokeStyle = 'red';
ctx.fillStyle = 'yellow'; ctx.fillStyle = 'yellow';
ctx.translate(geom.x + (fontAscent * Math.sin(geom.angle)), ctx.translate(tx[4] + (fontAscent * Math.sin(angle)),
geom.y - (fontAscent * Math.cos(geom.angle))); tx[5] - (fontAscent * Math.cos(angle)));
ctx.rotate(geom.angle); ctx.rotate(angle);
ctx.rect(0, 0, geom.canvasWidth * Math.abs(geom.hScale), fontHeight); ctx.rect(0, 0, geom.width * viewport.scale, geom.height * viewport.scale);
ctx.stroke(); ctx.stroke();
ctx.fill(); ctx.fill();
ctx.restore(); ctx.restore();
var textContent = this.textContent[this.textCounter].str; ctx.font = fontHeight + 'px ' + style.fontFamily;
ctx.font = fontHeight + 'px ' + geom.fontFamily;
ctx.fillStyle = 'black'; ctx.fillStyle = 'black';
ctx.fillText(textContent, geom.x, geom.y); ctx.fillText(geom.str, tx[4], tx[5]);
this.textCounter++; this.textCounter++;
}, },
setTextContent: function SimpleTextLayerBuilder_SetTextContent(textContent) { setTextContent: function SimpleTextLayerBuilder_SetTextContent(textContent) {
this.textContent = textContent; this.ctx.save();
var textItems = textContent.items;
for (var i = 0; i < textItems.length; i++) {
this.appendText(textItems[i], textContent.styles);
}
this.ctx.restore();
} }
}; };

25
web/page_view.js

@ -485,8 +485,8 @@ var PageView = function pageView(container, id, scale,
if (!PDFJS.disableTextLayer) { if (!PDFJS.disableTextLayer) {
textLayerDiv = document.createElement('div'); textLayerDiv = document.createElement('div');
textLayerDiv.className = 'textLayer'; textLayerDiv.className = 'textLayer';
textLayerDiv.style.width = canvas.width + 'px'; textLayerDiv.style.width = canvas.style.width;
textLayerDiv.style.height = canvas.height + 'px'; textLayerDiv.style.height = canvas.style.height;
div.appendChild(textLayerDiv); div.appendChild(textLayerDiv);
} }
var textLayer = this.textLayer = var textLayer = this.textLayer =
@ -503,14 +503,6 @@ var PageView = function pageView(container, id, scale,
if (outputScale.scaled) { if (outputScale.scaled) {
ctx.scale(outputScale.sx, outputScale.sy); ctx.scale(outputScale.sx, outputScale.sy);
} }
if (outputScale.scaled && textLayerDiv) {
var cssScale = 'scale(' + (1 / outputScale.sx) + ', ' +
(1 / outputScale.sy) + ')';
CustomStyle.setProp('transform' , textLayerDiv, cssScale);
CustomStyle.setProp('transformOrigin' , textLayerDiv, '0% 0%');
textLayerDiv.dataset._scaleX = outputScale.sx;
textLayerDiv.dataset._scaleY = outputScale.sy;
}
// Rendering area // Rendering area
@ -600,19 +592,18 @@ var PageView = function pageView(container, id, scale,
this.renderTask.promise.then( this.renderTask.promise.then(
function pdfPageRenderCallback() { function pdfPageRenderCallback() {
pageViewDrawCallback(null); pageViewDrawCallback(null);
},
function pdfPageRenderError(error) {
pageViewDrawCallback(error);
}
);
if (textLayer) { if (textLayer) {
this.getTextContent().then( self.getTextContent().then(
function textContentResolved(textContent) { function textContentResolved(textContent) {
textLayer.setTextContent(textContent); textLayer.setTextContent(textContent);
} }
); );
} }
},
function pdfPageRenderError(error) {
pageViewDrawCallback(error);
}
);
setupAnnotations(div, pdfPage, this.viewport); setupAnnotations(div, pdfPage, this.viewport);
div.setAttribute('data-loaded', true); div.setAttribute('data-loaded', true);

7
web/pdf_find_controller.js

@ -145,11 +145,12 @@ var PDFFindController = {
var self = this; var self = this;
function extractPageText(pageIndex) { function extractPageText(pageIndex) {
self.pdfPageSource.pages[pageIndex].getTextContent().then( self.pdfPageSource.pages[pageIndex].getTextContent().then(
function textContentResolved(bidiTexts) { function textContentResolved(textContent) {
var textItems = textContent.items;
var str = ''; var str = '';
for (var i = 0; i < bidiTexts.length; i++) { for (var i = 0; i < textItems.length; i++) {
str += bidiTexts[i].str; str += textItems[i].str;
} }
// Store the pageContent as a string. // Store the pageContent as a string.

97
web/text_layer_builder.js

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* globals CustomStyle, PDFFindController, scrollIntoView */ /* globals CustomStyle, PDFFindController, scrollIntoView, PDFJS */
'use strict'; 'use strict';
@ -40,6 +40,7 @@ var TextLayerBuilder = function textLayerBuilder(options) {
this.lastScrollSource = options.lastScrollSource; this.lastScrollSource = options.lastScrollSource;
this.viewport = options.viewport; this.viewport = options.viewport;
this.isViewerInPresentationMode = options.isViewerInPresentationMode; this.isViewerInPresentationMode = options.isViewerInPresentationMode;
this.textDivs = [];
if (typeof PDFFindController === 'undefined') { if (typeof PDFFindController === 'undefined') {
window.PDFFindController = null; window.PDFFindController = null;
@ -49,16 +50,6 @@ var TextLayerBuilder = function textLayerBuilder(options) {
this.lastScrollSource = null; this.lastScrollSource = null;
} }
this.beginLayout = function textLayerBuilderBeginLayout() {
this.textDivs = [];
this.renderingDone = false;
};
this.endLayout = function textLayerBuilderEndLayout() {
this.layoutDone = true;
this.insertDivContent();
};
this.renderLayer = function textLayerBuilderRenderLayer() { this.renderLayer = function textLayerBuilderRenderLayer() {
var textDivs = this.textDivs; var textDivs = this.textDivs;
var canvas = document.createElement('canvas'); var canvas = document.createElement('canvas');
@ -118,70 +109,56 @@ var TextLayerBuilder = function textLayerBuilder(options) {
} }
}; };
this.appendText = function textLayerBuilderAppendText(geom) { this.appendText = function textLayerBuilderAppendText(geom, styles) {
var style = styles[geom.fontName];
var textDiv = document.createElement('div'); var textDiv = document.createElement('div');
if (!/\S/.test(geom.str)) {
textDiv.dataset.isWhitespace = true;
return;
}
var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform);
var angle = Math.atan2(tx[1], tx[0]);
if (style.vertical) {
angle += Math.PI / 2;
}
var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
var fontAscent = (style.ascent ? style.ascent * fontHeight :
(style.descent ? (1 + style.descent) * fontHeight : fontHeight));
// vScale and hScale already contain the scaling to pixel units textDiv.style.position = 'absolute';
var fontHeight = geom.fontSize * Math.abs(geom.vScale); textDiv.style.left = (tx[4] + (fontAscent * Math.sin(angle))) + 'px';
textDiv.dataset.canvasWidth = geom.canvasWidth * Math.abs(geom.hScale); textDiv.style.top = (tx[5] - (fontAscent * Math.cos(angle))) + 'px';
textDiv.dataset.fontName = geom.fontName;
textDiv.dataset.angle = geom.angle * (180 / Math.PI);
textDiv.style.fontSize = fontHeight + 'px'; textDiv.style.fontSize = fontHeight + 'px';
textDiv.style.fontFamily = geom.fontFamily; textDiv.style.fontFamily = style.fontFamily;
var fontAscent = (geom.ascent ? geom.ascent * fontHeight :
(geom.descent ? (1 + geom.descent) * fontHeight : fontHeight));
textDiv.style.left = (geom.x + (fontAscent * Math.sin(geom.angle))) + 'px';
textDiv.style.top = (geom.y - (fontAscent * Math.cos(geom.angle))) + 'px';
// The content of the div is set in the `setTextContent` function. textDiv.textContent = geom.str;
textDiv.dataset.fontName = geom.fontName;
textDiv.dataset.angle = angle * (180 / Math.PI);
if (style.vertical) {
textDiv.dataset.canvasWidth = geom.height * this.viewport.scale;
} else {
textDiv.dataset.canvasWidth = geom.width * this.viewport.scale;
}
this.textDivs.push(textDiv); this.textDivs.push(textDiv);
}; };
this.insertDivContent = function textLayerUpdateTextContent() { this.setTextContent = function textLayerBuilderSetTextContent(textContent) {
// Only set the content of the divs once layout has finished, the content this.textContent = textContent;
// for the divs is available and content is not yet set on the divs.
if (!this.layoutDone || this.divContentDone || !this.textContent) {
return;
}
this.divContentDone = true;
var textDivs = this.textDivs;
var bidiTexts = this.textContent;
for (var i = 0; i < bidiTexts.length; i++) {
var bidiText = bidiTexts[i];
var textDiv = textDivs[i];
if (!/\S/.test(bidiText.str)) {
textDiv.dataset.isWhitespace = true;
continue;
}
textDiv.textContent = bidiText.str; var textItems = textContent.items;
// TODO refactor text layer to use text content position for (var i = 0; i < textItems.length; i++) {
/** this.appendText(textItems[i], textContent.styles);
* var arr = this.viewport.convertToViewportPoint(bidiText.x, bidiText.y);
* textDiv.style.left = arr[0] + 'px';
* textDiv.style.top = arr[1] + 'px';
*/
// bidiText.dir may be 'ttb' for vertical texts.
textDiv.dir = bidiText.dir;
} }
this.divContentDone = true;
this.setupRenderLayoutTimer(); this.setupRenderLayoutTimer();
}; };
this.setTextContent = function textLayerBuilderSetTextContent(textContent) {
this.textContent = textContent;
this.insertDivContent();
};
this.convertMatches = function textLayerBuilderConvertMatches(matches) { this.convertMatches = function textLayerBuilderConvertMatches(matches) {
var i = 0; var i = 0;
var iIndex = 0; var iIndex = 0;
var bidiTexts = this.textContent; var bidiTexts = this.textContent.items;
var end = bidiTexts.length - 1; var end = bidiTexts.length - 1;
var queryLen = (PDFFindController === null ? var queryLen = (PDFFindController === null ?
0 : PDFFindController.state.query.length); 0 : PDFFindController.state.query.length);
@ -240,7 +217,7 @@ var TextLayerBuilder = function textLayerBuilder(options) {
return; return;
} }
var bidiTexts = this.textContent; var bidiTexts = this.textContent.items;
var textDivs = this.textDivs; var textDivs = this.textDivs;
var prevEnd = null; var prevEnd = null;
var isSelectedPage = (PDFFindController === null ? var isSelectedPage = (PDFFindController === null ?
@ -356,7 +333,7 @@ var TextLayerBuilder = function textLayerBuilder(options) {
// Clear out all matches. // Clear out all matches.
var matches = this.matches; var matches = this.matches;
var textDivs = this.textDivs; var textDivs = this.textDivs;
var bidiTexts = this.textContent; var bidiTexts = this.textContent.items;
var clearedUntilDivIdx = -1; var clearedUntilDivIdx = -1;
// Clear out all current matches. // Clear out all current matches.

1
web/viewer.css

@ -1286,7 +1286,6 @@ canvas {
.textLayer > div { .textLayer > div {
color: transparent; color: transparent;
position: absolute; position: absolute;
line-height: 1;
white-space: pre; white-space: pre;
cursor: text; cursor: text;
} }

Loading…
Cancel
Save