@eturnity/dom_to_svg 8.10.1

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.
Files changed (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/lib/accessibility.d.ts +3 -0
  4. package/lib/accessibility.d.ts.map +1 -0
  5. package/lib/accessibility.js +201 -0
  6. package/lib/accessibility.js.map +1 -0
  7. package/lib/css.d.ts +26 -0
  8. package/lib/css.d.ts.map +1 -0
  9. package/lib/css.js +96 -0
  10. package/lib/css.js.map +1 -0
  11. package/lib/dom.d.ts +22 -0
  12. package/lib/dom.d.ts.map +1 -0
  13. package/lib/dom.js +33 -0
  14. package/lib/dom.js.map +1 -0
  15. package/lib/element.d.ts +3 -0
  16. package/lib/element.d.ts.map +1 -0
  17. package/lib/element.js +417 -0
  18. package/lib/element.js.map +1 -0
  19. package/lib/gradients.d.ts +3 -0
  20. package/lib/gradients.d.ts.map +1 -0
  21. package/lib/gradients.js +78 -0
  22. package/lib/gradients.js.map +1 -0
  23. package/lib/index.d.ts +6 -0
  24. package/lib/index.d.ts.map +1 -0
  25. package/lib/index.js +75 -0
  26. package/lib/index.js.map +1 -0
  27. package/lib/inline.d.ts +14 -0
  28. package/lib/inline.d.ts.map +1 -0
  29. package/lib/inline.js +138 -0
  30. package/lib/inline.js.map +1 -0
  31. package/lib/stacking.d.ts +38 -0
  32. package/lib/stacking.d.ts.map +1 -0
  33. package/lib/stacking.js +125 -0
  34. package/lib/stacking.js.map +1 -0
  35. package/lib/svg.d.ts +14 -0
  36. package/lib/svg.d.ts.map +1 -0
  37. package/lib/svg.js +245 -0
  38. package/lib/svg.js.map +1 -0
  39. package/lib/test/PuppeteerAdapter.d.ts +90 -0
  40. package/lib/test/PuppeteerAdapter.d.ts.map +1 -0
  41. package/lib/test/PuppeteerAdapter.js +196 -0
  42. package/lib/test/PuppeteerAdapter.js.map +1 -0
  43. package/lib/test/injected-script.d.ts +2 -0
  44. package/lib/test/injected-script.d.ts.map +1 -0
  45. package/lib/test/injected-script.js +26 -0
  46. package/lib/test/injected-script.js.map +1 -0
  47. package/lib/test/test.d.ts +6 -0
  48. package/lib/test/test.d.ts.map +1 -0
  49. package/lib/test/test.js +245 -0
  50. package/lib/test/test.js.map +1 -0
  51. package/lib/test/util.d.ts +9 -0
  52. package/lib/test/util.d.ts.map +1 -0
  53. package/lib/test/util.js +33 -0
  54. package/lib/test/util.js.map +1 -0
  55. package/lib/text.d.ts +5 -0
  56. package/lib/text.d.ts.map +1 -0
  57. package/lib/text.js +138 -0
  58. package/lib/text.js.map +1 -0
  59. package/lib/traversal.d.ts +32 -0
  60. package/lib/traversal.d.ts.map +1 -0
  61. package/lib/traversal.js +12 -0
  62. package/lib/traversal.js.map +1 -0
  63. package/lib/util.d.ts +20 -0
  64. package/lib/util.d.ts.map +1 -0
  65. package/lib/util.js +43 -0
  66. package/lib/util.js.map +1 -0
  67. package/package.json +112 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PuppeteerAdapter.js","sourceRoot":"","sources":["../../src/test/PuppeteerAdapter.ts"],"names":[],"mappings":"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","sourcesContent":["import PollyAdapter from '@pollyjs/adapter'\nimport { Polly, Request as PollyRequest } from '@pollyjs/core'\nimport * as chardet from 'chardet'\nimport contentType from 'content-type'\nimport { mapValues } from 'lodash-es'\nimport mime from 'mime-types'\nimport type * as Puppeteer from 'puppeteer'\nimport { Subscription, fromEvent } from 'rxjs'\n\nconst GOOGLE_LOGO_URL = 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png'\nconst GOOGLE_LOGO_BODY = Buffer.from(\n\t'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',\n\t'base64'\n)\n\nfunction getBodyData(response: Puppeteer.Response, buffer: Buffer): { body: string; isBinary: boolean } {\n\tconst type = response.headers()['content-type']\n\tlet encoding = type ? contentType.parse(type).parameters.charset || mime.charset(type) : chardet.detect(buffer)\n\tif (encoding) {\n\t\tencoding = encoding.toLowerCase()\n\t}\n\tif (encoding) {\n\t\tconst decoder = new TextDecoder(encoding)\n\t\tconst body = decoder.decode(buffer)\n\t\treturn { body, isBinary: false }\n\t}\n\treturn { body: buffer.toString('hex'), isBinary: true }\n}\n\ninterface PollyResponse {\n\tstatusCode: number\n\theaders: Record<string, string>\n\tbody: string\n\tisBinary: boolean\n}\n\ninterface PollyRequestArguments {\n\trequestArguments: { request: Puppeteer.Request }\n}\n\n/**\n * A Puppeteer adapter for Polly that supports all request resource types.\n *\n * TODO: upstream this?\n *\n * Polly's own Puppeteer adapter hangs when attempting to capture the page's initial document\n * (requestResourceType==='document'). See https://github.com/Netflix/pollyjs/issues/121\n *\n * Its very complex internal flow makes it hard to modify/fix. The internal flow of this adapter is much simpler,\n * and handles all request resource types.\n *\n */\nexport class PuppeteerAdapter extends PollyAdapter {\n\tprivate subscriptions = new Subscription()\n\n\t/**\n\t * The puppeteer Page this adapter is attached to, obtained from\n\t * options passed to the Polly constructor.\n\t */\n\tprivate page: Puppeteer.Page\n\n\t/**\n\t * The request resource types this adapter should intercept.\n\t */\n\tprivate requestResourceTypes: Puppeteer.ResourceType[]\n\n\t/**\n\t * A map of all intercepted requests to their respond function, which will be called by the\n\t * 'response' event listener, causing Polly to record the response content.\n\t */\n\tprivate pendingRequests = new Map<Puppeteer.Request, { respond: (response: Puppeteer.Response) => Promise<void> }>()\n\n\t/**\n\t * Maps passthrough requests to an object containing:\n\t * - The response promise, which will be awaited in this.onPassthrough\n\t * - A respond function, called by 'response' event listener, which resolves the response promise.\n\t */\n\tprivate passThroughRequests = new Map<\n\t\tPuppeteer.Request,\n\t\t{ responsePromise: Promise<Puppeteer.Response>; respond: (response: Puppeteer.Response) => void }\n\t>()\n\n\t/**\n\t * The adapter's ID, used to reference it in the Polly constructor.\n\t */\n\tpublic static get id(): string {\n\t\treturn 'puppeteer'\n\t}\n\n\tconstructor(polly: Polly) {\n\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t// @ts-ignore\n\t\tsuper(polly)\n\t\tthis.page = this.options.page\n\t\tthis.requestResourceTypes = this.options.requestResourceTypes\n\t}\n\n\t/**\n\t * Called when connecting to a Puppeteer page. Sets up request and response interceptors.\n\t */\n\tpublic onConnect(): void {\n\t\tthis.subscriptions.add(\n\t\t\tfromEvent<Puppeteer.Request>(this.page, 'request').subscribe(request => {\n\t\t\t\tconst url = request.url()\n\t\t\t\tconst method = request.method()\n\t\t\t\tconst headers = request.headers()\n\t\t\t\tconst isPreflight =\n\t\t\t\t\tmethod === 'OPTIONS' && !!headers.origin && !!headers['access-control-request-method']\n\t\t\t\tif (isPreflight || !this.requestResourceTypes.includes(request.resourceType())) {\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-floating-promises\n\t\t\t\t\trequest.continue()\n\t\t\t\t} else {\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n\t\t\t\t\t// @ts-ignore\n\t\t\t\t\tthis.handleRequest({\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\turl,\n\t\t\t\t\t\tmethod,\n\t\t\t\t\t\tbody: request.postData() ?? '',\n\t\t\t\t\t\trequestArguments: {\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t)\n\t\tthis.subscriptions.add(\n\t\t\tfromEvent<Puppeteer.Response>(this.page, 'response').subscribe(response => {\n\t\t\t\tconst request = response.request()\n\t\t\t\tif (this.pendingRequests.has(request)) {\n\t\t\t\t\tthis.pendingRequests\n\t\t\t\t\t\t.get(request)\n\t\t\t\t\t\t?.respond(response)\n\t\t\t\t\t\t.catch(error => {\n\t\t\t\t\t\t\tif (error.message.includes('No resource with given identifier found')) {\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconsole.error('Failed to respond:', error)\n\t\t\t\t\t\t})\n\t\t\t\t\tthis.pendingRequests.delete(request)\n\t\t\t\t}\n\t\t\t\tif (this.passThroughRequests.has(request)) {\n\t\t\t\t\tthis.passThroughRequests.get(request)?.respond(response)\n\t\t\t\t}\n\t\t\t})\n\t\t)\n\t}\n\n\t/**\n\t * Called when disconnecting from a Puppeteer.page.\n\t */\n\tpublic onDisconnect(): void {\n\t\tthis.subscriptions.unsubscribe()\n\t}\n\n\t/**\n\t * Given a request that should be allowed to pass through (not be intercepted),\n\t * return a Promise of the Response for that request, which will be passed to\n\t * request.respond().\n\t */\n\tpublic async passthroughRequest(pollyRequest: PollyRequest): Promise<PollyResponse> {\n\t\tconst {\n\t\t\trequestArguments: { request },\n\t\t} = (pollyRequest as unknown) as PollyRequestArguments\n\t\tlet respond: (response: Puppeteer.Response) => void\n\t\tconst responsePromise = new Promise<Puppeteer.Response>(resolve => (respond = resolve))\n\t\tthis.passThroughRequests.set(request, { respond: response => respond(response), responsePromise })\n\t\tawait request.continue()\n\t\tconst response = await responsePromise\n\t\tlet bodyData = { isBinary: false, body: '' }\n\t\tif (response.status() < 300 || response.status() >= 400) {\n\t\t\tlet buffer = await response.buffer()\n\t\t\t// No idea why, but CDP/Puppeteer report the body as empty for this request.\n\t\t\tif (response.url() === GOOGLE_LOGO_URL && response.status() === 200 && buffer.length === 0) {\n\t\t\t\tbuffer = GOOGLE_LOGO_BODY\n\t\t\t}\n\t\t\tbodyData = getBodyData(response, buffer)\n\t\t}\n\t\t// Important: CDP rejects headers values separated by \\n, but returns them this way for multiple header values (e.g. Vary).\n\t\tconst headers = mapValues(response.headers(), value => value.split('\\n').join(', '))\n\t\treturn {\n\t\t\tstatusCode: response.status(),\n\t\t\theaders,\n\t\t\t...bodyData,\n\t\t}\n\t}\n\n\t/**\n\t * Responds to an intercepted request with the given response.\n\t *\n\t * If an error happened when retreiving the response, abort the request.\n\t */\n\tpublic async respondToRequest(\n\t\tpollyRequest: {\n\t\t\trequestArguments: { request: Puppeteer.Request }\n\t\t\tresponse: PollyResponse\n\t\t},\n\t\terror?: unknown\n\t): Promise<void> {\n\t\tconst {\n\t\t\trequestArguments: { request },\n\t\t\tresponse: { statusCode: status, headers, body, isBinary },\n\t\t} = pollyRequest\n\t\t// Do nothing for passthrough requests: Polly calls request.respond() internally.\n\t\tif (this.passThroughRequests.has(request)) {\n\t\t\tthis.passThroughRequests.delete(request)\n\t\t\treturn\n\t\t}\n\t\tif (error) {\n\t\t\tawait request.abort()\n\t\t} else {\n\t\t\tawait request.respond({\n\t\t\t\tstatus,\n\t\t\t\theaders,\n\t\t\t\tbody: isBinary ? Buffer.from(body, 'hex') : body,\n\t\t\t})\n\t\t}\n\t}\n\n\t/**\n\t * Called when a request is intercepted, for all requests (passthrough or stubbed).\n\t *\n\t * Adds an entry to pendingRequests, that will call the provided promise.resolve function\n\t * when a response for this request is received.\n\t */\n\tpublic onRequest({\n\t\trequestArguments: { request },\n\t\tpromise,\n\t}: {\n\t\trequestArguments: { request: Puppeteer.Request }\n\t\tpromise: {\n\t\t\tresolve: (response: PollyResponse) => void\n\t\t}\n\t}): void {\n\t\tif (this.passThroughRequests.has(request)) {\n\t\t\treturn\n\t\t}\n\t\tconst respond = async (response: Puppeteer.Response): Promise<void> => {\n\t\t\tlet bodyData = { isBinary: false, body: '' }\n\t\t\tif (response.status() < 300 || response.status() >= 400) {\n\t\t\t\tlet buffer = await response.buffer()\n\t\t\t\t// No idea why, but CDP/Puppeteer report the body as empty for this request.\n\t\t\t\tif (response.url() === GOOGLE_LOGO_URL && response.status() === 200 && buffer.length === 0) {\n\t\t\t\t\tbuffer = GOOGLE_LOGO_BODY\n\t\t\t\t}\n\t\t\t\tbodyData = getBodyData(response, buffer)\n\t\t\t}\n\t\t\tconst headers = mapValues(response.headers(), value => value.split('\\n').join(', '))\n\t\t\tpromise.resolve({\n\t\t\t\tstatusCode: response.status(),\n\t\t\t\theaders,\n\t\t\t\t...bodyData,\n\t\t\t})\n\t\t}\n\t\tthis.pendingRequests.set(request, {\n\t\t\trespond,\n\t\t})\n\t}\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=injected-script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injected-script.d.ts","sourceRoot":"","sources":["../../src/test/injected-script.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ /* eslint-disable no-restricted-globals */
2
+ import { documentToSVG, inlineResources } from '../index.js';
3
+ async function main() {
4
+ console.log('Converting DOM to SVG');
5
+ const svgDocument = documentToSVG(document);
6
+ console.log('Inlining resources');
7
+ const svgRootElement = svgDocument.documentElement;
8
+ // Append to DOM so SVG elements are attached to a window/have defaultView, so window.getComputedStyle() works
9
+ document.body.prepend(svgRootElement);
10
+ try {
11
+ await inlineResources(svgRootElement);
12
+ }
13
+ finally {
14
+ svgRootElement.remove();
15
+ }
16
+ console.log('Serializing SVG');
17
+ const svgString = new XMLSerializer().serializeToString(svgRootElement);
18
+ console.log('Calling callback');
19
+ resolveSVG(svgString);
20
+ }
21
+ main().catch(error => {
22
+ console.error(error);
23
+ const { message, name, stack } = error;
24
+ rejectSVG({ message, name, stack });
25
+ });
26
+ //# sourceMappingURL=injected-script.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"injected-script.js","sourceRoot":"","sources":["../../src/test/injected-script.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE5D,KAAK,UAAU,IAAI;IAClB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAA;IACpC,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IAE3C,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAA;IACjC,MAAM,cAAc,GAAG,WAAW,CAAC,eAAe,CAAA;IAClD,8GAA8G;IAC9G,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;IACrC,IAAI;QACH,MAAM,eAAe,CAAC,cAAc,CAAC,CAAA;KACrC;YAAS;QACT,cAAc,CAAC,MAAM,EAAE,CAAA;KACvB;IAED,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IAC9B,MAAM,SAAS,GAAG,IAAI,aAAa,EAAE,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAA;IAEvE,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IAC/B,UAAU,CAAC,SAAS,CAAC,CAAA;AACtB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;IACpB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACpB,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,KAAK,CAAA;IACtC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;AACpC,CAAC,CAAC,CAAA","sourcesContent":["/* eslint-disable no-restricted-globals */\nimport { documentToSVG, inlineResources } from '../index.js'\n\nasync function main(): Promise<void> {\n\tconsole.log('Converting DOM to SVG')\n\tconst svgDocument = documentToSVG(document)\n\n\tconsole.log('Inlining resources')\n\tconst svgRootElement = svgDocument.documentElement\n\t// Append to DOM so SVG elements are attached to a window/have defaultView, so window.getComputedStyle() works\n\tdocument.body.prepend(svgRootElement)\n\ttry {\n\t\tawait inlineResources(svgRootElement)\n\t} finally {\n\t\tsvgRootElement.remove()\n\t}\n\n\tconsole.log('Serializing SVG')\n\tconst svgString = new XMLSerializer().serializeToString(svgRootElement)\n\n\tconsole.log('Calling callback')\n\tresolveSVG(svgString)\n}\n\nmain().catch(error => {\n\tconsole.error(error)\n\tconst { message, name, stack } = error\n\trejectSVG({ message, name, stack })\n})\n"]}
@@ -0,0 +1,6 @@
1
+ declare global {
2
+ function resolveSVG(svg: string): void;
3
+ function rejectSVG(error: unknown): void;
4
+ }
5
+ export {};
6
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/test/test.ts"],"names":[],"mappings":"AA0BA,OAAO,CAAC,MAAM,CAAC;IACd,SAAS,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IACtC,SAAS,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;CACxC"}
@@ -0,0 +1,245 @@
1
+ import { writeFile } from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { fileURLToPath, pathToFileURL } from 'url';
4
+ import * as util from 'util';
5
+ import { Polly } from '@pollyjs/core';
6
+ import FSPersister from '@pollyjs/persister-fs';
7
+ import { assert } from 'chai';
8
+ import delay from 'delay';
9
+ import ParcelBundler from 'parcel-bundler';
10
+ import pixelmatch from 'pixelmatch';
11
+ import { PNG } from 'pngjs';
12
+ import puppeteer from 'puppeteer';
13
+ import css from 'tagged-template-noop';
14
+ import formatXML from 'xml-formatter';
15
+ import { PuppeteerAdapter } from './PuppeteerAdapter.js';
16
+ import { createDeferred, readFileOrUndefined } from './util.js';
17
+ // Reduce log verbosity
18
+ util.inspect.defaultOptions.depth = 0;
19
+ util.inspect.defaultOptions.maxStringLength = 80;
20
+ Polly.register(PuppeteerAdapter);
21
+ const defaultViewport = {
22
+ width: 1200,
23
+ height: 800,
24
+ };
25
+ const mode = (process.env.POLLY_MODE || 'replay');
26
+ console.log('Using Polly mode', mode);
27
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
28
+ describe('documentToSVG()', () => {
29
+ let browser;
30
+ let server;
31
+ before('Launch devserver', async () => {
32
+ const bundler = new ParcelBundler(path.resolve(root, 'lib/test/injected-script.js'), {
33
+ hmr: false,
34
+ sourceMaps: false,
35
+ minify: false,
36
+ autoInstall: false,
37
+ });
38
+ server = await bundler.serve(8080);
39
+ });
40
+ before('Launch browser', async () => {
41
+ browser = await puppeteer.launch({
42
+ headless: true,
43
+ defaultViewport,
44
+ devtools: true,
45
+ args: [
46
+ '--window-size=1920,1080',
47
+ '--lang=en-US',
48
+ '--disable-web-security',
49
+ '--font-render-hinting=none',
50
+ '--enable-font-antialiasing',
51
+ ],
52
+ timeout: 0,
53
+ // slowMo: 100,
54
+ });
55
+ });
56
+ after('Close browser', () => browser === null || browser === void 0 ? void 0 : browser.close());
57
+ after('Close devserver', done => server === null || server === void 0 ? void 0 : server.close(done));
58
+ const snapshotDirectory = path.resolve(root, 'src/test/snapshots');
59
+ const sites = [
60
+ new URL('https://sourcegraph.com/search'),
61
+ new URL('https://sourcegraph.com/extensions'),
62
+ new URL('https://www.google.com?hl=en'),
63
+ new URL('https://news.ycombinator.com'),
64
+ new URL('https://github.com/felixfbecker/dom-to-svg/blob/fee7e1e7b63c888bc1c5205126b05c63073ebdd3/.vscode/settings.json'),
65
+ ];
66
+ for (const url of sites) {
67
+ const encodedName = encodeURIComponent(url.href);
68
+ const svgFilePath = path.resolve(snapshotDirectory, encodedName + '.svg');
69
+ describe(url.href, () => {
70
+ let polly;
71
+ let page;
72
+ before('Open tab and setup Polly', async () => {
73
+ page = await browser.newPage();
74
+ await page.setRequestInterception(true);
75
+ await page.setBypassCSP(true);
76
+ // Prevent Google cookie consent prompt
77
+ if (url.hostname.endsWith('google.com')) {
78
+ await page.setCookie({ name: 'CONSENT', value: 'YES+DE.de+V14+BX', domain: '.google.com' });
79
+ }
80
+ await page.setExtraHTTPHeaders({
81
+ 'Accept-Language': 'en-US',
82
+ DNT: '1',
83
+ });
84
+ page.on('console', message => {
85
+ console.log('🖥 ' + (message.type() !== 'log' ? message.type().toUpperCase() : ''), message.text());
86
+ });
87
+ const requestResourceTypes = [
88
+ 'xhr',
89
+ 'fetch',
90
+ 'document',
91
+ 'script',
92
+ 'stylesheet',
93
+ 'image',
94
+ 'font',
95
+ 'other',
96
+ ];
97
+ polly = new Polly(url.href, {
98
+ mode,
99
+ recordIfMissing: false,
100
+ recordFailedRequests: true,
101
+ flushRequestsOnStop: false,
102
+ logging: false,
103
+ adapters: [PuppeteerAdapter],
104
+ adapterOptions: {
105
+ puppeteer: {
106
+ page,
107
+ requestResourceTypes,
108
+ },
109
+ },
110
+ // Very lenient, but pages often have very complex URL parameters and this usually works fine.
111
+ matchRequestsBy: {
112
+ method: true,
113
+ body: false,
114
+ url: {
115
+ username: false,
116
+ password: false,
117
+ hostname: true,
118
+ pathname: true,
119
+ query: url.hostname !== 'www.google.com',
120
+ hash: false,
121
+ },
122
+ order: false,
123
+ headers: false,
124
+ },
125
+ persister: FSPersister,
126
+ persisterOptions: {
127
+ fs: {
128
+ recordingsDir: path.resolve(root, 'src/test/recordings'),
129
+ },
130
+ },
131
+ });
132
+ polly.server.get('http://localhost:8080/*').passthrough();
133
+ polly.server.get('data:*').passthrough();
134
+ polly.server.any('https://sentry.io/*rest').intercept((request, response) => {
135
+ response.sendStatus(204);
136
+ });
137
+ polly.server.any('https://www.googletagmanager.com/*').intercept((request, response) => {
138
+ response.sendStatus(204);
139
+ });
140
+ polly.server.any('https://api.github.com/_private/*rest').intercept((request, response) => {
141
+ response.sendStatus(204);
142
+ });
143
+ polly.server.any('https://collector.githubapp.com/*rest').intercept((request, response) => {
144
+ response.sendStatus(204);
145
+ });
146
+ polly.server.any('https://www.google.com/gen_204').intercept((request, response) => {
147
+ response.sendStatus(204);
148
+ });
149
+ });
150
+ before('Go to page', async () => {
151
+ await page.goto(url.href, {
152
+ waitUntil: url.host === 'github.com' ? 'domcontentloaded' : 'networkidle2',
153
+ timeout: 60000,
154
+ });
155
+ await page.waitForTimeout(2000);
156
+ await page.mouse.click(0, 0);
157
+ // Override system font to Arial to make screenshots deterministic cross-platform
158
+ await page.addStyleTag({
159
+ content: css `
160
+ @font-face {
161
+ font-family: system-ui;
162
+ font-style: normal;
163
+ font-weight: 300;
164
+ src: local('Arial');
165
+ }
166
+ @font-face {
167
+ font-family: -apple-system;
168
+ font-style: normal;
169
+ font-weight: 300;
170
+ src: local('Arial');
171
+ }
172
+ @font-face {
173
+ font-family: BlinkMacSystemFont;
174
+ font-style: normal;
175
+ font-weight: 300;
176
+ src: local('Arial');
177
+ }
178
+ `,
179
+ });
180
+ // await new Promise<never>(() => {})
181
+ });
182
+ after('Stop Polly', () => polly === null || polly === void 0 ? void 0 : polly.stop());
183
+ after('Close page', () => page === null || page === void 0 ? void 0 : page.close());
184
+ let svgPage;
185
+ before('Produce SVG', async () => {
186
+ const svgDeferred = createDeferred();
187
+ await page.exposeFunction('resolveSVG', svgDeferred.resolve);
188
+ await page.exposeFunction('rejectSVG', svgDeferred.reject);
189
+ const injectedScriptUrl = 'http://localhost:8080/injected-script.js';
190
+ await page.addScriptTag({ url: injectedScriptUrl });
191
+ const generatedSVGMarkup = await Promise.race([
192
+ svgDeferred.promise.catch(({ message, ...error }) => Promise.reject(Object.assign(new Error(message), error))),
193
+ delay(120000).then(() => Promise.reject(new Error('Timeout generating SVG'))),
194
+ ]);
195
+ console.log('Formatting SVG');
196
+ const generatedSVGMarkupFormatted = formatXML(generatedSVGMarkup);
197
+ await writeFile(svgFilePath, generatedSVGMarkupFormatted);
198
+ svgPage = await browser.newPage();
199
+ await svgPage.goto(pathToFileURL(svgFilePath).href);
200
+ // await new Promise<never>(() => {})
201
+ });
202
+ after('Close SVG page', () => svgPage === null || svgPage === void 0 ? void 0 : svgPage.close());
203
+ it('produces SVG that is visually the same', async () => {
204
+ console.log('Bringing page to front');
205
+ await page.bringToFront();
206
+ console.log('Snapshotting the original page');
207
+ const expectedScreenshot = await page.screenshot({ encoding: 'binary', type: 'png', fullPage: false });
208
+ await writeFile(path.resolve(snapshotDirectory, `${encodedName}.expected.png`), expectedScreenshot);
209
+ console.log('Snapshotting the SVG');
210
+ const actualScreenshot = await svgPage.screenshot({ encoding: 'binary', type: 'png', fullPage: false });
211
+ await writeFile(path.resolve(snapshotDirectory, `${encodedName}.actual.png`), actualScreenshot);
212
+ console.log('Snapshotted, comparing PNGs');
213
+ const expectedPNG = PNG.sync.read(expectedScreenshot);
214
+ const actualPNG = PNG.sync.read(actualScreenshot);
215
+ const { width, height } = expectedPNG;
216
+ const diffPNG = new PNG({ width, height });
217
+ const differentPixels = pixelmatch(expectedPNG.data, actualPNG.data, diffPNG.data, width, height, {
218
+ threshold: 0.3,
219
+ });
220
+ const differenceRatio = differentPixels / (width * height);
221
+ const diffPngBuffer = PNG.sync.write(diffPNG);
222
+ await writeFile(path.resolve(snapshotDirectory, `${encodedName}.diff.png`), diffPngBuffer);
223
+ if (process.env.TERM_PROGRAM === 'iTerm.app') {
224
+ const nameBase64 = Buffer.from(encodedName + '.diff.png').toString('base64');
225
+ const diffBase64 = diffPngBuffer.toString('base64');
226
+ console.log(`\u001B]1337;File=name=${nameBase64};inline=1;width=1080px:${diffBase64}\u0007`);
227
+ }
228
+ const differencePercentage = differenceRatio * 100;
229
+ console.log('Difference', differencePercentage.toFixed(2) + '%');
230
+ assert.isBelow(differencePercentage, 0.5); // %
231
+ });
232
+ it('produces SVG with the expected accessibility tree', async function () {
233
+ const snapshotPath = path.resolve(snapshotDirectory, encodedName + '.a11y.json');
234
+ const expectedAccessibilityTree = await readFileOrUndefined(snapshotPath);
235
+ const actualAccessibilityTree = await svgPage.accessibility.snapshot();
236
+ await writeFile(snapshotPath, JSON.stringify(actualAccessibilityTree, null, 2));
237
+ if (!expectedAccessibilityTree) {
238
+ this.skip();
239
+ }
240
+ assert.deepStrictEqual(actualAccessibilityTree, JSON.parse(expectedAccessibilityTree), 'Expected accessibility tree to be the same as snapshot');
241
+ });
242
+ });
243
+ }
244
+ });
245
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../../src/test/test.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeFile } from 'fs/promises'\nimport { Server } from 'net'\nimport * as path from 'path'\nimport { fileURLToPath, pathToFileURL } from 'url'\nimport * as util from 'util'\n\nimport { MODE, Polly } from '@pollyjs/core'\nimport FSPersister from '@pollyjs/persister-fs'\nimport { assert } from 'chai'\nimport delay from 'delay'\nimport ParcelBundler from 'parcel-bundler'\nimport pixelmatch from 'pixelmatch'\nimport { PNG } from 'pngjs'\nimport puppeteer, { ResourceType } from 'puppeteer'\nimport css from 'tagged-template-noop'\nimport formatXML from 'xml-formatter'\n\nimport { PuppeteerAdapter } from './PuppeteerAdapter.js'\nimport { createDeferred, readFileOrUndefined } from './util.js'\n\n// Reduce log verbosity\nutil.inspect.defaultOptions.depth = 0\nutil.inspect.defaultOptions.maxStringLength = 80\n\nPolly.register(PuppeteerAdapter as any)\n\ndeclare global {\n\tfunction resolveSVG(svg: string): void\n\tfunction rejectSVG(error: unknown): void\n}\n\nconst defaultViewport: puppeteer.Viewport = {\n\twidth: 1200,\n\theight: 800,\n}\n\nconst mode = (process.env.POLLY_MODE || 'replay') as MODE\nconsole.log('Using Polly mode', mode)\n\nconst root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..')\n\ndescribe('documentToSVG()', () => {\n\tlet browser: puppeteer.Browser\n\tlet server: Server\n\tbefore('Launch devserver', async () => {\n\t\tconst bundler = new ParcelBundler(path.resolve(root, 'lib/test/injected-script.js'), {\n\t\t\thmr: false,\n\t\t\tsourceMaps: false, // Workaround for \"Unterminated regular expression\" Parcel bug\n\t\t\tminify: false,\n\t\t\tautoInstall: false,\n\t\t})\n\t\tserver = await bundler.serve(8080)\n\t})\n\tbefore('Launch browser', async () => {\n\t\tbrowser = await puppeteer.launch({\n\t\t\theadless: true,\n\t\t\tdefaultViewport,\n\t\t\tdevtools: true,\n\t\t\targs: [\n\t\t\t\t'--window-size=1920,1080',\n\t\t\t\t'--lang=en-US',\n\t\t\t\t'--disable-web-security',\n\t\t\t\t'--font-render-hinting=none',\n\t\t\t\t'--enable-font-antialiasing',\n\t\t\t],\n\t\t\ttimeout: 0,\n\t\t\t// slowMo: 100,\n\t\t})\n\t})\n\n\tafter('Close browser', () => browser?.close())\n\tafter('Close devserver', done => server?.close(done))\n\n\tconst snapshotDirectory = path.resolve(root, 'src/test/snapshots')\n\tconst sites = [\n\t\tnew URL('https://sourcegraph.com/search'),\n\t\tnew URL('https://sourcegraph.com/extensions'),\n\t\tnew URL('https://www.google.com?hl=en'),\n\t\tnew URL('https://news.ycombinator.com'),\n\t\tnew URL(\n\t\t\t'https://github.com/felixfbecker/dom-to-svg/blob/fee7e1e7b63c888bc1c5205126b05c63073ebdd3/.vscode/settings.json'\n\t\t),\n\t]\n\tfor (const url of sites) {\n\t\tconst encodedName = encodeURIComponent(url.href)\n\t\tconst svgFilePath = path.resolve(snapshotDirectory, encodedName + '.svg')\n\t\tdescribe(url.href, () => {\n\t\t\tlet polly: Polly\n\t\t\tlet page: puppeteer.Page\n\t\t\tbefore('Open tab and setup Polly', async () => {\n\t\t\t\tpage = await browser.newPage()\n\t\t\t\tawait page.setRequestInterception(true)\n\t\t\t\tawait page.setBypassCSP(true)\n\t\t\t\t// Prevent Google cookie consent prompt\n\t\t\t\tif (url.hostname.endsWith('google.com')) {\n\t\t\t\t\tawait page.setCookie({ name: 'CONSENT', value: 'YES+DE.de+V14+BX', domain: '.google.com' })\n\t\t\t\t}\n\t\t\t\tawait page.setExtraHTTPHeaders({\n\t\t\t\t\t'Accept-Language': 'en-US',\n\t\t\t\t\tDNT: '1',\n\t\t\t\t})\n\t\t\t\tpage.on('console', message => {\n\t\t\t\t\tconsole.log('🖥 ' + (message.type() !== 'log' ? message.type().toUpperCase() : ''), message.text())\n\t\t\t\t})\n\n\t\t\t\tconst requestResourceTypes: ResourceType[] = [\n\t\t\t\t\t'xhr',\n\t\t\t\t\t'fetch',\n\t\t\t\t\t'document',\n\t\t\t\t\t'script',\n\t\t\t\t\t'stylesheet',\n\t\t\t\t\t'image',\n\t\t\t\t\t'font',\n\t\t\t\t\t'other',\n\t\t\t\t]\n\t\t\t\tpolly = new Polly(url.href, {\n\t\t\t\t\tmode,\n\t\t\t\t\trecordIfMissing: false,\n\t\t\t\t\trecordFailedRequests: true,\n\t\t\t\t\tflushRequestsOnStop: false,\n\t\t\t\t\tlogging: false,\n\t\t\t\t\tadapters: [PuppeteerAdapter as any],\n\t\t\t\t\tadapterOptions: {\n\t\t\t\t\t\tpuppeteer: {\n\t\t\t\t\t\t\tpage,\n\t\t\t\t\t\t\trequestResourceTypes,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t// Very lenient, but pages often have very complex URL parameters and this usually works fine.\n\t\t\t\t\tmatchRequestsBy: {\n\t\t\t\t\t\tmethod: true,\n\t\t\t\t\t\tbody: false,\n\t\t\t\t\t\turl: {\n\t\t\t\t\t\t\tusername: false,\n\t\t\t\t\t\t\tpassword: false,\n\t\t\t\t\t\t\thostname: true,\n\t\t\t\t\t\t\tpathname: true,\n\t\t\t\t\t\t\tquery: url.hostname !== 'www.google.com',\n\t\t\t\t\t\t\thash: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\torder: false,\n\t\t\t\t\t\theaders: false,\n\t\t\t\t\t},\n\t\t\t\t\tpersister: FSPersister,\n\t\t\t\t\tpersisterOptions: {\n\t\t\t\t\t\tfs: {\n\t\t\t\t\t\t\trecordingsDir: path.resolve(root, 'src/test/recordings'),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tpolly.server.get('http://localhost:8080/*').passthrough()\n\t\t\t\tpolly.server.get('data:*').passthrough()\n\t\t\t\tpolly.server.any('https://sentry.io/*rest').intercept((request, response) => {\n\t\t\t\t\tresponse.sendStatus(204)\n\t\t\t\t})\n\t\t\t\tpolly.server.any('https://www.googletagmanager.com/*').intercept((request, response) => {\n\t\t\t\t\tresponse.sendStatus(204)\n\t\t\t\t})\n\t\t\t\tpolly.server.any('https://api.github.com/_private/*rest').intercept((request, response) => {\n\t\t\t\t\tresponse.sendStatus(204)\n\t\t\t\t})\n\t\t\t\tpolly.server.any('https://collector.githubapp.com/*rest').intercept((request, response) => {\n\t\t\t\t\tresponse.sendStatus(204)\n\t\t\t\t})\n\t\t\t\tpolly.server.any('https://www.google.com/gen_204').intercept((request, response) => {\n\t\t\t\t\tresponse.sendStatus(204)\n\t\t\t\t})\n\t\t\t})\n\n\t\t\tbefore('Go to page', async () => {\n\t\t\t\tawait page.goto(url.href, {\n\t\t\t\t\twaitUntil: url.host === 'github.com' ? 'domcontentloaded' : 'networkidle2',\n\t\t\t\t\ttimeout: 60000,\n\t\t\t\t})\n\t\t\t\tawait page.waitForTimeout(2000)\n\t\t\t\tawait page.mouse.click(0, 0)\n\t\t\t\t// Override system font to Arial to make screenshots deterministic cross-platform\n\t\t\t\tawait page.addStyleTag({\n\t\t\t\t\tcontent: css`\n\t\t\t\t\t\t@font-face {\n\t\t\t\t\t\t\tfont-family: system-ui;\n\t\t\t\t\t\t\tfont-style: normal;\n\t\t\t\t\t\t\tfont-weight: 300;\n\t\t\t\t\t\t\tsrc: local('Arial');\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@font-face {\n\t\t\t\t\t\t\tfont-family: -apple-system;\n\t\t\t\t\t\t\tfont-style: normal;\n\t\t\t\t\t\t\tfont-weight: 300;\n\t\t\t\t\t\t\tsrc: local('Arial');\n\t\t\t\t\t\t}\n\t\t\t\t\t\t@font-face {\n\t\t\t\t\t\t\tfont-family: BlinkMacSystemFont;\n\t\t\t\t\t\t\tfont-style: normal;\n\t\t\t\t\t\t\tfont-weight: 300;\n\t\t\t\t\t\t\tsrc: local('Arial');\n\t\t\t\t\t\t}\n\t\t\t\t\t`,\n\t\t\t\t})\n\t\t\t\t// await new Promise<never>(() => {})\n\t\t\t})\n\n\t\t\tafter('Stop Polly', () => polly?.stop())\n\t\t\tafter('Close page', () => page?.close())\n\n\t\t\tlet svgPage: puppeteer.Page\n\t\t\tbefore('Produce SVG', async () => {\n\t\t\t\tconst svgDeferred = createDeferred<string>()\n\t\t\t\tawait page.exposeFunction('resolveSVG', svgDeferred.resolve)\n\t\t\t\tawait page.exposeFunction('rejectSVG', svgDeferred.reject)\n\t\t\t\tconst injectedScriptUrl = 'http://localhost:8080/injected-script.js'\n\t\t\t\tawait page.addScriptTag({ url: injectedScriptUrl })\n\t\t\t\tconst generatedSVGMarkup = await Promise.race([\n\t\t\t\t\tsvgDeferred.promise.catch(({ message, ...error }) =>\n\t\t\t\t\t\tPromise.reject(Object.assign(new Error(message), error))\n\t\t\t\t\t),\n\t\t\t\t\tdelay(120000).then(() => Promise.reject(new Error('Timeout generating SVG'))),\n\t\t\t\t])\n\t\t\t\tconsole.log('Formatting SVG')\n\t\t\t\tconst generatedSVGMarkupFormatted = formatXML(generatedSVGMarkup)\n\t\t\t\tawait writeFile(svgFilePath, generatedSVGMarkupFormatted)\n\t\t\t\tsvgPage = await browser.newPage()\n\t\t\t\tawait svgPage.goto(pathToFileURL(svgFilePath).href)\n\t\t\t\t// await new Promise<never>(() => {})\n\t\t\t})\n\t\t\tafter('Close SVG page', () => svgPage?.close())\n\n\t\t\tit('produces SVG that is visually the same', async () => {\n\t\t\t\tconsole.log('Bringing page to front')\n\t\t\t\tawait page.bringToFront()\n\t\t\t\tconsole.log('Snapshotting the original page')\n\t\t\t\tconst expectedScreenshot = await page.screenshot({ encoding: 'binary', type: 'png', fullPage: false })\n\t\t\t\tawait writeFile(path.resolve(snapshotDirectory, `${encodedName}.expected.png`), expectedScreenshot)\n\t\t\t\tconsole.log('Snapshotting the SVG')\n\t\t\t\tconst actualScreenshot = await svgPage.screenshot({ encoding: 'binary', type: 'png', fullPage: false })\n\t\t\t\tawait writeFile(path.resolve(snapshotDirectory, `${encodedName}.actual.png`), actualScreenshot)\n\t\t\t\tconsole.log('Snapshotted, comparing PNGs')\n\n\t\t\t\tconst expectedPNG = PNG.sync.read(expectedScreenshot)\n\t\t\t\tconst actualPNG = PNG.sync.read(actualScreenshot)\n\t\t\t\tconst { width, height } = expectedPNG\n\t\t\t\tconst diffPNG = new PNG({ width, height })\n\n\t\t\t\tconst differentPixels = pixelmatch(expectedPNG.data, actualPNG.data, diffPNG.data, width, height, {\n\t\t\t\t\tthreshold: 0.3,\n\t\t\t\t})\n\t\t\t\tconst differenceRatio = differentPixels / (width * height)\n\n\t\t\t\tconst diffPngBuffer = PNG.sync.write(diffPNG)\n\t\t\t\tawait writeFile(path.resolve(snapshotDirectory, `${encodedName}.diff.png`), diffPngBuffer)\n\n\t\t\t\tif (process.env.TERM_PROGRAM === 'iTerm.app') {\n\t\t\t\t\tconst nameBase64 = Buffer.from(encodedName + '.diff.png').toString('base64')\n\t\t\t\t\tconst diffBase64 = diffPngBuffer.toString('base64')\n\t\t\t\t\tconsole.log(`\\u001B]1337;File=name=${nameBase64};inline=1;width=1080px:${diffBase64}\\u0007`)\n\t\t\t\t}\n\n\t\t\t\tconst differencePercentage = differenceRatio * 100\n\n\t\t\t\tconsole.log('Difference', differencePercentage.toFixed(2) + '%')\n\n\t\t\t\tassert.isBelow(differencePercentage, 0.5) // %\n\t\t\t})\n\n\t\t\tit('produces SVG with the expected accessibility tree', async function () {\n\t\t\t\tconst snapshotPath = path.resolve(snapshotDirectory, encodedName + '.a11y.json')\n\t\t\t\tconst expectedAccessibilityTree = await readFileOrUndefined(snapshotPath)\n\t\t\t\tconst actualAccessibilityTree = await svgPage.accessibility.snapshot()\n\t\t\t\tawait writeFile(snapshotPath, JSON.stringify(actualAccessibilityTree, null, 2))\n\t\t\t\tif (!expectedAccessibilityTree) {\n\t\t\t\t\tthis.skip()\n\t\t\t\t}\n\t\t\t\tassert.deepStrictEqual(\n\t\t\t\t\tactualAccessibilityTree,\n\t\t\t\t\tJSON.parse(expectedAccessibilityTree),\n\t\t\t\t\t'Expected accessibility tree to be the same as snapshot'\n\t\t\t\t)\n\t\t\t})\n\t\t})\n\t}\n})\n"]}
@@ -0,0 +1,9 @@
1
+ import { Page } from 'puppeteer';
2
+ export declare const createDeferred: <T>() => {
3
+ promise: Promise<T>;
4
+ resolve: (value: T) => void;
5
+ reject: (value: unknown) => void;
6
+ };
7
+ export declare function forwardBrowserLogs(page: Page): void;
8
+ export declare function readFileOrUndefined(filePath: string): Promise<string | undefined>;
9
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/test/util.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,eAAO,MAAM,cAAc;;2BAEH,IAAI;oBACX,OAAO,KAAK,IAAI;CAShC,CAAA;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAUnD;AAED,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CASvF"}
@@ -0,0 +1,33 @@
1
+ import { readFile } from 'fs/promises';
2
+ export const createDeferred = () => {
3
+ let resolve;
4
+ let reject;
5
+ const promise = new Promise((resolve_, reject_) => {
6
+ resolve = resolve_;
7
+ reject = reject_;
8
+ });
9
+ return { promise, resolve, reject };
10
+ };
11
+ export function forwardBrowserLogs(page) {
12
+ page.on('console', message => {
13
+ console.log('Browser console:', message.type().toUpperCase(), message.text());
14
+ });
15
+ page.on('error', error => {
16
+ console.error(error);
17
+ });
18
+ page.on('pageerror', error => {
19
+ console.error(error);
20
+ });
21
+ }
22
+ export async function readFileOrUndefined(filePath) {
23
+ try {
24
+ return await readFile(filePath, 'utf-8');
25
+ }
26
+ catch (error) {
27
+ if (error.code === 'ENOENT') {
28
+ return undefined;
29
+ }
30
+ throw error;
31
+ }
32
+ }
33
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/test/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAItC,MAAM,CAAC,MAAM,cAAc,GAAG,GAI5B,EAAE;IACH,IAAI,OAA4B,CAAA;IAChC,IAAI,MAAiC,CAAA;IACrC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;QACpD,OAAO,GAAG,QAAQ,CAAA;QAClB,MAAM,GAAG,OAAO,CAAA;IACjB,CAAC,CAAC,CAAA;IACF,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;AACpC,CAAC,CAAA;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAU;IAC5C,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE;QAC5B,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IAC9E,CAAC,CAAC,CAAA;IACF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE;QACxB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC,CAAC,CAAA;IACF,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE;QAC5B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC,CAAC,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,QAAgB;IACzD,IAAI;QACH,OAAO,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;KACxC;IAAC,OAAO,KAAK,EAAE;QACf,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE;YAC5B,OAAO,SAAS,CAAA;SAChB;QACD,MAAM,KAAK,CAAA;KACX;AACF,CAAC","sourcesContent":["import { readFile } from 'fs/promises'\n\nimport { Page } from 'puppeteer'\n\nexport const createDeferred = <T>(): {\n\tpromise: Promise<T>\n\tresolve: (value: T) => void\n\treject: (value: unknown) => void\n} => {\n\tlet resolve!: (value: T) => void\n\tlet reject!: (value: unknown) => void\n\tconst promise = new Promise<T>((resolve_, reject_) => {\n\t\tresolve = resolve_\n\t\treject = reject_\n\t})\n\treturn { promise, resolve, reject }\n}\n\nexport function forwardBrowserLogs(page: Page): void {\n\tpage.on('console', message => {\n\t\tconsole.log('Browser console:', message.type().toUpperCase(), message.text())\n\t})\n\tpage.on('error', error => {\n\t\tconsole.error(error)\n\t})\n\tpage.on('pageerror', error => {\n\t\tconsole.error(error)\n\t})\n}\n\nexport async function readFileOrUndefined(filePath: string): Promise<string | undefined> {\n\ttry {\n\t\treturn await readFile(filePath, 'utf-8')\n\t} catch (error) {\n\t\tif (error.code === 'ENOENT') {\n\t\t\treturn undefined\n\t\t}\n\t\tthrow error\n\t}\n}\n"]}
package/lib/text.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { TraversalContext } from './traversal.js';
2
+ export declare function handleTextNode(textNode: Text, context: TraversalContext): void;
3
+ export declare const textAttributes: Set<"dominant-baseline" | "color" | "font-family" | "font-size" | "font-size-adjust" | "font-stretch" | "font-style" | "font-variant" | "font-weight" | "direction" | "letter-spacing" | "text-decoration" | "text-anchor" | "text-rendering" | "unicode-bidi" | "word-spacing" | "writing-mode" | "user-select">;
4
+ export declare function copyTextStyles(styles: CSSStyleDeclaration, svgElement: SVGElement): void;
5
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGjD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAkH9E;AAED,eAAO,MAAM,cAAc,mTAoBhB,CAAA;AACX,wBAAgB,cAAc,CAAC,MAAM,EAAE,mBAAmB,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI,CASxF"}
package/lib/text.js ADDED
@@ -0,0 +1,138 @@
1
+ import { isVisible } from './css.js';
2
+ import { svgNamespace } from './dom.js';
3
+ import { doRectanglesIntersect, assert } from './util.js';
4
+ export function handleTextNode(textNode, context) {
5
+ if (!textNode.ownerDocument.defaultView) {
6
+ throw new Error("Element's ownerDocument has no defaultView");
7
+ }
8
+ const window = textNode.ownerDocument.defaultView;
9
+ const parentElement = textNode.parentElement;
10
+ const styles = window.getComputedStyle(parentElement);
11
+ if (!isVisible(styles)) {
12
+ return;
13
+ }
14
+ const selection = window.getSelection();
15
+ assert(selection, 'Could not obtain selection from window. Selection is needed for detecting whitespace collapsing in text.');
16
+ const svgTextElement = context.svgDocument.createElementNS(svgNamespace, 'text');
17
+ // Copy text styles
18
+ // https://css-tricks.com/svg-properties-and-css
19
+ copyTextStyles(styles, svgTextElement);
20
+ const tabSize = parseInt(styles.tabSize, 10);
21
+ // Make sure the y attribute is the bottom of the box, not the baseline
22
+ svgTextElement.setAttribute('dominant-baseline', 'text-after-edge');
23
+ const lineRange = textNode.ownerDocument.createRange();
24
+ lineRange.setStart(textNode, 0);
25
+ lineRange.setEnd(textNode, 0);
26
+ while (true) {
27
+ const addTextSpanForLineRange = () => {
28
+ if (lineRange.collapsed) {
29
+ return;
30
+ }
31
+ const lineRectangle = lineRange.getClientRects()[0];
32
+ if (!doRectanglesIntersect(lineRectangle, context.options.captureArea)) {
33
+ return;
34
+ }
35
+ const textSpan = context.svgDocument.createElementNS(svgNamespace, 'tspan');
36
+ textSpan.setAttribute('xml:space', 'preserve');
37
+ // lineRange.toString() returns the text including whitespace.
38
+ // by adding the range to a Selection, then getting the text from that selection,
39
+ // we can let the DOM handle whitespace collapsing the same way as innerText (but for a Range).
40
+ // For this to work, the parent element must not forbid user selection.
41
+ const previousUserSelect = parentElement.style.userSelect;
42
+ parentElement.style.userSelect = 'all';
43
+ try {
44
+ selection.removeAllRanges();
45
+ selection.addRange(lineRange);
46
+ textSpan.textContent = selection
47
+ .toString()
48
+ // SVG does not support tabs in text. Tabs get rendered as one space character. Convert the
49
+ // tabs to spaces according to tab-size instead.
50
+ // Ideally we would keep the tab and create offset tspans.
51
+ .replace(/\t/g, ' '.repeat(tabSize));
52
+ }
53
+ finally {
54
+ parentElement.style.userSelect = previousUserSelect;
55
+ selection.removeAllRanges();
56
+ }
57
+ textSpan.setAttribute('x', lineRectangle.x.toString());
58
+ textSpan.setAttribute('y', lineRectangle.bottom.toString()); // intentionally bottom because of dominant-baseline setting
59
+ const textContent = textSpan.textContent || '';
60
+ const textLength = textContent.length;
61
+ console.log('textLength', textLength);
62
+ if (textLength > 0) {
63
+ // Calculate an appropriate font size based on available width
64
+ // This is a simple approach - you may need to adjust the divisor based on your specific needs
65
+ const calculatedFontSize = Math.max(8, Math.min(20, lineRectangle.width / (textLength * 0.6)));
66
+ console.log('calculatedFontSize', calculatedFontSize);
67
+ textSpan.setAttribute('font-size', `${calculatedFontSize}px`);
68
+ }
69
+ textSpan.setAttribute('textLength', lineRectangle.width.toString());
70
+ textSpan.setAttribute('lengthAdjust', 'spacingAndGlyphs');
71
+ const originalFontSize = styles.fontSize;
72
+ textSpan.setAttribute('data-original-font-size', originalFontSize);
73
+ svgTextElement.append(textSpan);
74
+ };
75
+ try {
76
+ lineRange.setEnd(textNode, lineRange.endOffset + 1);
77
+ }
78
+ catch (error) {
79
+ if (error.code === DOMException.INDEX_SIZE_ERR) {
80
+ // Reached the end
81
+ addTextSpanForLineRange();
82
+ break;
83
+ }
84
+ throw error;
85
+ }
86
+ // getClientRects() returns one rectangle for each line of a text node.
87
+ const lineRectangles = lineRange.getClientRects();
88
+ // If no lines
89
+ if (!lineRectangles[0]) {
90
+ // Pure whitespace text nodes are collapsed and not rendered.
91
+ return;
92
+ }
93
+ // If two (unique) lines
94
+ // For some reason, Chrome returns 2 identical DOMRects for text with text-overflow: ellipsis.
95
+ if (lineRectangles[1] && lineRectangles[0].top !== lineRectangles[1].top) {
96
+ // Crossed a line break.
97
+ // Go back one character to select exactly the previous line.
98
+ lineRange.setEnd(textNode, lineRange.endOffset - 1);
99
+ // Add <tspan> for exactly that line
100
+ addTextSpanForLineRange();
101
+ // Start on the next line.
102
+ lineRange.setStart(textNode, lineRange.endOffset);
103
+ }
104
+ }
105
+ context.currentSvgParent.append(svgTextElement);
106
+ }
107
+ export const textAttributes = new Set([
108
+ 'color',
109
+ 'dominant-baseline',
110
+ 'font-family',
111
+ 'font-size',
112
+ 'font-size-adjust',
113
+ 'font-stretch',
114
+ 'font-style',
115
+ 'font-variant',
116
+ 'font-weight',
117
+ 'direction',
118
+ 'letter-spacing',
119
+ 'text-decoration',
120
+ 'text-anchor',
121
+ 'text-decoration',
122
+ 'text-rendering',
123
+ 'unicode-bidi',
124
+ 'word-spacing',
125
+ 'writing-mode',
126
+ 'user-select',
127
+ ]);
128
+ export function copyTextStyles(styles, svgElement) {
129
+ for (const textProperty of textAttributes) {
130
+ const value = styles.getPropertyValue(textProperty);
131
+ if (value) {
132
+ svgElement.setAttribute(textProperty, value);
133
+ }
134
+ }
135
+ // tspan uses fill, CSS uses color
136
+ svgElement.setAttribute('fill', styles.color);
137
+ }
138
+ //# sourceMappingURL=text.js.map