@@ -2,7 +2,7 @@ import * as vscode from 'vscode';
22import { PlotViewer } from './types' ;
33import { PlotHistory , PlotFrame } from './jgdPlotHistory' ;
44import { JgdSocketServer , JgdMessage } from './jgdSocketServer' ;
5- import { config } from '../util' ;
5+ import { config , UriIcon } from '../util' ;
66
77interface MetricsRequest {
88 id : number ;
@@ -413,6 +413,7 @@ export class JgdViewer implements PlotViewer {
413413 }
414414 ) ;
415415
416+ this . panel . iconPath = new UriIcon ( 'graph' ) ;
416417 this . panel . webview . html = this . getWebviewHtml ( ) ;
417418
418419 this . panel . webview . onDidReceiveMessage ( ( raw : WebviewMessage ) => {
@@ -527,6 +528,7 @@ export class JgdViewer implements PlotViewer {
527528<html lang="en">
528529<head>
529530<meta charset="UTF-8">
531+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data: blob:;">
530532<meta name="viewport" content="width=device-width, initial-scale=1.0">
531533<style>
532534* { margin: 0; padding: 0; box-sizing: border-box; }
@@ -1108,7 +1110,7 @@ function handleExport(format, exportW, exportH) {
11081110 if (!blob) return;
11091111 const reader = new FileReader();
11101112 reader.onload = () => {
1111- const base64 = btoa(String.fromCharCode(... new Uint8Array(reader.result) ));
1113+ const base64 = uint8ToBase64( new Uint8Array(reader.result));
11121114 vscode.postMessage({ type: 'export_data', format: 'png', data: base64 });
11131115 };
11141116 reader.readAsArrayBuffer(blob);
@@ -1123,6 +1125,17 @@ function handleExport(format, exportW, exportH) {
11231125
11241126function svgEsc(s) { return s.replace(/&/g,'&').replace(/[<]/g,'<').replace(/[>]/g,'>').replace(/"/g,'"'); }
11251127
1128+ // Encode bytes to base64 in chunks. A naive String.fromCharCode(...bytes)
1129+ // overflows the call stack for large exports (e.g. high DPI at large sizes).
1130+ function uint8ToBase64(bytes) {
1131+ let binary = '';
1132+ const chunk = 8192;
1133+ for (let i = 0; i < bytes.length; i += chunk) {
1134+ binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
1135+ }
1136+ return btoa(binary);
1137+ }
1138+
11261139const cssFilterRe = /^(?:blur|brightness|contrast|drop-shadow|grayscale|hue-rotate|invert|opacity|saturate|sepia)\\s*\\([^()]*(?:\\([^)]*\\)[^()]*)*\\)(?:\\s+(?:blur|brightness|contrast|drop-shadow|grayscale|hue-rotate|invert|opacity|saturate|sepia)\\s*\\([^()]*(?:\\([^)]*\\)[^()]*)*\\))*$/;
11271140function isSafeCssFilter(s) {
11281141 if (typeof s !== 'string') return false;
@@ -1137,7 +1150,7 @@ function svgClose(name) { return String.fromCharCode(60) + '/' + name + '>'; }
11371150
11381151function svgGcStroke(gc) {
11391152 if (!gc || gc.col == null) return ' stroke="none"';
1140- let s = ' stroke="' + gc.col + '"';
1153+ let s = ' stroke="' + svgEsc(String( gc.col)) + '"';
11411154 s += ' stroke-width="' + (gc.lwd || 1) + '"';
11421155 s += ' stroke-linecap="' + (gc.lend || 'round') + '"';
11431156 s += ' stroke-linejoin="' + (gc.ljoin || 'round') + '"';
@@ -1147,7 +1160,7 @@ function svgGcStroke(gc) {
11471160
11481161function svgGcFill(gc) {
11491162 if (!gc || gc.fill == null) return ' fill="none"';
1150- return ' fill="' + gc.fill + '"';
1163+ return ' fill="' + svgEsc(String( gc.fill)) + '"';
11511164}
11521165
11531166function svgFont(gc) {
@@ -1241,7 +1254,7 @@ function plotToSvg(plot, exportW, exportH) {
12411254 const col = (op.gc && op.gc.col != null) ? op.gc.col : 'black';
12421255 let transform = 'translate(' + op.x + ',' + op.y + ')';
12431256 if (op.rot) transform += ' rotate(' + (-op.rot) + ')';
1244- s += svgTag('text', ' transform="' + transform + '" font-family="' + f.family + '" font-size="' + f.size + '" font-weight="' + f.weight + '" font-style="' + f.style + '" text-anchor="' + anchor + '" fill="' + col + '"') + svgEsc(op.str) + svgClose('text') + '\\n';
1257+ s += svgTag('text', ' transform="' + transform + '" font-family="' + svgEsc(String( f.family)) + '" font-size="' + f.size + '" font-weight="' + f.weight + '" font-style="' + f.style + '" text-anchor="' + anchor + '" fill="' + svgEsc(String( col)) + '"') + svgEsc(op.str) + svgClose('text') + '\\n';
12451258 break;
12461259 }
12471260 case 'raster': {
@@ -1253,7 +1266,7 @@ function plotToSvg(plot, exportW, exportH) {
12531266 const cx = dx + aw / 2, cy = dy + ah / 2;
12541267 transform = ' transform="rotate(' + (-op.rot) + ',' + cx + ',' + cy + ')"';
12551268 }
1256- s += svgTag('image', ' x="' + dx + '" y="' + dy + '" width="' + aw + '" height="' + ah + '" href="' + op.data + '"' + transform, true) + '\\n';
1269+ s += svgTag('image', ' x="' + dx + '" y="' + dy + '" width="' + aw + '" height="' + ah + '" href="' + svgEsc(String( op.data)) + '"' + transform, true) + '\\n';
12571270 break;
12581271 }
12591272 case 'beginGroup': {
0 commit comments