Skip to content

Commit 4c74600

Browse files
committed
[Add] support for oklch() color conversion in html2canvas
- Implemented a function to resolve oklch() colors to sRGB before rendering with html2canvas. - Updated the Worker class to call this function in the toCanvas method.
1 parent 07473b6 commit 4c74600

9 files changed

Lines changed: 59416 additions & 59213 deletions

dist/html2pdf.bundle.js

Lines changed: 38245 additions & 38166 deletions
Large diffs are not rendered by default.

dist/html2pdf.bundle.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/html2pdf.bundle.min.js.LICENSE.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ PERFORMANCE OF THIS SOFTWARE.
7272

7373
/*! ../internals/array-reduce */
7474

75+
/*! ../internals/array-set-length */
76+
7577
/*! ../internals/array-slice */
7678

7779
/*! ../internals/array-species-constructor */
@@ -534,7 +536,7 @@ PERFORMANCE OF THIS SOFTWARE.
534536

535537
/*! @babel/runtime/helpers/typeof */
536538

537-
/*! @license DOMPurify 3.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.1/LICENSE */
539+
/*! @license DOMPurify 3.3.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.2/LICENSE */
538540

539541
/*! canvg */
540542

@@ -1302,6 +1304,10 @@ PERFORMANCE OF THIS SOFTWARE.
13021304
!*** ./node_modules/fast-png/lib-esm/helpers/unfilter.js ***!
13031305
\***********************************************************/
13041306

1307+
/*!************************************************************!*\
1308+
!*** ./node_modules/core-js/internals/array-set-length.js ***!
1309+
\************************************************************/
1310+
13051311
/*!************************************************************!*\
13061312
!*** ./node_modules/core-js/internals/define-built-ins.js ***!
13071313
\************************************************************/

dist/html2pdf.bundle.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/html2pdf.js

Lines changed: 21094 additions & 21015 deletions
Large diffs are not rendered by default.

dist/html2pdf.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/html2pdf.min.js.LICENSE.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ PERFORMANCE OF THIS SOFTWARE.
5151

5252
/*! ../internals/array-reduce */
5353

54+
/*! ../internals/array-set-length */
55+
5456
/*! ../internals/array-slice */
5557

5658
/*! ../internals/array-species-constructor */
@@ -513,7 +515,7 @@ PERFORMANCE OF THIS SOFTWARE.
513515

514516
/*! @babel/runtime/helpers/typeof */
515517

516-
/*! @license DOMPurify 3.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.1/LICENSE */
518+
/*! @license DOMPurify 3.3.2 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.3.2/LICENSE */
517519

518520
/*! canvg */
519521

@@ -1281,6 +1283,10 @@ PERFORMANCE OF THIS SOFTWARE.
12811283
!*** ./node_modules/fast-png/lib-esm/helpers/unfilter.js ***!
12821284
\***********************************************************/
12831285

1286+
/*!************************************************************!*\
1287+
!*** ./node_modules/core-js/internals/array-set-length.js ***!
1288+
\************************************************************/
1289+
12841290
/*!************************************************************!*\
12851291
!*** ./node_modules/core-js/internals/define-built-ins.js ***!
12861292
\************************************************************/

dist/html2pdf.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/worker.js

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,40 @@ import html2canvas from 'html2canvas';
33
import { deepCloneBasic } from './snapdom/clone.js';
44
import { objType, createElement, toPx } from './utils.js';
55

6+
/* ----- OKLCH PREPROCESSING ----- */
7+
8+
// html2canvas cannot parse oklch() colors. This function walks the container
9+
// tree, reads each element's computed style, and for any property that still
10+
// carries an oklch() value it uses a canvas 2D context (browser-native color
11+
// conversion) to resolve it to an sRGB value and writes it back as an inline
12+
// style so that html2canvas only ever sees rgb/rgba/hex colours.
13+
function resolveOklchColors(container) {
14+
var colorProps = [
15+
'color', 'background-color',
16+
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
17+
'outline-color', 'text-decoration-color', 'column-rule-color',
18+
'fill', 'stroke',
19+
];
20+
var ctx = document.createElement('canvas').getContext('2d');
21+
var elements = [container].concat(Array.prototype.slice.call(container.querySelectorAll('*')));
22+
elements.forEach(function (el) {
23+
var computed = getComputedStyle(el);
24+
colorProps.forEach(function (prop) {
25+
var val = computed.getPropertyValue(prop);
26+
if (val && val.indexOf('oklch') !== -1) {
27+
ctx.fillStyle = val;
28+
el.style.setProperty(prop, ctx.fillStyle);
29+
}
30+
});
31+
});
32+
}
33+
634
/* ----- CONSTRUCTOR ----- */
735

