@ammduncan/easel 0.2.14 → 0.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/client/viewer.js +23 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to easel. This project adheres to [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## 0.2.16 — 2026-05-23
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- **PDF export quality bumped back up after the 0.2.15 size fix landed.** 0.2.15 traded crispness for size (JPEG 0.92 + DPR 2 → ~800 KB per card) — visible JPEG artefacts on type at zoom. Bumped to JPEG quality 1.0 + DPR 4 for the PDF target. Same compression trick (JPEG-in-PDF vs PNG-in-PDF) keeps the file at ~3–8 MB per card instead of the original 300+ MB; the higher quality settings just bring text back to "razor sharp at any zoom" without giving up the size win.
|
|
9
|
+
- PNG export unchanged (still lossless PNG @ DPR 4).
|
|
10
|
+
|
|
11
|
+
## 0.2.15 — 2026-05-23
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **PDF exports were enormous (300+ MB for a single-page card).** Two stacking causes: (1) the iframe always rasterised at `pixelRatio: 4` regardless of target, producing huge bitmaps for tall cards; (2) the parent then embedded the result into jsPDF as a `PNG`, which PDFs store using Flate compression — far less efficient than the DCT compression PDFs natively use for JPEGs. A tall card at DPR 4 → ~6000×10000 pixel PNG → ~300 MB PDF wrapper.
|
|
15
|
+
- Fix: for PDF targets only, the iframe now rasterises as JPEG at `quality: 0.92` and `pixelRatio: 2`, and the parent embeds with `'JPEG'` format + `'FAST'` compression flag + `compress: true` at the document level. PNG exports stay at lossless PNG + pixelRatio 4 — no quality loss for the standalone PNG download.
|
|
16
|
+
- Expected sizes for a typical card: ~3–8 MB (down from ~300 MB), text still crisp on screen and in print.
|
|
17
|
+
|
|
5
18
|
## 0.2.14 — 2026-05-23
|
|
6
19
|
|
|
7
20
|
### Changed
|
package/dist/client/viewer.js
CHANGED
|
@@ -196,8 +196,12 @@
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
|
-
* Embed a
|
|
200
|
-
* dimensions, producing a continuous (no page-breaks) document, then
|
|
199
|
+
* Embed a rasterised dataURL into a single-page PDF sized to the image's
|
|
200
|
+
* pixel dimensions, producing a continuous (no page-breaks) document, then
|
|
201
|
+
* save. The iframe sends a JPEG dataURL for PDF targets — PDFs natively use
|
|
202
|
+
* DCT compression for JPEGs, so embedding stays compact (vs PNGs which
|
|
203
|
+
* balloon the file). We detect format from the dataURL prefix; PNG still
|
|
204
|
+
* works as a fallback for any legacy caller that sends one.
|
|
201
205
|
*/
|
|
202
206
|
function downloadAsPdf(dataUrl, filename) {
|
|
203
207
|
return new Promise((resolve, reject) => {
|
|
@@ -206,6 +210,8 @@
|
|
|
206
210
|
reject(new Error("jsPDF not loaded"));
|
|
207
211
|
return;
|
|
208
212
|
}
|
|
213
|
+
const isJpeg = dataUrl.startsWith("data:image/jpeg") || dataUrl.startsWith("data:image/jpg");
|
|
214
|
+
const imageType = isJpeg ? "JPEG" : "PNG";
|
|
209
215
|
const img = new Image();
|
|
210
216
|
img.onload = () => {
|
|
211
217
|
try {
|
|
@@ -216,8 +222,9 @@
|
|
|
216
222
|
format: [w, h],
|
|
217
223
|
orientation: w > h ? "landscape" : "portrait",
|
|
218
224
|
hotfixes: ["px_scaling"],
|
|
225
|
+
compress: true,
|
|
219
226
|
});
|
|
220
|
-
pdf.addImage(dataUrl,
|
|
227
|
+
pdf.addImage(dataUrl, imageType, 0, 0, w, h, undefined, "FAST");
|
|
221
228
|
pdf.save(filename);
|
|
222
229
|
resolve();
|
|
223
230
|
} catch (err) {
|
|
@@ -859,13 +866,23 @@ ${body}
|
|
|
859
866
|
document.documentElement.scrollHeight,
|
|
860
867
|
document.body ? document.body.scrollHeight : 0,
|
|
861
868
|
);
|
|
862
|
-
|
|
869
|
+
// PNG target → lossless PNG @ pixelRatio 4 for crisp standalone files.
|
|
870
|
+
// PDF target → JPEG @ quality 1.0 + pixelRatio 4. PDFs natively use
|
|
871
|
+
// DCT compression for embedded JPEGs, so even at max quality the PDF
|
|
872
|
+
// stays in the ~3-8 MB range for a typical card (vs ~300 MB if we
|
|
873
|
+
// embedded as PNG — see 0.2.15). 1.0 + DPR 4 keeps text razor-sharp
|
|
874
|
+
// at any zoom level; tuned down to 0.92 + DPR 2 in 0.2.15 dropped to
|
|
875
|
+
// ~800 KB but at the cost of visible JPEG artefacts on type.
|
|
876
|
+
var rasterFn = format === "pdf" ? window.htmlToImage.toJpeg : window.htmlToImage.toPng;
|
|
877
|
+
var rasterOpts = {
|
|
863
878
|
backgroundColor: bgColor,
|
|
864
879
|
pixelRatio: 4,
|
|
865
880
|
cacheBust: true,
|
|
866
881
|
width: width,
|
|
867
882
|
height: height,
|
|
868
|
-
}
|
|
883
|
+
};
|
|
884
|
+
if (format === "pdf") rasterOpts.quality = 1.0;
|
|
885
|
+
rasterFn(document.documentElement, rasterOpts).then(function(dataUrl){
|
|
869
886
|
parent.postMessage({ type: "easel:image-ready", pushId: pushId, dataUrl: dataUrl, filename: filename, format: format }, "*");
|
|
870
887
|
}).catch(function(err){
|
|
871
888
|
console.error("[easel] export failed", err);
|
|
@@ -889,7 +906,7 @@ ${body}
|
|
|
889
906
|
const configScript =
|
|
890
907
|
"<script src='https://cdn.jsdelivr.net/npm/html-to-image@1.11.13/dist/html-to-image.js'></script><script>(function(){function a(c){if(!c)return;if(c.theme==='light'||c.theme==='dark'){document.documentElement.setAttribute('data-theme',c.theme);window.__claudeDisplayTheme=c.theme}if(c.preset==='paper'||c.preset==='aurora'||c.preset==='slate'){document.documentElement.setAttribute('data-preset',c.preset);window.__claudeDisplayPreset=c.preset}if(c.density==='carded'||c.density==='flat'){document.documentElement.setAttribute('data-density',c.density);window.__claudeDisplayDensity=c.density}}a(" +
|
|
891
908
|
JSON.stringify({ theme, preset, density }) +
|
|
892
|
-
");window.addEventListener('message',function(e){if(!e||!e.data)return;if(e.data.type==='easel:config')a(e.data);if(e.data.type==='easel:theme')a({theme:e.data.theme});if(e.data.type==='easel:print'){try{window.print()}catch(_){}}if(e.data.type==='easel:image'){var pid=e.data.pushId;var fn=e.data.filename||'push.png';var fmt=e.data.format==='pdf'?'pdf':'png';var bg=e.data.bgColor||'#ffffff';if(!window.htmlToImage)return;window.htmlToImage.toPng
|
|
909
|
+
");window.addEventListener('message',function(e){if(!e||!e.data)return;if(e.data.type==='easel:config')a(e.data);if(e.data.type==='easel:theme')a({theme:e.data.theme});if(e.data.type==='easel:print'){try{window.print()}catch(_){}}if(e.data.type==='easel:image'){var pid=e.data.pushId;var fn=e.data.filename||'push.png';var fmt=e.data.format==='pdf'?'pdf':'png';var bg=e.data.bgColor||'#ffffff';if(!window.htmlToImage)return;var rfn=fmt==='pdf'?window.htmlToImage.toJpeg:window.htmlToImage.toPng;var ropts={backgroundColor:bg,pixelRatio:4,cacheBust:true};if(fmt==='pdf')ropts.quality=1.0;rfn(document.body,ropts).then(function(u){parent.postMessage({type:'easel:image-ready',pushId:pid,dataUrl:u,filename:fn,format:fmt},'*')}).catch(function(err){console.error(err);parent.postMessage({type:'easel:image-error',pushId:pid,format:fmt,message:(err&&err.message)?err.message:String(err)},'*')})}})})();</script>";
|
|
893
910
|
const measureScript = "<script>" + selfMeasureScript(pushId) + "</script>";
|
|
894
911
|
const combined = configScript + measureScript;
|
|
895
912
|
if (/<\/body>/i.test(html)) return html.replace(/<\/body>/i, combined + "</body>");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ammduncan/easel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "A live browser tab for every Claude Code (and MCP) session. The push MCP tool appends HTML cards to a scrolling feed you keep open in split-screen.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|