PageSourceSearch

https://voteview.com/static/js/libs/saveSvgAsPng.js

js voteview.com collected 2026-10-01 10:12:51 UTC 13,987 bytes, 383 lines download raw bytes

1(function() {
2  const out$ = typeof exports != 'undefined' && exports || typeof define != 'undefined' && {} || this || window;
3  if (typeof define !== 'undefined') define('save-svg-as-png', [], () => out$);
4
5  const xmlns = 'http://www.w3.org/2000/xmlns/';
6  const doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [<!ENTITY nbsp "&#160;">]>';
7  const urlRegex = /url\(["']?(.+?)["']?\)/;
8  const fontFormats = {
9    woff2: 'font/woff2',
10    woff: 'font/woff',
11    otf: 'application/x-font-opentype',
12    ttf: 'application/x-font-ttf',
13    eot: 'application/vnd.ms-fontobject',
14    sfnt: 'application/font-sfnt',
15    svg: 'image/svg+xml'
16  };
17
18  const isElement = obj => obj instanceof HTMLElement || obj instanceof SVGElement;
19  const requireDomNode = el => {
20    if (!isElement(el)) throw new Error(`an HTMLElement or SVGElement is required; got ${el}`);
21  };
22  const isExternal = url => url && url.lastIndexOf('http',0) === 0 && url.lastIndexOf(window.location.host) === -1;
23
24  const getFontMimeTypeFromUrl = fontUrl => {
25    const formats = Object.keys(fontFormats)
26      .filter(extension => fontUrl.indexOf(`.${extension}`) > 0)
27      .map(extension => fontFormats[extension]);
28    if (formats) return formats[0];
29    console.error(`Unknown font format for ${fontUrl}. Fonts may not be working correctly.`);
30    return 'application/octet-stream';
31  };
32
33  const arrayBufferToBase64 = buffer => {
34    let binary = '';
35    const bytes = new Uint8Array(buffer);
36    for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
37    return window.btoa(binary);
38  }
39
40  const getDimension = (el, clone, dim) => {
41    const v =
42      (el.viewBox && el.viewBox.baseVal && el.viewBox.baseVal[dim]) ||
43      (clone.getAttribute(dim) !== null && !clone.getAttribute(dim).match(/%$/) && parseInt(clone.getAttribute(dim))) ||
44      el.getBoundingClientRect()[dim] ||
45      parseInt(clone.style[dim]) ||
46      parseInt(window.getComputedStyle(el).getPropertyValue(dim));
47    return typeof v === 'undefined' || v === null || isNaN(parseFloat(v)) ? 0 : v;
48  };
49
50  const getDimensions = (el, clone, width, height) => {
51    if (el.tagName === 'svg') return {
52      width: width || getDimension(el, clone, 'width'),
53      height: height || getDimension(el, clone, 'height')
54    };
55    else if (el.getBBox) {
56      const {x, y, width, height} = el.getBBox();
57      return {
58        width: x + width,
59        height: y + height
60      };
61    }
62  };
63
64  const reEncode = data =>
65    decodeURIComponent(
66      encodeURIComponent(data)
67        .replace(/%([0-9A-F]{2})/g, (match, p1) => {
68          const c = String.fromCharCode(`0x${p1}`);
69          return c === '%' ? '%25' : c;
70        })
71    );
72
73  const uriToBlob = uri => {
74    const byteString = window.atob(uri.split(',')[1]);
75    const mimeString = uri.split(',')[0].split(':')[1].split(';')[0]
76    const buffer = new ArrayBuffer(byteString.length);
77    const intArray = new Uint8Array(buffer);
78    for (let i = 0; i < byteString.length; i++) {
79      intArray[i] = byteString.charCodeAt(i);
80    }
81    return new Blob([buffer], {type: mimeString});
82  };
83
84  const query = (el, selector) => {
85    if (!selector) return;
86    try {
87      return el.querySelector(selector) || el.parentNode && el.parentNode.querySelector(selector);
88    } catch(err) {
89      console.warn(`Invalid CSS selector "${selector}"`, err);
90    }
91  };
92
93  const detectCssFont = (rule, href) => {
94    // Match CSS font-face rules to external links.
95    // @font-face {
96    //   src: local('Abel'), url(https://fonts.gstatic.com/s/abel/v6/UzN-iejR1VoXU2Oc-7LsbvesZW2xOQ-xsNqO47m55DA.woff2);
97    // }
98    const match = rule.cssText.match(urlRegex);
99    const url = (match && match[1]) || '';
100    if (!url || url.match(/^data:/) || url === 'about:blank') return;
101    const fullUrl =
102      url.startsWith('../') ? `${href}/../${url}`
103      : url.startsWith('./') ? `${href}/.${url}`
104      : url;
105    return {
106      text: rule.cssText,
107      format: getFontMimeTypeFromUrl(fullUrl),
108      url: fullUrl
109    };
110  };
111
112  const inlineImages = el => Promise.all(
113    Array.from(el.querySelectorAll('image')).map(image => {
114      let href = image.getAttributeNS('http://www.w3.org/1999/xlink', 'href') || image.getAttribute('href');
115      if (!href) return Promise.resolve(null);
116      if (isExternal(href)) {
117        href += (href.indexOf('?') === -1 ? '?' : '&') + 't=' + new Date().valueOf();
118      }
119      return new Promise((resolve, reject) => {
120        const canvas = document.createElement('canvas');
121        const img = new Image();
122        img.crossOrigin = 'anonymous';
123        img.src = href;
124        img.onerror = () => reject(new Error(`Could not load ${href}`));
125        img.onload = () => {
126          canvas.width = img.width;
127          canvas.height = img.height;
128          canvas.getContext('2d').drawImage(img, 0, 0);
129          image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', canvas.toDataURL('image/png'));
130          resolve(true);
131        };
132      });
133    })
134  );
135
136  const cachedFonts = {};
137  const inlineFonts = fonts => Promise.all(
138    fonts.map(font =>
139      new Promise((resolve, reject) => {
140        if (cachedFonts[font.url]) return resolve(cachedFonts[font.url]);
141
142        const req = new XMLHttpRequest();
143        req.addEventListener('load', () => {
144          // TODO: it may also be worth it to wait until fonts are fully loaded before
145          // attempting to rasterize them. (e.g. use https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet)
146          const fontInBase64 = arrayBufferToBase64(req.response);
147          const fontUri = font.text.replace(urlRegex, `url("data:${font.format};base64,${fontInBase64}")`)+'\n';
148          cachedFonts[font.url] = fontUri;
149          resolve(fontUri);
150        });
151        req.addEventListener('error', e => {
152          console.warn(`Failed to load font from: ${font.url}`, e);
153          cachedFonts[font.url] = null;
154          resolve(null);
155        });
156        req.addEventListener('abort', e => {
157          console.warn(`Aborted loading font from: ${font.url}`, e);
158          resolve(null);
159        });
160        req.open('GET', font.url);
161        req.responseType = 'arraybuffer';
162        req.send();
163      })
164    )
165  ).then(fontCss => fontCss.filter(x => x).join(''));
166
167  let cachedRules = null;
168  const styleSheetRules = () => {
169    if (cachedRules) return cachedRules;
170    return cachedRules = Array.from(document.styleSheets).map(sheet => {
171      try {
172        return {rules: sheet.cssRules, href: sheet.href};
173      } catch (e) {
174        console.warn(`Stylesheet could not be loaded: ${sheet.href}`, e);
175        return {};
176      }
177    });
178  };
179
180  const inlineCss = (el, options) => {
181    const {
182      selectorRemap,
183      modifyStyle,
184      modifyCss,
185      fonts
186    } = options || {};
187    const generateCss = modifyCss || ((selector, properties) => {
188      const sel = selectorRemap ? selectorRemap(selector) : selector;
189      const props = modifyStyle ? modifyStyle(properties) : properties;
190      return `${sel}{${props}}\n`;
191    });
192    const css = [];
193    const detectFonts = typeof fonts === 'undefined';
194    const fontList = fonts || [];
195    styleSheetRules().forEach(({rules, href}) => {
196      if (!rules) return;
197      Array.from(rules).forEach(rule => {
198        if (typeof rule.style != 'undefined') {
199          if (query(el, rule.selectorText)) css.push(generateCss(rule.selectorText, rule.style.cssText));
200          else if (detectFonts && rule.cssText.match(/^@font-face/)) {
201            const font = detectCssFont(rule, href);
202            if (font) fontList.push(font);
203          } else css.push(rule.cssText);
204        }
205      });
206    });
207
208    return inlineFonts(fontList).then(fontCss => css.join('\n') + fontCss);
209  };
210
211  out$.prepareSvg = (el, options, done) => {
212    requireDomNode(el);
213    const {
214      left = 0,
215      top = 0,
216      width: w,
217      height: h,
218      scale = 1,
219      responsive = false,
220    } = options || {};
221    
222    return inlineImages(el).then(() => {
223      let clone = el.cloneNode(true);
224      const {backgroundColor = 'transparent'} = options || {};
225      clone.style.backgroundColor = backgroundColor;
226      const {width, height} = getDimensions(el, clone, w, h);
227      
228      if (el.tagName !== 'svg') {
229        if (el.getBBox) {
230          clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));
231          const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
232          svg.appendChild(clone);
233          clone = svg;
234        } else {
235          console.error('Attempted to render non-SVG element', el);
236          return;
237        }
238      }
239
240	//left = el.viewBox.baseVal['x'];
241      xtop = +el.viewBox.baseVal['y'];
242      xleft = +el.viewBox.baseVal['x'];	
243      clone.setAttribute('version', '1.1');
244      clone.setAttribute('viewBox', [xleft, xtop, width, height].join(' '));
245      if (!clone.getAttribute('xmlns')) clone.setAttributeNS(xmlns, 'xmlns', 'http://www.w3.org/2000/svg');
246      if (!clone.getAttribute('xmlns:xlink')) clone.setAttributeNS(xmlns, 'xmlns:xlink', 'http://www.w3.org/1999/xlink');
247
248      if (responsive) {
249        clone.removeAttribute('width');
250        clone.removeAttribute('height');
251        clone.setAttribute('preserveAspectRatio', 'xMinYMin meet');
252      } else {
253        clone.setAttribute('width', width * scale);
254        clone.setAttribute('height', height * scale);
255      }
256
257      Array.from(clone.querySelectorAll('foreignObject > *')).forEach(foreignObject => {
258        if (!foreignObject.getAttribute('xmlns'))
259          foreignObject.setAttributeNS(xmlns, 'xmlns', 'http://www.w3.org/1999/xhtml');
260      });
261
262      return inlineCss(el, options).then(css => {
263        const style = document.createElement('style');
264        style.setAttribute('type', 'text/css');
265        style.innerHTML = `<![CDATA[\n${css}\n]]>`;
266
267        const defs = document.createElement('defs');
268        defs.appendChild(style);
269        clone.insertBefore(defs, clone.firstChild);
270
271        const outer = document.createElement('div');
272        outer.appendChild(clone);
273        const src = outer.innerHTML.replace(/NS\d+:href/gi, 'xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href');
274
275        if (typeof done === 'function') done(src, width, height);
276        else return {src, width, height};
277      });
278    });
279  };
280
281  out$.svgAsDataUri = (el, options, done) => {
282    requireDomNode(el);
283    const result = out$.prepareSvg(el, options)
284      .then(({src}) => `data:image/svg+xml;base64,${window.btoa(reEncode(doctype+src))}`);
285    if (typeof done === 'function') return result.then(done);
286    return result;
287  };
288
289  out$.svgAsPngUri = (el, options, done) => {
290    requireDomNode(el);
291    const {
292      encoderType = 'image/png',
293      encoderOptions = 0.8,
294      canvg
295    } = options || {};
296
297    const convertToPng = ({src, width, height}) => {
298      const canvas = document.createElement('canvas');
299      const context = canvas.getContext('2d');
300      pixelRatio = window.devicePixelRatio || 1;
301
302      //  A little JBL hackery to get panned/zoomed maps to png correctly	
303      xwidth = el.getAttribute("width");
304      xheight = el.getAttribute("height");
305      canvas.width = xwidth * pixelRatio;
306      pixelRatio = pixelRatio *  (xwidth / width);
307      canvas.height = height * pixelRatio ;
308      // End hackery
309
310      canvas.style.width = `${canvas.width}px`;
311      canvas.style.height = `${canvas.height}px`;	
312      context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
313
314
315      if (canvg) canvg(canvas, src);
316        else context.drawImage(src, 0, 0);
317
318      let png;
319      try {
320        png = canvas.toDataURL(encoderType, encoderOptions);
321      } catch (e) {
322        if ((typeof SecurityError !== 'undefined' && e instanceof SecurityError) || e.name === 'SecurityError') {
323          console.error('Rendered SVG images cannot be downloaded in this browser.');
324          return;
325        } else throw e;
326      }
327      if (typeof done === 'function') done(png);
328      return Promise.resolve(png);
329    }
330
331    if (canvg) return out$.prepareSvg(el, options).then(convertToPng);
332    else return out$.svgAsDataUri(el, options).then(uri => {
333      return new Promise((resolve, reject) => {
334        const image = new Image();
335        image.onload = () => resolve(convertToPng({
336          src: image,
337          width: image.width,
338          height: image.height
339        }));
340        image.onerror = () => {
341          reject(`There was an error loading the data URI as an image on the following SVG\n${window.atob(uri.slice(26))}Open the following link to see browser's diagnosis\n${uri}`);
342        }
343        image.src = uri;
344      })
345    });
346  };
347
348  out$.download = (name, uri) => {
349    if (navigator.msSaveOrOpenBlob) navigator.msSaveOrOpenBlob(uriToBlob(uri), name);
350    else {
351      const saveLink = document.createElement('a');
352      if ('download' in saveLink) {
353        saveLink.download = name;
354        saveLink.style.display = 'none';
355        document.body.appendChild(saveLink);
356        try {
357          const blob = uriToBlob(uri);
358          const url = URL.createObjectURL(blob);
359          saveLink.href = url;
360          saveLink.onclick = () => requestAnimationFrame(() => URL.revokeObjectURL(url));
361        } catch (e) {
362          console.warn('This browser does not support object URLs. Falling back to string URL.');
363          saveLink.href = uri;
364        }
365        saveLink.click();
366        document.body.removeChild(saveLink);
367      }
368      else {
369        window.open(uri, '_temp', 'menubar=no,toolbar=no,status=no');
370      }
371    }
372  };
373
374  out$.saveSvg = (el, name, options) => {
375    requireDomNode(el);
376    out$.svgAsDataUri(el, options || {}, uri => out$.download(name, uri));
377  };
378
379  out$.saveSvgAsPng = (el, name, options) => {
380    requireDomNode(el);
381    out$.svgAsPngUri(el, options || {}, uri => out$.download(name, uri));
382  };
383})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.