fui-studio-mcp 0.0.0-stage → 0.1.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.
Files changed (72) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +146 -2
  3. package/THIRD_PARTY_NOTICES.md +48 -0
  4. package/dist/LICENSES.txt +51 -0
  5. package/dist/chunk-CTQA7NSS.js +1 -0
  6. package/dist/chunk-RAHWGTHJ.js +234 -0
  7. package/dist/chunk-W7L36Z34.js +16096 -0
  8. package/dist/dist-3URVYEPT.js +60 -0
  9. package/dist/index.js +1504 -0
  10. package/dist/node-4RLELQ7B.js +39 -0
  11. package/dist/outline/LICENSES.txt +28 -0
  12. package/dist/outline/metafile-iife.json +1 -0
  13. package/dist/outline/outline.global.js +1223 -0
  14. package/dist/runtime.global.js +2868 -0
  15. package/files/LICENSE-Anton-SC.txt +93 -0
  16. package/files/LICENSE-IBM-Plex.txt +93 -0
  17. package/files/LICENSE-Inter.txt +92 -0
  18. package/files/LICENSE-JetBrains-Mono.txt +93 -0
  19. package/files/LICENSE-Misc-Fixed.txt +15 -0
  20. package/files/LICENSE-Nerd-Fonts-Symbols.txt +156 -0
  21. package/files/LICENSE-Noto-Sans-Symbols-2.txt +97 -0
  22. package/files/LICENSE-Orbitron.txt +93 -0
  23. package/files/LICENSE-Press-Start-2P.txt +93 -0
  24. package/files/LICENSE-Rajdhani.txt +93 -0
  25. package/files/LICENSE-Silkscreen.txt +93 -0
  26. package/files/LICENSE-Tektur.txt +93 -0
  27. package/files/anton-sc-400.ttf +0 -0
  28. package/files/anton-sc-400.woff2 +0 -0
  29. package/files/fixed-4x6.bdf +4585 -0
  30. package/files/fixed-5x7.bdf +5663 -0
  31. package/files/fixed-5x8.bdf +6964 -0
  32. package/files/fixed-6x10.bdf +6800 -0
  33. package/files/fixed-6x13-bold.bdf +4434 -0
  34. package/files/ibm-plex-mono-400.ttf +0 -0
  35. package/files/ibm-plex-mono-400.woff2 +0 -0
  36. package/files/ibm-plex-mono-600.ttf +0 -0
  37. package/files/ibm-plex-mono-600.woff2 +0 -0
  38. package/files/ibm-plex-mono-700.ttf +0 -0
  39. package/files/ibm-plex-mono-700.woff2 +0 -0
  40. package/files/inter-400.ttf +0 -0
  41. package/files/inter-400.woff2 +0 -0
  42. package/files/inter-600.ttf +0 -0
  43. package/files/inter-600.woff2 +0 -0
  44. package/files/inter-700.ttf +0 -0
  45. package/files/inter-700.woff2 +0 -0
  46. package/files/inter-900.ttf +0 -0
  47. package/files/inter-900.woff2 +0 -0
  48. package/files/jetbrains-mono-400.ttf +0 -0
  49. package/files/jetbrains-mono-400.woff2 +0 -0
  50. package/files/jetbrains-mono-700.ttf +0 -0
  51. package/files/jetbrains-mono-700.woff2 +0 -0
  52. package/files/noto-sans-symbols-2-400.ttf +0 -0
  53. package/files/noto-sans-symbols-2-400.woff2 +0 -0
  54. package/files/orbitron-variable.ttf +0 -0
  55. package/files/orbitron-variable.woff2 +0 -0
  56. package/files/press-start-2p-400.ttf +0 -0
  57. package/files/press-start-2p-400.woff2 +0 -0
  58. package/files/rajdhani-400.ttf +0 -0
  59. package/files/rajdhani-400.woff2 +0 -0
  60. package/files/rajdhani-600.ttf +0 -0
  61. package/files/rajdhani-600.woff2 +0 -0
  62. package/files/rajdhani-700.ttf +0 -0
  63. package/files/rajdhani-700.woff2 +0 -0
  64. package/files/silkscreen-400.ttf +0 -0
  65. package/files/silkscreen-400.woff2 +0 -0
  66. package/files/silkscreen-700.ttf +0 -0
  67. package/files/silkscreen-700.woff2 +0 -0
  68. package/files/symbols-nerd-font-mono-400.ttf +0 -0
  69. package/files/symbols-nerd-font-mono-400.woff2 +0 -0
  70. package/files/tektur-variable.ttf +0 -0
  71. package/files/tektur-variable.woff2 +0 -0
  72. package/package.json +57 -3
