7cb055307d
Some browsers render certain special characters with width 0, others with strictly positive width. (For example, the Greek Delta, Δ, has width 0 in Google Chrome, and a positive width in Firefox.) The `if` clause in operation so far results in different text layer DOM trees for different browsers. This commit fixes that by adding the elements independently of their width.
256 lines
8.2 KiB
JavaScript
256 lines
8.2 KiB
JavaScript
/* Copyright 2015 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
(function (root, factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
define('pdfjs/display/text_layer', ['exports', 'pdfjs/shared/util',
|
|
'pdfjs/display/dom_utils', 'pdfjs/display/global'], factory);
|
|
} else if (typeof exports !== 'undefined') {
|
|
factory(exports, require('../shared/util.js'), require('./dom_utils.js'),
|
|
require('./global.js'));
|
|
} else {
|
|
factory((root.pdfjsDisplayTextLayer = {}), root.pdfjsSharedUtil,
|
|
root.pdfjsDisplayDOMUtils, root.pdfjsDisplayGlobal);
|
|
}
|
|
}(this, function (exports, sharedUtil, displayDOMUtils, displayGlobal) {
|
|
|
|
var Util = sharedUtil.Util;
|
|
var createPromiseCapability = sharedUtil.createPromiseCapability;
|
|
var CustomStyle = displayDOMUtils.CustomStyle;
|
|
var PDFJS = displayGlobal.PDFJS;
|
|
|
|
/**
|
|
* Text layer render parameters.
|
|
*
|
|
* @typedef {Object} TextLayerRenderParameters
|
|
* @property {TextContent} textContent - Text content to render (the object is
|
|
* returned by the page's getTextContent() method).
|
|
* @property {HTMLElement} container - HTML element that will contain text runs.
|
|
* @property {PDFJS.PageViewport} viewport - The target viewport to properly
|
|
* layout the text runs.
|
|
* @property {Array} textDivs - (optional) HTML elements that are correspond
|
|
* the text items of the textContent input. This is output and shall be
|
|
* initially be set to empty array.
|
|
* @property {number} timeout - (optional) Delay in milliseconds before
|
|
* rendering of the text runs occurs.
|
|
*/
|
|
var renderTextLayer = (function renderTextLayerClosure() {
|
|
var MAX_TEXT_DIVS_TO_RENDER = 100000;
|
|
|
|
var NonWhitespaceRegexp = /\S/;
|
|
|
|
function isAllWhitespace(str) {
|
|
return !NonWhitespaceRegexp.test(str);
|
|
}
|
|
|
|
function appendText(textDivs, viewport, geom, styles) {
|
|
var style = styles[geom.fontName];
|
|
var textDiv = document.createElement('div');
|
|
textDivs.push(textDiv);
|
|
if (isAllWhitespace(geom.str)) {
|
|
textDiv.dataset.isWhitespace = true;
|
|
return;
|
|
}
|
|
var tx = Util.transform(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 = fontHeight;
|
|
if (style.ascent) {
|
|
fontAscent = style.ascent * fontAscent;
|
|
} else if (style.descent) {
|
|
fontAscent = (1 + style.descent) * fontAscent;
|
|
}
|
|
|
|
var left;
|
|
var top;
|
|
if (angle === 0) {
|
|
left = tx[4];
|
|
top = tx[5] - fontAscent;
|
|
} else {
|
|
left = tx[4] + (fontAscent * Math.sin(angle));
|
|
top = tx[5] - (fontAscent * Math.cos(angle));
|
|
}
|
|
textDiv.style.left = left + 'px';
|
|
textDiv.style.top = top + 'px';
|
|
textDiv.style.fontSize = fontHeight + 'px';
|
|
textDiv.style.fontFamily = style.fontFamily;
|
|
|
|
textDiv.textContent = geom.str;
|
|
// |fontName| is only used by the Font Inspector. This test will succeed
|
|
// when e.g. the Font Inspector is off but the Stepper is on, but it's
|
|
// not worth the effort to do a more accurate test.
|
|
if (PDFJS.pdfBug) {
|
|
textDiv.dataset.fontName = geom.fontName;
|
|
}
|
|
// Storing into dataset will convert number into string.
|
|
if (angle !== 0) {
|
|
textDiv.dataset.angle = angle * (180 / Math.PI);
|
|
}
|
|
// We don't bother scaling single-char text divs, because it has very
|
|
// little effect on text highlighting. This makes scrolling on docs with
|
|
// lots of such divs a lot faster.
|
|
if (geom.str.length > 1) {
|
|
if (style.vertical) {
|
|
textDiv.dataset.canvasWidth = geom.height * viewport.scale;
|
|
} else {
|
|
textDiv.dataset.canvasWidth = geom.width * viewport.scale;
|
|
}
|
|
}
|
|
}
|
|
|
|
function render(task) {
|
|
if (task._canceled) {
|
|
return;
|
|
}
|
|
var textLayerFrag = task._container;
|
|
var textDivs = task._textDivs;
|
|
var capability = task._capability;
|
|
var textDivsLength = textDivs.length;
|
|
|
|
// No point in rendering many divs as it would make the browser
|
|
// unusable even after the divs are rendered.
|
|
if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
|
|
capability.resolve();
|
|
return;
|
|
}
|
|
|
|
var canvas = document.createElement('canvas');
|
|
//#if MOZCENTRAL || FIREFOX || GENERIC
|
|
canvas.mozOpaque = true;
|
|
//#endif
|
|
var ctx = canvas.getContext('2d', {alpha: false});
|
|
|
|
var lastFontSize;
|
|
var lastFontFamily;
|
|
for (var i = 0; i < textDivsLength; i++) {
|
|
var textDiv = textDivs[i];
|
|
if (textDiv.dataset.isWhitespace !== undefined) {
|
|
continue;
|
|
}
|
|
|
|
var fontSize = textDiv.style.fontSize;
|
|
var fontFamily = textDiv.style.fontFamily;
|
|
|
|
// Only build font string and set to context if different from last.
|
|
if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
|
|
ctx.font = fontSize + ' ' + fontFamily;
|
|
lastFontSize = fontSize;
|
|
lastFontFamily = fontFamily;
|
|
}
|
|
|
|
var width = ctx.measureText(textDiv.textContent).width;
|
|
textLayerFrag.appendChild(textDiv);
|
|
var transform;
|
|
if (textDiv.dataset.canvasWidth !== undefined && width > 0) {
|
|
// Dataset values come of type string.
|
|
var textScale = textDiv.dataset.canvasWidth / width;
|
|
transform = 'scaleX(' + textScale + ')';
|
|
} else {
|
|
transform = '';
|
|
}
|
|
var rotation = textDiv.dataset.angle;
|
|
if (rotation) {
|
|
transform = 'rotate(' + rotation + 'deg) ' + transform;
|
|
}
|
|
if (transform) {
|
|
CustomStyle.setProp('transform' , textDiv, transform);
|
|
}
|
|
}
|
|
capability.resolve();
|
|
}
|
|
|
|
/**
|
|
* Text layer rendering task.
|
|
*
|
|
* @param {TextContent} textContent
|
|
* @param {HTMLElement} container
|
|
* @param {PDFJS.PageViewport} viewport
|
|
* @param {Array} textDivs
|
|
* @private
|
|
*/
|
|
function TextLayerRenderTask(textContent, container, viewport, textDivs) {
|
|
this._textContent = textContent;
|
|
this._container = container;
|
|
this._viewport = viewport;
|
|
textDivs = textDivs || [];
|
|
this._textDivs = textDivs;
|
|
this._canceled = false;
|
|
this._capability = createPromiseCapability();
|
|
this._renderTimer = null;
|
|
}
|
|
TextLayerRenderTask.prototype = {
|
|
get promise() {
|
|
return this._capability.promise;
|
|
},
|
|
|
|
cancel: function TextLayer_cancel() {
|
|
this._canceled = true;
|
|
if (this._renderTimer !== null) {
|
|
clearTimeout(this._renderTimer);
|
|
this._renderTimer = null;
|
|
}
|
|
this._capability.reject('canceled');
|
|
},
|
|
|
|
_render: function TextLayer_render(timeout) {
|
|
var textItems = this._textContent.items;
|
|
var styles = this._textContent.styles;
|
|
var textDivs = this._textDivs;
|
|
var viewport = this._viewport;
|
|
for (var i = 0, len = textItems.length; i < len; i++) {
|
|
appendText(textDivs, viewport, textItems[i], styles);
|
|
}
|
|
|
|
if (!timeout) { // Render right away
|
|
render(this);
|
|
} else { // Schedule
|
|
var self = this;
|
|
this._renderTimer = setTimeout(function() {
|
|
render(self);
|
|
self._renderTimer = null;
|
|
}, timeout);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Starts rendering of the text layer.
|
|
*
|
|
* @param {TextLayerRenderParameters} renderParameters
|
|
* @returns {TextLayerRenderTask}
|
|
*/
|
|
function renderTextLayer(renderParameters) {
|
|
var task = new TextLayerRenderTask(renderParameters.textContent,
|
|
renderParameters.container,
|
|
renderParameters.viewport,
|
|
renderParameters.textDivs);
|
|
task._render(renderParameters.timeout);
|
|
return task;
|
|
}
|
|
|
|
return renderTextLayer;
|
|
})();
|
|
|
|
PDFJS.renderTextLayer = renderTextLayer;
|
|
|
|
exports.renderTextLayer = renderTextLayer;
|
|
}));
|