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.
- package/LICENSE +33 -0
- package/README.md +146 -2
- package/THIRD_PARTY_NOTICES.md +48 -0
- package/dist/LICENSES.txt +51 -0
- package/dist/chunk-CTQA7NSS.js +1 -0
- package/dist/chunk-RAHWGTHJ.js +234 -0
- package/dist/chunk-W7L36Z34.js +16096 -0
- package/dist/dist-3URVYEPT.js +60 -0
- package/dist/index.js +1504 -0
- package/dist/node-4RLELQ7B.js +39 -0
- package/dist/outline/LICENSES.txt +28 -0
- package/dist/outline/metafile-iife.json +1 -0
- package/dist/outline/outline.global.js +1223 -0
- package/dist/runtime.global.js +2868 -0
- package/files/LICENSE-Anton-SC.txt +93 -0
- package/files/LICENSE-IBM-Plex.txt +93 -0
- package/files/LICENSE-Inter.txt +92 -0
- package/files/LICENSE-JetBrains-Mono.txt +93 -0
- package/files/LICENSE-Misc-Fixed.txt +15 -0
- package/files/LICENSE-Nerd-Fonts-Symbols.txt +156 -0
- package/files/LICENSE-Noto-Sans-Symbols-2.txt +97 -0
- package/files/LICENSE-Orbitron.txt +93 -0
- package/files/LICENSE-Press-Start-2P.txt +93 -0
- package/files/LICENSE-Rajdhani.txt +93 -0
- package/files/LICENSE-Silkscreen.txt +93 -0
- package/files/LICENSE-Tektur.txt +93 -0
- package/files/anton-sc-400.ttf +0 -0
- package/files/anton-sc-400.woff2 +0 -0
- package/files/fixed-4x6.bdf +4585 -0
- package/files/fixed-5x7.bdf +5663 -0
- package/files/fixed-5x8.bdf +6964 -0
- package/files/fixed-6x10.bdf +6800 -0
- package/files/fixed-6x13-bold.bdf +4434 -0
- package/files/ibm-plex-mono-400.ttf +0 -0
- package/files/ibm-plex-mono-400.woff2 +0 -0
- package/files/ibm-plex-mono-600.ttf +0 -0
- package/files/ibm-plex-mono-600.woff2 +0 -0
- package/files/ibm-plex-mono-700.ttf +0 -0
- package/files/ibm-plex-mono-700.woff2 +0 -0
- package/files/inter-400.ttf +0 -0
- package/files/inter-400.woff2 +0 -0
- package/files/inter-600.ttf +0 -0
- package/files/inter-600.woff2 +0 -0
- package/files/inter-700.ttf +0 -0
- package/files/inter-700.woff2 +0 -0
- package/files/inter-900.ttf +0 -0
- package/files/inter-900.woff2 +0 -0
- package/files/jetbrains-mono-400.ttf +0 -0
- package/files/jetbrains-mono-400.woff2 +0 -0
- package/files/jetbrains-mono-700.ttf +0 -0
- package/files/jetbrains-mono-700.woff2 +0 -0
- package/files/noto-sans-symbols-2-400.ttf +0 -0
- package/files/noto-sans-symbols-2-400.woff2 +0 -0
- package/files/orbitron-variable.ttf +0 -0
- package/files/orbitron-variable.woff2 +0 -0
- package/files/press-start-2p-400.ttf +0 -0
- package/files/press-start-2p-400.woff2 +0 -0
- package/files/rajdhani-400.ttf +0 -0
- package/files/rajdhani-400.woff2 +0 -0
- package/files/rajdhani-600.ttf +0 -0
- package/files/rajdhani-600.woff2 +0 -0
- package/files/rajdhani-700.ttf +0 -0
- package/files/rajdhani-700.woff2 +0 -0
- package/files/silkscreen-400.ttf +0 -0
- package/files/silkscreen-400.woff2 +0 -0
- package/files/silkscreen-700.ttf +0 -0
- package/files/silkscreen-700.woff2 +0 -0
- package/files/symbols-nerd-font-mono-400.ttf +0 -0
- package/files/symbols-nerd-font-mono-400.woff2 +0 -0
- package/files/tektur-variable.ttf +0 -0
- package/files/tektur-variable.woff2 +0 -0
- 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
|
-
#
|
|
1
|
+
# fui-studio-mcp
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
};
|