Make sure that a good constrast ratio is respected when darkening/lightening a color
This commit is contained in:
parent
d946de904c
commit
7f85c00ee6
@ -39,7 +39,8 @@ import {
|
|||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
import {
|
import {
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
changeLightness,
|
CSSConstants,
|
||||||
|
findContrastColor,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
renderRichText,
|
renderRichText,
|
||||||
setLayerDimensions,
|
setLayerDimensions,
|
||||||
@ -233,8 +234,10 @@ class AnnotationElement {
|
|||||||
if (!this.data.color) {
|
if (!this.data.color) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const [r, g, b] = applyOpacity(...this.data.color, this.data.opacity);
|
return findContrastColor(
|
||||||
return changeLightness(r, g, b);
|
applyOpacity(...this.data.color, this.data.opacity),
|
||||||
|
CSSConstants.commentForegroundColor
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_normalizePoint(point) {
|
_normalizePoint(point) {
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
import {
|
import {
|
||||||
BaseException,
|
BaseException,
|
||||||
FeatureTest,
|
FeatureTest,
|
||||||
|
MathClamp,
|
||||||
shadow,
|
shadow,
|
||||||
Util,
|
Util,
|
||||||
warn,
|
warn,
|
||||||
@ -781,44 +782,19 @@ class ColorScheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeLightness(
|
class CSSConstants {
|
||||||
r,
|
static get commentForegroundColor() {
|
||||||
g,
|
const element = document.createElement("span");
|
||||||
b,
|
element.classList.add("comment", "sidebar");
|
||||||
lumCallback = ColorScheme.isDarkMode
|
const { style } = element;
|
||||||
? l => (1 - Math.sqrt(1 - l)) / 2
|
style.width = style.height = "0";
|
||||||
: l => (1 + Math.sqrt(l)) / 2
|
style.display = "none";
|
||||||
) {
|
style.color = "var(--comment-fg-color)";
|
||||||
r /= 255;
|
document.body.append(element);
|
||||||
g /= 255;
|
const { color } = window.getComputedStyle(element);
|
||||||
b /= 255;
|
element.remove();
|
||||||
|
return shadow(this, "commentForegroundColor", getRGB(color));
|
||||||
const max = Math.max(r, g, b);
|
|
||||||
const min = Math.min(r, g, b);
|
|
||||||
const l = (max + min) / 2;
|
|
||||||
const newL = (lumCallback(l) * 100).toFixed(2);
|
|
||||||
|
|
||||||
if (max === min) {
|
|
||||||
// gray
|
|
||||||
return `hsl(0, 0%, ${newL}%)`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const d = max - min;
|
|
||||||
|
|
||||||
// hue (branch on max only; avoids mod)
|
|
||||||
let h;
|
|
||||||
if (max === r) {
|
|
||||||
h = (g - b) / d + (g < b ? 6 : 0);
|
|
||||||
} else if (max === g) {
|
|
||||||
h = (b - r) / d + 2;
|
|
||||||
} else {
|
|
||||||
// max === b
|
|
||||||
h = (r - g) / d + 4;
|
|
||||||
}
|
|
||||||
h = (h * 60).toFixed(2);
|
|
||||||
const s = ((d / (1 - Math.abs(2 * l - 1))) * 100).toFixed(2);
|
|
||||||
|
|
||||||
return `hsl(${h}, ${s}%, ${newL}%)`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyOpacity(r, g, b, opacity) {
|
function applyOpacity(r, g, b, opacity) {
|
||||||
@ -830,6 +806,153 @@ function applyOpacity(r, g, b, opacity) {
|
|||||||
return [r, g, b];
|
return [r, g, b];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RGBToHSL(rgb, output) {
|
||||||
|
const r = rgb[0] / 255;
|
||||||
|
const g = rgb[1] / 255;
|
||||||
|
const b = rgb[2] / 255;
|
||||||
|
|
||||||
|
const max = Math.max(r, g, b);
|
||||||
|
const min = Math.min(r, g, b);
|
||||||
|
const l = (max + min) / 2;
|
||||||
|
|
||||||
|
if (max === min) {
|
||||||
|
// achromatic
|
||||||
|
output[0] = output[1] = 0; // hue and saturation are 0
|
||||||
|
} else {
|
||||||
|
const d = max - min;
|
||||||
|
output[1] = l < 0.5 ? d / (max + min) : d / (2 - max - min);
|
||||||
|
// hue
|
||||||
|
switch (max) {
|
||||||
|
case r:
|
||||||
|
output[0] = ((g - b) / d + (g < b ? 6 : 0)) * 60;
|
||||||
|
break;
|
||||||
|
case g:
|
||||||
|
output[0] = ((b - r) / d + 2) * 60;
|
||||||
|
break;
|
||||||
|
case b:
|
||||||
|
output[0] = ((r - g) / d + 4) * 60;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output[2] = l;
|
||||||
|
}
|
||||||
|
|
||||||
|
function HSLToRGB(hsl, output) {
|
||||||
|
const h = hsl[0];
|
||||||
|
const s = hsl[1];
|
||||||
|
const l = hsl[2];
|
||||||
|
const c = (1 - Math.abs(2 * l - 1)) * s; // chroma
|
||||||
|
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
||||||
|
const m = l - c / 2;
|
||||||
|
|
||||||
|
switch (Math.floor(h / 60)) {
|
||||||
|
case 0:
|
||||||
|
output[0] = c + m;
|
||||||
|
output[1] = x + m;
|
||||||
|
output[2] = m;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
output[0] = x + m;
|
||||||
|
output[1] = c + m;
|
||||||
|
output[2] = m;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
output[0] = m;
|
||||||
|
output[1] = c + m;
|
||||||
|
output[2] = x + m;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
output[0] = m;
|
||||||
|
output[1] = x + m;
|
||||||
|
output[2] = c + m;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
output[0] = x + m;
|
||||||
|
output[1] = m;
|
||||||
|
output[2] = c + m;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
output[0] = c + m;
|
||||||
|
output[1] = m;
|
||||||
|
output[2] = x + m;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeLuminance(x) {
|
||||||
|
return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
function contrastRatio(hsl1, hsl2, output) {
|
||||||
|
HSLToRGB(hsl1, output);
|
||||||
|
output.map(computeLuminance);
|
||||||
|
const lum1 = 0.2126 * output[0] + 0.7152 * output[1] + 0.0722 * output[2];
|
||||||
|
HSLToRGB(hsl2, output);
|
||||||
|
output.map(computeLuminance);
|
||||||
|
const lum2 = 0.2126 * output[0] + 0.7152 * output[1] + 0.0722 * output[2];
|
||||||
|
return lum1 > lum2
|
||||||
|
? (lum1 + 0.05) / (lum2 + 0.05)
|
||||||
|
: (lum2 + 0.05) / (lum1 + 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache for the findContrastColor function, to improve performance.
|
||||||
|
const contrastCache = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a color that has sufficient contrast against a fixed color.
|
||||||
|
* The luminance (in HSL color space) of the base color is adjusted
|
||||||
|
* until the contrast ratio between the base color and the fixed color
|
||||||
|
* is at least the minimum contrast ratio required by WCAG 2.1.
|
||||||
|
* @param {Array<number>} baseColor
|
||||||
|
* @param {Array<number>} fixedColor
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function findContrastColor(baseColor, fixedColor) {
|
||||||
|
const key =
|
||||||
|
baseColor[0] +
|
||||||
|
baseColor[1] * 0x100 +
|
||||||
|
baseColor[2] * 0x10000 +
|
||||||
|
fixedColor[0] * 0x1000000 +
|
||||||
|
fixedColor[1] * 0x100000000 +
|
||||||
|
fixedColor[2] * 0x10000000000;
|
||||||
|
let cachedValue = contrastCache.get(key);
|
||||||
|
if (cachedValue) {
|
||||||
|
return cachedValue;
|
||||||
|
}
|
||||||
|
const array = new Float32Array(9);
|
||||||
|
const output = array.subarray(0, 3);
|
||||||
|
const baseHSL = array.subarray(3, 6);
|
||||||
|
RGBToHSL(baseColor, baseHSL);
|
||||||
|
const fixedHSL = array.subarray(6, 9);
|
||||||
|
RGBToHSL(fixedColor, fixedHSL);
|
||||||
|
const isFixedColorDark = fixedHSL[2] < 0.5;
|
||||||
|
|
||||||
|
// Use the contrast ratio requirements from WCAG 2.1.
|
||||||
|
// https://www.w3.org/TR/WCAG21/#contrast-minimum
|
||||||
|
// https://www.w3.org/TR/WCAG21/#contrast-enhanced
|
||||||
|
const minContrast = isFixedColorDark ? 7 : 4.5;
|
||||||
|
|
||||||
|
baseHSL[2] = isFixedColorDark
|
||||||
|
? Math.sqrt(baseHSL[2])
|
||||||
|
: 1 - Math.sqrt(1 - baseHSL[2]);
|
||||||
|
const increment = isFixedColorDark ? 0.01 : -0.01;
|
||||||
|
let contrast = contrastRatio(baseHSL, fixedHSL, output);
|
||||||
|
while (baseHSL[2] >= 0 && baseHSL[2] <= 1 && contrast < minContrast) {
|
||||||
|
baseHSL[2] += increment;
|
||||||
|
contrast = contrastRatio(baseHSL, fixedHSL, output);
|
||||||
|
}
|
||||||
|
baseHSL[2] = MathClamp(baseHSL[2], 0, 1);
|
||||||
|
HSLToRGB(baseHSL, output);
|
||||||
|
cachedValue = Util.makeHexColor(
|
||||||
|
Math.round(output[0] * 255),
|
||||||
|
Math.round(output[1] * 255),
|
||||||
|
Math.round(output[2] * 255)
|
||||||
|
);
|
||||||
|
contrastCache.set(key, cachedValue);
|
||||||
|
return cachedValue;
|
||||||
|
}
|
||||||
|
|
||||||
function renderRichText({ html, dir, className }, container) {
|
function renderRichText({ html, dir, className }, container) {
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
if (typeof html === "string") {
|
if (typeof html === "string") {
|
||||||
@ -857,10 +980,11 @@ function renderRichText({ html, dir, className }, container) {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
changeLightness,
|
|
||||||
ColorScheme,
|
ColorScheme,
|
||||||
|
CSSConstants,
|
||||||
deprecated,
|
deprecated,
|
||||||
fetchData,
|
fetchData,
|
||||||
|
findContrastColor,
|
||||||
getColorValues,
|
getColorValues,
|
||||||
getCurrentTransform,
|
getCurrentTransform,
|
||||||
getCurrentTransformInverse,
|
getCurrentTransformInverse,
|
||||||
|
|||||||
@ -24,7 +24,8 @@ import {
|
|||||||
} from "./tools.js";
|
} from "./tools.js";
|
||||||
import {
|
import {
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
changeLightness,
|
CSSConstants,
|
||||||
|
findContrastColor,
|
||||||
noContextMenu,
|
noContextMenu,
|
||||||
stopEvent,
|
stopEvent,
|
||||||
} from "../display_utils.js";
|
} from "../display_utils.js";
|
||||||
@ -1866,11 +1867,13 @@ class AnnotationEditor {
|
|||||||
if (!this.color) {
|
if (!this.color) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
let [r, g, b] = AnnotationEditor._colorManager.convert(
|
const [r, g, b] = AnnotationEditor._colorManager.convert(
|
||||||
this._uiManager.getNonHCMColor(this.color)
|
this._uiManager.getNonHCMColor(this.color)
|
||||||
);
|
);
|
||||||
[r, g, b] = applyOpacity(r, g, b, this.opacity);
|
return findContrastColor(
|
||||||
return changeLightness(r, g, b);
|
applyOpacity(r, g, b, this.opacity),
|
||||||
|
CSSConstants.commentForegroundColor
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -46,8 +46,9 @@ import {
|
|||||||
} from "./shared/util.js";
|
} from "./shared/util.js";
|
||||||
import {
|
import {
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
changeLightness,
|
CSSConstants,
|
||||||
fetchData,
|
fetchData,
|
||||||
|
findContrastColor,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
getRGB,
|
getRGB,
|
||||||
@ -102,13 +103,14 @@ globalThis.pdfjsLib = {
|
|||||||
AnnotationType,
|
AnnotationType,
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
build,
|
build,
|
||||||
changeLightness,
|
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
createValidAbsoluteUrl,
|
createValidAbsoluteUrl,
|
||||||
|
CSSConstants,
|
||||||
DOMSVGFactory,
|
DOMSVGFactory,
|
||||||
DrawLayer,
|
DrawLayer,
|
||||||
FeatureTest,
|
FeatureTest,
|
||||||
fetchData,
|
fetchData,
|
||||||
|
findContrastColor,
|
||||||
getDocument,
|
getDocument,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
@ -160,13 +162,14 @@ export {
|
|||||||
AnnotationType,
|
AnnotationType,
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
build,
|
build,
|
||||||
changeLightness,
|
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
createValidAbsoluteUrl,
|
createValidAbsoluteUrl,
|
||||||
|
CSSConstants,
|
||||||
DOMSVGFactory,
|
DOMSVGFactory,
|
||||||
DrawLayer,
|
DrawLayer,
|
||||||
FeatureTest,
|
FeatureTest,
|
||||||
fetchData,
|
fetchData,
|
||||||
|
findContrastColor,
|
||||||
getDocument,
|
getDocument,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
|
|||||||
@ -15,10 +15,9 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
changeLightness,
|
findContrastColor,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
getRGB,
|
|
||||||
isValidFetchUrl,
|
isValidFetchUrl,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
renderRichText,
|
renderRichText,
|
||||||
@ -305,24 +304,10 @@ describe("display_utils", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("changeLightness", function () {
|
describe("findContrastColor", function () {
|
||||||
it("Check that the lightness is changed correctly", function () {
|
it("Check that the lightness is changed correctly", function () {
|
||||||
if (isNodeJS) {
|
expect(findContrastColor([210, 98, 76], [197, 113, 89])).toEqual(
|
||||||
pending("DOM is not supported in Node.js.");
|
"#240d09"
|
||||||
}
|
|
||||||
const div = document.createElement("div");
|
|
||||||
const { style } = div;
|
|
||||||
style.width = style.height = "0";
|
|
||||||
style.backgroundColor = "hsl(123, 45%, 67%)";
|
|
||||||
document.body.append(div);
|
|
||||||
const [r, g, b] = getRGB(getComputedStyle(div).backgroundColor);
|
|
||||||
div.remove();
|
|
||||||
expect([r, g, b]).toEqual([133, 209, 137]);
|
|
||||||
expect(changeLightness(r, g, b, l => l)).toEqual(
|
|
||||||
"hsl(123.16, 45.24%, 67.06%)"
|
|
||||||
);
|
|
||||||
expect(changeLightness(r, g, b, l => l / 2)).toEqual(
|
|
||||||
"hsl(123.16, 45.24%, 33.53%)"
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -37,8 +37,9 @@ import {
|
|||||||
} from "../../src/shared/util.js";
|
} from "../../src/shared/util.js";
|
||||||
import {
|
import {
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
changeLightness,
|
CSSConstants,
|
||||||
fetchData,
|
fetchData,
|
||||||
|
findContrastColor,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
getRGB,
|
getRGB,
|
||||||
@ -86,13 +87,14 @@ const expectedAPI = Object.freeze({
|
|||||||
AnnotationType,
|
AnnotationType,
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
build,
|
build,
|
||||||
changeLightness,
|
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
createValidAbsoluteUrl,
|
createValidAbsoluteUrl,
|
||||||
|
CSSConstants,
|
||||||
DOMSVGFactory,
|
DOMSVGFactory,
|
||||||
DrawLayer,
|
DrawLayer,
|
||||||
FeatureTest,
|
FeatureTest,
|
||||||
fetchData,
|
fetchData,
|
||||||
|
findContrastColor,
|
||||||
getDocument,
|
getDocument,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
|
|||||||
@ -358,7 +358,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#editorCommentsSidebar {
|
.comment.sidebar {
|
||||||
--comment-close-button-icon: url(images/comment-closeButton.svg);
|
--comment-close-button-icon: url(images/comment-closeButton.svg);
|
||||||
|
|
||||||
--comment-date-fg-color: light-dark(
|
--comment-date-fg-color: light-dark(
|
||||||
|
|||||||
@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AnnotationEditorType,
|
AnnotationEditorType,
|
||||||
changeLightness,
|
CSSConstants,
|
||||||
|
findContrastColor,
|
||||||
getRGB,
|
getRGB,
|
||||||
noContextMenu,
|
noContextMenu,
|
||||||
PDFDateString,
|
PDFDateString,
|
||||||
@ -359,8 +360,10 @@ class CommentManager {
|
|||||||
if (!color) {
|
if (!color) {
|
||||||
return null; // No color provided.
|
return null; // No color provided.
|
||||||
}
|
}
|
||||||
const [r, g, b] = getRGB(color);
|
return findContrastColor(
|
||||||
return changeLightness(r, g, b);
|
getRGB(color),
|
||||||
|
CSSConstants.commentForegroundColor
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#setText(text) {
|
#setText(text) {
|
||||||
|
|||||||
@ -24,13 +24,14 @@ const {
|
|||||||
AnnotationType,
|
AnnotationType,
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
build,
|
build,
|
||||||
changeLightness,
|
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
createValidAbsoluteUrl,
|
createValidAbsoluteUrl,
|
||||||
|
CSSConstants,
|
||||||
DOMSVGFactory,
|
DOMSVGFactory,
|
||||||
DrawLayer,
|
DrawLayer,
|
||||||
FeatureTest,
|
FeatureTest,
|
||||||
fetchData,
|
fetchData,
|
||||||
|
findContrastColor,
|
||||||
getDocument,
|
getDocument,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
@ -82,13 +83,14 @@ export {
|
|||||||
AnnotationType,
|
AnnotationType,
|
||||||
applyOpacity,
|
applyOpacity,
|
||||||
build,
|
build,
|
||||||
changeLightness,
|
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
createValidAbsoluteUrl,
|
createValidAbsoluteUrl,
|
||||||
|
CSSConstants,
|
||||||
DOMSVGFactory,
|
DOMSVGFactory,
|
||||||
DrawLayer,
|
DrawLayer,
|
||||||
FeatureTest,
|
FeatureTest,
|
||||||
fetchData,
|
fetchData,
|
||||||
|
findContrastColor,
|
||||||
getDocument,
|
getDocument,
|
||||||
getFilenameFromUrl,
|
getFilenameFromUrl,
|
||||||
getPdfFilenameFromUrl,
|
getPdfFilenameFromUrl,
|
||||||
|
|||||||
@ -250,7 +250,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
|||||||
<span data-l10n-id="pdfjs-editor-comment-button-label"></span>
|
<span data-l10n-id="pdfjs-editor-comment-button-label"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="editorParamsToolbar sidebar hidden menu" id="editorCommentParamsToolbar">
|
<div class="editorParamsToolbar sidebar hidden menu" id="editorCommentParamsToolbar">
|
||||||
<div id="editorCommentsSidebar" class="menuContainer" role="landmark" aria-labelledby="editorCommentsSidebarHeader">
|
<div id="editorCommentsSidebar" class="menuContainer comment sidebar" role="landmark" aria-labelledby="editorCommentsSidebarHeader">
|
||||||
<div id="editorCommentsSidebarHeader" role="heading" aria-level="2">
|
<div id="editorCommentsSidebarHeader" role="heading" aria-level="2">
|
||||||
<span class="commentCount">
|
<span class="commentCount">
|
||||||
<span id="editorCommentsSidebarTitle" data-l10n-id="pdfjs-editor-comments-sidebar-title" data-l10n-args='{ "count": 0 }'></span>
|
<span id="editorCommentsSidebarTitle" data-l10n-id="pdfjs-editor-comments-sidebar-title" data-l10n-args='{ "count": 0 }'></span>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user