package/LICENSE ADDED
@@ -0,0 +1,33 @@
1
+ FUI Studio MCP Server: Licence
2
+
3
+ Copyright (c) 2026 sIRwa2. All rights reserved.
4
+
5
+ This licence covers the software in this package ("the Software"): the
6
+ fui-studio-mcp program and the FUI Studio engine compiled into it. It does
7
+ not cover the third-party components listed in THIRD_PARTY_NOTICES.md, which
8
+ are under their own licences.
9
+
10
+ 1. Use. You may install and run the Software, free of charge, for any
11
+ purpose, personal or commercial.
12
+
13
+ 2. Your work is yours. Graphics, project files and exports you make with the
14
+ Software belong to you. This licence claims nothing in them.
15
+
16
+ 3. Restrictions. Except where the law allows it regardless of this licence,
17
+ you may not:
18
+ a. copy, sell, sublicense or redistribute the Software, in whole or in
19
+ part, other than installing it from the official npm package;
20
+ b. modify the Software, or reverse engineer, decompile or disassemble it,
21
+ or use its code in another product;
22
+ c. remove or alter this licence or the copyright notices.
23
+
24
+ 4. No warranty. The Software is provided "as is", without warranty of any
25
+ kind, express or implied, including fitness for a particular purpose and
26
+ non-infringement.
27
+
28
+ 5. No liability. In no event shall the copyright holder be liable for any
29
+ claim, damages or other liability arising from the Software or its use,
30
+ to the extent the law allows this exclusion.
31
+
32
+ 6. Changes. Later versions of the Software may come with different terms.
33
+ The terms in the version you installed apply to that version.
package/README.md CHANGED
@@ -1,3 +1,147 @@
1
- # Temporary Holding Version
1
+ # fui-studio-mcp
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Let an AI assistant build animated HUD and FUI graphics in **FUI Studio**:
4
+ rings, ticks, readouts, crosshairs, scanning bars. You describe it in chat,
5
+ the assistant builds it as a node graph, and you watch it appear in the
6
+ studio, where you can keep editing by hand.
7
+
8
+ It is an [MCP](https://modelcontextprotocol.io) server: your AI app starts it
9
+ on your computer when it needs it. You don't run it yourself.
10
+
11
+ ## What you need
12
+
13
+ - **Node.js 18 or newer** ([nodejs.org](https://nodejs.org)). Check with
14
+ `node --version`.
15
+ - An AI app that supports MCP servers: Claude Desktop, Claude Code, Cursor,
16
+ VS Code, Windsurf and others.
17
+ - To watch along: FUI Studio in **Chrome or Edge**. In Brave, first turn on
18
+ `brave://flags/#file-system-access-api`. Firefox and Safari can't open a
19
+ folder on your computer.
20
+
21
+ ## 1. Pick a projects folder
22
+
23
+ Make a folder for your FUI projects, for example `FUI-Studio` in your home
24
+ folder. The assistant saves there, and the studio watches the same folder.
25
+
26
+ ## 2. Add the server to your AI app
27
+
28
+ Each snippet below uses `~/FUI-Studio`; change it if your folder is somewhere
29
+ else. On Windows, write the full path, for example
30
+ `C:\\Users\\you\\FUI-Studio`.
31
+
32
+ **Claude Desktop**: Settings → Developer → Edit Config, which opens
33
+ `claude_desktop_config.json`. Add:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "fui-studio": {
39
+ "command": "npx",
40
+ "args": ["-y", "fui-studio-mcp", "--projects", "~/FUI-Studio"]
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ Then quit and reopen Claude Desktop.
47
+
48
+ **Claude Code**: run once in a terminal:
49
+
50
+ ```sh
51
+ claude mcp add fui-studio --scope user -- npx -y fui-studio-mcp --projects ~/FUI-Studio
52
+ ```
53
+
54
+ **Cursor**: Settings → MCP → Add new MCP server, or edit `~/.cursor/mcp.json`
55
+ and use the same `mcpServers` block as Claude Desktop.
56
+
57
+ **Windsurf**: edit `~/.codeium/windsurf/mcp_config.json` and use the same
58
+ `mcpServers` block as Claude Desktop.
59
+
60
+ **VS Code (Copilot agent mode)**: create `.vscode/mcp.json` in your project,
61
+ or run *MCP: Open User Configuration*:
62
+
63
+ ```json
64
+ {
65
+ "servers": {
66
+ "fui-studio": {
67
+ "type": "stdio",
68
+ "command": "npx",
69
+ "args": ["-y", "fui-studio-mcp", "--projects", "~/FUI-Studio"]
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ **Any other app**: the command is `npx -y fui-studio-mcp --projects <folder>`,
76
+ over stdio. Instead of `--projects`, you can set the environment variable
77
+ `FUI_PROJECTS`.
78
+
79
+ > **Windows and Claude Desktop:** if the server doesn't start, use
80
+ > `"command": "cmd"` and put `"/c", "npx"` in front of the other args.
81
+
82
+ ## 3. Open the same folder in the studio
83
+
84
+ In FUI Studio, click **folder…** in the toolbar and choose your projects
85
+ folder. Once the assistant has saved a project, open it from **Open**. From
86
+ then on, every save the assistant makes shows up within a second, on the page
87
+ you're looking at. One undo takes a change back.
88
+
89
+ You can edit too:
90
+
91
+ - If you have unsaved changes when the assistant saves, the studio asks
92
+ whether to *load theirs* or *keep mine*.
93
+ - If you saved while the assistant was working, its next save is refused and
94
+ it reopens the file first, so your edits are never silently overwritten.
95
+
96
+ ## Try it
97
+
98
+ > Make a 1920×1080 radar HUD: a tick ring, a sweeping arc that rotates once
99
+ > every 4 seconds, and a readout with the bearing. Save it as radar.
100
+
101
+ The assistant looks at its own work as it goes, using `render_preview`. You
102
+ can ask for changes ("thinner ticks, more orange") or make them yourself in
103
+ the studio and ask it to carry on.
104
+
105
+ ## Video (optional)
106
+
107
+ Projects with a **Video** node need a 31 MB decoder that isn't installed by
108
+ default. Without it, everything else renders and Video nodes stay empty. To
109
+ add it, use these args instead:
110
+
111
+ ```json
112
+ "args": ["-y", "-p", "fui-studio-mcp", "-p", "@ffmpeg/core@0.12.10", "fui-studio-mcp", "--projects", "~/FUI-Studio"]
113
+ ```
114
+
115
+ ## What the assistant can do
116
+
117
+ These are the server's 21 tools:
118
+
119
+ - **Projects:** `new_project`, `open_project`, `save_project`,
120
+ `list_projects`, `get_project`.
121
+ - **Building:** `list_node_types`, `add_node`, `add_image`, `remove_node`,
122
+ `set_param`, `connect`, `disconnect`, `set_keyframe`, `list_nodes`.
123
+ - **Layers and timing:** `list_layers`, `add_clip`, `remove_clip`.
124
+ - **DaVinci Resolve titles:** `set_ograf_controls`.
125
+ - **Output:** `render_preview`, `export` (SVG, PNG, .ograf, .drfx),
126
+ `render_sequence` (PNG frames).
127
+
128
+ When it connects, the server gives the assistant a short guide to how
129
+ projects fit together.
130
+
131
+ ## Troubleshooting
132
+
133
+ - **The assistant says it has no projects folder.** Your config is missing
134
+ `--projects`.
135
+ - **"npx: command not found", or the server won't start.** Install Node.js,
136
+ then restart your AI app. Apps started from the Dock or Start menu may not
137
+ see a Node installed through nvm; in that case, put the full path to `npx`
138
+ in `"command"`.
139
+ - **The studio doesn't update.** Check that it has the same folder open
140
+ (**folder…**), and that you opened the very project the assistant is
141
+ saving.
142
+ - **Which version is this?** Run `npx fui-studio-mcp --version`.
143
+
144
+ ## Licence
145
+
146
+ Free to use, including commercially; what you make is yours. See `LICENSE`.
147
+ Third-party components are listed in `THIRD_PARTY_NOTICES.md`.
@@ -0,0 +1,48 @@
1
+ # Third-party notices
2
+
3
+ fui-studio-mcp is licensed under the terms in `LICENSE`. It uses the
4
+ components below, each under its own licence.
5
+
6
+ ## Installed alongside (npm dependencies)
7
+
8
+ npm installs these as separate packages; their licence files come with them.
9
+
10
+ | Package | Licence |
11
+ | --- | --- |
12
+ | @modelcontextprotocol/sdk | MIT |
13
+ | @resvg/resvg-wasm | MPL-2.0 |
14
+ | clipper-lib | Boost Software License 1.0 |
15
+ | opentype.js | MIT |
16
+ | polygon-clipping | MIT |
17
+ | zod | MIT |
18
+
19
+ ## Optional: video
20
+
21
+ `@ffmpeg/core` (ffmpeg.wasm) is not part of this package. Install it only if
22
+ you want Video nodes to render; it is licensed GPL-2.0-or-later and carries its
23
+ own notices.
24
+
25
+ ## Fonts (in `files/`)
26
+
27
+ The bundled typefaces are under the SIL Open Font License 1.1, except the X11
28
+ misc-fixed bitmap faces (public domain) and Symbols Nerd Font Mono (MIT, with
29
+ its glyph sets under their own licences). Each licence is in `files/`:
30
+
31
+ - `LICENSE-Anton-SC.txt`
32
+ - `LICENSE-IBM-Plex.txt`
33
+ - `LICENSE-Inter.txt`
34
+ - `LICENSE-JetBrains-Mono.txt`
35
+ - `LICENSE-Misc-Fixed.txt`
36
+ - `LICENSE-Nerd-Fonts-Symbols.txt`
37
+ - `LICENSE-Orbitron.txt`
38
+ - `LICENSE-Press-Start-2P.txt`
39
+ - `LICENSE-Rajdhani.txt`
40
+ - `LICENSE-Silkscreen.txt`
41
+ - `LICENSE-Tektur.txt`
42
+ - `LICENSE-Noto-Sans-Symbols-2.txt`
43
+
44
+ ## The OGraf runtime (in `dist/`)
45
+
46
+ Exported DaVinci Resolve titles embed a runtime script; the libraries it
47
+ bundles and their licences are listed in `dist/LICENSES.txt` and
48
+ `dist/outline/LICENSES.txt`, and are written into every export.
@@ -0,0 +1,51 @@
1
+ FUI Studio OGraf runtime: third-party notices
2
+
3
+ The component file in this package bundles the following libraries.
4
+
5
+ ========================================================================
6
+ clipper-lib 6.4.2
7
+ ========================================================================
8
+
9
+ License: BSL
10
+
11
+ ========================================================================
12
+ polygon-clipping 0.15.7
13
+ ========================================================================
14
+
15
+ The MIT License (MIT)
16
+
17
+ Copyright (c) 2018 Mike Fogel <mike@fogel.ca> - covers everything not specially attributed to others below.
18
+
19
+ Copyright (c) 2016 Alexander Milevski <info@w8r.name> - covers all portions originally part of github:w8r/martinez, from which this project was forked on Febuary 2, 2018.
20
+
21
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
22
+
23
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
24
+
25
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+
27
+ ========================================================================
28
+ zod 3.25.76
29
+ ========================================================================
30
+
31
+ MIT License
32
+
33
+ Copyright (c) 2025 Colin McDonnell
34
+
35
+ Permission is hereby granted, free of charge, to any person obtaining a copy
36
+ of this software and associated documentation files (the "Software"), to deal
37
+ in the Software without restriction, including without limitation the rights
38
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39
+ copies of the Software, and to permit persons to whom the Software is
40
+ furnished to do so, subject to the following conditions:
41
+
42
+ The above copyright notice and this permission notice shall be included in all
43
+ copies or substantial portions of the Software.
44
+
45
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
51
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ FONT_FACES,
4
+ bdfGlyphs,
5
+ bdfMeasure,
6
+ bdfOutline,
7
+ facesFor,
8
+ fileFormat,
9
+ fileName,
10
+ fontFaceCss,
11
+ localFontBytes,
12
+ parseBdf,
13
+ resolveFace
14
+ } from "./chunk-W7L36Z34.js";
15
+
16
+ // ../../packages/fonts/dist/chunk-MTHRWFM6.js
17
+ import * as opentype from "opentype.js";
18
+ var lib = typeof opentype.parse === "function" ? opentype : opentype.default;
19
+ var parse2 = (buffer, opt) => lib.parse(buffer, opt);
20
+
21
+ // ../../packages/fonts/dist/chunk-GW34POSA.js
22
+ var FALLBACK_CAP_HEIGHT = 0.7;
23
+ var FALLBACK_X_HEIGHT = 0.5;
24
+ var PATH_PRECISION = 3;
25
+ var internals = (font) => font;
26
+ function forgetCcmp(font) {
27
+ const gsub = internals(font).tables.gsub;
28
+ if (!gsub) return;
29
+ const ccmp = new Set(gsub.features.map((f, i) => f.tag === "ccmp" ? i : -1).filter((i) => i >= 0));
30
+ if (ccmp.size === 0) return;
31
+ const strip = (langSys) => {
32
+ if (langSys) langSys.featureIndexes = langSys.featureIndexes.filter((i) => !ccmp.has(i));
33
+ };
34
+ for (const s of gsub.scripts) {
35
+ strip(s.script.defaultLangSys);
36
+ for (const l of s.script.langSysRecords ?? []) strip(l.langSys);
37
+ }
38
+ }
39
+ function toPathData(commands) {
40
+ const num = (v) => Math.round(v) === v ? String(Math.round(v)) : v.toFixed(PATH_PRECISION);
41
+ const pack = (...vs) => vs.map((v, i) => (v >= 0 && i > 0 ? " " : "") + num(v)).join("");
42
+ let d = "";
43
+ let open = false;
44
+ for (const c of commands) {
45
+ switch (c.type) {
46
+ case "M":
47
+ if (open) d += "Z";
48
+ d += "M" + pack(c.x, c.y);
49
+ open = true;
50
+ break;
51
+ case "L":
52
+ d += "L" + pack(c.x, c.y);
53
+ break;
54
+ case "C":
55
+ d += "C" + pack(c.x1, c.y1, c.x2, c.y2, c.x, c.y);
56
+ break;
57
+ case "Q":
58
+ d += "Q" + pack(c.x1, c.y1, c.x, c.y);
59
+ break;
60
+ case "Z":
61
+ d += "Z";
62
+ open = false;
63
+ break;
64
+ }
65
+ }
66
+ if (open) d += "Z";
67
+ return d;
68
+ }
69
+ function walk({ font, variation }, text, size, letterSpacing, each) {
70
+ const f = internals(font);
71
+ const options = { ...f.defaultRenderOptions, kerning: true, letterSpacing: letterSpacing !== 0 ? letterSpacing / size : 0 };
72
+ const glyphs = f.stringToGlyphs(text, options);
73
+ const scale = size / font.unitsPerEm;
74
+ const kerning = f.position.getKerningTables(f.position.getDefaultScriptName(), void 0);
75
+ let x = 0;
76
+ for (let i = 0; i < glyphs.length; i++) {
77
+ const base = glyphs[i];
78
+ const glyph = variation && f.variation ? f.variation.getTransform(base, variation) : base;
79
+ each(glyph, x);
80
+ if (glyph.advanceWidth) x += glyph.advanceWidth * scale;
81
+ if (i < glyphs.length - 1) {
82
+ const next = glyphs[i + 1];
83
+ const kern = kerning ? f.position.getKerningValue(kerning, base.index, next.index) : f.getKerningValue(base, next);
84
+ x += kern * scale;
85
+ }
86
+ if (options.letterSpacing) x += options.letterSpacing * size;
87
+ }
88
+ return x;
89
+ }
90
+ function createOutliner(bytesFor) {
91
+ const fonts = /* @__PURE__ */ new Map();
92
+ const bitmaps = /* @__PURE__ */ new Map();
93
+ const bitmapFor = (family, weight) => {
94
+ const { face } = resolveFace(family, weight);
95
+ if (!face.bitmap) return void 0;
96
+ const cached = bitmaps.get(face.file);
97
+ if (cached !== void 0) return cached;
98
+ const bytes = localFontBytes(face) ?? bytesFor(face);
99
+ if (!bytes) return null;
100
+ let font = null;
101
+ try {
102
+ font = parseBdf(bytes);
103
+ } catch {
104
+ font = null;
105
+ }
106
+ bitmaps.set(face.file, font);
107
+ return font;
108
+ };
109
+ const instanceFor = (family, weight, stretch) => {
110
+ const { face, variation } = resolveFace(family, weight, stretch);
111
+ const key = variation ? `${face.file}@${JSON.stringify(variation)}` : face.file;
112
+ const cached = fonts.get(face.file);
113
+ if (cached !== void 0) return cached && { font: cached, variation, key };
114
+ const bytes = localFontBytes(face) ?? bytesFor(face);
115
+ if (!bytes) return null;
116
+ let font = null;
117
+ try {
118
+ font = parse2(bytes);
119
+ forgetCcmp(font);
120
+ } catch {
121
+ font = null;
122
+ }
123
+ fonts.set(face.file, font);
124
+ return font && { font, variation, key };
125
+ };
126
+ const whole = ({ text, family, weight, stretch, size, letterSpacing }) => {
127
+ const bitmap = bitmapFor(family, weight);
128
+ if (bitmap !== void 0) return bitmap ? bdfOutline(bitmap, text, size, letterSpacing) : void 0;
129
+ if (text.length === 0) return { d: "", width: 0 };
130
+ const instance = instanceFor(family, weight, stretch);
131
+ if (!instance) return void 0;
132
+ const commands = [];
133
+ const width = walk(instance, text, size, letterSpacing, (glyph, x) => {
134
+ commands.push(...glyph.getPath(x, 0, size).commands);
135
+ });
136
+ return { d: toPathData(commands), width };
137
+ };
138
+ const glyphs = ({ text, family, weight, stretch, size, letterSpacing }) => {
139
+ const bitmap = bitmapFor(family, weight);
140
+ if (bitmap !== void 0) return bitmap ? bdfGlyphs(bitmap, text, size, letterSpacing) : void 0;
141
+ if (text.length === 0) return { glyphs: [], width: 0 };
142
+ const instance = instanceFor(family, weight, stretch);
143
+ if (!instance) return void 0;
144
+ const scale = size / instance.font.unitsPerEm;
145
+ const out = [];
146
+ const width = walk(instance, text, size, letterSpacing, (glyph, x) => {
147
+ out.push({
148
+ d: toPathData(glyph.getPath(0, 0, size).commands),
149
+ x,
150
+ advance: (glyph.advanceWidth ?? 0) * scale
151
+ });
152
+ });
153
+ return { glyphs: out, width };
154
+ };
155
+ const verticals = /* @__PURE__ */ new Map();
156
+ const verticalsFor = ({ font, variation, key }) => {
157
+ const hit = verticals.get(key);
158
+ if (hit) return hit;
159
+ const upem = font.unitsPerEm;
160
+ const f = internals(font);
161
+ const top = (ch) => {
162
+ const base = font.charToGlyph(ch);
163
+ if (!base || base.index === 0) return void 0;
164
+ const glyph = variation && f.variation ? f.variation.getTransform(base, variation) : base;
165
+ const yMax = glyph.getMetrics().yMax;
166
+ return typeof yMax === "number" && yMax > 0 ? yMax / upem : void 0;
167
+ };
168
+ const os2 = f.tables.os2;
169
+ const fromTable = (k) => {
170
+ const v = os2?.[k];
171
+ return typeof v === "number" && v > 0 ? v / upem : void 0;
172
+ };
173
+ const made = {
174
+ capHeight: top("H") ?? fromTable("sCapHeight") ?? FALLBACK_CAP_HEIGHT,
175
+ xHeight: top("x") ?? fromTable("sxHeight") ?? FALLBACK_X_HEIGHT
176
+ };
177
+ verticals.set(key, made);
178
+ return made;
179
+ };
180
+ const measure = ({ text, family, weight, stretch, size, letterSpacing }) => {
181
+ const bitmap = bitmapFor(family, weight);
182
+ if (bitmap !== void 0) return bitmap ? bdfMeasure(bitmap, text, size, letterSpacing) : void 0;
183
+ const instance = instanceFor(family, weight, stretch);
184
+ if (!instance) return void 0;
185
+ const v = verticalsFor(instance);
186
+ return {
187
+ width: text.length === 0 ? 0 : walk(instance, text, size, letterSpacing, () => {
188
+ }),
189
+ capHeight: v.capHeight * size,
190
+ xHeight: v.xHeight * size
191
+ };
192
+ };
193
+ return Object.assign(whole, { glyphs, measure });
194
+ }
195
+
196
+ // ../../packages/fonts/dist/node.js
197
+ import { readFileSync } from "fs";
198
+ import { dirname, join } from "path";
199
+ import { fileURLToPath } from "url";
200
+ function fontsDir() {
201
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "files");
202
+ }
203
+ function fontPath(face, format) {
204
+ return join(fontsDir(), fileName(face, format));
205
+ }
206
+ function readFont(face, format) {
207
+ return readFileSync(fontPath(face, format));
208
+ }
209
+ function loadFaces(families, format = "woff2") {
210
+ return facesFor(families).map((face) => {
211
+ const actual = fileFormat(face, format);
212
+ return { ...face, format: actual, base64: readFont(face, actual).toString("base64") };
213
+ });
214
+ }
215
+ function fontDefs(families, format = "woff2") {
216
+ const css = fontFaceCss(loadFaces(families, format));
217
+ return css === "" ? "" : `<style>${css}</style>`;
218
+ }
219
+ function allTtfBuffers() {
220
+ return FONT_FACES.filter((f) => !f.bitmap).map((face) => ({ face, data: readFont(face, "ttf") }));
221
+ }
222
+ function nodeOutliner() {
223
+ return createOutliner((face) => {
224
+ const buf = readFont(face, fileFormat(face, "ttf"));
225
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
226
+ });
227
+ }
228
+
229
+ export {
230
+ loadFaces,
231
+ fontDefs,
232
+ allTtfBuffers,
233
+ nodeOutliner
234
+ };