diff --git a/src/core/evaluator.js b/src/core/evaluator.js index ec7e9dcfa..cf77ad510 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -920,7 +920,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { items: [], styles: Object.create(null) }; - var bidiTexts = textContent.items; + var textContentItem = { + initialized: false, + str: [], + width: 0, + height: 0, + vertical: false, + lastAdvanceWidth: 0, + lastAdvanceHeight: 0, + textAdvanceScale: 0, + transform: null, + fontName: null + }; var SPACE_FACTOR = 0.3; var MULTI_SPACE_FACTOR = 1.5; @@ -937,7 +948,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var textState; - function newTextChunk() { + function ensureTextContentItem() { + if (textContentItem.initialized) { + return textContentItem; + } var font = textState.font; if (!(font.loadedName in textContent.styles)) { textContent.styles[font.loadedName] = { @@ -947,24 +961,61 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { vertical: font.vertical }; } - return { - // |str| is initially an array which we push individual chars to, and - // then runBidi() overwrites it with the final string. - str: [], - dir: null, - width: 0, - height: 0, - transform: null, - fontName: font.loadedName - }; + textContentItem.fontName = font.loadedName; + + // 9.4.4 Text Space Details + var tsm = [textState.fontSize * textState.textHScale, 0, + 0, textState.fontSize, + 0, textState.textRise]; + + if (font.isType3Font && + textState.fontMatrix !== FONT_IDENTITY_MATRIX && + textState.fontSize === 1) { + var glyphHeight = font.bbox[3] - font.bbox[1]; + if (glyphHeight > 0) { + glyphHeight = glyphHeight * textState.fontMatrix[3]; + tsm[3] *= glyphHeight; + } + } + + var trm = Util.transform(textState.ctm, + Util.transform(textState.textMatrix, tsm)); + textContentItem.transform = trm; + if (!font.vertical) { + textContentItem.width = 0; + textContentItem.height = Math.sqrt(trm[2] * trm[2] + trm[3] * trm[3]); + textContentItem.vertical = false; + } else { + textContentItem.width = Math.sqrt(trm[0] * trm[0] + trm[1] * trm[1]); + textContentItem.height = 0; + textContentItem.vertical = true; + } + + 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); + textContentItem.textAdvanceScale = scaleCtmX * scaleLineX; + textContentItem.lastAdvanceWidth = 0; + textContentItem.lastAdvanceHeight = 0; + + textContentItem.initialized = true; + return textContentItem; } - function runBidi(textChunk) { + function runBidiTransform(textChunk) { var str = textChunk.str.join(''); - var bidiResult = PDFJS.bidi(str, -1, textState.font.vertical); - textChunk.str = bidiResult.str; - textChunk.dir = bidiResult.dir; - return textChunk; + var bidiResult = PDFJS.bidi(str, -1, textChunk.vertical); + return { + str: bidiResult.str, + dir: bidiResult.dir, + width: textChunk.width, + height: textChunk.height, + transform: textChunk.transform, + fontName: textChunk.fontName + }; } function handleSetFont(fontName, fontRef) { @@ -976,33 +1027,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }); } - function buildTextGeometry(chars, textChunk) { + function buildTextContentItem(chars) { 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]; - - if (font.isType3Font && - textState.fontMatrix !== FONT_IDENTITY_MATRIX && - textState.fontSize === 1) { - var glyphHeight = font.bbox[3] - font.bbox[1]; - if (glyphHeight > 0) { - glyphHeight = glyphHeight * textState.fontMatrix[3]; - tsm[3] *= glyphHeight; - } - } - - 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 textChunk = ensureTextContentItem(); var width = 0; var height = 0; var glyphs = font.charsToGlyphs(chars); @@ -1071,16 +1098,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { textChunk.str.push(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; + textChunk.lastAdvanceWidth = width; + textChunk.width += width * textChunk.textAdvanceScale; } else { - textChunk.height += Math.abs(height * scaleCtmX * scaleLineX); + textChunk.lastAdvanceHeight = height; + textChunk.height += Math.abs(height * textChunk.textAdvanceScale); } return textChunk; } @@ -1101,6 +1124,16 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } } + function flushTextContentItem() { + if (!textContentItem.initialized) { + return; + } + textContent.items.push(runBidiTransform(textContentItem)); + + textContentItem.initialized = false; + textContentItem.str.length = 0; + } + var timeSlotManager = new TimeSlotManager(); return new Promise(function next(resolve, reject) { @@ -1119,35 +1152,60 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { textState = stateManager.state; var fn = operation.fn; args = operation.args; + var advance; switch (fn | 0) { case OPS.setFont: + flushTextContentItem(); textState.fontSize = args[1]; return handleSetFont(args[0].name).then(function() { next(resolve, reject); }, reject); case OPS.setTextRise: + flushTextContentItem(); textState.textRise = args[0]; break; case OPS.setHScale: + flushTextContentItem(); textState.textHScale = args[0] / 100; break; case OPS.setLeading: + flushTextContentItem(); textState.leading = args[0]; break; case OPS.moveText: + // Optimization to treat same line movement as advance + var isSameTextLine = !textState.font ? false : + ((textState.font.vertical ? args[0] : args[1]) === 0); + if (isSameTextLine && textContentItem.initialized) { + textState.translateTextLineMatrix(args[0], args[1]); + textContentItem.width += + (args[0] - textContentItem.lastAdvanceWidth); + textContentItem.height += + (args[1] - textContentItem.lastAdvanceHeight); + advance = (args[0] - args[1]) * 1000 / textState.fontSize; + if (advance > 0) { + addFakeSpaces(advance, textContentItem.str); + } + break; + } + + flushTextContentItem(); textState.translateTextLineMatrix(args[0], args[1]); textState.textMatrix = textState.textLineMatrix.slice(); break; case OPS.setLeadingMoveText: + flushTextContentItem(); textState.leading = -args[1]; textState.translateTextLineMatrix(args[0], args[1]); textState.textMatrix = textState.textLineMatrix.slice(); break; case OPS.nextLine: + flushTextContentItem(); textState.carriageReturn(); break; case OPS.setTextMatrix: + flushTextContentItem(); textState.setTextMatrix(args[0], args[1], args[2], args[3], args[4], args[5]); textState.setTextLineMatrix(args[0], args[1], args[2], args[3], @@ -1160,17 +1218,20 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { textState.wordSpacing = args[0]; break; case OPS.beginText: + flushTextContentItem(); textState.textMatrix = IDENTITY_MATRIX.slice(); textState.textLineMatrix = IDENTITY_MATRIX.slice(); break; case OPS.showSpacedText: var items = args[0]; - var textChunk = newTextChunk(); var offset; for (var j = 0, jj = items.length; j < jj; j++) { if (typeof items[j] === 'string') { - buildTextGeometry(items[j], textChunk); + buildTextContentItem(items[j]); } else { + if (j === 0) { + ensureTextContentItem(); + } // PDF Specification 5.3.2 states: // The number is expressed in thousandths of a unit of text // space. @@ -1179,7 +1240,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // In the default coordinate system, a positive adjustment // has the effect of moving the next glyph painted either to // the left or down by the given amount. - var advance = items[j]; + advance = items[j]; var val = advance * textState.fontSize / 1000; if (textState.font.vertical) { offset = val * @@ -1187,37 +1248,39 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { textState.textMatrix[3]); textState.translateTextMatrix(0, val); // Value needs to be added to height to paint down. - textChunk.height += offset; + textContentItem.height += offset; } else { offset = val * ( textState.textHScale * textState.textMatrix[0] + textState.textMatrix[1]); textState.translateTextMatrix(-val, 0); // Value needs to be subtracted from width to paint left. - textChunk.width -= offset; + textContentItem.width -= offset; advance = -advance; } if (advance > 0) { - addFakeSpaces(advance, textChunk.str); + addFakeSpaces(advance, textContentItem.str); } } } - bidiTexts.push(runBidi(textChunk)); break; case OPS.showText: - bidiTexts.push(runBidi(buildTextGeometry(args[0]))); + buildTextContentItem(args[0]); break; case OPS.nextLineShowText: + flushTextContentItem(); textState.carriageReturn(); - bidiTexts.push(runBidi(buildTextGeometry(args[0]))); + buildTextContentItem(args[0]); break; case OPS.nextLineSetSpacingShowText: + flushTextContentItem(); textState.wordSpacing = args[0]; textState.charSpacing = args[1]; textState.carriageReturn(); - bidiTexts.push(runBidi(buildTextGeometry(args[2]))); + buildTextContentItem(args[2]); break; case OPS.paintXObject: + flushTextContentItem(); if (args[0].code) { break; } @@ -1229,7 +1292,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var name = args[0].name; if (xobjsCache.key === name) { if (xobjsCache.texts) { - Util.appendToArray(bidiTexts, xobjsCache.texts.items); + Util.appendToArray(textContent.items, xobjsCache.texts.items); Util.extendObj(textContent.styles, xobjsCache.texts.styles); } break; @@ -1260,7 +1323,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { return self.getTextContent(xobj, task, xobj.dict.get('Resources') || resources, stateManager). then(function (formTextContent) { - Util.appendToArray(bidiTexts, formTextContent.items); + Util.appendToArray(textContent.items, formTextContent.items); Util.extendObj(textContent.styles, formTextContent.styles); stateManager.restore(); @@ -1270,6 +1333,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { next(resolve, reject); }, reject); case OPS.setGState: + flushTextContentItem(); var dictName = args[0]; var extGState = resources.get('ExtGState'); @@ -1300,6 +1364,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { }, reject); return; } + flushTextContentItem(); resolve(textContent); }); },