From b0d6306b6286b86b36783dbcbce3ef2952d65e45 Mon Sep 17 00:00:00 2001 From: Shiva Prasad Date: Wed, 19 Oct 2022 16:52:38 +0530 Subject: [PATCH] Handle canvas styles with alpha in rgba() format The HTML Canvas standard specifies `#rrggbb` and `rgba(r, g, b, a)` as the only allowed serializations (the latter for alpha < 1) of CSS colors returned by fill and stroke styles. So far we handled only hex values. This was put off till now, as PDF.js never seemed to draw with styles having alpha, even with PDFs having transparent shapes. But the newly added Ink annotation editor with opacity control does draw with rgba() styles, finally necessitating this patch. --- addon/doq.js | 13 +++++++------ addon/lib/color.js | 38 +++++++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/addon/doq.js b/addon/doq.js index 484d6d4..6d4c452 100644 --- a/addon/doq.js +++ b/addon/doq.js @@ -154,14 +154,15 @@ const DOQReader = { ); if (isShape && !this.flags.shapesOn || !isColor) return style; + style = newColor(style); const bg = isText && this.getCanvasColor(ctx, ...args); - const key = style + (bg ? bg.toHex() : ""); + const key = style.hex + (bg ? bg.hex : ""); let newStyle = this.styleCache.get(key); if (!newStyle) { - newStyle = this.calcStyle(newColor(style), bg); + newStyle = this.calcStyle(style, bg); this.styleCache.set(key, newStyle); } - return newStyle; + return newStyle.toHex(style.alpha); }, getCanvasColor(ctx, text, tx, ty) { if (!this.canvasData.has(ctx)) @@ -186,12 +187,12 @@ const DOQReader = { if (color.chroma > 10) { const accents = acc.concat(this.readerTone.scheme.colors); if (accents.length) - style = this.findMatch(accents, e => e.deltaE(color), Math.min).toHex(); + style = this.findMatch(accents, e => e.deltaE(color), Math.min); } else if (textBg && bg.deltaE(textBg) > 2.3) { - style = this.findMatch([bg, fg], diffL, Math.max).toHex(); + style = this.findMatch([bg, fg], diffL, Math.max); } else { const whiteL = Color.white.lightness; - style = grad(1 - color.lightness / whiteL).toHex(); + style = grad(1 - color.lightness / whiteL); } return style; }, diff --git a/addon/lib/color.js b/addon/lib/color.js index ed7e23f..32b7fc9 100644 --- a/addon/lib/color.js +++ b/addon/lib/color.js @@ -14,16 +14,24 @@ export class Color { } else if (typeof args[0] === "string") { const str = args[0]; - if (str[0] !== "#" || str.length !== 7) { - console.error(`Invalid hex: "${str}"`); + if (str[0] === "#" && str.length === 7) { + this._hex = str; + this._rgb = Color.parseHex(str); + } else if (str.startsWith("rgba(")) { + [this._rgb, this._alpha] = Color.parseRGBA(str); + } else { + console.error(`Unsupported color format: "${str}"`); return null; } - this._rgb = Color.parseHex(str); } else { this._rgb = [0, 0, 0]; } } + get hex() { + this._hex = this._hex || this.toHex(); + return this._hex; + } get rgb() { this._rgb = this._rgb || sRGB.fromLab(this._lab); return this._rgb; @@ -39,6 +47,9 @@ export class Color { const [L, a, b] = this.lab; return Math.sqrt(a ** 2 + b ** 2); } + get alpha() { + return this._alpha ?? 1; + } deltaE(color) { return Math.sqrt(this.lab.reduce((a, c, i) => { @@ -67,12 +78,10 @@ export class Color { }; } - toHex() { - const coords = this.rgb.map(c => Math.round(c * 255)); - const hex = coords.map(c => { - c = Math.min(Math.max(c, 0), 255); - return c.toString(16).padStart(2, "0"); - }).join(""); + toHex(alpha = 1) { + let hex = this.rgb.map(Color.compToHex).join(""); + if (alpha !== 1) + hex += Color.compToHex(alpha); return "#" + hex; } static parseHex(str) { @@ -82,6 +91,17 @@ export class Color { }); return rgba.slice(0, 3); } + static parseRGBA(str) { + const rgba = str.slice(5, -1).split(","); + return [ + rgba.slice(0, 3).map(c => parseInt(c) / 255), /* [r, g, b] */ + parseFloat(rgba.pop()) /* alpha */ + ]; + } + static compToHex(c) { + c = Math.round(Math.min(Math.max(c * 255, 0), 255)); + return c.toString(16).padStart(2, "0"); + } } Color.white = new Color([1, 1, 1]);