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:
1 parent
7df7734eeb
commit
debbefcb43
2 files changed
+100
-66
No files matched your search
+50
-33
@@ -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
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user