2
0

text_layer.js 15 KB

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