text_layer_builder.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /**
  2. * Code extracted from pdf.js' viewer.js file. This contains code that is relevant to building the text overlays. I
  3. * have removed dependencies on viewer.js and viewer.html.
  4. *
  5. * -- Vivin Suresh Paliath (http://vivin.net)
  6. */
  7. var CustomStyle = (function CustomStyleClosure() {
  8. // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
  9. // animate-css-transforms-firefox-webkit.html
  10. // in some versions of IE9 it is critical that ms appear in this list
  11. // before Moz
  12. var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
  13. var _cache = { };
  14. function CustomStyle() {
  15. }
  16. CustomStyle.getProp = function get(propName, element) {
  17. // check cache only when no element is given
  18. if (arguments.length == 1 && typeof _cache[propName] == 'string') {
  19. return _cache[propName];
  20. }
  21. element = element || document.documentElement;
  22. var style = element.style, prefixed, uPropName;
  23. // test standard property first
  24. if (typeof style[propName] == 'string') {
  25. return (_cache[propName] = propName);
  26. }
  27. // capitalize
  28. uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
  29. // test vendor specific properties
  30. for (var i = 0, l = prefixes.length; i < l; i++) {
  31. prefixed = prefixes[i] + uPropName;
  32. if (typeof style[prefixed] == 'string') {
  33. return (_cache[propName] = prefixed);
  34. }
  35. }
  36. //if all fails then set to undefined
  37. return (_cache[propName] = 'undefined');
  38. };
  39. CustomStyle.setProp = function set(propName, element, str) {
  40. var prop = this.getProp(propName);
  41. if (prop != 'undefined')
  42. element.style[prop] = str;
  43. };
  44. return CustomStyle;
  45. })();
  46. var TextLayerBuilder = function textLayerBuilder(textLayerDiv, pageIdx) {
  47. var textLayerFrag = document.createDocumentFragment();
  48. this.textLayerDiv = textLayerDiv;
  49. this.layoutDone = false;
  50. this.divContentDone = false;
  51. this.pageIdx = pageIdx;
  52. this.matches = [];
  53. this.beginLayout = function textLayerBuilderBeginLayout() {
  54. this.textDivs = [];
  55. this.renderingDone = false;
  56. };
  57. this.endLayout = function textLayerBuilderEndLayout() {
  58. this.layoutDone = true;
  59. this.insertDivContent();
  60. };
  61. this.renderLayer = function textLayerBuilderRenderLayer() {
  62. var textDivs = this.textDivs;
  63. var bidiTexts = this.textContent.bidiTexts;
  64. var textLayerDiv = this.textLayerDiv;
  65. var canvas = document.createElement('canvas');
  66. var ctx = canvas.getContext('2d');
  67. // No point in rendering so many divs as it'd make the browser unusable
  68. // even after the divs are rendered
  69. var MAX_TEXT_DIVS_TO_RENDER = 100000;
  70. if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER)
  71. return;
  72. for (var i = 0, ii = textDivs.length; i < ii; i++) {
  73. var textDiv = textDivs[i];
  74. if ('isWhitespace' in textDiv.dataset) {
  75. continue;
  76. }
  77. textLayerFrag.appendChild(textDiv);
  78. ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
  79. var width = ctx.measureText(textDiv.textContent).width;
  80. if (width > 0) {
  81. var textScale = textDiv.dataset.canvasWidth / width;
  82. var transform = 'scale(' + textScale + ', 1)';
  83. if (bidiTexts[i].dir === 'ttb') {
  84. transform = 'rotate(90deg) ' + transform;
  85. }
  86. CustomStyle.setProp('transform', textDiv, transform);
  87. CustomStyle.setProp('transformOrigin', textDiv, '0% 0%');
  88. textLayerDiv.appendChild(textDiv);
  89. }
  90. }
  91. this.renderingDone = true;
  92. textLayerDiv.appendChild(textLayerFrag);
  93. };
  94. this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
  95. // Schedule renderLayout() if user has been scrolling, otherwise
  96. // run it right away
  97. var RENDER_DELAY = 200; // in ms
  98. var self = this;
  99. //0 was originally PDFView.lastScroll
  100. if (Date.now() - 0 > RENDER_DELAY) {
  101. // Render right away
  102. this.renderLayer();
  103. } else {
  104. // Schedule
  105. if (this.renderTimer)
  106. clearTimeout(this.renderTimer);
  107. this.renderTimer = setTimeout(function () {
  108. self.setupRenderLayoutTimer();
  109. }, RENDER_DELAY);
  110. }
  111. };
  112. this.appendText = function textLayerBuilderAppendText(geom) {
  113. var textDiv = document.createElement('div');
  114. // vScale and hScale already contain the scaling to pixel units
  115. var fontHeight = geom.fontSize * Math.abs(geom.vScale);
  116. textDiv.dataset.canvasWidth = geom.canvasWidth * geom.hScale;
  117. textDiv.dataset.fontName = geom.fontName;
  118. textDiv.style.fontSize = fontHeight + 'px';
  119. textDiv.style.fontFamily = geom.fontFamily;
  120. textDiv.style.left = geom.x + 'px';
  121. textDiv.style.top = (geom.y - fontHeight) + 'px';
  122. // The content of the div is set in the `setTextContent` function.
  123. this.textDivs.push(textDiv);
  124. };
  125. this.insertDivContent = function textLayerUpdateTextContent() {
  126. // Only set the content of the divs once layout has finished, the content
  127. // for the divs is available and content is not yet set on the divs.
  128. if (!this.layoutDone || this.divContentDone || !this.textContent)
  129. return;
  130. this.divContentDone = true;
  131. var textDivs = this.textDivs;
  132. var bidiTexts = this.textContent.bidiTexts;
  133. for (var i = 0; i < bidiTexts.length; i++) {
  134. var bidiText = bidiTexts[i];
  135. var textDiv = textDivs[i];
  136. if (!/\S/.test(bidiText.str)) {
  137. textDiv.dataset.isWhitespace = true;
  138. continue;
  139. }
  140. textDiv.textContent = bidiText.str;
  141. // bidiText.dir may be 'ttb' for vertical texts.
  142. textDiv.dir = bidiText.dir === 'rtl' ? 'rtl' : 'ltr';
  143. }
  144. this.setupRenderLayoutTimer();
  145. };
  146. this.setTextContent = function textLayerBuilderSetTextContent(textContent) {
  147. this.textContent = textContent;
  148. this.insertDivContent();
  149. };
  150. };
  151. /**
  152. * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
  153. * @return {Object} The object with horizontal (sx) and vertical (sy)
  154. scales. The scaled property is set to false if scaling is
  155. not required, true otherwise.
  156. */
  157. function getOutputScale() {
  158. var pixelRatio = 'devicePixelRatio' in window ? window.devicePixelRatio : 1;
  159. return {
  160. sx: pixelRatio,
  161. sy: pixelRatio,
  162. scaled: pixelRatio != 1
  163. };
  164. }