fix(pdf): use html2canvas onclone to inline all styles before render

Previous flattenColors approach failed because html2canvas reads CSS
rules from stylesheets, not computed styles. Now in the onclone callback:
1. Walk all elements, get computed styles from original doc
2. Set every property as inline style on cloned elements
3. Remove all <style> tags and <link rel=stylesheet> from clone
This ensures html2canvas never encounters raw color-mix() in stylesheets.
This commit is contained in:
Local Build committed 2026-08-13 21:01:55 +02:00
1 parent 7df7734eeb
commit debbefcb43
2 files changed
+100 -66

No files matched your search

+50 -33
View File
@@ -1034,37 +1034,32 @@
photoReady = Promise.resolve(null);
}
function flattenColors(el) {
var s = getComputedStyle(el);
var props = ['color','background-color','border-color','border-top-color','border-bottom-color','border-left-color','border-right-color','box-shadow','background'];
var originals = {};
props.forEach(function(p) {
var v = s.getPropertyValue(p);
if (v && v.indexOf('color-mix') !== -1) {
originals[p] = el.style.getPropertyValue(p);
el.style.setProperty(p, v);
function cloneAndInlineStyles(original) {
var clone = original.cloneNode(true);
var origEls = original.querySelectorAll('*');
var cloneEls = clone.querySelectorAll('*');
var allProps = ['color','background-color','background','border-color','border-top-color','border-bottom-color','border-left-color','border-right-color','box-shadow','opacity','font-family','font-size','font-weight','line-height','letter-spacing','text-align','text-decoration','text-transform','padding','padding-top','padding-bottom','padding-left','padding-right','margin','margin-top','margin-bottom','margin-left','margin-right','display','flex-direction','align-items','justify-content','gap','grid-template-columns','grid-column','width','height','min-width','min-height','max-width','max-height','position','top','bottom','left','right','z-index','overflow','border-radius','border-width','border-style','white-space','list-style-type','object-fit'];
for (var i = 0; i < origEls.length && i < cloneEls.length; i++) {
var cs = getComputedStyle(origEls[i]);
for (var j = 0; j < allProps.length; j++) {
var v = cs.getPropertyValue(allProps[j]);
if (v) cloneEls[i].style.setProperty(allProps[j], v);
}
});
var children = el.children;
for (var i = 0; i < children.length; i++) {
flattenColors(children[i]);
}
return originals;
}
function restoreColors(el, originals) {
Object.keys(originals).forEach(function(p) {
if (originals[p]) el.style.setProperty(p, originals[p]);
else el.style.removeProperty(p);
});
var children = el.children;
for (var i = 0; i < children.length; i++) {
restoreColors(children[i], originals);
var origCs = getComputedStyle(original);
for (var j = 0; j < allProps.length; j++) {
var v = origCs.getPropertyValue(allProps[j]);
if (v) clone.style.setProperty(allProps[j], v);
}
var styles = clone.querySelectorAll('style');
for (var s = 0; s < styles.length; s++) styles[s].textContent = '';
var links = clone.querySelectorAll('link[rel="stylesheet"]');
for (var l = 0; l < links.length; l++) links[l].remove();
return clone;
}
photoReady.then(function (dataUrl) {
console.log('[PDF] step 1: photo ready, dataUrl:', dataUrl ? 'yes (' + dataUrl.length + ' chars)' : 'null');
console.log('[PDF] step 1: photo ready');
if (dataUrl && photo) photo.setAttribute('src', dataUrl);
console.log('[PDF] step 2: waiting for fonts...');
return document.fonts ? document.fonts.ready : Promise.resolve();
@@ -1074,19 +1069,41 @@
return loadScript(CDN);
})
.then(function () {
console.log('[PDF] step 4: html2pdf loaded, type:', typeof html2pdf);
console.log('[PDF] step 4: flattening color-mix() values...');
var snapshot = flattenColors(doc);
console.log('[PDF] step 4: starting generation...');
console.log('[PDF] step 4: html2pdf loaded, generating...');
return html2pdf().from(doc).set({
margin: 10,
filename: 'Mamadaev_Resume.pdf',
image: { type: 'jpeg', quality: 0.95 },
html2canvas: { scale: 2, useCORS: true, backgroundColor: '#fff' },
html2canvas: {
scale: 2,
useCORS: true,
backgroundColor: '#fff',
onclone: function (clonedDoc) {
console.log('[PDF] onclone: inlining styles...');
var clonedEl = clonedDoc.querySelector('.resume-doc');
if (!clonedEl) return;
var origEls = doc.querySelectorAll('*');
var cloneEls = clonedEl.querySelectorAll('*');
var props = ['color','background-color','background','border-color','border-top-color','border-bottom-color','border-left-color','border-right-color','box-shadow','opacity','font-family','font-size','font-weight','line-height','letter-spacing','text-align','text-decoration','text-transform','padding','padding-top','padding-bottom','padding-left','padding-right','margin','margin-top','margin-bottom','margin-left','margin-right','display','flex-direction','align-items','justify-content','gap','grid-template-columns','grid-column','width','height','min-width','min-height','max-width','max-height','position','top','bottom','left','right','z-index','overflow','border-radius','border-width','border-style','white-space','list-style-type','object-fit'];
for (var i = 0; i < origEls.length && i < cloneEls.length; i++) {
var cs = getComputedStyle(origEls[i]);
for (var j = 0; j < props.length; j++) {
var v = cs.getPropertyValue(props[j]);
if (v) cloneEls[i].style.setProperty(props[j], v);
}
}
var rootCs = getComputedStyle(doc);
for (var j = 0; j < props.length; j++) {
var v = rootCs.getPropertyValue(props[j]);
if (v) clonedEl.style.setProperty(props[j], v);
}
clonedDoc.querySelectorAll('style').forEach(function(s){ s.textContent = ''; });
clonedDoc.querySelectorAll('link[rel="stylesheet"]').forEach(function(l){ l.remove(); });
console.log('[PDF] onclone: styles inlined');
}
},
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
}).save().then(function() {
restoreColors(doc, snapshot);
});
}).save();
})
.then(function (result) {
console.log('[PDF] step 5: generation COMPLETE, result:', result);
+50 -33
View File
@@ -903,37 +903,32 @@
photoReady = Promise.resolve(null);
}
function flattenColors(el) {
var s = getComputedStyle(el);
var props = ['color','background-color','border-color','border-top-color','border-bottom-color','border-left-color','border-right-color','box-shadow','background'];
var originals = {};
props.forEach(function(p) {
var v = s.getPropertyValue(p);
if (v && v.indexOf('color-mix') !== -1) {
originals[p] = el.style.getPropertyValue(p);
el.style.setProperty(p, v);
function cloneAndInlineStyles(original) {
var clone = original.cloneNode(true);
var origEls = original.querySelectorAll('*');
var cloneEls = clone.querySelectorAll('*');
var allProps = ['color','background-color','background','border-color','border-top-color','border-bottom-color','border-left-color','border-right-color','box-shadow','opacity','font-family','font-size','font-weight','line-height','letter-spacing','text-align','text-decoration','text-transform','padding','padding-top','padding-bottom','padding-left','padding-right','margin','margin-top','margin-bottom','margin-left','margin-right','display','flex-direction','align-items','justify-content','gap','grid-template-columns','grid-column','width','height','min-width','min-height','max-width','max-height','position','top','bottom','left','right','z-index','overflow','border-radius','border-width','border-style','white-space','list-style-type','object-fit'];
for (var i = 0; i < origEls.length && i < cloneEls.length; i++) {
var cs = getComputedStyle(origEls[i]);
for (var j = 0; j < allProps.length; j++) {
var v = cs.getPropertyValue(allProps[j]);
if (v) cloneEls[i].style.setProperty(allProps[j], v);
}
});
var children = el.children;
for (var i = 0; i < children.length; i++) {
flattenColors(children[i]);
}
return originals;
}
function restoreColors(el, originals) {
Object.keys(originals).forEach(function(p) {
if (originals[p]) el.style.setProperty(p, originals[p]);
else el.style.removeProperty(p);
});
var children = el.children;
for (var i = 0; i < children.length; i++) {
restoreColors(children[i], originals);
var origCs = getComputedStyle(original);
for (var j = 0; j < allProps.length; j++) {
var v = origCs.getPropertyValue(allProps[j]);
if (v) clone.style.setProperty(allProps[j], v);
}
var styles = clone.querySelectorAll('style');
for (var s = 0; s < styles.length; s++) styles[s].textContent = '';
var links = clone.querySelectorAll('link[rel="stylesheet"]');
for (var l = 0; l < links.length; l++) links[l].remove();
return clone;
}
photoReady.then(function (dataUrl) {
console.log('[PDF] step 1: photo ready, dataUrl:', dataUrl ? 'yes (' + dataUrl.length + ' chars)' : 'null');
console.log('[PDF] step 1: photo ready');
if (dataUrl && photo) photo.setAttribute('src', dataUrl);
console.log('[PDF] step 2: waiting for fonts...');
return document.fonts ? document.fonts.ready : Promise.resolve();
@@ -943,19 +938,41 @@
return loadScript(CDN);
})
.then(function () {
console.log('[PDF] step 4: html2pdf loaded, type:', typeof html2pdf);
console.log('[PDF] step 4: flattening color-mix() values...');
var snapshot = flattenColors(doc);
console.log('[PDF] step 4: starting generation...');
console.log('[PDF] step 4: html2pdf loaded, generating...');
return html2pdf().from(doc).set({
margin: 10,
filename: 'Mamadaev_Resume.pdf',
image: { type: 'jpeg', quality: 0.95 },
html2canvas: { scale: 2, useCORS: true, backgroundColor: '#fff' },
html2canvas: {
scale: 2,
useCORS: true,
backgroundColor: '#fff',
onclone: function (clonedDoc) {
console.log('[PDF] onclone: inlining styles...');
var clonedEl = clonedDoc.querySelector('.resume-doc');
if (!clonedEl) return;
var origEls = doc.querySelectorAll('*');
var cloneEls = clonedEl.querySelectorAll('*');
var props = ['color','background-color','background','border-color','border-top-color','border-bottom-color','border-left-color','border-right-color','box-shadow','opacity','font-family','font-size','font-weight','line-height','letter-spacing','text-align','text-decoration','text-transform','padding','padding-top','padding-bottom','padding-left','padding-right','margin','margin-top','margin-bottom','margin-left','margin-right','display','flex-direction','align-items','justify-content','gap','grid-template-columns','grid-column','width','height','min-width','min-height','max-width','max-height','position','top','bottom','left','right','z-index','overflow','border-radius','border-width','border-style','white-space','list-style-type','object-fit'];
for (var i = 0; i < origEls.length && i < cloneEls.length; i++) {
var cs = getComputedStyle(origEls[i]);
for (var j = 0; j < props.length; j++) {
var v = cs.getPropertyValue(props[j]);
if (v) cloneEls[i].style.setProperty(props[j], v);
}
}
var rootCs = getComputedStyle(doc);
for (var j = 0; j < props.length; j++) {
var v = rootCs.getPropertyValue(props[j]);
if (v) clonedEl.style.setProperty(props[j], v);
}
clonedDoc.querySelectorAll('style').forEach(function(s){ s.textContent = ''; });
clonedDoc.querySelectorAll('link[rel="stylesheet"]').forEach(function(l){ l.remove(); });
console.log('[PDF] onclone: styles inlined');
}
},
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
}).save().then(function() {
restoreColors(doc, snapshot);
});
}).save();
})
.then(function (result) {
console.log('[PDF] step 5: generation COMPLETE, result:', result);