Merge pull request #17476 from calixteman/bug1869001
Avoid to have the text layer mismatching the rendered text with mismatching locales (bug 1869001)
This commit is contained in:
commit
1019b9f821
@ -63,6 +63,7 @@ import {
|
|||||||
NodeStandardFontDataFactory,
|
NodeStandardFontDataFactory,
|
||||||
} from "display-node_utils";
|
} from "display-node_utils";
|
||||||
import { CanvasGraphics } from "./canvas.js";
|
import { CanvasGraphics } from "./canvas.js";
|
||||||
|
import { cleanupTextLayer } from "./text_layer.js";
|
||||||
import { GlobalWorkerOptions } from "./worker_options.js";
|
import { GlobalWorkerOptions } from "./worker_options.js";
|
||||||
import { MessageHandler } from "../shared/message_handler.js";
|
import { MessageHandler } from "../shared/message_handler.js";
|
||||||
import { Metadata } from "./metadata.js";
|
import { Metadata } from "./metadata.js";
|
||||||
@ -2481,6 +2482,7 @@ class WorkerTransport {
|
|||||||
this.fontLoader.clear();
|
this.fontLoader.clear();
|
||||||
this.#methodPromises.clear();
|
this.#methodPromises.clear();
|
||||||
this.filterFactory.destroy();
|
this.filterFactory.destroy();
|
||||||
|
cleanupTextLayer();
|
||||||
|
|
||||||
this._networkStream?.cancelAllRequests(
|
this._networkStream?.cancelAllRequests(
|
||||||
new AbortException("Worker was terminated.")
|
new AbortException("Worker was terminated.")
|
||||||
@ -3065,6 +3067,7 @@ class WorkerTransport {
|
|||||||
}
|
}
|
||||||
this.#methodPromises.clear();
|
this.#methodPromises.clear();
|
||||||
this.filterFactory.destroy(/* keepHCM = */ true);
|
this.filterFactory.destroy(/* keepHCM = */ true);
|
||||||
|
cleanupTextLayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
get loadingParams() {
|
get loadingParams() {
|
||||||
|
@ -16,12 +16,7 @@
|
|||||||
/** @typedef {import("./display_utils").PageViewport} PageViewport */
|
/** @typedef {import("./display_utils").PageViewport} PageViewport */
|
||||||
/** @typedef {import("./api").TextContent} TextContent */
|
/** @typedef {import("./api").TextContent} TextContent */
|
||||||
|
|
||||||
import {
|
import { AbortException, PromiseCapability, Util } from "../shared/util.js";
|
||||||
AbortException,
|
|
||||||
FeatureTest,
|
|
||||||
PromiseCapability,
|
|
||||||
Util,
|
|
||||||
} from "../shared/util.js";
|
|
||||||
import { setLayerDimensions } from "./display_utils.js";
|
import { setLayerDimensions } from "./display_utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -43,8 +38,6 @@ import { setLayerDimensions } from "./display_utils.js";
|
|||||||
* @property {Array<string>} [textContentItemsStr] - Strings that correspond to
|
* @property {Array<string>} [textContentItemsStr] - Strings that correspond to
|
||||||
* the `str` property of the text items of the textContent input.
|
* the `str` property of the text items of the textContent input.
|
||||||
* This is output and shall initially be set to an empty array.
|
* This is output and shall initially be set to an empty array.
|
||||||
* @property {boolean} [isOffscreenCanvasSupported] true if we can use
|
|
||||||
* OffscreenCanvas to measure string widths.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,8 +53,6 @@ import { setLayerDimensions } from "./display_utils.js";
|
|||||||
* This is output and shall initially be set to an empty array.
|
* This is output and shall initially be set to an empty array.
|
||||||
* @property {WeakMap<HTMLElement,Object>} [textDivProperties] - Some properties
|
* @property {WeakMap<HTMLElement,Object>} [textDivProperties] - Some properties
|
||||||
* weakly mapped to the HTML elements used to render the text.
|
* weakly mapped to the HTML elements used to render the text.
|
||||||
* @property {boolean} [isOffscreenCanvasSupported] true if we can use
|
|
||||||
* OffscreenCanvas to measure string widths.
|
|
||||||
* @property {boolean} [mustRotate] true if the text layer must be rotated.
|
* @property {boolean} [mustRotate] true if the text layer must be rotated.
|
||||||
* @property {boolean} [mustRescale] true if the text layer contents must be
|
* @property {boolean} [mustRescale] true if the text layer contents must be
|
||||||
* rescaled.
|
* rescaled.
|
||||||
@ -71,28 +62,43 @@ const MAX_TEXT_DIVS_TO_RENDER = 100000;
|
|||||||
const DEFAULT_FONT_SIZE = 30;
|
const DEFAULT_FONT_SIZE = 30;
|
||||||
const DEFAULT_FONT_ASCENT = 0.8;
|
const DEFAULT_FONT_ASCENT = 0.8;
|
||||||
const ascentCache = new Map();
|
const ascentCache = new Map();
|
||||||
|
let _canvasContext = null;
|
||||||
|
|
||||||
function getCtx(size, isOffscreenCanvasSupported) {
|
function getCtx() {
|
||||||
let ctx;
|
if (!_canvasContext) {
|
||||||
if (isOffscreenCanvasSupported && FeatureTest.isOffscreenCanvasSupported) {
|
// We don't use an OffscreenCanvas here because we use serif/sans serif
|
||||||
ctx = new OffscreenCanvas(size, size).getContext("2d", { alpha: false });
|
// fonts with it and they depends on the locale.
|
||||||
} else {
|
// In Firefox, the <html> element get a lang attribute that depends on what
|
||||||
|
// Fluent returns for the locale and the OffscreenCanvas uses the OS locale.
|
||||||
|
// Those two locales can be different and consequently the used fonts will
|
||||||
|
// be different (see bug 1869001).
|
||||||
|
// Ideally, we should use in the text layer the fonts we've in the pdf (or
|
||||||
|
// their replacements when they aren't embedded) and then we can use an
|
||||||
|
// OffscreenCanvas.
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = canvas.height = size;
|
canvas.className = "hiddenCanvasElement";
|
||||||
ctx = canvas.getContext("2d", { alpha: false });
|
document.body.append(canvas);
|
||||||
|
_canvasContext = canvas.getContext("2d", { alpha: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx;
|
return _canvasContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAscent(fontFamily, isOffscreenCanvasSupported) {
|
function cleanupTextLayer() {
|
||||||
|
_canvasContext?.canvas.remove();
|
||||||
|
_canvasContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAscent(fontFamily) {
|
||||||
const cachedAscent = ascentCache.get(fontFamily);
|
const cachedAscent = ascentCache.get(fontFamily);
|
||||||
if (cachedAscent) {
|
if (cachedAscent) {
|
||||||
return cachedAscent;
|
return cachedAscent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx = getCtx(DEFAULT_FONT_SIZE, isOffscreenCanvasSupported);
|
const ctx = getCtx();
|
||||||
|
|
||||||
|
const savedFont = ctx.font;
|
||||||
|
ctx.canvas.width = ctx.canvas.height = DEFAULT_FONT_SIZE;
|
||||||
ctx.font = `${DEFAULT_FONT_SIZE}px ${fontFamily}`;
|
ctx.font = `${DEFAULT_FONT_SIZE}px ${fontFamily}`;
|
||||||
const metrics = ctx.measureText("");
|
const metrics = ctx.measureText("");
|
||||||
|
|
||||||
@ -104,6 +110,7 @@ function getAscent(fontFamily, isOffscreenCanvasSupported) {
|
|||||||
ascentCache.set(fontFamily, ratio);
|
ascentCache.set(fontFamily, ratio);
|
||||||
|
|
||||||
ctx.canvas.width = ctx.canvas.height = 0;
|
ctx.canvas.width = ctx.canvas.height = 0;
|
||||||
|
ctx.font = savedFont;
|
||||||
return ratio;
|
return ratio;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +150,7 @@ function getAscent(fontFamily, isOffscreenCanvasSupported) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.canvas.width = ctx.canvas.height = 0;
|
ctx.canvas.width = ctx.canvas.height = 0;
|
||||||
|
ctx.font = savedFont;
|
||||||
|
|
||||||
if (ascent) {
|
if (ascent) {
|
||||||
const ratio = ascent / (ascent + descent);
|
const ratio = ascent / (ascent + descent);
|
||||||
@ -176,8 +184,7 @@ function appendText(task, geom, styles) {
|
|||||||
const fontFamily =
|
const fontFamily =
|
||||||
(task._fontInspectorEnabled && style.fontSubstitution) || style.fontFamily;
|
(task._fontInspectorEnabled && style.fontSubstitution) || style.fontFamily;
|
||||||
const fontHeight = Math.hypot(tx[2], tx[3]);
|
const fontHeight = Math.hypot(tx[2], tx[3]);
|
||||||
const fontAscent =
|
const fontAscent = fontHeight * getAscent(fontFamily);
|
||||||
fontHeight * getAscent(fontFamily, task._isOffscreenCanvasSupported);
|
|
||||||
|
|
||||||
let left, top;
|
let left, top;
|
||||||
if (angle === 0) {
|
if (angle === 0) {
|
||||||
@ -308,14 +315,12 @@ class TextLayerRenderTask {
|
|||||||
textDivs,
|
textDivs,
|
||||||
textDivProperties,
|
textDivProperties,
|
||||||
textContentItemsStr,
|
textContentItemsStr,
|
||||||
isOffscreenCanvasSupported,
|
|
||||||
}) {
|
}) {
|
||||||
this._textContentSource = textContentSource;
|
this._textContentSource = textContentSource;
|
||||||
this._isReadableStream = textContentSource instanceof ReadableStream;
|
this._isReadableStream = textContentSource instanceof ReadableStream;
|
||||||
this._container = this._rootContainer = container;
|
this._container = this._rootContainer = container;
|
||||||
this._textDivs = textDivs || [];
|
this._textDivs = textDivs || [];
|
||||||
this._textContentItemsStr = textContentItemsStr || [];
|
this._textContentItemsStr = textContentItemsStr || [];
|
||||||
this._isOffscreenCanvasSupported = isOffscreenCanvasSupported;
|
|
||||||
this._fontInspectorEnabled = !!globalThis.FontInspector?.enabled;
|
this._fontInspectorEnabled = !!globalThis.FontInspector?.enabled;
|
||||||
|
|
||||||
this._reader = null;
|
this._reader = null;
|
||||||
@ -328,7 +333,7 @@ class TextLayerRenderTask {
|
|||||||
div: null,
|
div: null,
|
||||||
scale: viewport.scale * (globalThis.devicePixelRatio || 1),
|
scale: viewport.scale * (globalThis.devicePixelRatio || 1),
|
||||||
properties: null,
|
properties: null,
|
||||||
ctx: getCtx(0, isOffscreenCanvasSupported),
|
ctx: getCtx(),
|
||||||
};
|
};
|
||||||
const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims;
|
const { pageWidth, pageHeight, pageX, pageY } = viewport.rawDims;
|
||||||
this._transform = [1, 0, 0, -1, -pageX, pageY + pageHeight];
|
this._transform = [1, 0, 0, -1, -pageX, pageY + pageHeight];
|
||||||
@ -474,7 +479,6 @@ function updateTextLayer({
|
|||||||
viewport,
|
viewport,
|
||||||
textDivs,
|
textDivs,
|
||||||
textDivProperties,
|
textDivProperties,
|
||||||
isOffscreenCanvasSupported,
|
|
||||||
mustRotate = true,
|
mustRotate = true,
|
||||||
mustRescale = true,
|
mustRescale = true,
|
||||||
}) {
|
}) {
|
||||||
@ -483,7 +487,7 @@ function updateTextLayer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mustRescale) {
|
if (mustRescale) {
|
||||||
const ctx = getCtx(0, isOffscreenCanvasSupported);
|
const ctx = getCtx();
|
||||||
const scale = viewport.scale * (globalThis.devicePixelRatio || 1);
|
const scale = viewport.scale * (globalThis.devicePixelRatio || 1);
|
||||||
const params = {
|
const params = {
|
||||||
prevFontSize: null,
|
prevFontSize: null,
|
||||||
@ -501,4 +505,9 @@ function updateTextLayer({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { renderTextLayer, TextLayerRenderTask, updateTextLayer };
|
export {
|
||||||
|
cleanupTextLayer,
|
||||||
|
renderTextLayer,
|
||||||
|
TextLayerRenderTask,
|
||||||
|
updateTextLayer,
|
||||||
|
};
|
||||||
|
@ -441,7 +441,6 @@ const PDFViewerApplication = {
|
|||||||
annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
|
annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
|
||||||
imageResourcesPath: AppOptions.get("imageResourcesPath"),
|
imageResourcesPath: AppOptions.get("imageResourcesPath"),
|
||||||
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
|
enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
|
||||||
isOffscreenCanvasSupported,
|
|
||||||
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
||||||
enablePermissions: AppOptions.get("enablePermissions"),
|
enablePermissions: AppOptions.get("enablePermissions"),
|
||||||
pageColors,
|
pageColors,
|
||||||
|
@ -72,8 +72,6 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js";
|
|||||||
* The default value is `AnnotationMode.ENABLE_FORMS`.
|
* The default value is `AnnotationMode.ENABLE_FORMS`.
|
||||||
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
||||||
* for annotation icons. Include trailing slash.
|
* for annotation icons. Include trailing slash.
|
||||||
* @property {boolean} [isOffscreenCanvasSupported] - Allows to use an
|
|
||||||
* OffscreenCanvas if needed.
|
|
||||||
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
||||||
* total pixels, i.e. width * height. Use `-1` for no limit, or `0` for
|
* total pixels, i.e. width * height. Use `-1` for no limit, or `0` for
|
||||||
* CSS-only zooming. The default value is 4096 * 4096 (16 mega-pixels).
|
* CSS-only zooming. The default value is 4096 * 4096 (16 mega-pixels).
|
||||||
@ -154,8 +152,6 @@ class PDFPageView {
|
|||||||
this.#annotationMode =
|
this.#annotationMode =
|
||||||
options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
|
options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
|
||||||
this.imageResourcesPath = options.imageResourcesPath || "";
|
this.imageResourcesPath = options.imageResourcesPath || "";
|
||||||
this.isOffscreenCanvasSupported =
|
|
||||||
options.isOffscreenCanvasSupported ?? true;
|
|
||||||
this.maxCanvasPixels = options.maxCanvasPixels ?? MAX_CANVAS_PIXELS;
|
this.maxCanvasPixels = options.maxCanvasPixels ?? MAX_CANVAS_PIXELS;
|
||||||
this.pageColors = options.pageColors || null;
|
this.pageColors = options.pageColors || null;
|
||||||
|
|
||||||
@ -879,7 +875,6 @@ class PDFPageView {
|
|||||||
this.textLayer = new TextLayerBuilder({
|
this.textLayer = new TextLayerBuilder({
|
||||||
highlighter: this._textHighlighter,
|
highlighter: this._textHighlighter,
|
||||||
accessibilityManager: this._accessibilityManager,
|
accessibilityManager: this._accessibilityManager,
|
||||||
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
|
||||||
enablePermissions:
|
enablePermissions:
|
||||||
this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS,
|
this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS,
|
||||||
});
|
});
|
||||||
|
@ -46,7 +46,8 @@
|
|||||||
transform: rotate(270deg) translateX(-100%);
|
transform: rotate(270deg) translateX(-100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#hiddenCopyElement {
|
#hiddenCopyElement,
|
||||||
|
.hiddenCanvasElement {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -115,8 +115,6 @@ function isValidAnnotationEditorMode(mode) {
|
|||||||
* mainly for annotation icons. Include trailing slash.
|
* mainly for annotation icons. Include trailing slash.
|
||||||
* @property {boolean} [enablePrintAutoRotate] - Enables automatic rotation of
|
* @property {boolean} [enablePrintAutoRotate] - Enables automatic rotation of
|
||||||
* landscape pages upon printing. The default is `false`.
|
* landscape pages upon printing. The default is `false`.
|
||||||
* @property {boolean} [isOffscreenCanvasSupported] - Allows to use an
|
|
||||||
* OffscreenCanvas if needed.
|
|
||||||
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
||||||
* total pixels, i.e. width * height. Use `-1` for no limit, or `0` for
|
* total pixels, i.e. width * height. Use `-1` for no limit, or `0` for
|
||||||
* CSS-only zooming. The default value is 4096 * 4096 (16 mega-pixels).
|
* CSS-only zooming. The default value is 4096 * 4096 (16 mega-pixels).
|
||||||
@ -287,8 +285,6 @@ class PDFViewer {
|
|||||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||||
this.removePageBorders = options.removePageBorders || false;
|
this.removePageBorders = options.removePageBorders || false;
|
||||||
}
|
}
|
||||||
this.isOffscreenCanvasSupported =
|
|
||||||
options.isOffscreenCanvasSupported ?? true;
|
|
||||||
this.maxCanvasPixels = options.maxCanvasPixels;
|
this.maxCanvasPixels = options.maxCanvasPixels;
|
||||||
this.l10n = options.l10n || NullL10n;
|
this.l10n = options.l10n || NullL10n;
|
||||||
this.#enablePermissions = options.enablePermissions || false;
|
this.#enablePermissions = options.enablePermissions || false;
|
||||||
@ -919,7 +915,6 @@ class PDFViewer {
|
|||||||
textLayerMode,
|
textLayerMode,
|
||||||
annotationMode,
|
annotationMode,
|
||||||
imageResourcesPath: this.imageResourcesPath,
|
imageResourcesPath: this.imageResourcesPath,
|
||||||
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
|
||||||
maxCanvasPixels: this.maxCanvasPixels,
|
maxCanvasPixels: this.maxCanvasPixels,
|
||||||
pageColors: this.pageColors,
|
pageColors: this.pageColors,
|
||||||
l10n: this.l10n,
|
l10n: this.l10n,
|
||||||
|
@ -28,8 +28,6 @@ import { removeNullCharacters } from "./ui_utils.js";
|
|||||||
* @property {TextHighlighter} highlighter - Optional object that will handle
|
* @property {TextHighlighter} highlighter - Optional object that will handle
|
||||||
* highlighting text from the find controller.
|
* highlighting text from the find controller.
|
||||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||||
* @property {boolean} [isOffscreenCanvasSupported] - Allows to use an
|
|
||||||
* OffscreenCanvas if needed.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,7 +47,6 @@ class TextLayerBuilder {
|
|||||||
constructor({
|
constructor({
|
||||||
highlighter = null,
|
highlighter = null,
|
||||||
accessibilityManager = null,
|
accessibilityManager = null,
|
||||||
isOffscreenCanvasSupported = true,
|
|
||||||
enablePermissions = false,
|
enablePermissions = false,
|
||||||
}) {
|
}) {
|
||||||
this.textContentItemsStr = [];
|
this.textContentItemsStr = [];
|
||||||
@ -59,7 +56,6 @@ class TextLayerBuilder {
|
|||||||
this.textLayerRenderTask = null;
|
this.textLayerRenderTask = null;
|
||||||
this.highlighter = highlighter;
|
this.highlighter = highlighter;
|
||||||
this.accessibilityManager = accessibilityManager;
|
this.accessibilityManager = accessibilityManager;
|
||||||
this.isOffscreenCanvasSupported = isOffscreenCanvasSupported;
|
|
||||||
this.#enablePermissions = enablePermissions === true;
|
this.#enablePermissions = enablePermissions === true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,7 +103,6 @@ class TextLayerBuilder {
|
|||||||
viewport,
|
viewport,
|
||||||
textDivs: this.textDivs,
|
textDivs: this.textDivs,
|
||||||
textDivProperties: this.textDivProperties,
|
textDivProperties: this.textDivProperties,
|
||||||
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
|
||||||
mustRescale,
|
mustRescale,
|
||||||
mustRotate,
|
mustRotate,
|
||||||
});
|
});
|
||||||
@ -129,7 +124,6 @@ class TextLayerBuilder {
|
|||||||
textDivs: this.textDivs,
|
textDivs: this.textDivs,
|
||||||
textDivProperties: this.textDivProperties,
|
textDivProperties: this.textDivProperties,
|
||||||
textContentItemsStr: this.textContentItemsStr,
|
textContentItemsStr: this.textContentItemsStr,
|
||||||
isOffscreenCanvasSupported: this.isOffscreenCanvasSupported,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.textLayerRenderTask.promise;
|
await this.textLayerRenderTask.promise;
|
||||||
|
Loading…
Reference in New Issue
Block a user