836
var Worker = function Worker(opt) {
937
// Create the root parent for the proto chain, and the starting Worker.
1038
var root = Object.assign(Worker.convert(Promise.resolve()),
11-
JSON.parse(JSON.stringify(Worker.template)));
39+
JSON.parse(JSON.stringify(Worker.template)));
1240
var self = Worker.convert(Promise.resolve(), root);
1341

1442
// Set progress, optional settings, and return.
@@ -46,7 +74,7 @@ Worker.template = {
4674
},
4775
opt: {
4876
filename: 'file.pdf',
49-
margin: [0,0,0,0],
77+
margin: [0, 0, 0, 0],
5078
image: { type: 'jpeg', quality: 0.95 },
5179
enableLinks: true,
5280
html2canvas: {},
@@ -59,20 +87,20 @@ Worker.template = {
5987
Worker.prototype.from = function from(src, type) {
6088
function getType(src) {
6189
switch (objType(src)) {
62-
case 'string': return 'string';
90+
case 'string': return 'string';
6391
case 'element': return src.nodeName.toLowerCase && src.nodeName.toLowerCase() === 'canvas' ? 'canvas' : 'element';
64-
default: return 'unknown';
92+
default: return 'unknown';
6593
}
6694
}
6795

6896
return this.then(function from_main() {
6997
type = type || getType(src);
7098
switch (type) {
71-
case 'string': return this.set({ src: createElement('div', {innerHTML: src}) });
99+
case 'string': return this.set({ src: createElement('div', { innerHTML: src }) });
72100
case 'element': return this.set({ src: src });
73-
case 'canvas': return this.set({ canvas: src });
74-
case 'img': return this.set({ img: src });
75-
default: return this.error('Unknown source type.');
101+
case 'canvas': return this.set({ canvas: src });
102+
case 'img': return this.set({ img: src });
103+
default: return this.error('Unknown source type.');
76104
}
77105
});
78106
};
@@ -118,7 +146,7 @@ Worker.prototype.toContainer = function toContainer() {
118146

119147
// Create and attach the elements.
120148
var source = deepCloneBasic(this.prop.src);
121-
this.prop.overlay = createElement('div', { className: 'html2pdf__overlay', style: overlayCSS });
149+
this.prop.overlay = createElement('div', { className: 'html2pdf__overlay', style: overlayCSS });
122150
this.prop.container = createElement('div', { className: 'html2pdf__container', style: containerCSS });
123151
this.prop.container.appendChild(source);
124152
this.prop.overlay.appendChild(this.prop.container);
@@ -132,8 +160,10 @@ Worker.prototype.toContainer = function toContainer() {
132160
Worker.prototype.toCanvas = function toCanvas() {
133161
// Set up function prerequisites.
134162
var prereqs = [
135-
function checkContainer() { return document.body.contains(this.prop.container)
136-
|| this.toContainer(); }
163+
function checkContainer() {
164+
return document.body.contains(this.prop.container)
165+
|| this.toContainer();
166+
}
137167
];
138168

139169
// Fulfill prereqs then create the canvas.
@@ -142,10 +172,13 @@ Worker.prototype.toCanvas = function toCanvas() {
142172
var options = Object.assign({}, this.opt.html2canvas);
143173
delete options.onrendered;
144174

175+
// Pre-convert oklch() colours to rgb so html2canvas can parse them.
176+
resolveOklchColors(this.prop.container);
177+
145178
return html2canvas(this.prop.container, options);
146179
}).then(function toCanvas_post(canvas) {
147180
// Handle old-fashioned 'onrendered' argument.
148-
var onRendered = this.opt.html2canvas.onrendered || function () {};
181+
var onRendered = this.opt.html2canvas.onrendered || function () { };
149182
onRendered(canvas);
150183

151184
this.prop.canvas = canvas;
@@ -197,9 +230,9 @@ Worker.prototype.toPdf = function toPdf() {
197230
// Initialize the PDF.
198231
this.prop.pdf = this.prop.pdf || new jsPDF(opt.jsPDF);
199232

200-
for (var page=0; page<nPages; page++) {
233+
for (var page = 0; page < nPages; page++) {
201234
// Trim the final page to reduce file size.
202-
if (page === nPages-1 && pxFullHeight % pxPageHeight !== 0) {
235+
if (page === nPages - 1 && pxFullHeight % pxPageHeight !== 0) {
203236
pageCanvas.height = pxFullHeight % pxPageHeight;
204237
pageHeight = pageCanvas.height * this.prop.pageSize.inner.width / pageCanvas.width;
205238
}
@@ -209,13 +242,13 @@ Worker.prototype.toPdf = function toPdf() {
209242
var h = pageCanvas.height;
210243
pageCtx.fillStyle = 'white';
211244
pageCtx.fillRect(0, 0, w, h);
212-
pageCtx.drawImage(canvas, 0, page*pxPageHeight, w, h, 0, 0, w, h);
245+
pageCtx.drawImage(canvas, 0, page * pxPageHeight, w, h, 0, 0, w, h);
213246

214247
// Add the page to the PDF.
215-
if (page) this.prop.pdf.addPage();
248+
if (page) this.prop.pdf.addPage();
216249
var imgData = pageCanvas.toDataURL('image/' + opt.image.type, opt.image.quality);
217250
this.prop.pdf.addImage(imgData, opt.image.type, opt.margin[1], opt.margin[0],
218-
this.prop.pageSize.inner.width, pageHeight);
251+
this.prop.pageSize.inner.width, pageHeight);
219252
}
220253
});
221254
};
@@ -362,11 +395,11 @@ Worker.prototype.setPageSize = function setPageSize(pageSize) {
362395
// Add 'inner' field if not present.
363396
if (!pageSize.hasOwnProperty('inner')) {
364397
pageSize.inner = {
365-
width: pageSize.width - this.opt.margin[1] - this.opt.margin[3],
398+
width: pageSize.width - this.opt.margin[1] - this.opt.margin[3],
366399
height: pageSize.height - this.opt.margin[0] - this.opt.margin[2]
367400
};
368401
pageSize.inner.px = {
369-
width: toPx(pageSize.inner.width, pageSize.k),
402+
width: toPx(pageSize.inner.width, pageSize.k),
370403
height: toPx(pageSize.inner.height, pageSize.k)
371404
};
372405
pageSize.inner.ratio = pageSize.inner.height / pageSize.inner.width;
@@ -379,10 +412,10 @@ Worker.prototype.setPageSize = function setPageSize(pageSize) {
379412

380413
Worker.prototype.setProgress = function setProgress(val, state, n, stack) {
381414
// Immediately update all progress values.
382-
if (val != null) this.progress.val = val;
383-
if (state != null) this.progress.state = state;
384-
if (n != null) this.progress.n = n;
385-
if (stack != null) this.progress.stack = stack;
415+
if (val != null) this.progress.val = val;
416+
if (state != null) this.progress.state = state;
417+
if (n != null) this.progress.n = n;
418+
if (stack != null) this.progress.stack = stack;
386419
this.progress.ratio = this.progress.val / this.progress.state;
387420

388421
// Return this for command chaining.
@@ -424,8 +457,8 @@ Worker.prototype.thenCore = function thenCore(onFulfilled, onRejected, thenBase)
424457

425458
// Wrap `this` for encapsulation and bind it to the promise handlers.
426459
var self = this;
427-
if (onFulfilled) { onFulfilled = onFulfilled.bind(self); }
428-
if (onRejected) { onRejected = onRejected.bind(self); }
460+
if (onFulfilled) { onFulfilled = onFulfilled.bind(self); }
461+
if (onRejected) { onRejected = onRejected.bind(self); }
429462

430463
// Cast self into a Promise to avoid polyfills recursively defining `then`.
431464
var isNative = Promise.toString().indexOf('[native code]') !== -1 && Promise.name === 'Promise';
@@ -452,7 +485,7 @@ Worker.prototype.thenList = function thenList(fns) {
452485

453486
Worker.prototype['catch'] = function (onRejected) {
454487
// Bind `this` to the promise handler, call `catch`, and return a Worker.
455-
if (onRejected) { onRejected = onRejected.bind(this); }
488+
if (onRejected) { onRejected = onRejected.bind(this); }
456489
var returnVal = Promise.prototype['catch'].call(this, onRejected);
457490
return Worker.convert(returnVal, this);
458491
};

0 commit comments

Comments
 (0)