Merge pull request #14874 from calixteman/colors
[api-minor] Improve pdf reading in high contrast mode
This commit is contained in:
commit
cfac6fa511
@ -208,6 +208,16 @@
|
||||
2
|
||||
],
|
||||
"default": -1
|
||||
},
|
||||
"pageBackgroundColor": {
|
||||
"description": "The color is a string as defined in CSS. Its goal is to help improve readability in high contrast mode",
|
||||
"type": "string",
|
||||
"default": "Canvas"
|
||||
},
|
||||
"pageForegroundColor": {
|
||||
"description": "The color is a string as defined in CSS. Its goal is to help improve readability in high contrast mode",
|
||||
"type": "string",
|
||||
"default": "CanvasText"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1162,6 +1162,12 @@ class PDFDocumentProxy {
|
||||
* <color> value, a `CanvasGradient` object (a linear or radial gradient) or
|
||||
* a `CanvasPattern` object (a repetitive image). The default value is
|
||||
* 'rgb(255,255,255)'.
|
||||
*
|
||||
* NOTE: This option may be partially, or completely, ignored when the
|
||||
* `pageColors`-option is used.
|
||||
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
||||
* with user defined ones in order to improve readability in high contrast
|
||||
* mode.
|
||||
* @property {Promise<OptionalContentConfig>} [optionalContentConfigPromise] -
|
||||
* A promise that should resolve with an {@link OptionalContentConfig}
|
||||
* created from `PDFDocumentProxy.getOptionalContentConfig`. If `null`,
|
||||
@ -1386,6 +1392,7 @@ class PDFPageProxy {
|
||||
background = null,
|
||||
optionalContentConfigPromise = null,
|
||||
annotationCanvasMap = null,
|
||||
pageColors = null,
|
||||
}) {
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC")) {
|
||||
if (arguments[0]?.renderInteractiveForms !== undefined) {
|
||||
@ -1509,6 +1516,7 @@ class PDFPageProxy {
|
||||
canvasFactory: canvasFactoryInstance,
|
||||
useRequestAnimationFrame: !intentPrint,
|
||||
pdfBug: this._pdfBug,
|
||||
pageColors,
|
||||
});
|
||||
|
||||
(intentState.renderTasks ||= new Set()).add(internalRenderTask);
|
||||
@ -3212,6 +3220,7 @@ class InternalRenderTask {
|
||||
canvasFactory,
|
||||
useRequestAnimationFrame = false,
|
||||
pdfBug = false,
|
||||
pageColors = null,
|
||||
}) {
|
||||
this.callback = callback;
|
||||
this.params = params;
|
||||
@ -3223,6 +3232,7 @@ class InternalRenderTask {
|
||||
this._pageIndex = pageIndex;
|
||||
this.canvasFactory = canvasFactory;
|
||||
this._pdfBug = pdfBug;
|
||||
this.pageColors = pageColors;
|
||||
|
||||
this.running = false;
|
||||
this.graphicsReadyCallback = null;
|
||||
@ -3277,7 +3287,8 @@ class InternalRenderTask {
|
||||
this.canvasFactory,
|
||||
imageLayer,
|
||||
optionalContentConfig,
|
||||
this.annotationCanvasMap
|
||||
this.annotationCanvasMap,
|
||||
this.pageColors
|
||||
);
|
||||
this.gfx.beginDrawing({
|
||||
transform,
|
||||
|
@ -1042,9 +1042,8 @@ function copyCtxState(sourceCtx, destCtx) {
|
||||
}
|
||||
}
|
||||
|
||||
function resetCtxToDefault(ctx) {
|
||||
ctx.strokeStyle = "#000000";
|
||||
ctx.fillStyle = "#000000";
|
||||
function resetCtxToDefault(ctx, foregroundColor) {
|
||||
ctx.strokeStyle = ctx.fillStyle = foregroundColor || "#000000";
|
||||
ctx.fillRule = "nonzero";
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.lineWidth = 1;
|
||||
@ -1212,7 +1211,8 @@ class CanvasGraphics {
|
||||
canvasFactory,
|
||||
imageLayer,
|
||||
optionalContentConfig,
|
||||
annotationCanvasMap
|
||||
annotationCanvasMap,
|
||||
pageColors
|
||||
) {
|
||||
this.ctx = canvasCtx;
|
||||
this.current = new CanvasExtraState(
|
||||
@ -1248,6 +1248,8 @@ class CanvasGraphics {
|
||||
this.viewportScale = 1;
|
||||
this.outputScaleX = 1;
|
||||
this.outputScaleY = 1;
|
||||
this.backgroundColor = pageColors?.background || null;
|
||||
this.foregroundColor = pageColors?.foreground || null;
|
||||
if (canvasCtx) {
|
||||
// NOTE: if mozCurrentTransform is polyfilled, then the current state of
|
||||
// the transformation must already be set in canvasCtx._transformMatrix.
|
||||
@ -1280,9 +1282,58 @@ class CanvasGraphics {
|
||||
// transparent canvas when we have blend modes.
|
||||
const width = this.ctx.canvas.width;
|
||||
const height = this.ctx.canvas.height;
|
||||
|
||||
const defaultBackgroundColor = background || "#ffffff";
|
||||
this.ctx.save();
|
||||
this.ctx.fillStyle = background || "rgb(255, 255, 255)";
|
||||
|
||||
if (this.foregroundColor && this.backgroundColor) {
|
||||
// Get the #RRGGBB value of the color. If it's a name (e.g. CanvasText)
|
||||
// then it'll be converted to its rgb value.
|
||||
this.ctx.fillStyle = this.foregroundColor;
|
||||
const fg = (this.foregroundColor = this.ctx.fillStyle);
|
||||
this.ctx.fillStyle = this.backgroundColor;
|
||||
const bg = (this.backgroundColor = this.ctx.fillStyle);
|
||||
let isValidDefaultBg = true;
|
||||
let defaultBg = defaultBackgroundColor;
|
||||
|
||||
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
|
||||
this.ctx.fillStyle = defaultBackgroundColor;
|
||||
defaultBg = this.ctx.fillStyle;
|
||||
isValidDefaultBg =
|
||||
typeof defaultBg === "string" && /^#[0-9A-Fa-f]{6}$/.test(defaultBg);
|
||||
}
|
||||
|
||||
if ((fg === "#000000" && bg === "#ffffff") || !isValidDefaultBg) {
|
||||
this.foregroundColor = this.backgroundColor = null;
|
||||
} else {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance
|
||||
//
|
||||
// Relative luminance:
|
||||
// https://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
//
|
||||
// We compute the rounded luminance of the default background color.
|
||||
// Then for every color in the pdf, if its rounded luminance is the
|
||||
// same as the background one then it's replaced by the new
|
||||
// background color else by the foreground one.
|
||||
const cB = parseInt(defaultBg.slice(1), 16);
|
||||
const rB = (cB && 0xff0000) >> 16;
|
||||
const gB = (cB && 0x00ff00) >> 8;
|
||||
const bB = cB && 0x0000ff;
|
||||
const newComp = x => {
|
||||
x /= 255;
|
||||
return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
||||
};
|
||||
const lumB = Math.round(
|
||||
0.2126 * newComp(rB) + 0.7152 * newComp(gB) + 0.0722 * newComp(bB)
|
||||
);
|
||||
this.selectColor = (r, g, b) => {
|
||||
const lumC =
|
||||
0.2126 * newComp(r) + 0.7152 * newComp(g) + 0.0722 * newComp(b);
|
||||
return Math.round(lumC) === lumB ? bg : fg;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.ctx.fillStyle = this.backgroundColor || defaultBackgroundColor;
|
||||
this.ctx.fillRect(0, 0, width, height);
|
||||
this.ctx.restore();
|
||||
|
||||
@ -1303,7 +1354,7 @@ class CanvasGraphics {
|
||||
}
|
||||
|
||||
this.ctx.save();
|
||||
resetCtxToDefault(this.ctx);
|
||||
resetCtxToDefault(this.ctx, this.foregroundColor);
|
||||
if (transform) {
|
||||
this.ctx.transform.apply(this.ctx, transform);
|
||||
this.outputScaleX = transform[0];
|
||||
@ -2636,13 +2687,13 @@ class CanvasGraphics {
|
||||
}
|
||||
|
||||
setStrokeRGBColor(r, g, b) {
|
||||
const color = Util.makeHexColor(r, g, b);
|
||||
const color = this.selectColor?.(r, g, b) || Util.makeHexColor(r, g, b);
|
||||
this.ctx.strokeStyle = color;
|
||||
this.current.strokeColor = color;
|
||||
}
|
||||
|
||||
setFillRGBColor(r, g, b) {
|
||||
const color = Util.makeHexColor(r, g, b);
|
||||
const color = this.selectColor?.(r, g, b) || Util.makeHexColor(r, g, b);
|
||||
this.ctx.fillStyle = color;
|
||||
this.current.fillColor = color;
|
||||
this.current.patternFill = false;
|
||||
@ -2964,9 +3015,9 @@ class CanvasGraphics {
|
||||
this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY);
|
||||
addContextCurrentTransform(this.ctx);
|
||||
|
||||
resetCtxToDefault(this.ctx);
|
||||
resetCtxToDefault(this.ctx, this.foregroundColor);
|
||||
} else {
|
||||
resetCtxToDefault(this.ctx);
|
||||
resetCtxToDefault(this.ctx, this.foregroundColor);
|
||||
|
||||
this.ctx.rect(rect[0], rect[1], width, height);
|
||||
this.ctx.clip();
|
||||
|
@ -648,7 +648,8 @@ class Driver {
|
||||
renderForms = false,
|
||||
renderPrint = false,
|
||||
renderXfa = false,
|
||||
annotationCanvasMap = null;
|
||||
annotationCanvasMap = null,
|
||||
pageColors = null;
|
||||
|
||||
if (task.annotationStorage) {
|
||||
const entries = Object.entries(task.annotationStorage),
|
||||
@ -699,6 +700,7 @@ class Driver {
|
||||
renderForms = !!task.forms;
|
||||
renderPrint = !!task.print;
|
||||
renderXfa = !!task.enableXfa;
|
||||
pageColors = task.pageColors || null;
|
||||
|
||||
// Render the annotation layer if necessary.
|
||||
if (renderAnnotations || renderForms || renderXfa) {
|
||||
@ -746,6 +748,7 @@ class Driver {
|
||||
viewport,
|
||||
optionalContentConfigPromise: task.optionalContentConfigPromise,
|
||||
annotationCanvasMap,
|
||||
pageColors,
|
||||
transform,
|
||||
};
|
||||
if (renderForms) {
|
||||
|
@ -60,6 +60,38 @@
|
||||
"enhance": true,
|
||||
"type": "text"
|
||||
},
|
||||
{ "id": "tracemonkey-forced-colors-eq",
|
||||
"file": "pdfs/tracemonkey.pdf",
|
||||
"md5": "9a192d8b1a7dc652a19835f6f08098bd",
|
||||
"rounds": 1,
|
||||
"pageColors": {
|
||||
"background": "black",
|
||||
"foreground": "#00FF00"
|
||||
},
|
||||
"type": "eq"
|
||||
},
|
||||
{ "id": "tracemonkey-viewer-colors-eq",
|
||||
"file": "pdfs/tracemonkey.pdf",
|
||||
"md5": "9a192d8b1a7dc652a19835f6f08098bd",
|
||||
"rounds": 1,
|
||||
"pageColors": {
|
||||
"background": "Canvas",
|
||||
"foreground": "CanvasText"
|
||||
},
|
||||
"type": "eq",
|
||||
"about": "Uses the same pageColors as the viewer."
|
||||
},
|
||||
{ "id": "tracemonkey-default-colors-eq",
|
||||
"file": "pdfs/tracemonkey.pdf",
|
||||
"md5": "9a192d8b1a7dc652a19835f6f08098bd",
|
||||
"rounds": 1,
|
||||
"pageColors": {
|
||||
"background": "white",
|
||||
"foreground": "black"
|
||||
},
|
||||
"type": "eq",
|
||||
"about": "Uses the default colors."
|
||||
},
|
||||
{ "id": "issue3925",
|
||||
"file": "pdfs/issue3925.pdf",
|
||||
"md5": "c5c895deecf7a7565393587e0d61be2b",
|
||||
|
@ -525,6 +525,10 @@ const PDFViewerApplication = {
|
||||
useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
|
||||
maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
|
||||
enablePermissions: AppOptions.get("enablePermissions"),
|
||||
pageColors: {
|
||||
background: AppOptions.get("pageBackgroundColor"),
|
||||
foreground: AppOptions.get("pageForegroundColor"),
|
||||
},
|
||||
});
|
||||
pdfRenderingQueue.setViewer(this.pdfViewer);
|
||||
pdfLinkService.setViewer(this.pdfViewer);
|
||||
|
@ -129,6 +129,16 @@ const defaultOptions = {
|
||||
compatibility: compatibilityParams.maxCanvasPixels,
|
||||
kind: OptionKind.VIEWER,
|
||||
},
|
||||
pageBackgroundColor: {
|
||||
/** @type {string} */
|
||||
value: "Canvas",
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
pageForegroundColor: {
|
||||
/** @type {string} */
|
||||
value: "CanvasText",
|
||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||
},
|
||||
pdfBugEnabled: {
|
||||
/** @type {boolean} */
|
||||
value: typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION"),
|
||||
|
@ -116,6 +116,9 @@ const PagesCountLimit = {
|
||||
* @property {IL10n} l10n - Localization service.
|
||||
* @property {boolean} [enablePermissions] - Enables PDF document permissions,
|
||||
* when they exist. The default value is `false`.
|
||||
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
||||
* with user defined ones in order to improve readability in high contrast
|
||||
* mode.
|
||||
*/
|
||||
|
||||
class PDFPageViewBuffer {
|
||||
@ -262,6 +265,22 @@ class BaseViewer {
|
||||
this.maxCanvasPixels = options.maxCanvasPixels;
|
||||
this.l10n = options.l10n || NullL10n;
|
||||
this.#enablePermissions = options.enablePermissions || false;
|
||||
this.pageColors = options.pageColors || null;
|
||||
|
||||
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
|
||||
if (
|
||||
options.pageColors &&
|
||||
(!CSS.supports("color", options.pageColors.background) ||
|
||||
!CSS.supports("color", options.pageColors.foreground))
|
||||
) {
|
||||
if (options.pageColors.background || options.pageColors.foreground) {
|
||||
console.warn(
|
||||
"Ignoring `pageColors`-option, since the browser doesn't support the values used."
|
||||
);
|
||||
}
|
||||
this.pageColors = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.defaultRenderingQueue = !options.renderingQueue;
|
||||
if (this.defaultRenderingQueue) {
|
||||
@ -698,6 +717,7 @@ class BaseViewer {
|
||||
renderer: this.renderer,
|
||||
useOnlyCssZoom: this.useOnlyCssZoom,
|
||||
maxCanvasPixels: this.maxCanvasPixels,
|
||||
pageColors: this.pageColors,
|
||||
l10n: this.l10n,
|
||||
});
|
||||
this._pages.push(pageView);
|
||||
|
@ -82,6 +82,9 @@ import { NullL10n } from "./l10n_utils.js";
|
||||
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
||||
* total pixels, i.e. width * height. Use -1 for no limit. The default value
|
||||
* is 4096 * 4096 (16 mega-pixels).
|
||||
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
||||
* with user defined ones in order to improve readability in high contrast
|
||||
* mode.
|
||||
* @property {IL10n} l10n - Localization service.
|
||||
*/
|
||||
|
||||
@ -118,6 +121,7 @@ class PDFPageView {
|
||||
this.imageResourcesPath = options.imageResourcesPath || "";
|
||||
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
|
||||
this.maxCanvasPixels = options.maxCanvasPixels || MAX_CANVAS_PIXELS;
|
||||
this.pageColors = options.pageColors || null;
|
||||
|
||||
this.eventBus = options.eventBus;
|
||||
this.renderingQueue = options.renderingQueue;
|
||||
@ -832,6 +836,7 @@ class PDFPageView {
|
||||
annotationMode: this.#annotationMode,
|
||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||
annotationCanvasMap: this._annotationCanvasMap,
|
||||
pageColors: this.pageColors,
|
||||
};
|
||||
const renderTask = this.pdfPage.render(renderContext);
|
||||
renderTask.onContinue = function (cont) {
|
||||
|
Loading…
Reference in New Issue
Block a user