@tin.so/core 0.1.0 → 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chris Cook
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,33 +1,121 @@
1
1
  # @tin.so/core
2
2
 
3
- Renders code images from a JSON spec. The pipeline is `spec → Shiki tokens → layout → SVG → PNG` and needs no browser: the layout places every character on a monospace grid with the metrics of the bundled fonts, and [resvg](https://github.com/linebender/resvg) (WebAssembly) rasterizes the SVG. The same code runs in Node and in the browser.
3
+ Renders code images from a JSON spec, to SVG and PNG. One image can show several snippets: windows next to each other or below each other, panes in a window, and popovers that point to a word. It has all [ray.so](https://ray.so) themes, line highlights, text styles and colors, tabs with file icons, and fixed aspect ratios for social media.
4
+
5
+ The pipeline is `spec → Shiki tokens → layout → SVG → PNG` and needs no browser. The layout puts every character on a monospace grid with the metrics of the bundled fonts, and [resvg](https://github.com/linebender/resvg) (WebAssembly) rasterizes the SVG. The same code runs in Node and in the browser.
6
+
7
+ This package is the renderer of the [tin.so](https://tin.so) web editor and of the [`tin` CLI](https://www.npmjs.com/package/tin.so). To make images from the command line, use the CLI.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @tin.so/core
13
+ ```
14
+
15
+ Node 22.18 or later.
16
+
17
+ ## Usage
4
18
 
5
19
  ```ts
20
+ import { writeFile } from "node:fs/promises";
6
21
  import { render } from "@tin.so/core/node";
7
22
 
8
23
  const png = await render({
9
24
  theme: "vercel",
10
25
  windows: [
11
26
  {
12
- titleBar: false,
13
27
  tabs: true,
14
- split: "row",
28
+ titleBar: false,
15
29
  panes: [
16
- { title: "main.ts", code: 'import plans from "./plans.toml";' },
30
+ {
31
+ title: "main.ts",
32
+ code: 'import plans from "./plans.toml";\n\nplans.free.price;',
33
+ highlight: [3],
34
+ annotations: [
35
+ { line: 1, match: "plans", code: "const plans: {\n free: { price: 0 };\n}" },
36
+ ],
37
+ },
17
38
  { title: "plans.toml", code: "[free]\nprice = 0" },
18
39
  ],
19
40
  },
20
41
  ],
21
42
  });
43
+
44
+ await writeFile("plans.png", png);
45
+ ```
46
+
47
+ `render(spec, { format: "svg" })` gives an SVG with the fonts inside it. `scale` sets the pixel ratio of a PNG (default 2). By default, the image contains the spec and a link that opens it in the web editor. `metadata: false` turns this off.
48
+
49
+ `createLink(spec)` gives that link without an image:
50
+
51
+ ```ts
52
+ import { createLink } from "@tin.so/core";
53
+
54
+ const link = await createLink(spec); // https://tin.so/#spec=…
55
+ ```
56
+
57
+ ## The spec
58
+
59
+ A spec is JSON with the image settings, its windows, the panes of each window, and the code of each pane. A spec needs only `windows`, `panes` and `code`.
60
+
61
+ ```json
62
+ {
63
+ "$schema": "https://tin.so/schema/v1.json",
64
+ "theme": "candy",
65
+ "windows": [{ "panes": [{ "title": "main.ts", "code": "const a = 1;" }] }]
66
+ }
67
+ ```
68
+
69
+ | Level | Fields |
70
+ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
71
+ | Image | `theme`, `mode`, `background`, `padding`, `font`, `lineNumbers`, `direction`, `gap`, `aspectRatio`, `width`, `height`, `windows` |
72
+ | Window | `panes`, `split`, `titleBar`, `tabs`, `frame`, `controls`, `title`, `width`, `height`, `x`, `y` |
73
+ | Pane | `code`, `title`, `language`, `highlight`, `marks` (bold, italic, colors, highlighter fills), `annotations` (popovers), `icon`, `lineNumbers` |
74
+
75
+ The full spec, with a description, a default and the limits of each field, is the JSON Schema at [`https://tin.so/schema/v1.json`](https://tin.so/schema/v1.json). `specJsonSchema()` gives the same schema.
76
+
77
+ The `$schema` URL (`/schema/v1.json`) or the field `v` gives the version of the spec. A spec without both is version 1. A new version comes only with a change that can break a spec. New optional fields and new themes, fonts or languages come without one.
78
+
79
+ The renderer ignores keys that start with `x-` in every object, so a spec can carry data of other tools.
80
+
81
+ An invalid spec throws a `SpecError` that names each problem with its path, for example `windows.0.panes.0.annotations.0.match: "plnas" (occurrence 1) is not in line 1`.
82
+
83
+ ## API
84
+
85
+ | Function | What it does |
86
+ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
87
+ | `render(spec, options?)` | Renders a spec to PNG or SVG bytes, in Node (`@tin.so/core/node`) |
88
+ | `parseSpec(input)` | Checks a spec and fills in the defaults. Throws a `SpecError` |
89
+ | `layout(spec, options?)` | Highlights the code and places the windows, panes and popovers. Gives a `Scene` |
90
+ | `toSvg(scene, options?)` | Writes the SVG of a scene, with or without embedded fonts and metadata |
91
+ | `initPng(load)`, `svgToPng(svg, fonts, options?)` | Loads the resvg WebAssembly once, then rasterizes an SVG |
92
+ | `loadSceneFonts(scene, loadFont)` | Loads the font files that a scene uses |
93
+ | `createLink(spec, appUrl?)`, `readLink(link)` | Writes a spec into an editor link, and reads it back |
94
+ | `encodeSpec(spec)`, `decodeSpec(encoded)` | The compressed spec of a link, without the URL |
95
+ | `readImageSpec(image)` | Reads the spec from a PNG or SVG that has the metadata. Gives `undefined` for other images |
96
+ | `specJsonSchema()`, `SPEC_SCHEMA_URL` | The JSON Schema of the spec, and its URL |
97
+ | `THEME_IDS`, `CODE_FONT_IDS`, `LANGUAGE_IDS`, `ICON_IDS` | The ids that the spec accepts |
98
+
99
+ ### In the browser
100
+
101
+ Import from `@tin.so/core`, and give the steps that read files a loader, because the browser has no file system:
102
+
103
+ ```ts
104
+ import { initPng, layout, loadSceneFonts, svgToPng, toSvg } from "@tin.so/core";
105
+
106
+ const scene = await layout(spec);
107
+ // loadFont gets a path in the fonts/ directory of this package, for example "fira-code/fira-code-400.ttf"
108
+ const fonts = await loadSceneFonts(scene, loadFont);
109
+ await initPng(() => fetch(wasmUrl)); // the index_bg.wasm file of @resvg/resvg-wasm
110
+ const png = await svgToPng(toSvg(scene), fonts.values());
22
111
  ```
23
112
 
24
113
  ## Scripts
25
114
 
26
115
  - `pnpm port-themes` fetches the ray.so themes at a pinned commit and writes `src/themes/*.json`.
27
116
  - `pnpm download-fonts` downloads the fonts to `fonts/` and writes their metrics to `src/fonts/fonts.generated.json`.
117
+ - `pnpm generate-icons` writes the language icons from Simple Icons.
28
118
 
29
- ## Attribution
119
+ ## License
30
120
 
31
- - The themes, the token colors and the frame styles come from [ray.so](https://github.com/raycast/ray-so) (MIT).
32
- - The fonts are under the SIL Open Font License, except Commit Mono (MIT). Each font directory contains its license.
33
- - The file icon is from [Lucide](https://lucide.dev) (ISC), the language icons from [Simple Icons](https://simpleicons.org) (CC0).
121
+ MIT. The themes come from ray.so (MIT), the file icon from Lucide (ISC), the language icons from Simple Icons (CC0), and the fonts are under the SIL Open Font License, except Commit Mono (MIT). See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
@@ -0,0 +1,61 @@
1
+ # Third-party notices
2
+
3
+ `@tin.so/core` contains work of other projects. Their licenses follow.
4
+
5
+ ## ray.so
6
+
7
+ The themes, the token colors and the frame styles come from [ray.so](https://github.com/raycast/ray-so).
8
+
9
+ ```
10
+ MIT License
11
+
12
+ Copyright (c) 2024 Raycast Technologies Ltd
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ ```
32
+
33
+ ## Lucide
34
+
35
+ The file icon comes from [Lucide](https://lucide.dev).
36
+
37
+ ```
38
+ ISC License
39
+
40
+ Copyright (c) 2026 Lucide Icons and Contributors
41
+
42
+ Permission to use, copy, modify, and/or distribute this software for any
43
+ purpose with or without fee is hereby granted, provided that the above
44
+ copyright notice and this permission notice appear in all copies.
45
+
46
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
47
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
48
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
49
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
50
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
51
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
52
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
53
+ ```
54
+
55
+ ## Simple Icons
56
+
57
+ The language icons come from [Simple Icons](https://simpleicons.org), under [CC0 1.0](https://github.com/simple-icons/simple-icons/blob/develop/LICENSE.md).
58
+
59
+ ## Fonts
60
+
61
+ The fonts in `fonts/` are under the SIL Open Font License 1.1, except Commit Mono, which is under the MIT License. Each font directory contains its license.
@@ -77,25 +77,38 @@ declare const SPEC_SCHEMA_URL: string;
77
77
  * the spec, such as line ranges, so their rules are only in the descriptions.
78
78
  */
79
79
  declare function specJsonSchema(): Record<string, unknown>;
80
+ /**
81
+ * True when the value is the URL of a tin.so spec schema: a path that ends with
82
+ * /schema/v<number>.json on the host of the hosted app, on an earlier host of the app, or on the
83
+ * host of the given app URL (for example a local editor).
84
+ */
85
+ declare function isSpecSchemaUrl(value: unknown, appUrl?: string): boolean;
80
86
  /**
81
87
  * The hosted web app. The CLI takes another app from its TIN_APP_URL environment variable,
82
88
  * for example the local editor during development.
83
89
  */
84
- declare const DEFAULT_APP_URL = "https://code-image.zirkelc.workers.dev/";
90
+ declare const DEFAULT_APP_URL = "https://tin.so/";
91
+ type DecodeOptions = {
92
+ /** The largest size of the inflated spec in bytes, so that a hostile input cannot use much memory */
93
+ maxBytes?: number;
94
+ };
85
95
  /**
86
96
  * Compresses a spec into a URL-safe string. A spec without a version gets version 1, so that the
87
97
  * link keeps its meaning when a later version changes the format.
88
98
  */
89
99
  declare function encodeSpec(spec: unknown): Promise<string>;
90
- /** Reads a spec from a string of the encoder, with the fields of older links changed */
91
- declare function decodeSpec(encoded: string): Promise<unknown>;
100
+ /**
101
+ * Reads a spec from a string of the encoder, with the fields of older links changed. Throws for
102
+ * bad data and for a spec larger than the limit.
103
+ */
104
+ declare function decodeSpec(encoded: string, options?: DecodeOptions): Promise<unknown>;
92
105
  /**
93
106
  * A link that opens the spec in the web app. The spec is in the fragment, so it never goes to a
94
107
  * server and has no length limit of a request line.
95
108
  */
96
109
  declare function createLink(spec: unknown, appUrl?: string): Promise<string>;
97
110
  /** Reads the spec from the fragment of a link, or returns undefined when the fragment has none */
98
- declare function readLink(link: string): Promise<unknown>;
111
+ declare function readLink(link: string, options?: DecodeOptions): Promise<unknown>;
99
112
  type Mode = "light" | "dark";
100
113
  /** Token colors of a theme. Keys that are missing use the foreground color. */
101
114
  type Syntax = {
@@ -332,14 +345,16 @@ declare const SOFTWARE = "tin.so";
332
345
  declare const PNG_KEYWORDS: {
333
346
  readonly software: "Software";
334
347
  readonly source: "Source";
335
- readonly link: "code-image:link";
336
- readonly spec: "code-image:spec";
348
+ readonly link: "tin.so:link";
337
349
  };
338
350
  /** The namespace of the metadata element in an SVG */
339
- declare const SVG_NAMESPACE = "urn:code-image:metadata:1";
340
- /** The data that an exported image carries, so that the editor can open the image again */
351
+ declare const SVG_NAMESPACE = "urn:tin.so:metadata:1";
352
+ /**
353
+ * The data that an exported image carries, so that the editor can open the image again. The image
354
+ * stores only the link, because the link holds the whole spec.
355
+ */
341
356
  type ImageMetadata = {
342
- /** The spec input, as the user wrote it */
357
+ /** The spec input, as the user wrote it, with its version */
343
358
  spec: unknown;
344
359
  /** A link that opens the spec in the editor */
345
360
  link: string;
@@ -350,8 +365,8 @@ type ImageMetadata = {
350
365
  */
351
366
  declare function createImageMetadata(spec: unknown, appUrl?: string): Promise<ImageMetadata>;
352
367
  /**
353
- * Adds text chunks after the header of a PNG: the program, the app URL, the editor link and the
354
- * compressed spec. A PNG without the expected header comes back unchanged.
368
+ * Adds text chunks after the header of a PNG: the program, the app URL and the editor link. A PNG
369
+ * without the expected header comes back unchanged.
355
370
  */
356
371
  declare function addPngMetadata(png: Uint8Array, metadata: ImageMetadata): Promise<Uint8Array>;
357
372
  /**
@@ -361,6 +376,16 @@ declare function addPngMetadata(png: Uint8Array, metadata: ImageMetadata): Promi
361
376
  */
362
377
  declare function readImageSpec(input: Uint8Array | ArrayBuffer | Blob | string): Promise<unknown>;
363
378
  declare const IMPLICIT_SPEC_VERSION = 1;
379
+ /**
380
+ * The version that the $schema URL of a spec names, or undefined when the spec has no such URL.
381
+ * Only the path counts, so that the URL of each host that serves the schema gives the version.
382
+ */
383
+ declare function schemaVersion(spec: unknown): number | undefined;
384
+ /**
385
+ * Keys with this prefix hold data of other tools, such as an editor state. The renderer ignores
386
+ * them in each object of a spec, so that a spec with such keys stays valid in all versions.
387
+ */
388
+ declare const EXTENSION_PREFIX = "x-";
364
389
  /**
365
390
  * Changes the fields of older specs from before the v field to the current fields, so that old
366
391
  * links, files and images stay valid. Input that is not a spec stays as it is, so that the spec
@@ -369,7 +394,8 @@ declare const IMPLICIT_SPEC_VERSION = 1;
369
394
  declare function migrateLegacySpec(spec: unknown): unknown;
370
395
  /**
371
396
  * Adds the version to a spec without one, so that a stored spec keeps its meaning when a later
372
- * version changes the format. A spec with a version stays as it is.
397
+ * version changes the format. The version comes from the $schema URL, or is version 1 without it.
398
+ * A spec with a version stays as it is.
373
399
  */
374
400
  declare function stampSpecVersion(spec: unknown): unknown;
375
401
  type PngOptions = {
@@ -400,12 +426,19 @@ declare const SPEC_LIMITS: {
400
426
  readonly size: 8192;
401
427
  };
402
428
  declare const MARK_STYLES: readonly ["strong", "emphasis", "underline", "strikethrough"];
429
+ declare const ANNOTATION_TYPES: readonly ["popover"];
403
430
  /**
404
431
  * Versions of the spec format.
405
432
  *
406
433
  * - Increase v only for a breaking change: a field that changes its meaning, gets a new name or
407
- * goes away. A new optional field does not increase v, because older specs stay valid.
408
- * - A spec without v is version 1, now and in all later versions, because older specs have no v.
434
+ * goes away, a changed default, a value that a list no longer has (a theme, font, icon or
435
+ * language id), or a smaller limit. Specs leave out the defaults, so a new default changes old
436
+ * images. A new optional field, a new value or a larger limit does not increase v, because older
437
+ * specs stay valid.
438
+ * - The version is v, or the version of the $schema URL (/schema/v<number>.json) without v. When
439
+ * both are there, they must be the same. A spec without both is version 1, now and in all later
440
+ * versions, because older specs have neither.
441
+ * - Keys that start with "x-" are for other tools. The renderer ignores them in every object.
409
442
  * - For a new version, add its schema (with v as a required literal of the new number) to
410
443
  * SPEC_SCHEMAS, a function that changes a parsed spec of the version before into the new
411
444
  * version to MIGRATIONS, and increase LATEST_SPEC_VERSION. The schema of the latest version is
@@ -446,6 +479,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
446
479
  readonly icon: v.SchemaWithPipe<readonly [v.OptionalSchema<v.PicklistSchema<["auto", "none", ...string[]], undefined>, "auto">, v.DescriptionAction<string, string>]>;
447
480
  readonly lineNumbers: v.SchemaWithPipe<readonly [v.OptionalSchema<v.BooleanSchema<undefined>, undefined>, v.DescriptionAction<boolean | undefined, string>]>;
448
481
  readonly annotations: v.SchemaWithPipe<readonly [v.OptionalSchema<v.SchemaWithPipe<readonly [v.ArraySchema<v.StrictObjectSchema<{
482
+ readonly type: v.SchemaWithPipe<readonly [v.OptionalSchema<v.PicklistSchema<readonly ["popover"], undefined>, "popover">, v.DescriptionAction<"popover", string>]>;
449
483
  readonly line: v.SchemaWithPipe<readonly [v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, v.DescriptionAction<number, string>]>;
450
484
  readonly match: v.SchemaWithPipe<readonly [v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, v.DescriptionAction<string, string>]>;
451
485
  readonly occurrence: v.SchemaWithPipe<readonly [v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, 1>, v.DescriptionAction<number, string>]>;
@@ -492,6 +526,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
492
526
  background?: string | undefined;
493
527
  }[], string>]>;
494
528
  }, undefined>, undefined>, v.MaxLengthAction<{
529
+ type: "popover";
495
530
  line: number;
496
531
  match: string;
497
532
  occurrence: number;
@@ -510,6 +545,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
510
545
  background?: string | undefined;
511
546
  }[];
512
547
  }[], number, (issue: v.MaxLengthIssue<{
548
+ type: "popover";
513
549
  line: number;
514
550
  match: string;
515
551
  occurrence: number;
@@ -528,6 +564,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
528
564
  background?: string | undefined;
529
565
  }[];
530
566
  }[], number>) => string>]>, readonly []>, v.DescriptionAction<{
567
+ type: "popover";
531
568
  line: number;
532
569
  match: string;
533
570
  occurrence: number;
@@ -591,6 +628,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
591
628
  icon: string;
592
629
  lineNumbers?: boolean | undefined;
593
630
  annotations: {
631
+ type: "popover";
594
632
  line: number;
595
633
  match: string;
596
634
  occurrence: number;
@@ -626,6 +664,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
626
664
  icon: string;
627
665
  lineNumbers?: boolean | undefined;
628
666
  annotations: {
667
+ type: "popover";
629
668
  line: number;
630
669
  match: string;
631
670
  occurrence: number;
@@ -661,6 +700,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
661
700
  icon: string;
662
701
  lineNumbers?: boolean | undefined;
663
702
  annotations: {
703
+ type: "popover";
664
704
  line: number;
665
705
  match: string;
666
706
  occurrence: number;
@@ -696,6 +736,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
696
736
  icon: string;
697
737
  lineNumbers?: boolean | undefined;
698
738
  annotations: {
739
+ type: "popover";
699
740
  line: number;
700
741
  match: string;
701
742
  occurrence: number;
@@ -743,6 +784,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
743
784
  icon: string;
744
785
  lineNumbers?: boolean | undefined;
745
786
  annotations: {
787
+ type: "popover";
746
788
  line: number;
747
789
  match: string;
748
790
  occurrence: number;
@@ -790,6 +832,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
790
832
  icon: string;
791
833
  lineNumbers?: boolean | undefined;
792
834
  annotations: {
835
+ type: "popover";
793
836
  line: number;
794
837
  match: string;
795
838
  occurrence: number;
@@ -837,6 +880,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
837
880
  icon: string;
838
881
  lineNumbers?: boolean | undefined;
839
882
  annotations: {
883
+ type: "popover";
840
884
  line: number;
841
885
  match: string;
842
886
  occurrence: number;
@@ -884,6 +928,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
884
928
  icon: string;
885
929
  lineNumbers?: boolean | undefined;
886
930
  annotations: {
931
+ type: "popover";
887
932
  line: number;
888
933
  match: string;
889
934
  occurrence: number;
@@ -946,6 +991,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
946
991
  icon: string;
947
992
  lineNumbers?: boolean | undefined;
948
993
  annotations: {
994
+ type: "popover";
949
995
  line: number;
950
996
  match: string;
951
997
  occurrence: number;
@@ -1008,6 +1054,7 @@ declare const SpecSchema: v.SchemaWithPipe<readonly [v.StrictObjectSchema<{
1008
1054
  icon: string;
1009
1055
  lineNumbers?: boolean | undefined;
1010
1056
  annotations: {
1057
+ type: "popover";
1011
1058
  line: number;
1012
1059
  match: string;
1013
1060
  occurrence: number;
@@ -1055,9 +1102,9 @@ declare class SpecError extends Error {
1055
1102
  constructor(issues: Array<string>);
1056
1103
  }
1057
1104
  /**
1058
- * Validates a spec and applies the defaults. It first changes the fields of older specs, then
1059
- * validates the spec with the schema of its version and changes it to the latest version. Throws
1060
- * a SpecError that lists every issue.
1105
+ * Validates a spec and applies the defaults. It first removes the keys of other tools and changes
1106
+ * the fields of older specs, then validates the spec with the schema of its version and changes it
1107
+ * to the latest version. Throws a SpecError that lists every issue.
1061
1108
  */
1062
1109
  declare function parseSpec(input: unknown): Spec;
1063
1110
  /** Expands highlight entries such as [2, "4-6"] to a set of line numbers, up to the last line */
@@ -1073,4 +1120,4 @@ type SvgOptions = {
1073
1120
  };
1074
1121
  /** Writes the scene as a standalone SVG document */
1075
1122
  declare function toSvg(scene: Scene, options?: SvgOptions): string;
1076
- export { resolveLanguage as $, readImageSpec as A, FontFace as At, WINDOW_RADIUS as B, ImageMetadata as C, getIcon as Ct, SVG_NAMESPACE as D, CodeFont as Dt, SOFTWARE as E, CODE_FONT_IDS as Et, PaneBox as F, cssFontFamily as Ft, MAC_HEADER as G, cellCount as H, Rect as I, getCodeFont as It, PLAIN_TEXT as J, HighlightedLine as K, Scene as L, getFace as Lt, LINE_HEIGHT as M, FontMetrics as Mt, LayoutOptions as N, UI_FONT as Nt, addPngMetadata as O, DEFAULT_CODE_FONT as Ot, POPOVER as P, UiFont as Pt, isKnownLanguage as Q, TAB_HEADER as R, measureUiText as Rt, stampSpecVersion as S, NO_ICON as St, PNG_KEYWORDS as T, CODE_FONTS as Tt, layout as U, WindowBox as V, normalizeCode as W, highlight as X, Token as Y, inferLanguage as Z, loadSceneFonts as _, specJsonSchema as _t, MarkStyle as a, THEME_IDS as at, IMPLICIT_SPEC_VERSION as b, ICON_IDS as bt, Spec as c, getSyntax as ct, SpecSchema as d, DEFAULT_APP_URL as dt, DEFAULT_THEME as et, WindowSpec as f, createLink as ft, initPng as g, SPEC_SCHEMA_URL as gt, PngOptions as h, readLink as ht, MARK_STYLES as i, THEMES as it, FONT_SIZE as j, FontLoader as jt, createImageMetadata as k, Font as kt, SpecError as l, getTheme as lt, parseSpec as m, encodeSpec as mt, toSvg as n, Mode as nt, PaneSpec as o, Theme as ot, expandLineRanges as p, decodeSpec as pt, LANGUAGE_IDS as q, LATEST_SPEC_VERSION as r, Syntax as rt, SPEC_LIMITS as s, getFrameColors as st, SvgOptions as t, FrameColors as tt, SpecInput as u, resolveMode as ut, sceneFontFiles as v, AUTO_ICON as vt, METADATA_BY_DEFAULT as w, resolveIcon as wt, migrateLegacySpec as x, Icon as xt, svgToPng as y, FILE_ICON as yt, TextMetrics as z };
1123
+ export { highlight as $, SVG_NAMESPACE as A, CODE_FONTS as At, Scene as B, cssFontFamily as Bt, migrateLegacySpec as C, AUTO_ICON as Ct, METADATA_BY_DEFAULT as D, NO_ICON as Dt, ImageMetadata as E, Icon as Et, LINE_HEIGHT as F, FontFace as Ft, cellCount as G, TextMetrics as H, getFace as Ht, LayoutOptions as I, FontLoader as It, MAC_HEADER as J, layout as K, POPOVER as L, FontMetrics as Lt, createImageMetadata as M, CodeFont as Mt, readImageSpec as N, DEFAULT_CODE_FONT as Nt, PNG_KEYWORDS as O, getIcon as Ot, FONT_SIZE as P, Font as Pt, Token as Q, PaneBox as R, UI_FONT as Rt, IMPLICIT_SPEC_VERSION as S, specJsonSchema as St, stampSpecVersion as T, ICON_IDS as Tt, WINDOW_RADIUS as U, measureUiText as Ut, TAB_HEADER as V, getCodeFont as Vt, WindowBox as W, LANGUAGE_IDS as X, HighlightedLine as Y, PLAIN_TEXT as Z, initPng as _, decodeSpec as _t, MARK_STYLES as a, Mode as at, svgToPng as b, SPEC_SCHEMA_URL as bt, SPEC_LIMITS as c, THEME_IDS as ct, SpecInput as d, getSyntax as dt, inferLanguage as et, SpecSchema as f, getTheme as ft, PngOptions as g, createLink as gt, parseSpec as h, DecodeOptions as ht, LATEST_SPEC_VERSION as i, FrameColors as it, addPngMetadata as j, CODE_FONT_IDS as jt, SOFTWARE as k, resolveIcon as kt, Spec as l, Theme as lt, expandLineRanges as m, DEFAULT_APP_URL as mt, toSvg as n, resolveLanguage as nt, MarkStyle as o, Syntax as ot, WindowSpec as p, resolveMode as pt, normalizeCode as q, ANNOTATION_TYPES as r, DEFAULT_THEME as rt, PaneSpec as s, THEMES as st, SvgOptions as t, isKnownLanguage as tt, SpecError as u, getFrameColors as ut, loadSceneFonts as v, encodeSpec as vt, schemaVersion as w, FILE_ICON as wt, EXTENSION_PREFIX as x, isSpecSchemaUrl as xt, sceneFontFiles as y, readLink as yt, Rect as z, UiFont as zt };
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as resolveLanguage, A as readImageSpec, At as FontFace, B as WINDOW_RADIUS, C as ImageMetadata, Ct as getIcon, D as SVG_NAMESPACE, Dt as CodeFont, E as SOFTWARE, Et as CODE_FONT_IDS, F as PaneBox, Ft as cssFontFamily, G as MAC_HEADER, H as cellCount, I as Rect, It as getCodeFont, J as PLAIN_TEXT, K as HighlightedLine, L as Scene, Lt as getFace, M as LINE_HEIGHT, Mt as FontMetrics, N as LayoutOptions, Nt as UI_FONT, O as addPngMetadata, Ot as DEFAULT_CODE_FONT, P as POPOVER, Pt as UiFont, Q as isKnownLanguage, R as TAB_HEADER, Rt as measureUiText, S as stampSpecVersion, St as NO_ICON, T as PNG_KEYWORDS, Tt as CODE_FONTS, U as layout, V as WindowBox, W as normalizeCode, X as highlight, Y as Token, Z as inferLanguage, _ as loadSceneFonts, _t as specJsonSchema, a as MarkStyle, at as THEME_IDS, b as IMPLICIT_SPEC_VERSION, bt as ICON_IDS, c as Spec, ct as getSyntax, d as SpecSchema, dt as DEFAULT_APP_URL, et as DEFAULT_THEME, f as WindowSpec, ft as createLink, g as initPng, gt as SPEC_SCHEMA_URL, h as PngOptions, ht as readLink, i as MARK_STYLES, it as THEMES, j as FONT_SIZE, jt as FontLoader, k as createImageMetadata, kt as Font, l as SpecError, lt as getTheme, m as parseSpec, mt as encodeSpec, n as toSvg, nt as Mode, o as PaneSpec, ot as Theme, p as expandLineRanges, pt as decodeSpec, q as LANGUAGE_IDS, r as LATEST_SPEC_VERSION, rt as Syntax, s as SPEC_LIMITS, st as getFrameColors, t as SvgOptions, tt as FrameColors, u as SpecInput, ut as resolveMode, v as sceneFontFiles, vt as AUTO_ICON, w as METADATA_BY_DEFAULT, wt as resolveIcon, x as migrateLegacySpec, xt as Icon, y as svgToPng, yt as FILE_ICON, z as TextMetrics } from "./index-CtUX1lFe.mjs";
2
- export { AUTO_ICON, CODE_FONTS, CODE_FONT_IDS, CodeFont, DEFAULT_APP_URL, DEFAULT_CODE_FONT, DEFAULT_THEME, FILE_ICON, FONT_SIZE, Font, FontFace, FontLoader, FontMetrics, FrameColors, type HighlightedLine, ICON_IDS, IMPLICIT_SPEC_VERSION, type Icon, type ImageMetadata, LANGUAGE_IDS, LATEST_SPEC_VERSION, LINE_HEIGHT, type LayoutOptions, MAC_HEADER, MARK_STYLES, METADATA_BY_DEFAULT, type MarkStyle, Mode, NO_ICON, PLAIN_TEXT, PNG_KEYWORDS, POPOVER, type PaneBox, type PaneSpec, type PngOptions, type Rect, SOFTWARE, SPEC_LIMITS, SPEC_SCHEMA_URL, SVG_NAMESPACE, type Scene, type Spec, SpecError, type SpecInput, SpecSchema, type SvgOptions, Syntax, TAB_HEADER, THEMES, THEME_IDS, type TextMetrics, Theme, type Token, UI_FONT, UiFont, WINDOW_RADIUS, type WindowBox, type WindowSpec, addPngMetadata, cellCount, createImageMetadata, createLink, cssFontFamily, decodeSpec, encodeSpec, expandLineRanges, getCodeFont, getFace, getFrameColors, getIcon, getSyntax, getTheme, highlight, inferLanguage, initPng, isKnownLanguage, layout, loadSceneFonts, measureUiText, migrateLegacySpec, normalizeCode, parseSpec, readImageSpec, readLink, resolveIcon, resolveLanguage, resolveMode, sceneFontFiles, specJsonSchema, stampSpecVersion, svgToPng, toSvg };
1
+ import { $ as highlight, A as SVG_NAMESPACE, At as CODE_FONTS, B as Scene, Bt as cssFontFamily, C as migrateLegacySpec, Ct as AUTO_ICON, D as METADATA_BY_DEFAULT, Dt as NO_ICON, E as ImageMetadata, Et as Icon, F as LINE_HEIGHT, Ft as FontFace, G as cellCount, H as TextMetrics, Ht as getFace, I as LayoutOptions, It as FontLoader, J as MAC_HEADER, K as layout, L as POPOVER, Lt as FontMetrics, M as createImageMetadata, Mt as CodeFont, N as readImageSpec, Nt as DEFAULT_CODE_FONT, O as PNG_KEYWORDS, Ot as getIcon, P as FONT_SIZE, Pt as Font, Q as Token, R as PaneBox, Rt as UI_FONT, S as IMPLICIT_SPEC_VERSION, St as specJsonSchema, T as stampSpecVersion, Tt as ICON_IDS, U as WINDOW_RADIUS, Ut as measureUiText, V as TAB_HEADER, Vt as getCodeFont, W as WindowBox, X as LANGUAGE_IDS, Y as HighlightedLine, Z as PLAIN_TEXT, _ as initPng, _t as decodeSpec, a as MARK_STYLES, at as Mode, b as svgToPng, bt as SPEC_SCHEMA_URL, c as SPEC_LIMITS, ct as THEME_IDS, d as SpecInput, dt as getSyntax, et as inferLanguage, f as SpecSchema, ft as getTheme, g as PngOptions, gt as createLink, h as parseSpec, ht as DecodeOptions, i as LATEST_SPEC_VERSION, it as FrameColors, j as addPngMetadata, jt as CODE_FONT_IDS, k as SOFTWARE, kt as resolveIcon, l as Spec, lt as Theme, m as expandLineRanges, mt as DEFAULT_APP_URL, n as toSvg, nt as resolveLanguage, o as MarkStyle, ot as Syntax, p as WindowSpec, pt as resolveMode, q as normalizeCode, r as ANNOTATION_TYPES, rt as DEFAULT_THEME, s as PaneSpec, st as THEMES, t as SvgOptions, tt as isKnownLanguage, u as SpecError, ut as getFrameColors, v as loadSceneFonts, vt as encodeSpec, w as schemaVersion, wt as FILE_ICON, x as EXTENSION_PREFIX, xt as isSpecSchemaUrl, y as sceneFontFiles, yt as readLink, z as Rect, zt as UiFont } from "./index-blYEvwPp.mjs";
2
+ export { ANNOTATION_TYPES, AUTO_ICON, CODE_FONTS, CODE_FONT_IDS, CodeFont, DEFAULT_APP_URL, DEFAULT_CODE_FONT, DEFAULT_THEME, type DecodeOptions, EXTENSION_PREFIX, FILE_ICON, FONT_SIZE, Font, FontFace, FontLoader, FontMetrics, FrameColors, type HighlightedLine, ICON_IDS, IMPLICIT_SPEC_VERSION, type Icon, type ImageMetadata, LANGUAGE_IDS, LATEST_SPEC_VERSION, LINE_HEIGHT, type LayoutOptions, MAC_HEADER, MARK_STYLES, METADATA_BY_DEFAULT, type MarkStyle, Mode, NO_ICON, PLAIN_TEXT, PNG_KEYWORDS, POPOVER, type PaneBox, type PaneSpec, type PngOptions, type Rect, SOFTWARE, SPEC_LIMITS, SPEC_SCHEMA_URL, SVG_NAMESPACE, type Scene, type Spec, SpecError, type SpecInput, SpecSchema, type SvgOptions, Syntax, TAB_HEADER, THEMES, THEME_IDS, type TextMetrics, Theme, type Token, UI_FONT, UiFont, WINDOW_RADIUS, type WindowBox, type WindowSpec, addPngMetadata, cellCount, createImageMetadata, createLink, cssFontFamily, decodeSpec, encodeSpec, expandLineRanges, getCodeFont, getFace, getFrameColors, getIcon, getSyntax, getTheme, highlight, inferLanguage, initPng, isKnownLanguage, isSpecSchemaUrl, layout, loadSceneFonts, measureUiText, migrateLegacySpec, normalizeCode, parseSpec, readImageSpec, readLink, resolveIcon, resolveLanguage, resolveMode, sceneFontFiles, schemaVersion, specJsonSchema, stampSpecVersion, svgToPng, toSvg };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- export { Q as AUTO_ICON, it as CODE_FONTS, at as CODE_FONT_IDS, W as DEFAULT_APP_URL, ot as DEFAULT_CODE_FONT, j as DEFAULT_THEME, $ as FILE_ICON, p as FONT_SIZE, et as ICON_IDS, Y as IMPLICIT_SPEC_VERSION, R as LANGUAGE_IDS, w as LATEST_SPEC_VERSION, m as LINE_HEIGHT, x as MAC_HEADER, T as MARK_STYLES, o as METADATA_BY_DEFAULT, tt as NO_ICON, z as PLAIN_TEXT, s as PNG_KEYWORDS, h as POPOVER, c as SOFTWARE, E as SPEC_LIMITS, S as SPEC_SCHEMA_URL, l as SVG_NAMESPACE, D as SpecError, O as SpecSchema, g as TAB_HEADER, M as THEMES, N as THEME_IDS, st as UI_FONT, _ as WINDOW_RADIUS, u as addPngMetadata, v as cellCount, d as createImageMetadata, G as createLink, ct as cssFontFamily, K as decodeSpec, q as encodeSpec, k as expandLineRanges, lt as getCodeFont, ut as getFace, P as getFrameColors, nt as getIcon, F as getSyntax, I as getTheme, B as highlight, V as inferLanguage, n as initPng, H as isKnownLanguage, y as layout, r as loadSceneFonts, dt as measureUiText, X as migrateLegacySpec, b as normalizeCode, A as parseSpec, f as readImageSpec, J as readLink, rt as resolveIcon, U as resolveLanguage, L as resolveMode, i as sceneFontFiles, C as specJsonSchema, Z as stampSpecVersion, a as svgToPng, t as toSvg } from "./src-BdXa6abO.mjs";
1
+ export { T as ANNOTATION_TYPES, nt as AUTO_ICON, ct as CODE_FONTS, lt as CODE_FONT_IDS, K as DEFAULT_APP_URL, ut as DEFAULT_CODE_FONT, N as DEFAULT_THEME, Z as EXTENSION_PREFIX, rt as FILE_ICON, p as FONT_SIZE, it as ICON_IDS, Q as IMPLICIT_SPEC_VERSION, B as LANGUAGE_IDS, E as LATEST_SPEC_VERSION, m as LINE_HEIGHT, x as MAC_HEADER, D as MARK_STYLES, o as METADATA_BY_DEFAULT, at as NO_ICON, V as PLAIN_TEXT, s as PNG_KEYWORDS, h as POPOVER, c as SOFTWARE, O as SPEC_LIMITS, S as SPEC_SCHEMA_URL, l as SVG_NAMESPACE, k as SpecError, A as SpecSchema, g as TAB_HEADER, P as THEMES, F as THEME_IDS, dt as UI_FONT, _ as WINDOW_RADIUS, u as addPngMetadata, v as cellCount, d as createImageMetadata, q as createLink, ft as cssFontFamily, J as decodeSpec, Y as encodeSpec, j as expandLineRanges, pt as getCodeFont, mt as getFace, I as getFrameColors, ot as getIcon, L as getSyntax, R as getTheme, H as highlight, U as inferLanguage, n as initPng, W as isKnownLanguage, C as isSpecSchemaUrl, y as layout, r as loadSceneFonts, ht as measureUiText, $ as migrateLegacySpec, b as normalizeCode, M as parseSpec, f as readImageSpec, X as readLink, st as resolveIcon, G as resolveLanguage, z as resolveMode, i as sceneFontFiles, et as schemaVersion, w as specJsonSchema, tt as stampSpecVersion, a as svgToPng, t as toSvg } from "./src-VhDXsdB0.mjs";
package/dist/node.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as resolveLanguage, A as readImageSpec, At as FontFace, B as WINDOW_RADIUS, C as ImageMetadata, Ct as getIcon, D as SVG_NAMESPACE, Dt as CodeFont, E as SOFTWARE, Et as CODE_FONT_IDS, F as PaneBox, Ft as cssFontFamily, G as MAC_HEADER, H as cellCount, I as Rect, It as getCodeFont, J as PLAIN_TEXT, K as HighlightedLine, L as Scene, Lt as getFace, M as LINE_HEIGHT, Mt as FontMetrics, N as LayoutOptions, Nt as UI_FONT, O as addPngMetadata, Ot as DEFAULT_CODE_FONT, P as POPOVER, Pt as UiFont, Q as isKnownLanguage, R as TAB_HEADER, Rt as measureUiText, S as stampSpecVersion, St as NO_ICON, T as PNG_KEYWORDS, Tt as CODE_FONTS, U as layout, V as WindowBox, W as normalizeCode, X as highlight, Y as Token, Z as inferLanguage, _ as loadSceneFonts, _t as specJsonSchema, a as MarkStyle, at as THEME_IDS, b as IMPLICIT_SPEC_VERSION, bt as ICON_IDS, c as Spec, ct as getSyntax, d as SpecSchema, dt as DEFAULT_APP_URL, et as DEFAULT_THEME, f as WindowSpec, ft as createLink, g as initPng, gt as SPEC_SCHEMA_URL, h as PngOptions, ht as readLink, i as MARK_STYLES, it as THEMES, j as FONT_SIZE, jt as FontLoader, k as createImageMetadata, kt as Font, l as SpecError, lt as getTheme, m as parseSpec, mt as encodeSpec, n as toSvg, nt as Mode, o as PaneSpec, ot as Theme, p as expandLineRanges, pt as decodeSpec, q as LANGUAGE_IDS, r as LATEST_SPEC_VERSION, rt as Syntax, s as SPEC_LIMITS, st as getFrameColors, t as SvgOptions, tt as FrameColors, u as SpecInput, ut as resolveMode, v as sceneFontFiles, vt as AUTO_ICON, w as METADATA_BY_DEFAULT, wt as resolveIcon, x as migrateLegacySpec, xt as Icon, y as svgToPng, yt as FILE_ICON, z as TextMetrics } from "./index-CtUX1lFe.mjs";
1
+ import { $ as highlight, A as SVG_NAMESPACE, At as CODE_FONTS, B as Scene, Bt as cssFontFamily, C as migrateLegacySpec, Ct as AUTO_ICON, D as METADATA_BY_DEFAULT, Dt as NO_ICON, E as ImageMetadata, Et as Icon, F as LINE_HEIGHT, Ft as FontFace, G as cellCount, H as TextMetrics, Ht as getFace, I as LayoutOptions, It as FontLoader, J as MAC_HEADER, K as layout, L as POPOVER, Lt as FontMetrics, M as createImageMetadata, Mt as CodeFont, N as readImageSpec, Nt as DEFAULT_CODE_FONT, O as PNG_KEYWORDS, Ot as getIcon, P as FONT_SIZE, Pt as Font, Q as Token, R as PaneBox, Rt as UI_FONT, S as IMPLICIT_SPEC_VERSION, St as specJsonSchema, T as stampSpecVersion, Tt as ICON_IDS, U as WINDOW_RADIUS, Ut as measureUiText, V as TAB_HEADER, Vt as getCodeFont, W as WindowBox, X as LANGUAGE_IDS, Y as HighlightedLine, Z as PLAIN_TEXT, _ as initPng, _t as decodeSpec, a as MARK_STYLES, at as Mode, b as svgToPng, bt as SPEC_SCHEMA_URL, c as SPEC_LIMITS, ct as THEME_IDS, d as SpecInput, dt as getSyntax, et as inferLanguage, f as SpecSchema, ft as getTheme, g as PngOptions, gt as createLink, h as parseSpec, ht as DecodeOptions, i as LATEST_SPEC_VERSION, it as FrameColors, j as addPngMetadata, jt as CODE_FONT_IDS, k as SOFTWARE, kt as resolveIcon, l as Spec, lt as Theme, m as expandLineRanges, mt as DEFAULT_APP_URL, n as toSvg, nt as resolveLanguage, o as MarkStyle, ot as Syntax, p as WindowSpec, pt as resolveMode, q as normalizeCode, r as ANNOTATION_TYPES, rt as DEFAULT_THEME, s as PaneSpec, st as THEMES, t as SvgOptions, tt as isKnownLanguage, u as SpecError, ut as getFrameColors, v as loadSceneFonts, vt as encodeSpec, w as schemaVersion, wt as FILE_ICON, x as EXTENSION_PREFIX, xt as isSpecSchemaUrl, y as sceneFontFiles, yt as readLink, z as Rect, zt as UiFont } from "./index-blYEvwPp.mjs";
2
2
  export type RenderFormat = "png" | "svg";
3
3
  export type RenderOptions = {
4
4
  format?: RenderFormat;
@@ -18,4 +18,4 @@ export declare const loadFont: FontLoader;
18
18
  export declare function render(spec: unknown, options?: RenderOptions): Promise<Uint8Array>;
19
19
  /** Renders the scene of a layout, so that a caller that needs the scene lays out only once */
20
20
  export declare function renderScene(scene: Scene, options?: RenderSceneOptions): Promise<Uint8Array>;
21
- export { AUTO_ICON, CODE_FONTS, CODE_FONT_IDS, CodeFont, DEFAULT_APP_URL, DEFAULT_CODE_FONT, DEFAULT_THEME, FILE_ICON, FONT_SIZE, Font, FontFace, FontLoader, FontMetrics, FrameColors, type HighlightedLine, ICON_IDS, IMPLICIT_SPEC_VERSION, type Icon, type ImageMetadata, LANGUAGE_IDS, LATEST_SPEC_VERSION, LINE_HEIGHT, type LayoutOptions, MAC_HEADER, MARK_STYLES, METADATA_BY_DEFAULT, type MarkStyle, Mode, NO_ICON, PLAIN_TEXT, PNG_KEYWORDS, POPOVER, type PaneBox, type PaneSpec, type PngOptions, type Rect, SOFTWARE, SPEC_LIMITS, SPEC_SCHEMA_URL, SVG_NAMESPACE, type Scene, type Spec, SpecError, type SpecInput, SpecSchema, type SvgOptions, Syntax, TAB_HEADER, THEMES, THEME_IDS, type TextMetrics, Theme, type Token, UI_FONT, UiFont, WINDOW_RADIUS, type WindowBox, type WindowSpec, addPngMetadata, cellCount, createImageMetadata, createLink, cssFontFamily, decodeSpec, encodeSpec, expandLineRanges, getCodeFont, getFace, getFrameColors, getIcon, getSyntax, getTheme, highlight, inferLanguage, initPng, isKnownLanguage, layout, loadSceneFonts, measureUiText, migrateLegacySpec, normalizeCode, parseSpec, readImageSpec, readLink, resolveIcon, resolveLanguage, resolveMode, sceneFontFiles, specJsonSchema, stampSpecVersion, svgToPng, toSvg };
21
+ export { ANNOTATION_TYPES, AUTO_ICON, CODE_FONTS, CODE_FONT_IDS, CodeFont, DEFAULT_APP_URL, DEFAULT_CODE_FONT, DEFAULT_THEME, type DecodeOptions, EXTENSION_PREFIX, FILE_ICON, FONT_SIZE, Font, FontFace, FontLoader, FontMetrics, FrameColors, type HighlightedLine, ICON_IDS, IMPLICIT_SPEC_VERSION, type Icon, type ImageMetadata, LANGUAGE_IDS, LATEST_SPEC_VERSION, LINE_HEIGHT, type LayoutOptions, MAC_HEADER, MARK_STYLES, METADATA_BY_DEFAULT, type MarkStyle, Mode, NO_ICON, PLAIN_TEXT, PNG_KEYWORDS, POPOVER, type PaneBox, type PaneSpec, type PngOptions, type Rect, SOFTWARE, SPEC_LIMITS, SPEC_SCHEMA_URL, SVG_NAMESPACE, type Scene, type Spec, SpecError, type SpecInput, SpecSchema, type SvgOptions, Syntax, TAB_HEADER, THEMES, THEME_IDS, type TextMetrics, Theme, type Token, UI_FONT, UiFont, WINDOW_RADIUS, type WindowBox, type WindowSpec, addPngMetadata, cellCount, createImageMetadata, createLink, cssFontFamily, decodeSpec, encodeSpec, expandLineRanges, getCodeFont, getFace, getFrameColors, getIcon, getSyntax, getTheme, highlight, inferLanguage, initPng, isKnownLanguage, isSpecSchemaUrl, layout, loadSceneFonts, measureUiText, migrateLegacySpec, normalizeCode, parseSpec, readImageSpec, readLink, resolveIcon, resolveLanguage, resolveMode, sceneFontFiles, schemaVersion, specJsonSchema, stampSpecVersion, svgToPng, toSvg };
package/dist/node.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as FILE_ICON, A as parseSpec, B as highlight, C as specJsonSchema, D as SpecError, E as SPEC_LIMITS, F as getSyntax, G as createLink, H as isKnownLanguage, I as getTheme, J as readLink, K as decodeSpec, L as resolveMode, M as THEMES, N as THEME_IDS, O as SpecSchema, P as getFrameColors, Q as AUTO_ICON, R as LANGUAGE_IDS, S as SPEC_SCHEMA_URL, T as MARK_STYLES, U as resolveLanguage, V as inferLanguage, W as DEFAULT_APP_URL, X as migrateLegacySpec, Y as IMPLICIT_SPEC_VERSION, Z as stampSpecVersion, _ as WINDOW_RADIUS, a as svgToPng, at as CODE_FONT_IDS, b as normalizeCode, c as SOFTWARE, ct as cssFontFamily, d as createImageMetadata, dt as measureUiText, et as ICON_IDS, f as readImageSpec, g as TAB_HEADER, h as POPOVER, i as sceneFontFiles, it as CODE_FONTS, j as DEFAULT_THEME, k as expandLineRanges, l as SVG_NAMESPACE, lt as getCodeFont, m as LINE_HEIGHT, n as initPng, nt as getIcon, o as METADATA_BY_DEFAULT, ot as DEFAULT_CODE_FONT, p as FONT_SIZE, q as encodeSpec, r as loadSceneFonts, rt as resolveIcon, s as PNG_KEYWORDS, st as UI_FONT, t as toSvg, tt as NO_ICON, u as addPngMetadata, ut as getFace, v as cellCount, w as LATEST_SPEC_VERSION, x as MAC_HEADER, y as layout, z as PLAIN_TEXT } from "./src-BdXa6abO.mjs";
1
+ import { $ as migrateLegacySpec, A as SpecSchema, B as LANGUAGE_IDS, C as isSpecSchemaUrl, D as MARK_STYLES, E as LATEST_SPEC_VERSION, F as THEME_IDS, G as resolveLanguage, H as highlight, I as getFrameColors, J as decodeSpec, K as DEFAULT_APP_URL, L as getSyntax, M as parseSpec, N as DEFAULT_THEME, O as SPEC_LIMITS, P as THEMES, Q as IMPLICIT_SPEC_VERSION, R as getTheme, S as SPEC_SCHEMA_URL, T as ANNOTATION_TYPES, U as inferLanguage, V as PLAIN_TEXT, W as isKnownLanguage, X as readLink, Y as encodeSpec, Z as EXTENSION_PREFIX, _ as WINDOW_RADIUS, a as svgToPng, at as NO_ICON, b as normalizeCode, c as SOFTWARE, ct as CODE_FONTS, d as createImageMetadata, dt as UI_FONT, et as schemaVersion, f as readImageSpec, ft as cssFontFamily, g as TAB_HEADER, h as POPOVER, ht as measureUiText, i as sceneFontFiles, it as ICON_IDS, j as expandLineRanges, k as SpecError, l as SVG_NAMESPACE, lt as CODE_FONT_IDS, m as LINE_HEIGHT, mt as getFace, n as initPng, nt as AUTO_ICON, o as METADATA_BY_DEFAULT, ot as getIcon, p as FONT_SIZE, pt as getCodeFont, q as createLink, r as loadSceneFonts, rt as FILE_ICON, s as PNG_KEYWORDS, st as resolveIcon, t as toSvg, tt as stampSpecVersion, u as addPngMetadata, ut as DEFAULT_CODE_FONT, v as cellCount, w as specJsonSchema, x as MAC_HEADER, y as layout, z as resolveMode } from "./src-VhDXsdB0.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { readFile } from "node:fs/promises";
4
4
  // src/node.ts
@@ -28,4 +28,4 @@ async function renderScene(scene, options = {}) {
28
28
  metadata
29
29
  });
30
30
  }
31
- export { AUTO_ICON, CODE_FONTS, CODE_FONT_IDS, DEFAULT_APP_URL, DEFAULT_CODE_FONT, DEFAULT_THEME, FILE_ICON, FONT_SIZE, ICON_IDS, IMPLICIT_SPEC_VERSION, LANGUAGE_IDS, LATEST_SPEC_VERSION, LINE_HEIGHT, MAC_HEADER, MARK_STYLES, METADATA_BY_DEFAULT, NO_ICON, PLAIN_TEXT, PNG_KEYWORDS, POPOVER, SOFTWARE, SPEC_LIMITS, SPEC_SCHEMA_URL, SVG_NAMESPACE, SpecError, SpecSchema, TAB_HEADER, THEMES, THEME_IDS, UI_FONT, WINDOW_RADIUS, addPngMetadata, cellCount, createImageMetadata, createLink, cssFontFamily, decodeSpec, encodeSpec, expandLineRanges, getCodeFont, getFace, getFrameColors, getIcon, getSyntax, getTheme, highlight, inferLanguage, initPng, isKnownLanguage, layout, loadFont, loadSceneFonts, measureUiText, migrateLegacySpec, normalizeCode, parseSpec, readImageSpec, readLink, render, renderScene, resolveIcon, resolveLanguage, resolveMode, sceneFontFiles, specJsonSchema, stampSpecVersion, svgToPng, toSvg };
31
+ export { ANNOTATION_TYPES, AUTO_ICON, CODE_FONTS, CODE_FONT_IDS, DEFAULT_APP_URL, DEFAULT_CODE_FONT, DEFAULT_THEME, EXTENSION_PREFIX, FILE_ICON, FONT_SIZE, ICON_IDS, IMPLICIT_SPEC_VERSION, LANGUAGE_IDS, LATEST_SPEC_VERSION, LINE_HEIGHT, MAC_HEADER, MARK_STYLES, METADATA_BY_DEFAULT, NO_ICON, PLAIN_TEXT, PNG_KEYWORDS, POPOVER, SOFTWARE, SPEC_LIMITS, SPEC_SCHEMA_URL, SVG_NAMESPACE, SpecError, SpecSchema, TAB_HEADER, THEMES, THEME_IDS, UI_FONT, WINDOW_RADIUS, addPngMetadata, cellCount, createImageMetadata, createLink, cssFontFamily, decodeSpec, encodeSpec, expandLineRanges, getCodeFont, getFace, getFrameColors, getIcon, getSyntax, getTheme, highlight, inferLanguage, initPng, isKnownLanguage, isSpecSchemaUrl, layout, loadFont, loadSceneFonts, measureUiText, migrateLegacySpec, normalizeCode, parseSpec, readImageSpec, readLink, render, renderScene, resolveIcon, resolveLanguage, resolveMode, sceneFontFiles, schemaVersion, specJsonSchema, stampSpecVersion, svgToPng, toSvg };
@@ -773,6 +773,29 @@ function base64UrlToBytes(base64url) {
773
773
  }
774
774
  // src/migrate.ts
775
775
  const IMPLICIT_SPEC_VERSION = 1;
776
+ const SCHEMA_VERSION = /\/schema\/v(\d+)\.json$/;
777
+ /**
778
+ * The version that the $schema URL of a spec names, or undefined when the spec has no such URL.
779
+ * Only the path counts, so that the URL of each host that serves the schema gives the version.
780
+ */
781
+ function schemaVersion(spec) {
782
+ if (typeof spec !== "object" || spec === null || !("$schema" in spec)) return void 0;
783
+ const url = spec.$schema;
784
+ if (typeof url !== "string" || !URL.canParse(url)) return void 0;
785
+ const match = SCHEMA_VERSION.exec(new URL(url).pathname);
786
+ return match ? Number(match[1]) : void 0;
787
+ }
788
+ /**
789
+ * Keys with this prefix hold data of other tools, such as an editor state. The renderer ignores
790
+ * them in each object of a spec, so that a spec with such keys stays valid in all versions.
791
+ */
792
+ const EXTENSION_PREFIX = "x-";
793
+ /** Removes the keys of other tools from all objects of a spec */
794
+ function withoutExtensions(value) {
795
+ if (Array.isArray(value)) return value.map(withoutExtensions);
796
+ if (typeof value !== "object" || value === null) return value;
797
+ return Object.fromEntries(Object.entries(value).filter(([key]) => !key.startsWith("x-")).map(([key, entry]) => [key, withoutExtensions(entry)]));
798
+ }
776
799
  const CHROME_FIELDS = {
777
800
  mac: {},
778
801
  tabs: {
@@ -806,12 +829,13 @@ function migrateLegacySpec(spec) {
806
829
  }
807
830
  /**
808
831
  * Adds the version to a spec without one, so that a stored spec keeps its meaning when a later
809
- * version changes the format. A spec with a version stays as it is.
832
+ * version changes the format. The version comes from the $schema URL, or is version 1 without it.
833
+ * A spec with a version stays as it is.
810
834
  */
811
835
  function stampSpecVersion(spec) {
812
836
  if (typeof spec !== "object" || spec === null || Array.isArray(spec) || "v" in spec) return spec;
813
837
  return {
814
- v: 1,
838
+ v: schemaVersion(spec) ?? 1,
815
839
  ...spec
816
840
  };
817
841
  }
@@ -820,22 +844,53 @@ function stampSpecVersion(spec) {
820
844
  * The hosted web app. The CLI takes another app from its TIN_APP_URL environment variable,
821
845
  * for example the local editor during development.
822
846
  */
823
- const DEFAULT_APP_URL = "https://code-image.zirkelc.workers.dev/";
847
+ const DEFAULT_APP_URL = "https://tin.so/";
824
848
  const HASH_KEY = "spec=";
825
- async function transform(bytes, stream) {
826
- const response = new Response(new Blob([bytes]).stream().pipeThrough(stream));
827
- return new Uint8Array(await response.arrayBuffer());
849
+ async function compress(bytes) {
850
+ const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("deflate-raw"));
851
+ return new Uint8Array(await new Response(stream).arrayBuffer());
852
+ }
853
+ /** Inflates data, or returns undefined when the data is bad or larger than the limit */
854
+ async function inflate$1(bytes, format, limit = Number.POSITIVE_INFINITY) {
855
+ const reader = new Blob([bytes]).stream().pipeThrough(new DecompressionStream(format)).getReader();
856
+ const parts = [];
857
+ let size = 0;
858
+ try {
859
+ for (;;) {
860
+ const { done, value } = await reader.read();
861
+ if (done) break;
862
+ size += value.length;
863
+ if (size > limit) {
864
+ await reader.cancel().catch(() => void 0);
865
+ return;
866
+ }
867
+ parts.push(value);
868
+ }
869
+ } catch {
870
+ return;
871
+ }
872
+ const out = new Uint8Array(size);
873
+ let offset = 0;
874
+ for (const part of parts) {
875
+ out.set(part, offset);
876
+ offset += part.length;
877
+ }
878
+ return out;
828
879
  }
829
880
  /**
830
881
  * Compresses a spec into a URL-safe string. A spec without a version gets version 1, so that the
831
882
  * link keeps its meaning when a later version changes the format.
832
883
  */
833
884
  async function encodeSpec(spec) {
834
- return bytesToBase64Url(await transform(new TextEncoder().encode(JSON.stringify(stampSpecVersion(spec))), new CompressionStream("deflate-raw")));
885
+ return bytesToBase64Url(await compress(new TextEncoder().encode(JSON.stringify(stampSpecVersion(spec)))));
835
886
  }
836
- /** Reads a spec from a string of the encoder, with the fields of older links changed */
837
- async function decodeSpec(encoded) {
838
- const json = await transform(base64UrlToBytes(encoded), new DecompressionStream("deflate-raw"));
887
+ /**
888
+ * Reads a spec from a string of the encoder, with the fields of older links changed. Throws for
889
+ * bad data and for a spec larger than the limit.
890
+ */
891
+ async function decodeSpec(encoded, options = {}) {
892
+ const json = await inflate$1(base64UrlToBytes(encoded), "deflate-raw", options.maxBytes);
893
+ if (!json) throw new Error("The link has bad data or a spec that is too large");
839
894
  return migrateLegacySpec(JSON.parse(new TextDecoder().decode(json)));
840
895
  }
841
896
  /**
@@ -848,10 +903,10 @@ async function createLink(spec, appUrl = DEFAULT_APP_URL) {
848
903
  return url.toString();
849
904
  }
850
905
  /** Reads the spec from the fragment of a link, or returns undefined when the fragment has none */
851
- async function readLink(link) {
906
+ async function readLink(link, options = {}) {
852
907
  const hash = new URL(link).hash.slice(1);
853
908
  if (!hash.startsWith(HASH_KEY)) return void 0;
854
- return decodeSpec(hash.slice(5));
909
+ return decodeSpec(hash.slice(5), options);
855
910
  }
856
911
  // src/themes/tailwind-dark.json
857
912
  var tailwind_dark_default = {
@@ -1704,7 +1759,43 @@ const EXTENSIONS = {
1704
1759
  htm: "html",
1705
1760
  txt: PLAIN_TEXT,
1706
1761
  makefile: "make",
1707
- gemfile: "ruby"
1762
+ gemfile: "ruby",
1763
+ adb: "ada",
1764
+ as: "actionscript-3",
1765
+ bal: "ballerina",
1766
+ bib: "bibtex",
1767
+ clar: "clarity",
1768
+ cls: "apex",
1769
+ cob: "cobol",
1770
+ cr: "crystal",
1771
+ dm: "dream-maker",
1772
+ do: "stata",
1773
+ el: "emacs-lisp",
1774
+ ex: "elixir",
1775
+ feature: "gherkin",
1776
+ fnl: "fennel",
1777
+ gs: "genie",
1778
+ hx: "haxe",
1779
+ ll: "llvm",
1780
+ ml: "ocaml",
1781
+ mm: "objective-cpp",
1782
+ msg: "rosmsg",
1783
+ nsi: "nsis",
1784
+ pas: "pascal",
1785
+ pp: "puppet",
1786
+ pq: "powerquery",
1787
+ purs: "purescript",
1788
+ rkt: "racket",
1789
+ scm: "scheme",
1790
+ service: "systemd",
1791
+ sol: "solidity",
1792
+ st: "smalltalk",
1793
+ sv: "system-verilog",
1794
+ ttl: "turtle",
1795
+ vhd: "vhdl",
1796
+ wat: "wasm",
1797
+ wy: "wenyan",
1798
+ zs: "zenscript"
1708
1799
  };
1709
1800
  const TAILWIND_THEMES = {
1710
1801
  light: {
@@ -3407,7 +3498,9 @@ const MarkSchema = v.strictObject({
3407
3498
  background: describe(v.optional(hexColor()), "Fill behind the text, as a hex color such as \"#ffd60a\" or \"#ffd60a40\" with alpha")
3408
3499
  });
3409
3500
  const MarkWithEffectSchema = v.pipe(MarkSchema, v.check((mark) => mark.style !== void 0 || mark.color !== void 0 || mark.background !== void 0, "A mark needs a style, a color or a background"));
3501
+ const ANNOTATION_TYPES = ["popover"];
3410
3502
  const AnnotationSchema = v.strictObject({
3503
+ type: describe(v.optional(v.picklist(ANNOTATION_TYPES), "popover"), "Kind of the annotation. popover is a card below the text, as the hover card of an editor"),
3411
3504
  line: describe(v.pipe(v.number(), v.integer(), v.minValue(1)), "Line of the anchor, from 1"),
3412
3505
  match: describe(v.pipe(v.string(), v.minLength(1)), "Text in the line that the popover points to"),
3413
3506
  occurrence: describe(v.optional(v.pipe(v.number(), v.integer(), v.minValue(1)), 1), "Which occurrence of the match in the line, from 1"),
@@ -3451,8 +3544,14 @@ const WindowSchema = v.strictObject({
3451
3544
  * Versions of the spec format.
3452
3545
  *
3453
3546
  * - Increase v only for a breaking change: a field that changes its meaning, gets a new name or
3454
- * goes away. A new optional field does not increase v, because older specs stay valid.
3455
- * - A spec without v is version 1, now and in all later versions, because older specs have no v.
3547
+ * goes away, a changed default, a value that a list no longer has (a theme, font, icon or
3548
+ * language id), or a smaller limit. Specs leave out the defaults, so a new default changes old
3549
+ * images. A new optional field, a new value or a larger limit does not increase v, because older
3550
+ * specs stay valid.
3551
+ * - The version is v, or the version of the $schema URL (/schema/v<number>.json) without v. When
3552
+ * both are there, they must be the same. A spec without both is version 1, now and in all later
3553
+ * versions, because older specs have neither.
3554
+ * - Keys that start with "x-" are for other tools. The renderer ignores them in every object.
3456
3555
  * - For a new version, add its schema (with v as a required literal of the new number) to
3457
3556
  * SPEC_SCHEMAS, a function that changes a parsed spec of the version before into the new
3458
3557
  * version to MIGRATIONS, and increase LATEST_SPEC_VERSION. The schema of the latest version is
@@ -3460,8 +3559,8 @@ const WindowSchema = v.strictObject({
3460
3559
  */
3461
3560
  const LATEST_SPEC_VERSION = 1;
3462
3561
  const SpecV1Schema = v.pipe(v.strictObject({
3463
- $schema: describe(v.optional(v.pipe(v.string(), v.url())), "URL of the JSON Schema of the spec, so that an editor can check the spec and complete its fields. The renderer does not use it"),
3464
- v: describe(v.optional(v.literal(1), 1), "Version of the spec format. Leave it out or use 1. A spec without v is version 1"),
3562
+ $schema: describe(v.optional(v.pipe(v.string(), v.url())), "URL of the JSON Schema of the spec, so that an editor can check the spec and complete its fields. Its path names the version, such as /schema/v1.json, which counts as v. The CLI also uses it to tell a spec from other JSON"),
3563
+ v: describe(v.optional(v.literal(1), 1), "Version of the spec format. Leave it out or use 1. Without v, the version of the $schema URL counts, and without both, the version is 1"),
3465
3564
  theme: describe(v.optional(v.picklist(THEME_IDS), DEFAULT_THEME), "Theme from ray.so"),
3466
3565
  mode: describe(v.optional(v.picklist(["dark", "light"]), "dark"), "Color mode. A theme with only one mode ignores it"),
3467
3566
  background: describe(v.optional(v.boolean(), true), "Gradient background. false is transparent"),
@@ -3495,25 +3594,36 @@ function formatIssue(issue) {
3495
3594
  const path = v.getDotPath(issue) ?? "(root)";
3496
3595
  if (issue.type === "strict_object" && issue.expected === "never") return `${path}: unknown field. Check the name, or update tin.so, because a newer version can support it`;
3497
3596
  if (issue.type === "strict_object" && issue.input === void 0) return `${path}: required field missing`;
3498
- if (issue.type === "picklist" && issue.expected) return `${path}: expected one of ${issue.expected}, received ${issue.received}`;
3597
+ if (issue.type === "picklist" && issue.expected) return `${path}: expected one of ${issue.expected}, received ${issue.received}. Check the value, or update tin.so, because a newer version can support it`;
3499
3598
  return `${path}: ${issue.message}`;
3500
3599
  }
3501
- /** The version of a spec input. A spec without v is version 1. */
3600
+ /**
3601
+ * The version of a spec input: v, or the version of the $schema URL without v. A spec without
3602
+ * both is version 1.
3603
+ */
3502
3604
  function readVersion(input) {
3503
- if (typeof input !== "object" || input === null || !("v" in input)) return 1;
3504
- const version = input.v;
3505
- if (version === void 0) return 1;
3506
- if (typeof version !== "number" || !Number.isInteger(version) || version < 1) throw new SpecError([`v: the version must be a whole number from 1, received ${typeof version === "number" ? version : JSON.stringify(version) ?? typeof version}`]);
3507
- if (version > 1) throw new SpecError([`v: This spec has version ${version}, but this tin.so supports up to version 1. Update tin.so.`]);
3605
+ const fromSchema = schemaVersion(input);
3606
+ const field = typeof input === "object" && input !== null && "v" in input ? input.v : void 0;
3607
+ if (field === void 0) {
3608
+ const version = fromSchema ?? 1;
3609
+ if (version < 1) throw new SpecError([`$schema: the URL names version ${version}, but versions start at 1`]);
3610
+ return checkSupported(version, "$schema");
3611
+ }
3612
+ if (typeof field !== "number" || !Number.isInteger(field) || field < 1) throw new SpecError([`v: the version must be a whole number from 1, received ${typeof field === "number" ? field : JSON.stringify(field) ?? typeof field}`]);
3613
+ if (fromSchema !== void 0 && fromSchema !== field) throw new SpecError([`v: the spec has version ${field}, but its $schema URL names version ${fromSchema}. Make them the same, or remove one`]);
3614
+ return checkSupported(field, "v");
3615
+ }
3616
+ function checkSupported(version, path) {
3617
+ if (version > 1) throw new SpecError([`${path}: This spec has version ${version}, but this tin.so supports up to version 1. Update tin.so.`]);
3508
3618
  return version;
3509
3619
  }
3510
3620
  /**
3511
- * Validates a spec and applies the defaults. It first changes the fields of older specs, then
3512
- * validates the spec with the schema of its version and changes it to the latest version. Throws
3513
- * a SpecError that lists every issue.
3621
+ * Validates a spec and applies the defaults. It first removes the keys of other tools and changes
3622
+ * the fields of older specs, then validates the spec with the schema of its version and changes it
3623
+ * to the latest version. Throws a SpecError that lists every issue.
3514
3624
  */
3515
3625
  function parseSpec(input) {
3516
- const migrated = migrateLegacySpec(input);
3626
+ const migrated = migrateLegacySpec(withoutExtensions(input));
3517
3627
  const version = readVersion(migrated);
3518
3628
  const result = v.safeParse(SPEC_SCHEMAS[version], migrated);
3519
3629
  if (!result.success) throw new SpecError(result.issues.map(formatIssue));
@@ -3547,9 +3657,32 @@ function specJsonSchema() {
3547
3657
  $schema,
3548
3658
  $id: SPEC_SCHEMA_URL,
3549
3659
  title: "tin.so spec",
3550
- ...schema
3660
+ ...allowExtensions(schema)
3551
3661
  };
3552
3662
  }
3663
+ function allowExtensions(node) {
3664
+ if (Array.isArray(node)) return node.map(allowExtensions);
3665
+ if (typeof node !== "object" || node === null) return node;
3666
+ const out = Object.fromEntries(Object.entries(node).map(([key, value]) => [key, allowExtensions(value)]));
3667
+ if (out.type === "object" && out.additionalProperties === false) out.patternProperties = { [`^x-`]: {} };
3668
+ return out;
3669
+ }
3670
+ const SPEC_SCHEMA_HOSTS = ["code-image.zirkelc.workers.dev"];
3671
+ const SPEC_SCHEMA_PATH = /\/schema\/v\d+\.json$/;
3672
+ /**
3673
+ * True when the value is the URL of a tin.so spec schema: a path that ends with
3674
+ * /schema/v<number>.json on the host of the hosted app, on an earlier host of the app, or on the
3675
+ * host of the given app URL (for example a local editor).
3676
+ */
3677
+ function isSpecSchemaUrl(value, appUrl) {
3678
+ if (typeof value !== "string" || !URL.canParse(value)) return false;
3679
+ const url = new URL(value);
3680
+ if (url.protocol !== "https:" && url.protocol !== "http:") return false;
3681
+ if (!SPEC_SCHEMA_PATH.test(url.pathname)) return false;
3682
+ const hosts = [new URL(SPEC_SCHEMA_URL).host, ...SPEC_SCHEMA_HOSTS];
3683
+ if (appUrl !== void 0 && URL.canParse(appUrl)) hosts.push(new URL(appUrl).host);
3684
+ return hosts.includes(url.host);
3685
+ }
3553
3686
  // src/sizes.ts
3554
3687
  const MAC_HEADER = {
3555
3688
  paddingTop: 10,
@@ -3590,7 +3723,7 @@ function line(x1, y1, x2, y2, extra) {
3590
3723
  ...extra
3591
3724
  })}/>`;
3592
3725
  }
3593
- function inflate$1(rect, by) {
3726
+ function inflate(rect, by) {
3594
3727
  return {
3595
3728
  x: rect.x - by,
3596
3729
  y: rect.y - by,
@@ -4036,7 +4169,7 @@ function boxShadow(context, window, layers) {
4036
4169
  })}><rect width="${width}" height="${height}" fill="white"/>${roundedRect(window, window.radius, { fill: "black" })}</mask></defs>`, `<g mask="url(#${mask})">`];
4037
4170
  for (const layer of layers) {
4038
4171
  const spread = layer.spread ?? 0;
4039
- const box = inflate$1({
4172
+ const box = inflate({
4040
4173
  ...window,
4041
4174
  y: window.y + layer.y
4042
4175
  }, spread);
@@ -4134,11 +4267,11 @@ const DEFAULT_FRAME = {
4134
4267
  behind: (context, window) => window.frame ? boxShadow(context, window, RAYSO_SHADOW) : "",
4135
4268
  body: ({ scene }, window) => roundedRect(window, window.radius, { fill: scene.frame.background }),
4136
4269
  header: (context, window) => windowControls(window, context.scene.frame.control) + titleBarText(context, window, { title: context.scene.frame.title }),
4137
- border: ({ scene }, window) => roundedRect(inflate$1(window, .5), window.radius + .5, {
4270
+ border: ({ scene }, window) => roundedRect(inflate(window, .5), window.radius + .5, {
4138
4271
  fill: "none",
4139
4272
  stroke: scene.frame.highlightBorder,
4140
4273
  "stroke-width": 1
4141
- }) + roundedRect(inflate$1(window, 1.25), window.radius + 1.25, {
4274
+ }) + roundedRect(inflate(window, 1.25), window.radius + 1.25, {
4142
4275
  fill: "none",
4143
4276
  stroke: scene.frame.shadowBorder,
4144
4277
  "stroke-width": .5
@@ -4319,11 +4452,11 @@ const frame$20 = {
4319
4452
  x2: 0,
4320
4453
  y2: inner.y + inner.height * .15
4321
4454
  })}><stop offset="0" stop-color="${style.shine[0]}"/><stop offset="1" stop-color="${style.shine[1]}"/></linearGradient>`}</defs>`, roundedRect(window, window.radius, { fill: style.shell })];
4322
- if (context.mode === "light") out.push(roundedRect(inflate$1(window, .5), window.radius + .5, {
4455
+ if (context.mode === "light") out.push(roundedRect(inflate(window, .5), window.radius + .5, {
4323
4456
  fill: "none",
4324
4457
  stroke: style.ring
4325
4458
  }));
4326
- out.push(roundedRect(inner, 10, { fill: style.window }), roundedRect(inner, 10, { fill: `url(#${shine})` }), roundedRect(inflate$1(inner, .5), 10.5, {
4459
+ out.push(roundedRect(inner, 10, { fill: style.window }), roundedRect(inner, 10, { fill: `url(#${shine})` }), roundedRect(inflate(inner, .5), 10.5, {
4327
4460
  fill: "none",
4328
4461
  stroke: style.ring
4329
4462
  }));
@@ -4556,7 +4689,7 @@ const frame$18 = {
4556
4689
  behind: (context, window) => {
4557
4690
  if (window.frame) return DEFAULT_FRAME.behind(context, window);
4558
4691
  const id = context.id(`haze-${window.x}-${window.y}`);
4559
- return `<defs>${blurFilter(id, 12)}</defs>${roundedRect(inflate$1(window, 8), 24, {
4692
+ return `<defs>${blurFilter(id, 12)}</defs>${roundedRect(inflate(window, 8), 24, {
4560
4693
  fill: context.scene.frame.background,
4561
4694
  filter: `url(#${id})`
4562
4695
  })}`;
@@ -4635,7 +4768,7 @@ const frame$17 = {
4635
4768
  return out.join("");
4636
4769
  },
4637
4770
  behind: () => "",
4638
- border: ({ mode }, window) => roundedRect(inflate$1(window, -.5), 0, {
4771
+ border: ({ mode }, window) => roundedRect(inflate(window, -.5), 0, {
4639
4772
  fill: "none",
4640
4773
  stroke: COLORS$14[mode].border
4641
4774
  })
@@ -5114,7 +5247,7 @@ function diagonals(window, stroke) {
5114
5247
  }
5115
5248
  /** The thin rounded outline of the window */
5116
5249
  function outline(window, stroke) {
5117
- return roundedRect(inflate$1(window, -.5), 23.5, {
5250
+ return roundedRect(inflate(window, -.5), 23.5, {
5118
5251
  fill: "none",
5119
5252
  stroke
5120
5253
  });
@@ -5901,7 +6034,7 @@ const frame$9 = {
5901
6034
  behind: (context, window) => {
5902
6035
  if (window.frame) return DEFAULT_FRAME.behind(context, window);
5903
6036
  const id = context.id(`haze-${window.x}-${window.y}`);
5904
- return `<defs>${blurFilter(id, 12)}</defs>${roundedRect(inflate$1(window, 8), 24, {
6037
+ return `<defs>${blurFilter(id, 12)}</defs>${roundedRect(inflate(window, 8), 24, {
5905
6038
  fill: context.scene.frame.background,
5906
6039
  filter: `url(#${id})`
5907
6040
  })}`;
@@ -6032,7 +6165,7 @@ function glows(context, window) {
6032
6165
  function rings(context, window, from) {
6033
6166
  const out = [];
6034
6167
  RINGS.slice(from).forEach((ring, index) => {
6035
- const box = inflate$1(window, ring.offset);
6168
+ const box = inflate(window, ring.offset);
6036
6169
  const id = context.id(`ring-${index + from}-${window.x}-${window.y}`);
6037
6170
  const [a, b, c, d] = ring.stops;
6038
6171
  out.push(`<defs>${linearGradient(id, 135, box, [
@@ -6042,7 +6175,7 @@ function rings(context, window, from) {
6042
6175
  [c, "rgba(0, 220, 130, 0)"],
6043
6176
  [d, GREEN],
6044
6177
  [1, GREEN]
6045
- ])}</defs>`, roundedRect(inflate$1(box, -.5), window.radius + ring.radius - .5, {
6178
+ ])}</defs>`, roundedRect(inflate(box, -.5), window.radius + ring.radius - .5, {
6046
6179
  fill: "none",
6047
6180
  stroke: `url(#${id})`,
6048
6181
  "stroke-opacity": ring.opacity
@@ -6200,7 +6333,7 @@ const frame$7 = {
6200
6333
  return `<defs>${linearGradient(id, 238, box, [[0, from], [1, to]])}</defs><rect width="${scene.width}" height="${scene.height}" fill="url(#${id})"/>`;
6201
6334
  },
6202
6335
  behind: (context, window) => window.frame ? boxShadow(context, window, shadow(context.mode)) : "",
6203
- border: ({ mode }, window) => roundedRect(inflate$1(window, -.25), window.radius - .25, {
6336
+ border: ({ mode }, window) => roundedRect(inflate(window, -.25), window.radius - .25, {
6204
6337
  fill: "none",
6205
6338
  stroke: COLORS$5[mode].border,
6206
6339
  "stroke-width": .5
@@ -6399,19 +6532,19 @@ const frame$6 = {
6399
6532
  border: (context, window) => {
6400
6533
  const style = COLORS$4[context.mode];
6401
6534
  const r = window.radius;
6402
- const inner = inflate$1(window, -1);
6535
+ const inner = inflate(window, -1);
6403
6536
  const mask = context.id(`highlight-${window.x}-${window.y}`);
6404
6537
  return `<defs><mask ${attrs({
6405
6538
  id: mask,
6406
6539
  maskUnits: "userSpaceOnUse",
6407
- ...inflate$1(window, 2)
6408
- })}>${roundedRect(inflate$1(window, 2), 0, { fill: "white" })}${roundedRect({
6540
+ ...inflate(window, 2)
6541
+ })}>${roundedRect(inflate(window, 2), 0, { fill: "white" })}${roundedRect({
6409
6542
  ...inner,
6410
6543
  y: inner.y + 1
6411
6544
  }, r - 1, { fill: "black" })}</mask></defs>${roundedRect(inner, r - 1, {
6412
6545
  fill: style.highlight,
6413
6546
  mask: `url(#${mask})`
6414
- })}` + roundedRect(inflate$1(window, -.5), r - .5, {
6547
+ })}` + roundedRect(inflate(window, -.5), r - .5, {
6415
6548
  fill: "none",
6416
6549
  stroke: style.border
6417
6550
  });
@@ -6737,7 +6870,7 @@ const frame$4 = {
6737
6870
  }]));
6738
6871
  return out.join("");
6739
6872
  },
6740
- border: (_context, window) => roundedRect(inflate$1(window, -.5), window.radius - .5, {
6873
+ border: (_context, window) => roundedRect(inflate(window, -.5), window.radius - .5, {
6741
6874
  fill: "none",
6742
6875
  stroke: COLORS$3.border
6743
6876
  })
@@ -6856,7 +6989,7 @@ const frame$3 = {
6856
6989
  }));
6857
6990
  return out.join("");
6858
6991
  },
6859
- border: ({ mode }, window) => roundedRect(inflate$1(window, .5), window.radius + .5, {
6992
+ border: ({ mode }, window) => roundedRect(inflate(window, .5), window.radius + .5, {
6860
6993
  fill: "none",
6861
6994
  stroke: COLORS$2[mode].border
6862
6995
  })
@@ -7025,7 +7158,7 @@ function blur(id, radius, box) {
7025
7158
  return `<filter ${attrs({
7026
7159
  id,
7027
7160
  filterUnits: "userSpaceOnUse",
7028
- ...inflate$1(box, radius * 3)
7161
+ ...inflate(box, radius * 3)
7029
7162
  })}><feGaussianBlur stdDeviation="${radius}"/></filter>`;
7030
7163
  }
7031
7164
  /** A thin line of light from sky blue to pink on the lower grid line, which glows downward */
@@ -7138,11 +7271,11 @@ const frame$2 = {
7138
7271
  return line(header.x, y, header.x + header.width, y, { stroke: scene.frame.divider }) + controls(window, scene.frame.control) + titleBarText(context, window, { title: scene.frame.title });
7139
7272
  },
7140
7273
  border: ({ mode }, window) => {
7141
- const out = [roundedRect(inflate$1(window, -.5), window.radius - .5, {
7274
+ const out = [roundedRect(inflate(window, -.5), window.radius - .5, {
7142
7275
  fill: "none",
7143
7276
  stroke: COLORS$1[mode].border
7144
7277
  })];
7145
- if (mode === "light") out.push(roundedRect(inflate$1(window, .5), window.radius + .5, {
7278
+ if (mode === "light") out.push(roundedRect(inflate(window, .5), window.radius + .5, {
7146
7279
  fill: "none",
7147
7280
  stroke: "rgba(15, 23, 42, 0.1)"
7148
7281
  }));
@@ -7960,11 +8093,25 @@ const SOFTWARE = "tin.so";
7960
8093
  const PNG_KEYWORDS = {
7961
8094
  software: "Software",
7962
8095
  source: "Source",
7963
- link: "code-image:link",
7964
- spec: "code-image:spec"
8096
+ link: "tin.so:link"
7965
8097
  };
7966
8098
  /** The namespace of the metadata element in an SVG */
7967
- const SVG_NAMESPACE = "urn:code-image:metadata:1";
8099
+ const SVG_NAMESPACE = "urn:tin.so:metadata:1";
8100
+ /**
8101
+ * The keywords and the SVG element of the first image format, which stored the spec a second time
8102
+ * next to the link. The reader still reads them, so that older images open.
8103
+ */
8104
+ const LEGACY = {
8105
+ link: "code-image:link",
8106
+ spec: "code-image:spec",
8107
+ element: "code-image",
8108
+ namespace: "urn:code-image:metadata:1"
8109
+ };
8110
+ const OUR_KEYWORDS = /* @__PURE__ */ new Set([
8111
+ PNG_KEYWORDS.link,
8112
+ LEGACY.link,
8113
+ LEGACY.spec
8114
+ ]);
7968
8115
  const MAX_INPUT_BYTES = 67108864;
7969
8116
  const MAX_CHUNK_BYTES = 4194304;
7970
8117
  const MAX_SPEC_BYTES = 16777216;
@@ -7990,27 +8137,20 @@ async function createImageMetadata(spec, appUrl = DEFAULT_APP_URL) {
7990
8137
  link: await createLink(stamped, appUrl)
7991
8138
  };
7992
8139
  }
7993
- /**
7994
- * The spec as JSON that is safe in XML text. JSON escapes the control characters, and this escapes
7995
- * the two noncharacters that XML does not allow. Both are only possible in JSON strings.
7996
- */
7997
- function specJson(spec) {
7998
- return (JSON.stringify(spec) ?? "null").replace(/[￾￿]/g, (char) => `\\u${char.charCodeAt(0).toString(16)}`);
7999
- }
8000
8140
  /** The app URL of a link, without the spec in the fragment */
8001
8141
  function sourceUrl(link) {
8002
8142
  const url = new URL(link);
8003
8143
  url.hash = "";
8004
8144
  return url.toString();
8005
8145
  }
8006
- /** The metadata element of an SVG */
8146
+ /** The metadata element of an SVG: the program, the app URL and the editor link */
8007
8147
  function svgMetadata(metadata) {
8008
- return `<metadata><code-image ${[
8148
+ return `<metadata><tin ${[
8009
8149
  `xmlns="${SVG_NAMESPACE}"`,
8010
8150
  `software="${SOFTWARE}"`,
8011
8151
  `source="${escapeXml(sourceUrl(metadata.link))}"`,
8012
8152
  `link="${escapeXml(metadata.link)}"`
8013
- ].join(" ")}>${escapeXml(specJson(metadata.spec))}</code-image></metadata>`;
8153
+ ].join(" ")}/></metadata>`;
8014
8154
  }
8015
8155
  const CRC_TABLE = (() => {
8016
8156
  const table = /* @__PURE__ */ new Uint32Array(256);
@@ -8053,60 +8193,20 @@ function textChunk(keyword, text) {
8053
8193
  encoder.encode(text)
8054
8194
  ]));
8055
8195
  }
8056
- function compressedTextChunk(keyword, compressed) {
8057
- return pngChunk("iTXt", concat([
8058
- encoder.encode(keyword),
8059
- new Uint8Array([
8060
- 0,
8061
- 1,
8062
- 0,
8063
- 0,
8064
- 0
8065
- ]),
8066
- compressed
8067
- ]));
8068
- }
8069
- async function deflate(bytes) {
8070
- const stream = new Blob([bytes]).stream().pipeThrough(new CompressionStream("deflate"));
8071
- return new Uint8Array(await new Response(stream).arrayBuffer());
8072
- }
8073
- /** Inflates zlib data, or returns undefined when the data is bad or larger than the limit */
8074
- async function inflate(bytes, limit) {
8075
- const reader = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("deflate")).getReader();
8076
- const parts = [];
8077
- let size = 0;
8078
- try {
8079
- for (;;) {
8080
- const { done, value } = await reader.read();
8081
- if (done) break;
8082
- size += value.length;
8083
- if (size > limit) {
8084
- await reader.cancel().catch(() => void 0);
8085
- return;
8086
- }
8087
- parts.push(value);
8088
- }
8089
- } catch {
8090
- return;
8091
- }
8092
- return concat(parts);
8093
- }
8094
8196
  function isPng(bytes) {
8095
8197
  return PNG_SIGNATURE.every((byte, index) => bytes[index] === byte);
8096
8198
  }
8097
8199
  /**
8098
- * Adds text chunks after the header of a PNG: the program, the app URL, the editor link and the
8099
- * compressed spec. A PNG without the expected header comes back unchanged.
8200
+ * Adds text chunks after the header of a PNG: the program, the app URL and the editor link. A PNG
8201
+ * without the expected header comes back unchanged.
8100
8202
  */
8101
8203
  async function addPngMetadata(png, metadata) {
8102
8204
  if (!isPng(png) || png.length < IHDR_END || decoder.decode(png.subarray(12, 16)) !== "IHDR") return png;
8103
- const spec = await deflate(encoder.encode(specJson(metadata.spec)));
8104
8205
  return concat([
8105
8206
  png.subarray(0, IHDR_END),
8106
8207
  textChunk(PNG_KEYWORDS.software, SOFTWARE),
8107
8208
  textChunk(PNG_KEYWORDS.source, sourceUrl(metadata.link)),
8108
8209
  textChunk(PNG_KEYWORDS.link, metadata.link),
8109
- compressedTextChunk(PNG_KEYWORDS.spec, spec),
8110
8210
  png.subarray(IHDR_END)
8111
8211
  ]);
8112
8212
  }
@@ -8115,7 +8215,7 @@ function readTextChunk(type, data) {
8115
8215
  const end = data.indexOf(0);
8116
8216
  if (end < 1 || end > 79) return void 0;
8117
8217
  const keyword = decoder.decode(data.subarray(0, end));
8118
- if (keyword !== PNG_KEYWORDS.spec && keyword !== PNG_KEYWORDS.link) return void 0;
8218
+ if (!OUR_KEYWORDS.has(keyword)) return void 0;
8119
8219
  if (type === "tEXt") return {
8120
8220
  keyword,
8121
8221
  entry: {
@@ -8160,7 +8260,7 @@ function readPngEntries(png) {
8160
8260
  async function entryText(entry) {
8161
8261
  if (!entry) return void 0;
8162
8262
  if (!entry.compressed) return decoder.decode(entry.text);
8163
- const bytes = await inflate(entry.text, MAX_SPEC_BYTES);
8263
+ const bytes = await inflate$1(entry.text, "deflate", MAX_SPEC_BYTES);
8164
8264
  return bytes && decoder.decode(bytes);
8165
8265
  }
8166
8266
  function parseSpecJson(json) {
@@ -8175,7 +8275,7 @@ function parseSpecJson(json) {
8175
8275
  async function specFromLink(link) {
8176
8276
  if (!link) return void 0;
8177
8277
  try {
8178
- const spec = await readLink(link);
8278
+ const spec = await readLink(link, { maxBytes: MAX_SPEC_BYTES });
8179
8279
  return typeof spec === "object" && spec !== null ? spec : void 0;
8180
8280
  } catch {
8181
8281
  return;
@@ -8183,7 +8283,9 @@ async function specFromLink(link) {
8183
8283
  }
8184
8284
  async function readPngSpec(png) {
8185
8285
  const entries = readPngEntries(png);
8186
- return parseSpecJson(await entryText(entries.get(PNG_KEYWORDS.spec))) ?? specFromLink(await entryText(entries.get(PNG_KEYWORDS.link)));
8286
+ const fromLink = await specFromLink(await entryText(entries.get(PNG_KEYWORDS.link)));
8287
+ if (fromLink !== void 0) return fromLink;
8288
+ return parseSpecJson(await entryText(entries.get(LEGACY.spec))) ?? specFromLink(await entryText(entries.get(LEGACY.link)));
8187
8289
  }
8188
8290
  const ENTITIES = {
8189
8291
  amp: "&",
@@ -8199,18 +8301,32 @@ function unescapeXml(text) {
8199
8301
  return code <= 1114111 ? String.fromCodePoint(code) : match;
8200
8302
  });
8201
8303
  }
8304
+ /** The value of the link attribute of an element tag, unescaped */
8305
+ function linkAttribute(tag) {
8306
+ const link = /\slink=(?:"([^"]*)"|'([^']*)')/.exec(tag);
8307
+ return link ? unescapeXml(link[1] ?? link[2]) : void 0;
8308
+ }
8202
8309
  function readSvgSpec(svg) {
8203
- const start = svg.indexOf("<code-image ");
8310
+ const start = svg.indexOf("<tin ");
8311
+ if (start >= 0) {
8312
+ const end = svg.indexOf(">", start);
8313
+ const tag = end < 0 ? "" : svg.slice(start, end);
8314
+ if (tag.includes("urn:tin.so:metadata:1")) return specFromLink(linkAttribute(tag));
8315
+ }
8316
+ return readLegacySvgSpec(svg);
8317
+ }
8318
+ /** An SVG of the first format, with the spec as the text of the element and the link */
8319
+ function readLegacySvgSpec(svg) {
8320
+ const start = svg.indexOf(`<${LEGACY.element} `);
8204
8321
  if (start < 0) return void 0;
8205
8322
  const tagEnd = svg.indexOf(">", start);
8206
- const close = svg.indexOf("</code-image>", tagEnd);
8323
+ const close = svg.indexOf(`</${LEGACY.element}>`, tagEnd);
8207
8324
  if (tagEnd < 0 || close < 0) return void 0;
8208
8325
  const tag = svg.slice(start, tagEnd);
8209
- if (!tag.includes("urn:code-image:metadata:1")) return void 0;
8326
+ if (!tag.includes(LEGACY.namespace)) return void 0;
8210
8327
  const spec = parseSpecJson(unescapeXml(svg.slice(tagEnd + 1, close)));
8211
8328
  if (spec !== void 0) return Promise.resolve(spec);
8212
- const link = /\slink=(?:"([^"]*)"|'([^']*)')/.exec(tag);
8213
- return specFromLink(link ? unescapeXml(link[1] ?? link[2]) : void 0);
8329
+ return specFromLink(linkAttribute(tag));
8214
8330
  }
8215
8331
  /**
8216
8332
  * Reads the spec from a PNG or an SVG that this package made with metadata, with the fields of
@@ -8543,4 +8659,4 @@ function toSvg(scene, options = {}) {
8543
8659
  "</svg>"
8544
8660
  ].join("\n");
8545
8661
  }
8546
- export { FILE_ICON as $, parseSpec as A, highlight as B, specJsonSchema as C, SpecError as D, SPEC_LIMITS as E, getSyntax as F, createLink as G, isKnownLanguage as H, getTheme as I, readLink as J, decodeSpec as K, resolveMode as L, THEMES as M, THEME_IDS as N, SpecSchema as O, getFrameColors as P, AUTO_ICON as Q, LANGUAGE_IDS as R, SPEC_SCHEMA_URL as S, MARK_STYLES as T, resolveLanguage as U, inferLanguage as V, DEFAULT_APP_URL as W, migrateLegacySpec as X, IMPLICIT_SPEC_VERSION as Y, stampSpecVersion as Z, WINDOW_RADIUS as _, svgToPng as a, CODE_FONT_IDS as at, normalizeCode as b, SOFTWARE as c, cssFontFamily as ct, createImageMetadata as d, measureUiText as dt, ICON_IDS as et, readImageSpec as f, TAB_HEADER as g, POPOVER as h, sceneFontFiles as i, CODE_FONTS as it, DEFAULT_THEME as j, expandLineRanges as k, SVG_NAMESPACE as l, getCodeFont as lt, LINE_HEIGHT as m, initPng as n, getIcon as nt, METADATA_BY_DEFAULT as o, DEFAULT_CODE_FONT as ot, FONT_SIZE as p, encodeSpec as q, loadSceneFonts as r, resolveIcon as rt, PNG_KEYWORDS as s, UI_FONT as st, toSvg as t, NO_ICON as tt, addPngMetadata as u, getFace as ut, cellCount as v, LATEST_SPEC_VERSION as w, MAC_HEADER as x, layout as y, PLAIN_TEXT as z };
8662
+ export { migrateLegacySpec as $, SpecSchema as A, LANGUAGE_IDS as B, isSpecSchemaUrl as C, MARK_STYLES as D, LATEST_SPEC_VERSION as E, THEME_IDS as F, resolveLanguage as G, highlight as H, getFrameColors as I, decodeSpec as J, DEFAULT_APP_URL as K, getSyntax as L, parseSpec as M, DEFAULT_THEME as N, SPEC_LIMITS as O, THEMES as P, IMPLICIT_SPEC_VERSION as Q, getTheme as R, SPEC_SCHEMA_URL as S, ANNOTATION_TYPES as T, inferLanguage as U, PLAIN_TEXT as V, isKnownLanguage as W, readLink as X, encodeSpec as Y, EXTENSION_PREFIX as Z, WINDOW_RADIUS as _, svgToPng as a, NO_ICON as at, normalizeCode as b, SOFTWARE as c, CODE_FONTS as ct, createImageMetadata as d, UI_FONT as dt, schemaVersion as et, readImageSpec as f, cssFontFamily as ft, TAB_HEADER as g, POPOVER as h, measureUiText as ht, sceneFontFiles as i, ICON_IDS as it, expandLineRanges as j, SpecError as k, SVG_NAMESPACE as l, CODE_FONT_IDS as lt, LINE_HEIGHT as m, getFace as mt, initPng as n, AUTO_ICON as nt, METADATA_BY_DEFAULT as o, getIcon as ot, FONT_SIZE as p, getCodeFont as pt, createLink as q, loadSceneFonts as r, FILE_ICON as rt, PNG_KEYWORDS as s, resolveIcon as st, toSvg as t, stampSpecVersion as tt, addPngMetadata as u, DEFAULT_CODE_FONT as ut, cellCount as v, specJsonSchema as w, MAC_HEADER as x, layout as y, resolveMode as z };
package/package.json CHANGED
@@ -1,29 +1,27 @@
1
1
  {
2
2
  "name": "@tin.so/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Renders code images from a JSON spec to SVG and PNG",
5
- "license": "MIT",
6
- "author": "Chris Cook",
7
- "homepage": "https://tin.so",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/zirkelc/code-image.git"
11
- },
12
5
  "keywords": [
6
+ "cli",
13
7
  "code",
14
- "screenshot",
15
- "image",
16
8
  "code-image",
9
+ "image",
17
10
  "ray.so",
18
- "shiki",
19
- "cli"
11
+ "screenshot",
12
+ "shiki"
20
13
  ],
21
- "publishConfig": {
22
- "access": "public"
14
+ "homepage": "https://tin.so",
15
+ "license": "MIT",
16
+ "author": "Chris Cook",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/zirkelc/tin.so.git"
23
20
  },
24
21
  "files": [
25
22
  "dist",
26
- "fonts"
23
+ "fonts",
24
+ "THIRD_PARTY_NOTICES.md"
27
25
  ],
28
26
  "type": "module",
29
27
  "exports": {
@@ -31,6 +29,9 @@
31
29
  "./node": "./dist/node.mjs",
32
30
  "./package.json": "./package.json"
33
31
  },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
34
35
  "dependencies": {
35
36
  "@resvg/resvg-wasm": "^2.6.2",
36
37
  "@valibot/to-json-schema": "^1.8.0",