mirror of
https://github.com/zen-browser/pdf.js.git
synced 2025-07-09 09:45:42 +02:00
Improve highlightments and popups in HCM (bug 1830850)
- Modify the text and background colors in popup to fit a11y requirements - Add a backdrop filter on clickable areas in using a svg filter mapping canvas colors to Highlight and HighlightText ones.
This commit is contained in:
parent
ccb72073b0
commit
c519cc821b
6 changed files with 248 additions and 69 deletions
|
@ -64,6 +64,12 @@ class DOMFilterFactory extends BaseFilterFactory {
|
|||
|
||||
#hcmUrl;
|
||||
|
||||
#hcmHighlightFilter;
|
||||
|
||||
#hcmHighlightKey;
|
||||
|
||||
#hcmHighlightUrl;
|
||||
|
||||
#id = 0;
|
||||
|
||||
constructor({ docId, ownerDocument = globalThis.document } = {}) {
|
||||
|
@ -98,13 +104,6 @@ class DOMFilterFactory extends BaseFilterFactory {
|
|||
return this.#_defs;
|
||||
}
|
||||
|
||||
#appendFeFunc(feComponentTransfer, func, table) {
|
||||
const feFunc = this.#document.createElementNS(SVG_NS, func);
|
||||
feFunc.setAttribute("type", "discrete");
|
||||
feFunc.setAttribute("tableValues", table);
|
||||
feComponentTransfer.append(feFunc);
|
||||
}
|
||||
|
||||
addFilter(maps) {
|
||||
if (!maps) {
|
||||
return "none";
|
||||
|
@ -155,20 +154,8 @@ class DOMFilterFactory extends BaseFilterFactory {
|
|||
this.#cache.set(maps, url);
|
||||
this.#cache.set(key, url);
|
||||
|
||||
const filter = this.#document.createElementNS(SVG_NS, "filter", SVG_NS);
|
||||
filter.setAttribute("id", id);
|
||||
filter.setAttribute("color-interpolation-filters", "sRGB");
|
||||
const feComponentTransfer = this.#document.createElementNS(
|
||||
SVG_NS,
|
||||
"feComponentTransfer"
|
||||
);
|
||||
filter.append(feComponentTransfer);
|
||||
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncR", tableR);
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncG", tableG);
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncB", tableB);
|
||||
|
||||
this.#defs.append(filter);
|
||||
const filter = this.#createFilter(id);
|
||||
this.#addTransferMapConversion(tableR, tableG, tableB, filter);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
@ -187,13 +174,9 @@ class DOMFilterFactory extends BaseFilterFactory {
|
|||
return this.#hcmUrl;
|
||||
}
|
||||
|
||||
this.#defs.style.color = fgColor;
|
||||
fgColor = getComputedStyle(this.#defs).getPropertyValue("color");
|
||||
const fgRGB = getRGB(fgColor);
|
||||
const fgRGB = this.#getRGB(fgColor);
|
||||
fgColor = Util.makeHexColor(...fgRGB);
|
||||
this.#defs.style.color = bgColor;
|
||||
bgColor = getComputedStyle(this.#defs).getPropertyValue("color");
|
||||
const bgRGB = getRGB(bgColor);
|
||||
const bgRGB = this.#getRGB(bgColor);
|
||||
bgColor = Util.makeHexColor(...bgRGB);
|
||||
this.#defs.style.color = "";
|
||||
|
||||
|
@ -221,39 +204,9 @@ class DOMFilterFactory extends BaseFilterFactory {
|
|||
const table = map.join(",");
|
||||
|
||||
const id = `g_${this.#docId}_hcm_filter`;
|
||||
const filter = (this.#hcmFilter = this.#document.createElementNS(
|
||||
SVG_NS,
|
||||
"filter",
|
||||
SVG_NS
|
||||
));
|
||||
filter.setAttribute("id", id);
|
||||
filter.setAttribute("color-interpolation-filters", "sRGB");
|
||||
let feComponentTransfer = this.#document.createElementNS(
|
||||
SVG_NS,
|
||||
"feComponentTransfer"
|
||||
);
|
||||
filter.append(feComponentTransfer);
|
||||
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncR", table);
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncG", table);
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncB", table);
|
||||
|
||||
const feColorMatrix = this.#document.createElementNS(
|
||||
SVG_NS,
|
||||
"feColorMatrix"
|
||||
);
|
||||
feColorMatrix.setAttribute("type", "matrix");
|
||||
feColorMatrix.setAttribute(
|
||||
"values",
|
||||
"0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"
|
||||
);
|
||||
filter.append(feColorMatrix);
|
||||
|
||||
feComponentTransfer = this.#document.createElementNS(
|
||||
SVG_NS,
|
||||
"feComponentTransfer"
|
||||
);
|
||||
filter.append(feComponentTransfer);
|
||||
const filter = (this.#hcmHighlightFilter = this.#createFilter(id));
|
||||
this.#addTransferMapConversion(table, table, table, filter);
|
||||
this.#addGrayConversion(filter);
|
||||
|
||||
const getSteps = (c, n) => {
|
||||
const start = fgRGB[c] / 255;
|
||||
|
@ -264,18 +217,101 @@ class DOMFilterFactory extends BaseFilterFactory {
|
|||
}
|
||||
return arr.join(",");
|
||||
};
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncR", getSteps(0, 5));
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncG", getSteps(1, 5));
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncB", getSteps(2, 5));
|
||||
|
||||
this.#defs.append(filter);
|
||||
this.#addTransferMapConversion(
|
||||
getSteps(0, 5),
|
||||
getSteps(1, 5),
|
||||
getSteps(2, 5),
|
||||
filter
|
||||
);
|
||||
|
||||
this.#hcmUrl = `url(#${id})`;
|
||||
return this.#hcmUrl;
|
||||
}
|
||||
|
||||
addHighlightHCMFilter(fgColor, bgColor, newFgColor, newBgColor) {
|
||||
const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`;
|
||||
if (this.#hcmHighlightKey === key) {
|
||||
return this.#hcmHighlightUrl;
|
||||
}
|
||||
|
||||
this.#hcmHighlightKey = key;
|
||||
this.#hcmHighlightUrl = "none";
|
||||
this.#hcmHighlightFilter?.remove();
|
||||
|
||||
if (!fgColor || !bgColor) {
|
||||
return this.#hcmHighlightUrl;
|
||||
}
|
||||
|
||||
const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this));
|
||||
let fgGray = Math.round(
|
||||
0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]
|
||||
);
|
||||
let bgGray = Math.round(
|
||||
0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]
|
||||
);
|
||||
let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(
|
||||
this.#getRGB.bind(this)
|
||||
);
|
||||
if (bgGray < fgGray) {
|
||||
[fgGray, bgGray, newFgRGB, newBgRGB] = [
|
||||
bgGray,
|
||||
fgGray,
|
||||
newBgRGB,
|
||||
newFgRGB,
|
||||
];
|
||||
}
|
||||
this.#defs.style.color = "";
|
||||
|
||||
// Now we can create the filters to highlight some canvas parts.
|
||||
// The colors in the pdf will almost be Canvas and CanvasText, hence we
|
||||
// want to filter them to finally get Highlight and HighlightText.
|
||||
// Since we're in HCM the background color and the foreground color should
|
||||
// be really different when converted to grayscale (if they're not then it
|
||||
// means that we've a poor contrast). Once the canvas colors are converted
|
||||
// to grayscale we can easily map them on their new colors.
|
||||
// The grayscale step is important because if we've something like:
|
||||
// fgColor = #FF....
|
||||
// bgColor = #FF....
|
||||
// then we are enable to map the red component on the new red components
|
||||
// which can be different.
|
||||
|
||||
const getSteps = (fg, bg, n) => {
|
||||
const arr = new Array(256);
|
||||
const step = (bgGray - fgGray) / n;
|
||||
const newStart = fg / 255;
|
||||
const newStep = (bg - fg) / (255 * n);
|
||||
let prev = 0;
|
||||
for (let i = 0; i <= n; i++) {
|
||||
const k = Math.round(fgGray + i * step);
|
||||
const value = newStart + i * newStep;
|
||||
for (let j = prev; j <= k; j++) {
|
||||
arr[j] = value;
|
||||
}
|
||||
prev = k + 1;
|
||||
}
|
||||
for (let i = prev; i < 256; i++) {
|
||||
arr[i] = arr[prev - 1];
|
||||
}
|
||||
return arr.join(",");
|
||||
};
|
||||
|
||||
const id = `g_${this.#docId}_hcm_highlight_filter`;
|
||||
const filter = (this.#hcmHighlightFilter = this.#createFilter(id));
|
||||
|
||||
this.#addGrayConversion(filter);
|
||||
this.#addTransferMapConversion(
|
||||
getSteps(newFgRGB[0], newBgRGB[0], 5),
|
||||
getSteps(newFgRGB[1], newBgRGB[1], 5),
|
||||
getSteps(newFgRGB[2], newBgRGB[2], 5),
|
||||
filter
|
||||
);
|
||||
|
||||
this.#hcmHighlightUrl = `url(#${id})`;
|
||||
return this.#hcmHighlightUrl;
|
||||
}
|
||||
|
||||
destroy(keepHCM = false) {
|
||||
if (keepHCM && this.#hcmUrl) {
|
||||
if (keepHCM && (this.#hcmUrl || this.#hcmHighlightUrl)) {
|
||||
return;
|
||||
}
|
||||
if (this.#_defs) {
|
||||
|
@ -288,6 +324,51 @@ class DOMFilterFactory extends BaseFilterFactory {
|
|||
}
|
||||
this.#id = 0;
|
||||
}
|
||||
|
||||
#addGrayConversion(filter) {
|
||||
const feColorMatrix = this.#document.createElementNS(
|
||||
SVG_NS,
|
||||
"feColorMatrix"
|
||||
);
|
||||
feColorMatrix.setAttribute("type", "matrix");
|
||||
feColorMatrix.setAttribute(
|
||||
"values",
|
||||
"0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0"
|
||||
);
|
||||
filter.append(feColorMatrix);
|
||||
}
|
||||
|
||||
#createFilter(id) {
|
||||
const filter = this.#document.createElementNS(SVG_NS, "filter");
|
||||
filter.setAttribute("color-interpolation-filters", "sRGB");
|
||||
filter.setAttribute("id", id);
|
||||
this.#defs.append(filter);
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
#appendFeFunc(feComponentTransfer, func, table) {
|
||||
const feFunc = this.#document.createElementNS(SVG_NS, func);
|
||||
feFunc.setAttribute("type", "discrete");
|
||||
feFunc.setAttribute("tableValues", table);
|
||||
feComponentTransfer.append(feFunc);
|
||||
}
|
||||
|
||||
#addTransferMapConversion(rTable, gTable, bTable, filter) {
|
||||
const feComponentTransfer = this.#document.createElementNS(
|
||||
SVG_NS,
|
||||
"feComponentTransfer"
|
||||
);
|
||||
filter.append(feComponentTransfer);
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable);
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable);
|
||||
this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable);
|
||||
}
|
||||
|
||||
#getRGB(color) {
|
||||
this.#defs.style.color = color;
|
||||
return getRGB(getComputedStyle(this.#defs).getPropertyValue("color"));
|
||||
}
|
||||
}
|
||||
|
||||
class DOMCanvasFactory extends BaseCanvasFactory {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue