mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 14:48:08 +02:00
Merge pull request #18749 from calixteman/issue18626
[Editor] Take into account the device pixel ratio when drawing an added image
This commit is contained in:
commit
f68310b7b1
9 changed files with 164 additions and 100 deletions
|
@ -1124,6 +1124,36 @@ function setLayerDimensions(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale factors for the canvas, necessary with HiDPI displays.
|
||||
*/
|
||||
class OutputScale {
|
||||
constructor() {
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
|
||||
/**
|
||||
* @type {number} Horizontal scale.
|
||||
*/
|
||||
this.sx = pixelRatio;
|
||||
|
||||
/**
|
||||
* @type {number} Vertical scale.
|
||||
*/
|
||||
this.sy = pixelRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean} Returns `true` when scaling is required, `false` otherwise.
|
||||
*/
|
||||
get scaled() {
|
||||
return this.sx !== 1 || this.sy !== 1;
|
||||
}
|
||||
|
||||
get symmetric() {
|
||||
return this.sx === this.sy;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
deprecated,
|
||||
DOMCanvasFactory,
|
||||
|
@ -1143,6 +1173,7 @@ export {
|
|||
isPdfFile,
|
||||
isValidFetchUrl,
|
||||
noContextMenu,
|
||||
OutputScale,
|
||||
PageViewport,
|
||||
PDFDateString,
|
||||
PixelsPerInch,
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
*/
|
||||
|
||||
import { AnnotationEditorType, shadow } from "../../shared/util.js";
|
||||
import { OutputScale, PixelsPerInch } from "../display_utils.js";
|
||||
import { AnnotationEditor } from "./editor.js";
|
||||
import { PixelsPerInch } from "../display_utils.js";
|
||||
import { StampAnnotationElement } from "../annotation_layer.js";
|
||||
|
||||
/**
|
||||
|
@ -185,7 +185,7 @@ class StampEditor extends AnnotationEditor {
|
|||
}
|
||||
const { data, width, height } =
|
||||
imageData ||
|
||||
this.copyCanvas(null, /* createImageData = */ true).imageData;
|
||||
this.copyCanvas(null, null, /* createImageData = */ true).imageData;
|
||||
const response = await mlManager.guess({
|
||||
name: "altText",
|
||||
request: {
|
||||
|
@ -453,61 +453,108 @@ class StampEditor extends AnnotationEditor {
|
|||
}
|
||||
}
|
||||
|
||||
copyCanvas(maxDimension, createImageData = false) {
|
||||
if (!maxDimension) {
|
||||
copyCanvas(maxDataDimension, maxPreviewDimension, createImageData = false) {
|
||||
if (!maxDataDimension) {
|
||||
// TODO: get this value from Firefox
|
||||
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1908184)
|
||||
// It's the maximum dimension that the AI can handle.
|
||||
maxDimension = 224;
|
||||
maxDataDimension = 224;
|
||||
}
|
||||
|
||||
const { width: bitmapWidth, height: bitmapHeight } = this.#bitmap;
|
||||
const canvas = document.createElement("canvas");
|
||||
const outputScale = new OutputScale();
|
||||
|
||||
let bitmap = this.#bitmap;
|
||||
let width = bitmapWidth,
|
||||
height = bitmapHeight;
|
||||
if (bitmapWidth > maxDimension || bitmapHeight > maxDimension) {
|
||||
const ratio = Math.min(
|
||||
maxDimension / bitmapWidth,
|
||||
maxDimension / bitmapHeight
|
||||
);
|
||||
width = Math.floor(bitmapWidth * ratio);
|
||||
height = Math.floor(bitmapHeight * ratio);
|
||||
let canvas = null;
|
||||
|
||||
if (maxPreviewDimension) {
|
||||
if (
|
||||
bitmapWidth > maxPreviewDimension ||
|
||||
bitmapHeight > maxPreviewDimension
|
||||
) {
|
||||
const ratio = Math.min(
|
||||
maxPreviewDimension / bitmapWidth,
|
||||
maxPreviewDimension / bitmapHeight
|
||||
);
|
||||
width = Math.floor(bitmapWidth * ratio);
|
||||
height = Math.floor(bitmapHeight * ratio);
|
||||
}
|
||||
|
||||
canvas = document.createElement("canvas");
|
||||
const scaledWidth = (canvas.width = Math.ceil(width * outputScale.sx));
|
||||
const scaledHeight = (canvas.height = Math.ceil(height * outputScale.sy));
|
||||
|
||||
if (!this.#isSvg) {
|
||||
bitmap = this.#scaleBitmap(width, height);
|
||||
bitmap = this.#scaleBitmap(scaledWidth, scaledHeight);
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.filter = this._uiManager.hcmFilter;
|
||||
|
||||
// Add a checkerboard pattern as a background in case the image has some
|
||||
// transparency.
|
||||
let white = "white",
|
||||
black = "#cfcfd8";
|
||||
if (this._uiManager.hcmFilter !== "none") {
|
||||
black = "black";
|
||||
} else if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
|
||||
white = "#8f8f9d";
|
||||
black = "#42414d";
|
||||
}
|
||||
const boxDim = 15;
|
||||
const boxDimWidth = boxDim * outputScale.sx;
|
||||
const boxDimHeight = boxDim * outputScale.sy;
|
||||
const pattern = new OffscreenCanvas(boxDimWidth * 2, boxDimHeight * 2);
|
||||
const patternCtx = pattern.getContext("2d");
|
||||
patternCtx.fillStyle = white;
|
||||
patternCtx.fillRect(0, 0, boxDimWidth * 2, boxDimHeight * 2);
|
||||
patternCtx.fillStyle = black;
|
||||
patternCtx.fillRect(0, 0, boxDimWidth, boxDimHeight);
|
||||
patternCtx.fillRect(boxDimWidth, boxDimHeight, boxDimWidth, boxDimHeight);
|
||||
ctx.fillStyle = ctx.createPattern(pattern, "repeat");
|
||||
ctx.fillRect(0, 0, scaledWidth, scaledHeight);
|
||||
ctx.drawImage(
|
||||
bitmap,
|
||||
0,
|
||||
0,
|
||||
bitmap.width,
|
||||
bitmap.height,
|
||||
0,
|
||||
0,
|
||||
scaledWidth,
|
||||
scaledHeight
|
||||
);
|
||||
}
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.filter = this._uiManager.hcmFilter;
|
||||
|
||||
// Add a checkerboard pattern as a background in case the image has some
|
||||
// transparency.
|
||||
let white = "white",
|
||||
black = "#cfcfd8";
|
||||
if (this._uiManager.hcmFilter !== "none") {
|
||||
black = "black";
|
||||
} else if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
|
||||
white = "#8f8f9d";
|
||||
black = "#42414d";
|
||||
}
|
||||
const boxDim = 15;
|
||||
const pattern = new OffscreenCanvas(boxDim * 2, boxDim * 2);
|
||||
const patternCtx = pattern.getContext("2d");
|
||||
patternCtx.fillStyle = white;
|
||||
patternCtx.fillRect(0, 0, boxDim * 2, boxDim * 2);
|
||||
patternCtx.fillStyle = black;
|
||||
patternCtx.fillRect(0, 0, boxDim, boxDim);
|
||||
patternCtx.fillRect(boxDim, boxDim, boxDim, boxDim);
|
||||
ctx.fillStyle = ctx.createPattern(pattern, "repeat");
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
let imageData = null;
|
||||
if (createImageData) {
|
||||
const offscreen = new OffscreenCanvas(width, height);
|
||||
let dataWidth, dataHeight;
|
||||
if (
|
||||
outputScale.symmetric &&
|
||||
bitmap.width < maxDataDimension &&
|
||||
bitmap.height < maxDataDimension
|
||||
) {
|
||||
dataWidth = bitmap.width;
|
||||
dataHeight = bitmap.height;
|
||||
} else {
|
||||
bitmap = this.#bitmap;
|
||||
if (bitmapWidth > maxDataDimension || bitmapHeight > maxDataDimension) {
|
||||
const ratio = Math.min(
|
||||
maxDataDimension / bitmapWidth,
|
||||
maxDataDimension / bitmapHeight
|
||||
);
|
||||
dataWidth = Math.floor(bitmapWidth * ratio);
|
||||
dataHeight = Math.floor(bitmapHeight * ratio);
|
||||
|
||||
if (!this.#isSvg) {
|
||||
bitmap = this.#scaleBitmap(dataWidth, dataHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const offscreen = new OffscreenCanvas(dataWidth, dataHeight);
|
||||
const offscreenCtx = offscreen.getContext("2d", {
|
||||
willReadFrequently: true,
|
||||
});
|
||||
|
@ -519,27 +566,17 @@ class StampEditor extends AnnotationEditor {
|
|||
bitmap.height,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height
|
||||
dataWidth,
|
||||
dataHeight
|
||||
);
|
||||
const data = offscreenCtx.getImageData(0, 0, width, height).data;
|
||||
ctx.drawImage(offscreen, 0, 0);
|
||||
|
||||
return { canvas, imageData: { width, height, data } };
|
||||
imageData = {
|
||||
width: dataWidth,
|
||||
height: dataHeight,
|
||||
data: offscreenCtx.getImageData(0, 0, dataWidth, dataHeight).data,
|
||||
};
|
||||
}
|
||||
|
||||
ctx.drawImage(
|
||||
bitmap,
|
||||
0,
|
||||
0,
|
||||
bitmap.width,
|
||||
bitmap.height,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height
|
||||
);
|
||||
return { canvas, imageData: null };
|
||||
return { canvas, width, height, imageData };
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -619,17 +656,23 @@ class StampEditor extends AnnotationEditor {
|
|||
}
|
||||
|
||||
#drawBitmap(width, height) {
|
||||
width = Math.ceil(width);
|
||||
height = Math.ceil(height);
|
||||
const outputScale = new OutputScale();
|
||||
const scaledWidth = Math.ceil(width * outputScale.sx);
|
||||
const scaledHeight = Math.ceil(height * outputScale.sy);
|
||||
|
||||
const canvas = this.#canvas;
|
||||
if (!canvas || (canvas.width === width && canvas.height === height)) {
|
||||
if (
|
||||
!canvas ||
|
||||
(canvas.width === scaledWidth && canvas.height === scaledHeight)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvas.width = scaledWidth;
|
||||
canvas.height = scaledHeight;
|
||||
|
||||
const bitmap = this.#isSvg
|
||||
? this.#bitmap
|
||||
: this.#scaleBitmap(width, height);
|
||||
: this.#scaleBitmap(scaledWidth, scaledHeight);
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.filter = this._uiManager.hcmFilter;
|
||||
|
@ -641,8 +684,8 @@ class StampEditor extends AnnotationEditor {
|
|||
bitmap.height,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height
|
||||
scaledWidth,
|
||||
scaledHeight
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ import {
|
|||
isDataScheme,
|
||||
isPdfFile,
|
||||
noContextMenu,
|
||||
OutputScale,
|
||||
PDFDateString,
|
||||
PixelsPerInch,
|
||||
RenderingCancelledException,
|
||||
|
@ -115,6 +116,7 @@ export {
|
|||
noContextMenu,
|
||||
normalizeUnicode,
|
||||
OPS,
|
||||
OutputScale,
|
||||
PasswordResponses,
|
||||
PDFDataRangeTransport,
|
||||
PDFDateString,
|
||||
|
|
|
@ -50,6 +50,7 @@ import {
|
|||
isDataScheme,
|
||||
isPdfFile,
|
||||
noContextMenu,
|
||||
OutputScale,
|
||||
PDFDateString,
|
||||
PixelsPerInch,
|
||||
RenderingCancelledException,
|
||||
|
@ -93,6 +94,7 @@ const expectedAPI = Object.freeze({
|
|||
noContextMenu,
|
||||
normalizeUnicode,
|
||||
OPS,
|
||||
OutputScale,
|
||||
PasswordResponses,
|
||||
PDFDataRangeTransport,
|
||||
PDFDateString,
|
||||
|
|
|
@ -371,14 +371,21 @@ class NewAltTextManager {
|
|||
// TODO: get this value from Firefox
|
||||
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1908184)
|
||||
const AI_MAX_IMAGE_DIMENSION = 224;
|
||||
const MAX_PREVIEW_DIMENSION = 180;
|
||||
|
||||
// The max dimension of the preview in the dialog is 180px, so we keep 224px
|
||||
// and rescale it thanks to css.
|
||||
|
||||
let canvas;
|
||||
let canvas, width, height;
|
||||
if (mlManager) {
|
||||
({ canvas, imageData: this.#imageData } = editor.copyCanvas(
|
||||
({
|
||||
canvas,
|
||||
width,
|
||||
height,
|
||||
imageData: this.#imageData,
|
||||
} = editor.copyCanvas(
|
||||
AI_MAX_IMAGE_DIMENSION,
|
||||
MAX_PREVIEW_DIMENSION,
|
||||
/* createImageData = */ true
|
||||
));
|
||||
if (hasAI) {
|
||||
|
@ -388,13 +395,17 @@ class NewAltTextManager {
|
|||
);
|
||||
}
|
||||
} else {
|
||||
({ canvas } = editor.copyCanvas(
|
||||
({ canvas, width, height } = editor.copyCanvas(
|
||||
AI_MAX_IMAGE_DIMENSION,
|
||||
MAX_PREVIEW_DIMENSION,
|
||||
/* createImageData = */ false
|
||||
));
|
||||
}
|
||||
|
||||
canvas.setAttribute("role", "presentation");
|
||||
const { style } = canvas;
|
||||
style.width = `${width}px`;
|
||||
style.height = `${height}px`;
|
||||
this.#imagePreview.append(canvas);
|
||||
|
||||
this.#toggleNotNow();
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
import {
|
||||
AbortException,
|
||||
AnnotationMode,
|
||||
OutputScale,
|
||||
PixelsPerInch,
|
||||
RenderingCancelledException,
|
||||
setLayerDimensions,
|
||||
|
@ -36,7 +37,6 @@ import {
|
|||
calcRound,
|
||||
DEFAULT_SCALE,
|
||||
floorToDivide,
|
||||
OutputScale,
|
||||
RenderingStates,
|
||||
TextLayerMode,
|
||||
} from "./ui_utils.js";
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
||||
|
||||
import { OutputScale, RenderingStates } from "./ui_utils.js";
|
||||
import { RenderingCancelledException } from "pdfjs-lib";
|
||||
import { OutputScale, RenderingCancelledException } from "pdfjs-lib";
|
||||
import { RenderingStates } from "./ui_utils.js";
|
||||
|
||||
const DRAW_UPSCALE_FACTOR = 2; // See comment in `PDFThumbnailView.draw` below.
|
||||
const MAX_NUM_SCALING_STEPS = 3;
|
||||
|
|
|
@ -42,6 +42,7 @@ const {
|
|||
noContextMenu,
|
||||
normalizeUnicode,
|
||||
OPS,
|
||||
OutputScale,
|
||||
PasswordResponses,
|
||||
PDFDataRangeTransport,
|
||||
PDFDateString,
|
||||
|
@ -88,6 +89,7 @@ export {
|
|||
noContextMenu,
|
||||
normalizeUnicode,
|
||||
OPS,
|
||||
OutputScale,
|
||||
PasswordResponses,
|
||||
PDFDataRangeTransport,
|
||||
PDFDateString,
|
||||
|
|
|
@ -76,32 +76,6 @@ const CursorTool = {
|
|||
// Used by `PDFViewerApplication`, and by the API unit-tests.
|
||||
const AutoPrintRegExp = /\bprint\s*\(/;
|
||||
|
||||
/**
|
||||
* Scale factors for the canvas, necessary with HiDPI displays.
|
||||
*/
|
||||
class OutputScale {
|
||||
constructor() {
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
|
||||
/**
|
||||
* @type {number} Horizontal scale.
|
||||
*/
|
||||
this.sx = pixelRatio;
|
||||
|
||||
/**
|
||||
* @type {number} Vertical scale.
|
||||
*/
|
||||
this.sy = pixelRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {boolean} Returns `true` when scaling is required, `false` otherwise.
|
||||
*/
|
||||
get scaled() {
|
||||
return this.sx !== 1 || this.sy !== 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls specified element into view of its parent.
|
||||
* @param {HTMLElement} element - The element to be visible.
|
||||
|
@ -908,7 +882,6 @@ export {
|
|||
MIN_SCALE,
|
||||
normalizeWheelEventDelta,
|
||||
normalizeWheelEventDirection,
|
||||
OutputScale,
|
||||
parseQueryString,
|
||||
PresentationModeState,
|
||||
ProgressBar,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue