text_layer.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /* Copyright 2017 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. 'use strict';
  16. Object.defineProperty(exports, "__esModule", {
  17. value: true
  18. });
  19. exports.renderTextLayer = undefined;
  20. var _util = require('../shared/util');
  21. var _dom_utils = require('./dom_utils');
  22. var renderTextLayer = function renderTextLayerClosure() {
  23. var MAX_TEXT_DIVS_TO_RENDER = 100000;
  24. var NonWhitespaceRegexp = /\S/;
  25. function isAllWhitespace(str) {
  26. return !NonWhitespaceRegexp.test(str);
  27. }
  28. var styleBuf = ['left: ', 0, 'px; top: ', 0, 'px; font-size: ', 0, 'px; font-family: ', '', ';'];
  29. function appendText(task, geom, styles) {
  30. var textDiv = document.createElement('div');
  31. var textDivProperties = {
  32. style: null,
  33. angle: 0,
  34. canvasWidth: 0,
  35. isWhitespace: false,
  36. originalTransform: null,
  37. paddingBottom: 0,
  38. paddingLeft: 0,
  39. paddingRight: 0,
  40. paddingTop: 0,
  41. scale: 1
  42. };
  43. task._textDivs.push(textDiv);
  44. if (isAllWhitespace(geom.str)) {
  45. textDivProperties.isWhitespace = true;
  46. task._textDivProperties.set(textDiv, textDivProperties);
  47. return;
  48. }
  49. var tx = _util.Util.transform(task._viewport.transform, geom.transform);
  50. var angle = Math.atan2(tx[1], tx[0]);
  51. var style = styles[geom.fontName];
  52. if (style.vertical) {
  53. angle += Math.PI / 2;
  54. }
  55. var fontHeight = Math.sqrt(tx[2] * tx[2] + tx[3] * tx[3]);
  56. var fontAscent = fontHeight;
  57. if (style.ascent) {
  58. fontAscent = style.ascent * fontAscent;
  59. } else if (style.descent) {
  60. fontAscent = (1 + style.descent) * fontAscent;
  61. }
  62. var left;
  63. var top;
  64. if (angle === 0) {
  65. left = tx[4];
  66. top = tx[5] - fontAscent;
  67. } else {
  68. left = tx[4] + fontAscent * Math.sin(angle);
  69. top = tx[5] - fontAscent * Math.cos(angle);
  70. }
  71. styleBuf[1] = left;
  72. styleBuf[3] = top;
  73. styleBuf[5] = fontHeight;
  74. styleBuf[7] = style.fontFamily;
  75. textDivProperties.style = styleBuf.join('');
  76. textDiv.setAttribute('style', textDivProperties.style);
  77. textDiv.textContent = geom.str;
  78. if ((0, _dom_utils.getDefaultSetting)('pdfBug')) {
  79. textDiv.dataset.fontName = geom.fontName;
  80. }
  81. if (angle !== 0) {
  82. textDivProperties.angle = angle * (180 / Math.PI);
  83. }
  84. if (geom.str.length > 1) {
  85. if (style.vertical) {
  86. textDivProperties.canvasWidth = geom.height * task._viewport.scale;
  87. } else {
  88. textDivProperties.canvasWidth = geom.width * task._viewport.scale;
  89. }
  90. }
  91. task._textDivProperties.set(textDiv, textDivProperties);
  92. if (task._enhanceTextSelection) {
  93. var angleCos = 1,
  94. angleSin = 0;
  95. if (angle !== 0) {
  96. angleCos = Math.cos(angle);
  97. angleSin = Math.sin(angle);
  98. }
  99. var divWidth = (style.vertical ? geom.height : geom.width) * task._viewport.scale;
  100. var divHeight = fontHeight;
  101. var m, b;
  102. if (angle !== 0) {
  103. m = [angleCos, angleSin, -angleSin, angleCos, left, top];
  104. b = _util.Util.getAxialAlignedBoundingBox([0, 0, divWidth, divHeight], m);
  105. } else {
  106. b = [left, top, left + divWidth, top + divHeight];
  107. }
  108. task._bounds.push({
  109. left: b[0],
  110. top: b[1],
  111. right: b[2],
  112. bottom: b[3],
  113. div: textDiv,
  114. size: [divWidth, divHeight],
  115. m: m
  116. });
  117. }
  118. }
  119. function render(task) {
  120. if (task._canceled) {
  121. return;
  122. }
  123. var textLayerFrag = task._container;
  124. var textDivs = task._textDivs;
  125. var capability = task._capability;
  126. var textDivsLength = textDivs.length;
  127. if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
  128. task._renderingDone = true;
  129. capability.resolve();
  130. return;
  131. }
  132. var canvas = document.createElement('canvas');
  133. canvas.mozOpaque = true;
  134. var ctx = canvas.getContext('2d', { alpha: false });
  135. var lastFontSize;
  136. var lastFontFamily;
  137. for (var i = 0; i < textDivsLength; i++) {
  138. var textDiv = textDivs[i];
  139. var textDivProperties = task._textDivProperties.get(textDiv);
  140. if (textDivProperties.isWhitespace) {
  141. continue;
  142. }
  143. var fontSize = textDiv.style.fontSize;
  144. var fontFamily = textDiv.style.fontFamily;
  145. if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
  146. ctx.font = fontSize + ' ' + fontFamily;
  147. lastFontSize = fontSize;
  148. lastFontFamily = fontFamily;
  149. }
  150. var width = ctx.measureText(textDiv.textContent).width;
  151. textLayerFrag.appendChild(textDiv);
  152. var transform = '';
  153. if (textDivProperties.canvasWidth !== 0 && width > 0) {
  154. textDivProperties.scale = textDivProperties.canvasWidth / width;
  155. transform = 'scaleX(' + textDivProperties.scale + ')';
  156. }
  157. if (textDivProperties.angle !== 0) {
  158. transform = 'rotate(' + textDivProperties.angle + 'deg) ' + transform;
  159. }
  160. if (transform !== '') {
  161. textDivProperties.originalTransform = transform;
  162. _dom_utils.CustomStyle.setProp('transform', textDiv, transform);
  163. }
  164. task._textDivProperties.set(textDiv, textDivProperties);
  165. }
  166. task._renderingDone = true;
  167. capability.resolve();
  168. }
  169. function expand(task) {
  170. var bounds = task._bounds;
  171. var viewport = task._viewport;
  172. var expanded = expandBounds(viewport.width, viewport.height, bounds);
  173. for (var i = 0; i < expanded.length; i++) {
  174. var div = bounds[i].div;
  175. var divProperties = task._textDivProperties.get(div);
  176. if (divProperties.angle === 0) {
  177. divProperties.paddingLeft = bounds[i].left - expanded[i].left;
  178. divProperties.paddingTop = bounds[i].top - expanded[i].top;
  179. divProperties.paddingRight = expanded[i].right - bounds[i].right;
  180. divProperties.paddingBottom = expanded[i].bottom - bounds[i].bottom;
  181. task._textDivProperties.set(div, divProperties);
  182. continue;
  183. }
  184. var e = expanded[i],
  185. b = bounds[i];
  186. var m = b.m,
  187. c = m[0],
  188. s = m[1];
  189. var points = [[0, 0], [0, b.size[1]], [b.size[0], 0], b.size];
  190. var ts = new Float64Array(64);
  191. points.forEach(function (p, i) {
  192. var t = _util.Util.applyTransform(p, m);
  193. ts[i + 0] = c && (e.left - t[0]) / c;
  194. ts[i + 4] = s && (e.top - t[1]) / s;
  195. ts[i + 8] = c && (e.right - t[0]) / c;
  196. ts[i + 12] = s && (e.bottom - t[1]) / s;
  197. ts[i + 16] = s && (e.left - t[0]) / -s;
  198. ts[i + 20] = c && (e.top - t[1]) / c;
  199. ts[i + 24] = s && (e.right - t[0]) / -s;
  200. ts[i + 28] = c && (e.bottom - t[1]) / c;
  201. ts[i + 32] = c && (e.left - t[0]) / -c;
  202. ts[i + 36] = s && (e.top - t[1]) / -s;
  203. ts[i + 40] = c && (e.right - t[0]) / -c;
  204. ts[i + 44] = s && (e.bottom - t[1]) / -s;
  205. ts[i + 48] = s && (e.left - t[0]) / s;
  206. ts[i + 52] = c && (e.top - t[1]) / -c;
  207. ts[i + 56] = s && (e.right - t[0]) / s;
  208. ts[i + 60] = c && (e.bottom - t[1]) / -c;
  209. });
  210. var findPositiveMin = function findPositiveMin(ts, offset, count) {
  211. var result = 0;
  212. for (var i = 0; i < count; i++) {
  213. var t = ts[offset++];
  214. if (t > 0) {
  215. result = result ? Math.min(t, result) : t;
  216. }
  217. }
  218. return result;
  219. };
  220. var boxScale = 1 + Math.min(Math.abs(c), Math.abs(s));
  221. divProperties.paddingLeft = findPositiveMin(ts, 32, 16) / boxScale;
  222. divProperties.paddingTop = findPositiveMin(ts, 48, 16) / boxScale;
  223. divProperties.paddingRight = findPositiveMin(ts, 0, 16) / boxScale;
  224. divProperties.paddingBottom = findPositiveMin(ts, 16, 16) / boxScale;
  225. task._textDivProperties.set(div, divProperties);
  226. }
  227. }
  228. function expandBounds(width, height, boxes) {
  229. var bounds = boxes.map(function (box, i) {
  230. return {
  231. x1: box.left,
  232. y1: box.top,
  233. x2: box.right,
  234. y2: box.bottom,
  235. index: i,
  236. x1New: undefined,
  237. x2New: undefined
  238. };
  239. });
  240. expandBoundsLTR(width, bounds);
  241. var expanded = new Array(boxes.length);
  242. bounds.forEach(function (b) {
  243. var i = b.index;
  244. expanded[i] = {
  245. left: b.x1New,
  246. top: 0,
  247. right: b.x2New,
  248. bottom: 0
  249. };
  250. });
  251. boxes.map(function (box, i) {
  252. var e = expanded[i],
  253. b = bounds[i];
  254. b.x1 = box.top;
  255. b.y1 = width - e.right;
  256. b.x2 = box.bottom;
  257. b.y2 = width - e.left;
  258. b.index = i;
  259. b.x1New = undefined;
  260. b.x2New = undefined;
  261. });
  262. expandBoundsLTR(height, bounds);
  263. bounds.forEach(function (b) {
  264. var i = b.index;
  265. expanded[i].top = b.x1New;
  266. expanded[i].bottom = b.x2New;
  267. });
  268. return expanded;
  269. }
  270. function expandBoundsLTR(width, bounds) {
  271. bounds.sort(function (a, b) {
  272. return a.x1 - b.x1 || a.index - b.index;
  273. });
  274. var fakeBoundary = {
  275. x1: -Infinity,
  276. y1: -Infinity,
  277. x2: 0,
  278. y2: Infinity,
  279. index: -1,
  280. x1New: 0,
  281. x2New: 0
  282. };
  283. var horizon = [{
  284. start: -Infinity,
  285. end: Infinity,
  286. boundary: fakeBoundary
  287. }];
  288. bounds.forEach(function (boundary) {
  289. var i = 0;
  290. while (i < horizon.length && horizon[i].end <= boundary.y1) {
  291. i++;
  292. }
  293. var j = horizon.length - 1;
  294. while (j >= 0 && horizon[j].start >= boundary.y2) {
  295. j--;
  296. }
  297. var horizonPart, affectedBoundary;
  298. var q,
  299. k,
  300. maxXNew = -Infinity;
  301. for (q = i; q <= j; q++) {
  302. horizonPart = horizon[q];
  303. affectedBoundary = horizonPart.boundary;
  304. var xNew;
  305. if (affectedBoundary.x2 > boundary.x1) {
  306. xNew = affectedBoundary.index > boundary.index ? affectedBoundary.x1New : boundary.x1;
  307. } else if (affectedBoundary.x2New === undefined) {
  308. xNew = (affectedBoundary.x2 + boundary.x1) / 2;
  309. } else {
  310. xNew = affectedBoundary.x2New;
  311. }
  312. if (xNew > maxXNew) {
  313. maxXNew = xNew;
  314. }
  315. }
  316. boundary.x1New = maxXNew;
  317. for (q = i; q <= j; q++) {
  318. horizonPart = horizon[q];
  319. affectedBoundary = horizonPart.boundary;
  320. if (affectedBoundary.x2New === undefined) {
  321. if (affectedBoundary.x2 > boundary.x1) {
  322. if (affectedBoundary.index > boundary.index) {
  323. affectedBoundary.x2New = affectedBoundary.x2;
  324. }
  325. } else {
  326. affectedBoundary.x2New = maxXNew;
  327. }
  328. } else if (affectedBoundary.x2New > maxXNew) {
  329. affectedBoundary.x2New = Math.max(maxXNew, affectedBoundary.x2);
  330. }
  331. }
  332. var changedHorizon = [],
  333. lastBoundary = null;
  334. for (q = i; q <= j; q++) {
  335. horizonPart = horizon[q];
  336. affectedBoundary = horizonPart.boundary;
  337. var useBoundary = affectedBoundary.x2 > boundary.x2 ? affectedBoundary : boundary;
  338. if (lastBoundary === useBoundary) {
  339. changedHorizon[changedHorizon.length - 1].end = horizonPart.end;
  340. } else {
  341. changedHorizon.push({
  342. start: horizonPart.start,
  343. end: horizonPart.end,
  344. boundary: useBoundary
  345. });
  346. lastBoundary = useBoundary;
  347. }
  348. }
  349. if (horizon[i].start < boundary.y1) {
  350. changedHorizon[0].start = boundary.y1;
  351. changedHorizon.unshift({
  352. start: horizon[i].start,
  353. end: boundary.y1,
  354. boundary: horizon[i].boundary
  355. });
  356. }
  357. if (boundary.y2 < horizon[j].end) {
  358. changedHorizon[changedHorizon.length - 1].end = boundary.y2;
  359. changedHorizon.push({
  360. start: boundary.y2,
  361. end: horizon[j].end,
  362. boundary: horizon[j].boundary
  363. });
  364. }
  365. for (q = i; q <= j; q++) {
  366. horizonPart = horizon[q];
  367. affectedBoundary = horizonPart.boundary;
  368. if (affectedBoundary.x2New !== undefined) {
  369. continue;
  370. }
  371. var used = false;
  372. for (k = i - 1; !used && k >= 0 && horizon[k].start >= affectedBoundary.y1; k--) {
  373. used = horizon[k].boundary === affectedBoundary;
  374. }
  375. for (k = j + 1; !used && k < horizon.length && horizon[k].end <= affectedBoundary.y2; k++) {
  376. used = horizon[k].boundary === affectedBoundary;
  377. }
  378. for (k = 0; !used && k < changedHorizon.length; k++) {
  379. used = changedHorizon[k].boundary === affectedBoundary;
  380. }
  381. if (!used) {
  382. affectedBoundary.x2New = maxXNew;
  383. }
  384. }
  385. Array.prototype.splice.apply(horizon, [i, j - i + 1].concat(changedHorizon));
  386. });
  387. horizon.forEach(function (horizonPart) {
  388. var affectedBoundary = horizonPart.boundary;
  389. if (affectedBoundary.x2New === undefined) {
  390. affectedBoundary.x2New = Math.max(width, affectedBoundary.x2);
  391. }
  392. });
  393. }
  394. function TextLayerRenderTask(textContent, container, viewport, textDivs, enhanceTextSelection) {
  395. this._textContent = textContent;
  396. this._container = container;
  397. this._viewport = viewport;
  398. this._textDivs = textDivs || [];
  399. this._textDivProperties = new WeakMap();
  400. this._renderingDone = false;
  401. this._canceled = false;
  402. this._capability = (0, _util.createPromiseCapability)();
  403. this._renderTimer = null;
  404. this._bounds = [];
  405. this._enhanceTextSelection = !!enhanceTextSelection;
  406. }
  407. TextLayerRenderTask.prototype = {
  408. get promise() {
  409. return this._capability.promise;
  410. },
  411. cancel: function TextLayer_cancel() {
  412. this._canceled = true;
  413. if (this._renderTimer !== null) {
  414. clearTimeout(this._renderTimer);
  415. this._renderTimer = null;
  416. }
  417. this._capability.reject('canceled');
  418. },
  419. _render: function TextLayer_render(timeout) {
  420. var _this = this;
  421. var textItems = this._textContent.items;
  422. var textStyles = this._textContent.styles;
  423. for (var i = 0, len = textItems.length; i < len; i++) {
  424. appendText(this, textItems[i], textStyles);
  425. }
  426. if (!timeout) {
  427. render(this);
  428. } else {
  429. this._renderTimer = setTimeout(function () {
  430. render(_this);
  431. _this._renderTimer = null;
  432. }, timeout);
  433. }
  434. },
  435. expandTextDivs: function TextLayer_expandTextDivs(expandDivs) {
  436. if (!this._enhanceTextSelection || !this._renderingDone) {
  437. return;
  438. }
  439. if (this._bounds !== null) {
  440. expand(this);
  441. this._bounds = null;
  442. }
  443. for (var i = 0, ii = this._textDivs.length; i < ii; i++) {
  444. var div = this._textDivs[i];
  445. var divProperties = this._textDivProperties.get(div);
  446. if (divProperties.isWhitespace) {
  447. continue;
  448. }
  449. if (expandDivs) {
  450. var transform = '',
  451. padding = '';
  452. if (divProperties.scale !== 1) {
  453. transform = 'scaleX(' + divProperties.scale + ')';
  454. }
  455. if (divProperties.angle !== 0) {
  456. transform = 'rotate(' + divProperties.angle + 'deg) ' + transform;
  457. }
  458. if (divProperties.paddingLeft !== 0) {
  459. padding += ' padding-left: ' + divProperties.paddingLeft / divProperties.scale + 'px;';
  460. transform += ' translateX(' + -divProperties.paddingLeft / divProperties.scale + 'px)';
  461. }
  462. if (divProperties.paddingTop !== 0) {
  463. padding += ' padding-top: ' + divProperties.paddingTop + 'px;';
  464. transform += ' translateY(' + -divProperties.paddingTop + 'px)';
  465. }
  466. if (divProperties.paddingRight !== 0) {
  467. padding += ' padding-right: ' + divProperties.paddingRight / divProperties.scale + 'px;';
  468. }
  469. if (divProperties.paddingBottom !== 0) {
  470. padding += ' padding-bottom: ' + divProperties.paddingBottom + 'px;';
  471. }
  472. if (padding !== '') {
  473. div.setAttribute('style', divProperties.style + padding);
  474. }
  475. if (transform !== '') {
  476. _dom_utils.CustomStyle.setProp('transform', div, transform);
  477. }
  478. } else {
  479. div.style.padding = 0;
  480. _dom_utils.CustomStyle.setProp('transform', div, divProperties.originalTransform || '');
  481. }
  482. }
  483. }
  484. };
  485. function renderTextLayer(renderParameters) {
  486. var task = new TextLayerRenderTask(renderParameters.textContent, renderParameters.container, renderParameters.viewport, renderParameters.textDivs, renderParameters.enhanceTextSelection);
  487. task._render(renderParameters.timeout);
  488. return task;
  489. }
  490. return renderTextLayer;
  491. }();
  492. exports.renderTextLayer = renderTextLayer;