UNPKG

21.2 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * Javascript code in this page
4 *
5 * Copyright 2021 Mozilla Foundation
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * @licend The above is the entire license notice for the
20 * Javascript code in this page
21 */
22"use strict";
23
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.renderTextLayer = renderTextLayer;
28
29var _util = require("../shared/util.js");
30
31const MAX_TEXT_DIVS_TO_RENDER = 100000;
32const DEFAULT_FONT_SIZE = 30;
33const DEFAULT_FONT_ASCENT = 0.8;
34const ascentCache = new Map();
35const AllWhitespaceRegexp = /^\s+$/g;
36
37function getAscent(fontFamily, ctx) {
38 const cachedAscent = ascentCache.get(fontFamily);
39
40 if (cachedAscent) {
41 return cachedAscent;
42 }
43
44 ctx.save();
45 ctx.font = `${DEFAULT_FONT_SIZE}px ${fontFamily}`;
46 const metrics = ctx.measureText("");
47 let ascent = metrics.fontBoundingBoxAscent;
48 let descent = Math.abs(metrics.fontBoundingBoxDescent);
49
50 if (ascent) {
51 ctx.restore();
52 const ratio = ascent / (ascent + descent);
53 ascentCache.set(fontFamily, ratio);
54 return ratio;
55 }
56
57 ctx.strokeStyle = "red";
58 ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
59 ctx.strokeText("g", 0, 0);
60 let pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data;
61 descent = 0;
62
63 for (let i = pixels.length - 1 - 3; i >= 0; i -= 4) {
64 if (pixels[i] > 0) {
65 descent = Math.ceil(i / 4 / DEFAULT_FONT_SIZE);
66 break;
67 }
68 }
69
70 ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
71 ctx.strokeText("A", 0, DEFAULT_FONT_SIZE);
72 pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data;
73 ascent = 0;
74
75 for (let i = 0, ii = pixels.length; i < ii; i += 4) {
76 if (pixels[i] > 0) {
77 ascent = DEFAULT_FONT_SIZE - Math.floor(i / 4 / DEFAULT_FONT_SIZE);
78 break;
79 }
80 }
81
82 ctx.restore();
83
84 if (ascent) {
85 const ratio = ascent / (ascent + descent);
86 ascentCache.set(fontFamily, ratio);
87 return ratio;
88 }
89
90 ascentCache.set(fontFamily, DEFAULT_FONT_ASCENT);
91 return DEFAULT_FONT_ASCENT;
92}
93
94function appendText(task, geom, styles, ctx) {
95 const textDiv = document.createElement("span");
96 const textDivProperties = task._enhanceTextSelection ? {
97 angle: 0,
98 canvasWidth: 0,
99 hasText: geom.str !== "",
100 hasEOL: geom.hasEOL,
101 originalTransform: null,
102 paddingBottom: 0,
103 paddingLeft: 0,
104 paddingRight: 0,
105 paddingTop: 0,
106 scale: 1
107 } : {
108 angle: 0,
109 canvasWidth: 0,
110 hasText: geom.str !== "",
111 hasEOL: geom.hasEOL
112 };
113
114 task._textDivs.push(textDiv);
115
116 const tx = _util.Util.transform(task._viewport.transform, geom.transform);
117
118 let angle = Math.atan2(tx[1], tx[0]);
119 const style = styles[geom.fontName];
120
121 if (style.vertical) {
122 angle += Math.PI / 2;
123 }
124
125 const fontHeight = Math.hypot(tx[2], tx[3]);
126 const fontAscent = fontHeight * getAscent(style.fontFamily, ctx);
127 let left, top;
128
129 if (angle === 0) {
130 left = tx[4];
131 top = tx[5] - fontAscent;
132 } else {
133 left = tx[4] + fontAscent * Math.sin(angle);
134 top = tx[5] - fontAscent * Math.cos(angle);
135 }
136
137 textDiv.style.left = `${left}px`;
138 textDiv.style.top = `${top}px`;
139 textDiv.style.fontSize = `${fontHeight}px`;
140 textDiv.style.fontFamily = style.fontFamily;
141 textDiv.setAttribute("role", "presentation");
142 textDiv.textContent = geom.str;
143 textDiv.dir = geom.dir;
144
145 if (task._fontInspectorEnabled) {
146 textDiv.dataset.fontName = geom.fontName;
147 }
148
149 if (angle !== 0) {
150 textDivProperties.angle = angle * (180 / Math.PI);
151 }
152
153 let shouldScaleText = false;
154
155 if (geom.str.length > 1 || task._enhanceTextSelection && AllWhitespaceRegexp.test(geom.str)) {
156 shouldScaleText = true;
157 } else if (geom.str !== " " && geom.transform[0] !== geom.transform[3]) {
158 const absScaleX = Math.abs(geom.transform[0]),
159 absScaleY = Math.abs(geom.transform[3]);
160
161 if (absScaleX !== absScaleY && Math.max(absScaleX, absScaleY) / Math.min(absScaleX, absScaleY) > 1.5) {
162 shouldScaleText = true;
163 }
164 }
165
166 if (shouldScaleText) {
167 if (style.vertical) {
168 textDivProperties.canvasWidth = geom.height * task._viewport.scale;
169 } else {
170 textDivProperties.canvasWidth = geom.width * task._viewport.scale;
171 }
172 }
173
174 task._textDivProperties.set(textDiv, textDivProperties);
175
176 if (task._textContentStream) {
177 task._layoutText(textDiv);
178 }
179
180 if (task._enhanceTextSelection && textDivProperties.hasText) {
181 let angleCos = 1,
182 angleSin = 0;
183
184 if (angle !== 0) {
185 angleCos = Math.cos(angle);
186 angleSin = Math.sin(angle);
187 }
188
189 const divWidth = (style.vertical ? geom.height : geom.width) * task._viewport.scale;
190 const divHeight = fontHeight;
191 let m, b;
192
193 if (angle !== 0) {
194 m = [angleCos, angleSin, -angleSin, angleCos, left, top];
195 b = _util.Util.getAxialAlignedBoundingBox([0, 0, divWidth, divHeight], m);
196 } else {
197 b = [left, top, left + divWidth, top + divHeight];
198 }
199
200 task._bounds.push({
201 left: b[0],
202 top: b[1],
203 right: b[2],
204 bottom: b[3],
205 div: textDiv,
206 size: [divWidth, divHeight],
207 m
208 });
209 }
210}
211
212function render(task) {
213 if (task._canceled) {
214 return;
215 }
216
217 const textDivs = task._textDivs;
218 const capability = task._capability;
219 const textDivsLength = textDivs.length;
220
221 if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
222 task._renderingDone = true;
223 capability.resolve();
224 return;
225 }
226
227 if (!task._textContentStream) {
228 for (let i = 0; i < textDivsLength; i++) {
229 task._layoutText(textDivs[i]);
230 }
231 }
232
233 task._renderingDone = true;
234 capability.resolve();
235}
236
237function findPositiveMin(ts, offset, count) {
238 let result = 0;
239
240 for (let i = 0; i < count; i++) {
241 const t = ts[offset++];
242
243 if (t > 0) {
244 result = result ? Math.min(t, result) : t;
245 }
246 }
247
248 return result;
249}
250
251function expand(task) {
252 const bounds = task._bounds;
253 const viewport = task._viewport;
254 const expanded = expandBounds(viewport.width, viewport.height, bounds);
255
256 for (let i = 0; i < expanded.length; i++) {
257 const div = bounds[i].div;
258
259 const divProperties = task._textDivProperties.get(div);
260
261 if (divProperties.angle === 0) {
262 divProperties.paddingLeft = bounds[i].left - expanded[i].left;
263 divProperties.paddingTop = bounds[i].top - expanded[i].top;
264 divProperties.paddingRight = expanded[i].right - bounds[i].right;
265 divProperties.paddingBottom = expanded[i].bottom - bounds[i].bottom;
266
267 task._textDivProperties.set(div, divProperties);
268
269 continue;
270 }
271
272 const e = expanded[i],
273 b = bounds[i];
274 const m = b.m,
275 c = m[0],
276 s = m[1];
277 const points = [[0, 0], [0, b.size[1]], [b.size[0], 0], b.size];
278 const ts = new Float64Array(64);
279
280 for (let j = 0, jj = points.length; j < jj; j++) {
281 const t = _util.Util.applyTransform(points[j], m);
282
283 ts[j + 0] = c && (e.left - t[0]) / c;
284 ts[j + 4] = s && (e.top - t[1]) / s;
285 ts[j + 8] = c && (e.right - t[0]) / c;
286 ts[j + 12] = s && (e.bottom - t[1]) / s;
287 ts[j + 16] = s && (e.left - t[0]) / -s;
288 ts[j + 20] = c && (e.top - t[1]) / c;
289 ts[j + 24] = s && (e.right - t[0]) / -s;
290 ts[j + 28] = c && (e.bottom - t[1]) / c;
291 ts[j + 32] = c && (e.left - t[0]) / -c;
292 ts[j + 36] = s && (e.top - t[1]) / -s;
293 ts[j + 40] = c && (e.right - t[0]) / -c;
294 ts[j + 44] = s && (e.bottom - t[1]) / -s;
295 ts[j + 48] = s && (e.left - t[0]) / s;
296 ts[j + 52] = c && (e.top - t[1]) / -c;
297 ts[j + 56] = s && (e.right - t[0]) / s;
298 ts[j + 60] = c && (e.bottom - t[1]) / -c;
299 }
300
301 const boxScale = 1 + Math.min(Math.abs(c), Math.abs(s));
302 divProperties.paddingLeft = findPositiveMin(ts, 32, 16) / boxScale;
303 divProperties.paddingTop = findPositiveMin(ts, 48, 16) / boxScale;
304 divProperties.paddingRight = findPositiveMin(ts, 0, 16) / boxScale;
305 divProperties.paddingBottom = findPositiveMin(ts, 16, 16) / boxScale;
306
307 task._textDivProperties.set(div, divProperties);
308 }
309}
310
311function expandBounds(width, height, boxes) {
312 const bounds = boxes.map(function (box, i) {
313 return {
314 x1: box.left,
315 y1: box.top,
316 x2: box.right,
317 y2: box.bottom,
318 index: i,
319 x1New: undefined,
320 x2New: undefined
321 };
322 });
323 expandBoundsLTR(width, bounds);
324 const expanded = new Array(boxes.length);
325
326 for (const b of bounds) {
327 const i = b.index;
328 expanded[i] = {
329 left: b.x1New,
330 top: 0,
331 right: b.x2New,
332 bottom: 0
333 };
334 }
335
336 boxes.map(function (box, i) {
337 const e = expanded[i],
338 b = bounds[i];
339 b.x1 = box.top;
340 b.y1 = width - e.right;
341 b.x2 = box.bottom;
342 b.y2 = width - e.left;
343 b.index = i;
344 b.x1New = undefined;
345 b.x2New = undefined;
346 });
347 expandBoundsLTR(height, bounds);
348
349 for (const b of bounds) {
350 const i = b.index;
351 expanded[i].top = b.x1New;
352 expanded[i].bottom = b.x2New;
353 }
354
355 return expanded;
356}
357
358function expandBoundsLTR(width, bounds) {
359 bounds.sort(function (a, b) {
360 return a.x1 - b.x1 || a.index - b.index;
361 });
362 const fakeBoundary = {
363 x1: -Infinity,
364 y1: -Infinity,
365 x2: 0,
366 y2: Infinity,
367 index: -1,
368 x1New: 0,
369 x2New: 0
370 };
371 const horizon = [{
372 start: -Infinity,
373 end: Infinity,
374 boundary: fakeBoundary
375 }];
376
377 for (const boundary of bounds) {
378 let i = 0;
379
380 while (i < horizon.length && horizon[i].end <= boundary.y1) {
381 i++;
382 }
383
384 let j = horizon.length - 1;
385
386 while (j >= 0 && horizon[j].start >= boundary.y2) {
387 j--;
388 }
389
390 let horizonPart, affectedBoundary;
391 let q,
392 k,
393 maxXNew = -Infinity;
394
395 for (q = i; q <= j; q++) {
396 horizonPart = horizon[q];
397 affectedBoundary = horizonPart.boundary;
398 let xNew;
399
400 if (affectedBoundary.x2 > boundary.x1) {
401 xNew = affectedBoundary.index > boundary.index ? affectedBoundary.x1New : boundary.x1;
402 } else if (affectedBoundary.x2New === undefined) {
403 xNew = (affectedBoundary.x2 + boundary.x1) / 2;
404 } else {
405 xNew = affectedBoundary.x2New;
406 }
407
408 if (xNew > maxXNew) {
409 maxXNew = xNew;
410 }
411 }
412
413 boundary.x1New = maxXNew;
414
415 for (q = i; q <= j; q++) {
416 horizonPart = horizon[q];
417 affectedBoundary = horizonPart.boundary;
418
419 if (affectedBoundary.x2New === undefined) {
420 if (affectedBoundary.x2 > boundary.x1) {
421 if (affectedBoundary.index > boundary.index) {
422 affectedBoundary.x2New = affectedBoundary.x2;
423 }
424 } else {
425 affectedBoundary.x2New = maxXNew;
426 }
427 } else if (affectedBoundary.x2New > maxXNew) {
428 affectedBoundary.x2New = Math.max(maxXNew, affectedBoundary.x2);
429 }
430 }
431
432 const changedHorizon = [];
433 let lastBoundary = null;
434
435 for (q = i; q <= j; q++) {
436 horizonPart = horizon[q];
437 affectedBoundary = horizonPart.boundary;
438 const useBoundary = affectedBoundary.x2 > boundary.x2 ? affectedBoundary : boundary;
439
440 if (lastBoundary === useBoundary) {
441 changedHorizon[changedHorizon.length - 1].end = horizonPart.end;
442 } else {
443 changedHorizon.push({
444 start: horizonPart.start,
445 end: horizonPart.end,
446 boundary: useBoundary
447 });
448 lastBoundary = useBoundary;
449 }
450 }
451
452 if (horizon[i].start < boundary.y1) {
453 changedHorizon[0].start = boundary.y1;
454 changedHorizon.unshift({
455 start: horizon[i].start,
456 end: boundary.y1,
457 boundary: horizon[i].boundary
458 });
459 }
460
461 if (boundary.y2 < horizon[j].end) {
462 changedHorizon[changedHorizon.length - 1].end = boundary.y2;
463 changedHorizon.push({
464 start: boundary.y2,
465 end: horizon[j].end,
466 boundary: horizon[j].boundary
467 });
468 }
469
470 for (q = i; q <= j; q++) {
471 horizonPart = horizon[q];
472 affectedBoundary = horizonPart.boundary;
473
474 if (affectedBoundary.x2New !== undefined) {
475 continue;
476 }
477
478 let used = false;
479
480 for (k = i - 1; !used && k >= 0 && horizon[k].start >= affectedBoundary.y1; k--) {
481 used = horizon[k].boundary === affectedBoundary;
482 }
483
484 for (k = j + 1; !used && k < horizon.length && horizon[k].end <= affectedBoundary.y2; k++) {
485 used = horizon[k].boundary === affectedBoundary;
486 }
487
488 for (k = 0; !used && k < changedHorizon.length; k++) {
489 used = changedHorizon[k].boundary === affectedBoundary;
490 }
491
492 if (!used) {
493 affectedBoundary.x2New = maxXNew;
494 }
495 }
496
497 Array.prototype.splice.apply(horizon, [i, j - i + 1].concat(changedHorizon));
498 }
499
500 for (const horizonPart of horizon) {
501 const affectedBoundary = horizonPart.boundary;
502
503 if (affectedBoundary.x2New === undefined) {
504 affectedBoundary.x2New = Math.max(width, affectedBoundary.x2);
505 }
506 }
507}
508
509class TextLayerRenderTask {
510 constructor({
511 textContent,
512 textContentStream,
513 container,
514 viewport,
515 textDivs,
516 textContentItemsStr,
517 enhanceTextSelection
518 }) {
519 this._textContent = textContent;
520 this._textContentStream = textContentStream;
521 this._container = container;
522 this._document = container.ownerDocument;
523 this._viewport = viewport;
524 this._textDivs = textDivs || [];
525 this._textContentItemsStr = textContentItemsStr || [];
526 this._enhanceTextSelection = !!enhanceTextSelection;
527 this._fontInspectorEnabled = !!globalThis.FontInspector?.enabled;
528 this._reader = null;
529 this._layoutTextLastFontSize = null;
530 this._layoutTextLastFontFamily = null;
531 this._layoutTextCtx = null;
532 this._textDivProperties = new WeakMap();
533 this._renderingDone = false;
534 this._canceled = false;
535 this._capability = (0, _util.createPromiseCapability)();
536 this._renderTimer = null;
537 this._bounds = [];
538
539 this._capability.promise.finally(() => {
540 if (!this._enhanceTextSelection) {
541 this._textDivProperties = null;
542 }
543
544 if (this._layoutTextCtx) {
545 this._layoutTextCtx.canvas.width = 0;
546 this._layoutTextCtx.canvas.height = 0;
547 this._layoutTextCtx = null;
548 }
549 }).catch(() => {});
550 }
551
552 get promise() {
553 return this._capability.promise;
554 }
555
556 cancel() {
557 this._canceled = true;
558
559 if (this._reader) {
560 this._reader.cancel(new _util.AbortException("TextLayer task cancelled.")).catch(() => {});
561
562 this._reader = null;
563 }
564
565 if (this._renderTimer !== null) {
566 clearTimeout(this._renderTimer);
567 this._renderTimer = null;
568 }
569
570 this._capability.reject(new Error("TextLayer task cancelled."));
571 }
572
573 _processItems(items, styleCache) {
574 for (let i = 0, len = items.length; i < len; i++) {
575 if (items[i].str === undefined) {
576 if (items[i].type === "beginMarkedContentProps" || items[i].type === "beginMarkedContent") {
577 const parent = this._container;
578 this._container = document.createElement("span");
579
580 this._container.classList.add("markedContent");
581
582 if (items[i].id !== null) {
583 this._container.setAttribute("id", `${items[i].id}`);
584 }
585
586 parent.appendChild(this._container);
587 } else if (items[i].type === "endMarkedContent") {
588 this._container = this._container.parentNode;
589 }
590
591 continue;
592 }
593
594 this._textContentItemsStr.push(items[i].str);
595
596 appendText(this, items[i], styleCache, this._layoutTextCtx);
597 }
598 }
599
600 _layoutText(textDiv) {
601 const textDivProperties = this._textDivProperties.get(textDiv);
602
603 let transform = "";
604
605 if (textDivProperties.canvasWidth !== 0 && textDivProperties.hasText) {
606 const {
607 fontSize,
608 fontFamily
609 } = textDiv.style;
610
611 if (fontSize !== this._layoutTextLastFontSize || fontFamily !== this._layoutTextLastFontFamily) {
612 this._layoutTextCtx.font = `${fontSize} ${fontFamily}`;
613 this._layoutTextLastFontSize = fontSize;
614 this._layoutTextLastFontFamily = fontFamily;
615 }
616
617 const {
618 width
619 } = this._layoutTextCtx.measureText(textDiv.textContent);
620
621 if (width > 0) {
622 const scale = textDivProperties.canvasWidth / width;
623
624 if (this._enhanceTextSelection) {
625 textDivProperties.scale = scale;
626 }
627
628 transform = `scaleX(${scale})`;
629 }
630 }
631
632 if (textDivProperties.angle !== 0) {
633 transform = `rotate(${textDivProperties.angle}deg) ${transform}`;
634 }
635
636 if (transform.length > 0) {
637 if (this._enhanceTextSelection) {
638 textDivProperties.originalTransform = transform;
639 }
640
641 textDiv.style.transform = transform;
642 }
643
644 if (textDivProperties.hasText) {
645 this._container.appendChild(textDiv);
646 }
647
648 if (textDivProperties.hasEOL) {
649 const br = document.createElement("br");
650 br.setAttribute("role", "presentation");
651
652 this._container.appendChild(br);
653 }
654 }
655
656 _render(timeout = 0) {
657 const capability = (0, _util.createPromiseCapability)();
658 let styleCache = Object.create(null);
659
660 const canvas = this._document.createElement("canvas");
661
662 canvas.height = canvas.width = DEFAULT_FONT_SIZE;
663 canvas.mozOpaque = true;
664 this._layoutTextCtx = canvas.getContext("2d", {
665 alpha: false
666 });
667
668 if (this._textContent) {
669 const textItems = this._textContent.items;
670 const textStyles = this._textContent.styles;
671
672 this._processItems(textItems, textStyles);
673
674 capability.resolve();
675 } else if (this._textContentStream) {
676 const pump = () => {
677 this._reader.read().then(({
678 value,
679 done
680 }) => {
681 if (done) {
682 capability.resolve();
683 return;
684 }
685
686 Object.assign(styleCache, value.styles);
687
688 this._processItems(value.items, styleCache);
689
690 pump();
691 }, capability.reject);
692 };
693
694 this._reader = this._textContentStream.getReader();
695 pump();
696 } else {
697 throw new Error('Neither "textContent" nor "textContentStream" parameters specified.');
698 }
699
700 capability.promise.then(() => {
701 styleCache = null;
702
703 if (!timeout) {
704 render(this);
705 } else {
706 this._renderTimer = setTimeout(() => {
707 render(this);
708 this._renderTimer = null;
709 }, timeout);
710 }
711 }, this._capability.reject);
712 }
713
714 expandTextDivs(expandDivs = false) {
715 if (!this._enhanceTextSelection || !this._renderingDone) {
716 return;
717 }
718
719 if (this._bounds !== null) {
720 expand(this);
721 this._bounds = null;
722 }
723
724 const transformBuf = [],
725 paddingBuf = [];
726
727 for (let i = 0, ii = this._textDivs.length; i < ii; i++) {
728 const div = this._textDivs[i];
729
730 const divProps = this._textDivProperties.get(div);
731
732 if (!divProps.hasText) {
733 continue;
734 }
735
736 if (expandDivs) {
737 transformBuf.length = 0;
738 paddingBuf.length = 0;
739
740 if (divProps.originalTransform) {
741 transformBuf.push(divProps.originalTransform);
742 }
743
744 if (divProps.paddingTop > 0) {
745 paddingBuf.push(`${divProps.paddingTop}px`);
746 transformBuf.push(`translateY(${-divProps.paddingTop}px)`);
747 } else {
748 paddingBuf.push(0);
749 }
750
751 if (divProps.paddingRight > 0) {
752 paddingBuf.push(`${divProps.paddingRight / divProps.scale}px`);
753 } else {
754 paddingBuf.push(0);
755 }
756
757 if (divProps.paddingBottom > 0) {
758 paddingBuf.push(`${divProps.paddingBottom}px`);
759 } else {
760 paddingBuf.push(0);
761 }
762
763 if (divProps.paddingLeft > 0) {
764 paddingBuf.push(`${divProps.paddingLeft / divProps.scale}px`);
765 transformBuf.push(`translateX(${-divProps.paddingLeft / divProps.scale}px)`);
766 } else {
767 paddingBuf.push(0);
768 }
769
770 div.style.padding = paddingBuf.join(" ");
771
772 if (transformBuf.length) {
773 div.style.transform = transformBuf.join(" ");
774 }
775 } else {
776 div.style.padding = null;
777 div.style.transform = divProps.originalTransform;
778 }
779 }
780 }
781
782}
783
784function renderTextLayer(renderParameters) {
785 const task = new TextLayerRenderTask({
786 textContent: renderParameters.textContent,
787 textContentStream: renderParameters.textContentStream,
788 container: renderParameters.container,
789 viewport: renderParameters.viewport,
790 textDivs: renderParameters.textDivs,
791 textContentItemsStr: renderParameters.textContentItemsStr,
792 enhanceTextSelection: renderParameters.enhanceTextSelection
793 });
794
795 task._render(renderParameters.timeout);
796
797 return task;
798}
\No newline at end of file