123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- /**
- * @licstart The following is the entire license notice for the
- * JavaScript code in this page
- *
- * Copyright 2022 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.
- *
- * @licend The above is the entire license notice for the
- * JavaScript code in this page
- */
- "use strict";
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
- exports.TextLayerRenderTask = void 0;
- exports.renderTextLayer = renderTextLayer;
- var _util = require("../shared/util.js");
- const MAX_TEXT_DIVS_TO_RENDER = 100000;
- const DEFAULT_FONT_SIZE = 30;
- const DEFAULT_FONT_ASCENT = 0.8;
- const ascentCache = new Map();
- function getAscent(fontFamily, ctx) {
- const cachedAscent = ascentCache.get(fontFamily);
- if (cachedAscent) {
- return cachedAscent;
- }
- ctx.save();
- ctx.font = `${DEFAULT_FONT_SIZE}px ${fontFamily}`;
- const metrics = ctx.measureText("");
- let ascent = metrics.fontBoundingBoxAscent;
- let descent = Math.abs(metrics.fontBoundingBoxDescent);
- if (ascent) {
- ctx.restore();
- const ratio = ascent / (ascent + descent);
- ascentCache.set(fontFamily, ratio);
- return ratio;
- }
- ctx.strokeStyle = "red";
- ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
- ctx.strokeText("g", 0, 0);
- let pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data;
- descent = 0;
- for (let i = pixels.length - 1 - 3; i >= 0; i -= 4) {
- if (pixels[i] > 0) {
- descent = Math.ceil(i / 4 / DEFAULT_FONT_SIZE);
- break;
- }
- }
- ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
- ctx.strokeText("A", 0, DEFAULT_FONT_SIZE);
- pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data;
- ascent = 0;
- for (let i = 0, ii = pixels.length; i < ii; i += 4) {
- if (pixels[i] > 0) {
- ascent = DEFAULT_FONT_SIZE - Math.floor(i / 4 / DEFAULT_FONT_SIZE);
- break;
- }
- }
- ctx.restore();
- if (ascent) {
- const ratio = ascent / (ascent + descent);
- ascentCache.set(fontFamily, ratio);
- return ratio;
- }
- ascentCache.set(fontFamily, DEFAULT_FONT_ASCENT);
- return DEFAULT_FONT_ASCENT;
- }
- function appendText(task, geom, styles, ctx) {
- const textDiv = document.createElement("span");
- const textDivProperties = {
- angle: 0,
- canvasWidth: 0,
- hasText: geom.str !== "",
- hasEOL: geom.hasEOL,
- fontSize: 0
- };
- task._textDivs.push(textDiv);
- const tx = _util.Util.transform(task._viewport.transform, geom.transform);
- let angle = Math.atan2(tx[1], tx[0]);
- const style = styles[geom.fontName];
- if (style.vertical) {
- angle += Math.PI / 2;
- }
- const fontHeight = Math.hypot(tx[2], tx[3]);
- const fontAscent = fontHeight * getAscent(style.fontFamily, ctx);
- let left, 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;
- textDivProperties.fontSize = fontHeight;
- textDiv.setAttribute("role", "presentation");
- textDiv.textContent = geom.str;
- textDiv.dir = geom.dir;
- if (task._fontInspectorEnabled) {
- textDiv.dataset.fontName = geom.fontName;
- }
- if (angle !== 0) {
- textDivProperties.angle = angle * (180 / Math.PI);
- }
- let shouldScaleText = false;
- if (geom.str.length > 1) {
- shouldScaleText = true;
- } else if (geom.str !== " " && geom.transform[0] !== geom.transform[3]) {
- const absScaleX = Math.abs(geom.transform[0]),
- absScaleY = Math.abs(geom.transform[3]);
- if (absScaleX !== absScaleY && Math.max(absScaleX, absScaleY) / Math.min(absScaleX, absScaleY) > 1.5) {
- shouldScaleText = true;
- }
- }
- if (shouldScaleText) {
- if (style.vertical) {
- textDivProperties.canvasWidth = geom.height * task._viewport.scale;
- } else {
- textDivProperties.canvasWidth = geom.width * task._viewport.scale;
- }
- }
- task._textDivProperties.set(textDiv, textDivProperties);
- if (task._textContentStream) {
- task._layoutText(textDiv);
- }
- }
- function render(task) {
- if (task._canceled) {
- return;
- }
- const textDivs = task._textDivs;
- const capability = task._capability;
- const textDivsLength = textDivs.length;
- if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
- task._renderingDone = true;
- capability.resolve();
- return;
- }
- if (!task._textContentStream) {
- for (const textDiv of textDivs) {
- task._layoutText(textDiv);
- }
- }
- task._renderingDone = true;
- capability.resolve();
- }
- class TextLayerRenderTask {
- constructor({
- textContent,
- textContentStream,
- container,
- viewport,
- textDivs,
- textContentItemsStr
- }) {
- this._textContent = textContent;
- this._textContentStream = textContentStream;
- this._container = container;
- this._document = container.ownerDocument;
- this._viewport = viewport;
- this._textDivs = textDivs || [];
- this._textContentItemsStr = textContentItemsStr || [];
- this._fontInspectorEnabled = !!globalThis.FontInspector?.enabled;
- this._reader = null;
- this._layoutTextLastFontSize = null;
- this._layoutTextLastFontFamily = null;
- this._layoutTextCtx = null;
- this._textDivProperties = new WeakMap();
- this._renderingDone = false;
- this._canceled = false;
- this._capability = (0, _util.createPromiseCapability)();
- this._renderTimer = null;
- this._bounds = [];
- this._devicePixelRatio = globalThis.devicePixelRatio || 1;
- this._capability.promise.finally(() => {
- this._textDivProperties = null;
- if (this._layoutTextCtx) {
- this._layoutTextCtx.canvas.width = 0;
- this._layoutTextCtx.canvas.height = 0;
- this._layoutTextCtx = null;
- }
- }).catch(() => {});
- }
- get promise() {
- return this._capability.promise;
- }
- cancel() {
- this._canceled = true;
- if (this._reader) {
- this._reader.cancel(new _util.AbortException("TextLayer task cancelled.")).catch(() => {});
- this._reader = null;
- }
- if (this._renderTimer !== null) {
- clearTimeout(this._renderTimer);
- this._renderTimer = null;
- }
- this._capability.reject(new Error("TextLayer task cancelled."));
- }
- _processItems(items, styleCache) {
- for (const item of items) {
- if (item.str === undefined) {
- if (item.type === "beginMarkedContentProps" || item.type === "beginMarkedContent") {
- const parent = this._container;
- this._container = document.createElement("span");
- this._container.classList.add("markedContent");
- if (item.id !== null) {
- this._container.setAttribute("id", `${item.id}`);
- }
- parent.append(this._container);
- } else if (item.type === "endMarkedContent") {
- this._container = this._container.parentNode;
- }
- continue;
- }
- this._textContentItemsStr.push(item.str);
- appendText(this, item, styleCache, this._layoutTextCtx);
- }
- }
- _layoutText(textDiv) {
- const textDivProperties = this._textDivProperties.get(textDiv);
- let transform = "";
- if (textDivProperties.canvasWidth !== 0 && textDivProperties.hasText) {
- const {
- fontFamily
- } = textDiv.style;
- const {
- fontSize
- } = textDivProperties;
- if (fontSize !== this._layoutTextLastFontSize || fontFamily !== this._layoutTextLastFontFamily) {
- this._layoutTextCtx.font = `${fontSize * this._devicePixelRatio}px ${fontFamily}`;
- this._layoutTextLastFontSize = fontSize;
- this._layoutTextLastFontFamily = fontFamily;
- }
- const {
- width
- } = this._layoutTextCtx.measureText(textDiv.textContent);
- if (width > 0) {
- transform = `scaleX(${this._devicePixelRatio * textDivProperties.canvasWidth / width})`;
- }
- }
- if (textDivProperties.angle !== 0) {
- transform = `rotate(${textDivProperties.angle}deg) ${transform}`;
- }
- if (transform.length > 0) {
- textDiv.style.transform = transform;
- }
- if (textDivProperties.hasText) {
- this._container.append(textDiv);
- }
- if (textDivProperties.hasEOL) {
- const br = document.createElement("br");
- br.setAttribute("role", "presentation");
- this._container.append(br);
- }
- }
- _render(timeout = 0) {
- const capability = (0, _util.createPromiseCapability)();
- let styleCache = Object.create(null);
- const canvas = this._document.createElement("canvas");
- canvas.height = canvas.width = DEFAULT_FONT_SIZE;
- this._layoutTextCtx = canvas.getContext("2d", {
- alpha: false
- });
- if (this._textContent) {
- const textItems = this._textContent.items;
- const textStyles = this._textContent.styles;
- this._processItems(textItems, textStyles);
- capability.resolve();
- } else if (this._textContentStream) {
- const pump = () => {
- this._reader.read().then(({
- value,
- done
- }) => {
- if (done) {
- capability.resolve();
- return;
- }
- Object.assign(styleCache, value.styles);
- this._processItems(value.items, styleCache);
- pump();
- }, capability.reject);
- };
- this._reader = this._textContentStream.getReader();
- pump();
- } else {
- throw new Error('Neither "textContent" nor "textContentStream" parameters specified.');
- }
- capability.promise.then(() => {
- styleCache = null;
- if (!timeout) {
- render(this);
- } else {
- this._renderTimer = setTimeout(() => {
- render(this);
- this._renderTimer = null;
- }, timeout);
- }
- }, this._capability.reject);
- }
- }
- exports.TextLayerRenderTask = TextLayerRenderTask;
- function renderTextLayer(renderParameters) {
- const task = new TextLayerRenderTask({
- textContent: renderParameters.textContent,
- textContentStream: renderParameters.textContentStream,
- container: renderParameters.container,
- viewport: renderParameters.viewport,
- textDivs: renderParameters.textDivs,
- textContentItemsStr: renderParameters.textContentItemsStr
- });
- task._render(renderParameters.timeout);
- return task;
- }
|