gum-jsx 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/text/Graph.md +6 -5
- package/docs/text/Group.md +6 -1
- package/docs/text/Plot.md +12 -0
- package/docs/text/Rect.md +1 -1
- package/docs/text/Stack.md +1 -1
- package/docs/text/Text.md +3 -3
- package/package.json +2 -2
- package/scripts/skill.ts +7 -12
- package/skills/gum-jsx/SKILL.md +6 -1
- package/skills/gum-jsx/references/geometry.md +1 -1
- package/skills/gum-jsx/references/layout.md +1 -1
- package/skills/gum-jsx/references/plotting.md +18 -5
- package/skills/gum-jsx/references/text.md +3 -3
- package/src/elems/core.ts +8 -7
- package/src/elems/layout.ts +1 -2
- package/src/elems/plot.ts +2 -2
- package/src/elems/text.ts +5 -9
- package/src/fonts/IBMPlexMono-Bold.ttf +0 -0
- package/src/fonts/IBMPlexMono-Light.ttf +0 -0
- package/src/fonts/IBMPlexMono-Regular.ttf +0 -0
- package/src/fonts/IBMPlexSans-Bold.ttf +0 -0
- package/src/fonts/IBMPlexSans-Light.ttf +0 -0
- package/src/fonts/IBMPlexSans-Regular.ttf +0 -0
- package/src/fonts/fonts.ts +74 -16
- package/src/gum.ts +3 -3
- package/src/lib/const.ts +6 -3
- package/src/lib/text.ts +75 -14
- package/src/lib/utils.ts +1 -11
- package/src/meta.ts +5 -5
- package/src/render.ts +26 -7
- package/scripts/claude.ts +0 -25
- package/src/fonts/IBMPlexSans-Variable.ttf +0 -0
package/docs/text/Graph.md
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
*Inherits*: [Group](/docs/Group) > [Element](/docs/Element)
|
|
4
4
|
|
|
5
|
-
This is the core graphing functionality used in [Plot](/docs/Plot) without the axes and labels. By default, the coordinate system is automatically inferred from the limits of child elements. This can be overridden with custom `xlim`/`ylim` specifications. The Elements that are passed to **Graph** can express their position and size information in this new coordinate system.
|
|
5
|
+
This is the core graphing functionality used in [Plot](/docs/Plot) without the axes and labels. By default, the coordinate system is automatically inferred from the limits of child elements. This can be overridden with custom `xlim`/`ylim`/`coord` specifications. The Elements that are passed to **Graph** can express their position and size information in this new coordinate system.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Unlike [Group](/docs/Group), **Graph** will automatically pass the given `coord` to all children, so they can express their position and size information in this new coordinate system. This is very useful for elements like [Line](/docs/Line) or [Points](/docs/Points), which evaluate their `points` values based on their own coordinate system, not that of the container.
|
|
8
|
+
|
|
9
|
+
You'll often want to use **Graph** (directly or indirectly) to display mathematical curves, as they might otherwise come out looking upside down relative to what you expect (as higher y-values mean "down" in raw SVG).
|
|
8
10
|
|
|
9
11
|
Parameters:
|
|
10
|
-
- `xlim`/`ylim` = `
|
|
11
|
-
- `padding` = `0` —
|
|
12
|
-
- `coord` = `'auto'` — the coordinate system to use for the graph (overrides `xlim`/`ylim`)
|
|
12
|
+
- `xlim`/`ylim`/`coord` = `'auto'` — the coordinate system to use for the graph
|
|
13
|
+
- `padding` = `0` — proportional padding to add when limits are auto-detected from children
|
package/docs/text/Group.md
CHANGED
|
@@ -4,10 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
This is the main container class that all compound elements are derived from. It accepts a list of child elements and attempts to place them according to their declared properties. Child placement positions are specified in the group's internal coordinates (`coord`), which defaults to the unit square. The coordinate space is specified in `[left, top, right, bottom]` format.
|
|
6
6
|
|
|
7
|
-
The child's `aspect` is an important determinant of its placement. When
|
|
7
|
+
The child's `aspect` is an important determinant of its placement. When the child does not have an aspect, it will fit exactly in the given `rect`. When it does have an aspect, it will be made as large as possible while still fitting in the given `rect`. The `align` argument governs the exact placement in this case, while the `expand` flag makes it as small as possible while still covering the given `rect`.
|
|
8
|
+
|
|
9
|
+
One common pitfall: using `coord` will affect that placement of child elements. But for graphing elements like [Line](/docs/Line) or [Points](/docs/Points), their `points` values are evaluate based on their own coordinate system, not the containing **Group**'s. You must either give them their own `coord` or use [Graph](/docs/Graph), which automatically propagates the coordinate system to all children.
|
|
10
|
+
|
|
11
|
+
To help with debugging, a `debug` flag can be passed to show stencil lines indicating the childrens' placement. The allocated space is shown in dashed blue, while the realized position (accounting for aspect and alignment) is shown in dashed red.
|
|
8
12
|
|
|
9
13
|
Parameters:
|
|
10
14
|
- `children` = `[]` — a list of child elements
|
|
11
15
|
- `aspect` = `null` — the aspect ratio of the group's rectangle (can pass `'auto'` to infer from the children)
|
|
12
16
|
- `coord` = `[0, 0, 1, 1]` — the internal coordinate space to use for child elements (can pass `'auto'` to contain children's rects)
|
|
13
17
|
- `clip` = `false` — clip children to the group's rectangle if `true` (or a custom shape if specified)
|
|
18
|
+
- `debug` = `false` — show debug boxes for the children
|
package/docs/text/Plot.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
Uses [Graph](/docs/Graph) to plot one or more elements over the desired limits and frame them with axes. If not specified by `xlim` and `ylim`, the limits of the plot will be computed from the bounding box of the constituent elements. By default, the `aspect` will be the ratio of the range of the `xlim` and `ylim`. See [Axis](/docs/Axis) for more details on how to customize the axes, ticks, and labels.
|
|
6
6
|
|
|
7
|
+
By default, the extent of **Plot** only includes the graphing area itself, not the axes, labels, or title. To include these, you can set the `margin` parameter to a non-zero value. However, it many cases it makes more sense to enclose **Plot** in a [Frame](/docs/Frame) or [Box](/docs/Box) element and set the `margin` parameter on that instead. This is useful if you want to add border that exactly encloses the graphing area.
|
|
8
|
+
|
|
7
9
|
Parameters:
|
|
8
10
|
- `xlim`/`ylim` = `[0, 1]` — the range over which to graph
|
|
9
11
|
- `xanchor`/`yanchor` — the value at which to place the respective axis. Note that the `xanchor` is a y-value and vice versa. Defaults to `xmin`/`ymin`
|
|
@@ -21,3 +23,13 @@ Subunits:
|
|
|
21
23
|
- `grid`/`xgrid`/`ygrid` — the grid lines arrayed under the graph
|
|
22
24
|
- `label`/`xlabel`/`ylabel` — the axis label elements
|
|
23
25
|
- `title` — the plot title element
|
|
26
|
+
|
|
27
|
+
Title:
|
|
28
|
+
- `title-size` = `0.075` — the size of the title element
|
|
29
|
+
- `title-offset` = `0.05` — the offset of the title element from the top of the plot
|
|
30
|
+
|
|
31
|
+
Labels:
|
|
32
|
+
- `label-size` = `0.05` — the size of the label elements
|
|
33
|
+
- `label-offset` = `0.125` — the offset of the label elements from the axis
|
|
34
|
+
- `xlabel-size`/`ylabel-size` — the size of the x/y label element (overrides `label-size`)
|
|
35
|
+
- `xlabel-offset`/`ylabel-offset` — the offset of the x/y label element from the axis (overrides `label-offset`)
|
package/docs/text/Rect.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
This makes a rectangle. Without any arguments it will fill its entire allocated space. Unless otherwise specified, it has a `null` aspect. Use **Square** for a square with a unit aspect.
|
|
6
6
|
|
|
7
|
-
Specifying a `rounded` argument will round the borders by the same amount for each corner. This can be either a scalar or a pair of scalars corresponding to the x and y radii of the corners. To specify different roundings for each corner, use the **RoundedRect** element.
|
|
7
|
+
Specifying a `rounded` argument will round the borders by the same amount for each corner. This can be either a scalar or a pair of scalars corresponding to the x and y radii of the corners. To specify different roundings for each corner, use the **RoundedRect** element. Rounding is expressed as a fractional value between 0 and 1, where 0 means no rounding and 1 will essentially render an ellipse. Values much greater than 0.1 will start to look a little goofy.
|
|
8
8
|
|
|
9
9
|
Parameters:
|
|
10
10
|
- `rounded` = `null` — proportional border rounding, accepts either scalar or pair of scalars
|
package/docs/text/Stack.md
CHANGED
|
@@ -6,7 +6,7 @@ Stack one or more **Element** either vertically or horizontally. There are speci
|
|
|
6
6
|
|
|
7
7
|
The simplest case is when all children have aspect ratios. In the **HStack** case, the overall aspect ratio of the stack is the sum of the child aspect ratios. Children are allocated space in proportion to their aspect ratios. The **VStack** case is similar, but we need to deal in inverse aspect ratio terms.
|
|
8
8
|
|
|
9
|
-
When some children lack aspect ratios, they will be allocated any remaining space evenly. Regardless of whether a child has an aspect ratio or not, it can be given a fixed size with the `stack-size` parameter. This is specified as a fraction between 0 and 1, independent of spacing.
|
|
9
|
+
When some children lack aspect ratios, they will be allocated any remaining space evenly. Regardless of whether a child has an aspect ratio or not, it can be given a fixed size with the `stack-size` parameter. This is specified as a fraction between 0 and 1, independent of spacing. Be warned though, if you specify `stack-size` for a child with an aspect ratio, it will be ignored. If you specify `stack-size` for every child, the resulting stack will have no aspect ratio.
|
|
10
10
|
|
|
11
11
|
Whenever possible, the aspect ratio of the overall stack is set so that all elements with defined aspect ratios will reach full width (in the **VStack** case) or full height (in the **HStack** case).
|
|
12
12
|
|
package/docs/text/Text.md
CHANGED
|
@@ -6,11 +6,11 @@ Displays text and other elements. Note that you will typically not set the font
|
|
|
6
6
|
|
|
7
7
|
If `wrap` is specified, the text will be wrapped to the specified width. In either case, single newlines will be respected, though whitespace will be compressed. There are two wrapper elements related to text:
|
|
8
8
|
|
|
9
|
-
There are two default fonts that are always provided: `sans = 'IBM Plex Sans'` and `mono ='IBM Plex Mono'`. You can use these global variables anywhere.
|
|
10
|
-
|
|
11
9
|
- [TextBox](/docs/TextBox) / **TextFrame** can handle text with a border and background
|
|
12
10
|
- **TextStack** can handle multiple lines of text that are passed in as an array
|
|
13
11
|
|
|
12
|
+
There are two default fonts that are always provided: `sans = 'IBM Plex Sans'` and `mono ='IBM Plex Mono'`. There are three availabe font weights: `light = 300`, `regular = 400`, and `bold = 700`. The default weight is `light`. You can use these global variables anywhere.
|
|
13
|
+
|
|
14
14
|
Parameters:
|
|
15
15
|
- `children` — the text to display
|
|
16
16
|
- `wrap` = `null` — the width (in ems) to wrap the text at (if `null`, the text will not be wrapped)
|
|
@@ -18,4 +18,4 @@ Parameters:
|
|
|
18
18
|
- `justify` = `'left'` — the horizontal justification of the text
|
|
19
19
|
- `color` = `black` — sets the text color using both stroke and fill (this is the usual way)
|
|
20
20
|
- `font-family` = `sans` — the font family (for display and size calculations)
|
|
21
|
-
- `font-weight` = `
|
|
21
|
+
- `font-weight` = `300` — the font weight (for display and size calculations)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gum-jsx",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Language for vector graphics generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Douglas Hanley",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"scripts": {
|
|
42
42
|
"cli": "bun ./scripts/gum.ts",
|
|
43
43
|
"test": "bun ./scripts/test.ts",
|
|
44
|
-
"skill": "bun ./scripts/skill.ts && cd skills
|
|
44
|
+
"skill": "bun ./scripts/skill.ts && cd skills && rm -f gum-jsx.skill && zip -r gum-jsx.skill gum-jsx",
|
|
45
45
|
"claude": "bun run skill && bun ./scripts/claude.ts"
|
|
46
46
|
},
|
|
47
47
|
"bin": {
|
package/scripts/skill.ts
CHANGED
|
@@ -20,13 +20,6 @@ const { tags: docs_tags, cats, text: docs_text, code: docs_code } = getDocs('doc
|
|
|
20
20
|
const { tags: gala_tags, text: gala_text, code: gala_code } = getGala('gala')
|
|
21
21
|
|
|
22
22
|
// make reference pages
|
|
23
|
-
const docs_pages = Object.fromEntries(docs_tags.map(tag =>
|
|
24
|
-
[ tag, prepareDocsPage(docs_text[tag], docs_code[tag]) ]
|
|
25
|
-
))
|
|
26
|
-
|
|
27
|
-
const gala_pages = Object.fromEntries(gala_tags.map(tag =>
|
|
28
|
-
[ tag, prepareGalaPage(gala_text[tag], gala_code[tag]) ]
|
|
29
|
-
))
|
|
30
23
|
|
|
31
24
|
// load prompt files
|
|
32
25
|
const head = readFileSync('prompt/head.md', 'utf8').trim()
|
|
@@ -43,11 +36,11 @@ ${intro}
|
|
|
43
36
|
|
|
44
37
|
${docs}
|
|
45
38
|
|
|
46
|
-
${
|
|
39
|
+
${prepareDocsPage(docs_text['Element'], docs_code['Element'], true)}
|
|
47
40
|
|
|
48
|
-
${
|
|
41
|
+
${prepareDocsPage(docs_text['Group'], docs_code['Group'], true)}
|
|
49
42
|
|
|
50
|
-
${
|
|
43
|
+
${prepareDocsPage(docs_text['Box'], docs_code['Box'], true)}
|
|
51
44
|
|
|
52
45
|
${refs}
|
|
53
46
|
|
|
@@ -62,7 +55,9 @@ writeFileSync(`${output}/SKILL.md`, skill + '\n')
|
|
|
62
55
|
mkdirSync(`${output}/references`, { recursive: true })
|
|
63
56
|
Object.entries(cats).forEach(([c, ps]) => {
|
|
64
57
|
if (c == 'core') return
|
|
65
|
-
const content = ps.map(p =>
|
|
58
|
+
const content = ps.map(p =>
|
|
59
|
+
prepareDocsPage(docs_text[p], docs_code[p], true)
|
|
60
|
+
).join('\n\n')
|
|
66
61
|
const entry = `# ${capitalize(c)} Elements\n\n${content}\n`
|
|
67
62
|
writeFileSync(`${output}/references/${c}.md`, entry)
|
|
68
63
|
})
|
|
@@ -70,6 +65,6 @@ Object.entries(cats).forEach(([c, ps]) => {
|
|
|
70
65
|
// write gala pages
|
|
71
66
|
mkdirSync(`${output}/references/gala`, { recursive: true })
|
|
72
67
|
gala_tags.forEach(t => {
|
|
73
|
-
const entry =
|
|
68
|
+
const entry = prepareGalaPage(gala_text[t], gala_code[t])
|
|
74
69
|
writeFileSync(`${output}/references/gala/${t}.md`, entry)
|
|
75
70
|
})
|
package/skills/gum-jsx/SKILL.md
CHANGED
|
@@ -202,13 +202,18 @@ return <Tri pos0={[0.5, 0.1]} pos1={[0.9, 0.9]} pos2={[0.1, 0.9]} fill={gray} />
|
|
|
202
202
|
|
|
203
203
|
This is the main container class that all compound elements are derived from. It accepts a list of child elements and attempts to place them according to their declared properties. Child placement positions are specified in the group's internal coordinates (`coord`), which defaults to the unit square. The coordinate space is specified in `[left, top, right, bottom]` format.
|
|
204
204
|
|
|
205
|
-
The child's `aspect` is an important determinant of its placement. When
|
|
205
|
+
The child's `aspect` is an important determinant of its placement. When the child does not have an aspect, it will fit exactly in the given `rect`. When it does have an aspect, it will be made as large as possible while still fitting in the given `rect`. The `align` argument governs the exact placement in this case, while the `expand` flag makes it as small as possible while still covering the given `rect`.
|
|
206
|
+
|
|
207
|
+
One common pitfall: using `coord` will affect that placement of child elements. But for graphing elements like **Line** or **Points**, their `points` values are evaluate based on their own coordinate system, not the containing **Group**'s. You must either give them their own `coord` or use **Graph**, which automatically propagates the coordinate system to all children.
|
|
208
|
+
|
|
209
|
+
To help with debugging, a `debug` flag can be passed to show stencil lines indicating the childrens' placement. The allocated space is shown in dashed blue, while the realized position (accounting for aspect and alignment) is shown in dashed red.
|
|
206
210
|
|
|
207
211
|
Parameters:
|
|
208
212
|
- `children` = `[]` — a list of child elements
|
|
209
213
|
- `aspect` = `null` — the aspect ratio of the group's rectangle (can pass `'auto'` to infer from the children)
|
|
210
214
|
- `coord` = `[0, 0, 1, 1]` — the internal coordinate space to use for child elements (can pass `'auto'` to contain children's rects)
|
|
211
215
|
- `clip` = `false` — clip children to the group's rectangle if `true` (or a custom shape if specified)
|
|
216
|
+
- `debug` = `false` — show debug boxes for the children
|
|
212
217
|
|
|
213
218
|
**Example**
|
|
214
219
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
This makes a rectangle. Without any arguments it will fill its entire allocated space. Unless otherwise specified, it has a `null` aspect. Use **Square** for a square with a unit aspect.
|
|
8
8
|
|
|
9
|
-
Specifying a `rounded` argument will round the borders by the same amount for each corner. This can be either a scalar or a pair of scalars corresponding to the x and y radii of the corners. To specify different roundings for each corner, use the **RoundedRect** element.
|
|
9
|
+
Specifying a `rounded` argument will round the borders by the same amount for each corner. This can be either a scalar or a pair of scalars corresponding to the x and y radii of the corners. To specify different roundings for each corner, use the **RoundedRect** element. Rounding is expressed as a fractional value between 0 and 1, where 0 means no rounding and 1 will essentially render an ellipse. Values much greater than 0.1 will start to look a little goofy.
|
|
10
10
|
|
|
11
11
|
Parameters:
|
|
12
12
|
- `rounded` = `null` — proportional border rounding, accepts either scalar or pair of scalars
|
|
@@ -44,7 +44,7 @@ Stack one or more **Element** either vertically or horizontally. There are speci
|
|
|
44
44
|
|
|
45
45
|
The simplest case is when all children have aspect ratios. In the **HStack** case, the overall aspect ratio of the stack is the sum of the child aspect ratios. Children are allocated space in proportion to their aspect ratios. The **VStack** case is similar, but we need to deal in inverse aspect ratio terms.
|
|
46
46
|
|
|
47
|
-
When some children lack aspect ratios, they will be allocated any remaining space evenly. Regardless of whether a child has an aspect ratio or not, it can be given a fixed size with the `stack-size` parameter. This is specified as a fraction between 0 and 1, independent of spacing.
|
|
47
|
+
When some children lack aspect ratios, they will be allocated any remaining space evenly. Regardless of whether a child has an aspect ratio or not, it can be given a fixed size with the `stack-size` parameter. This is specified as a fraction between 0 and 1, independent of spacing. Be warned though, if you specify `stack-size` for a child with an aspect ratio, it will be ignored. If you specify `stack-size` for every child, the resulting stack will have no aspect ratio.
|
|
48
48
|
|
|
49
49
|
Whenever possible, the aspect ratio of the overall stack is set so that all elements with defined aspect ratios will reach full width (in the **VStack** case) or full height (in the **HStack** case).
|
|
50
50
|
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
*Inherits*: **Group** > **Element**
|
|
6
6
|
|
|
7
|
-
This is the core graphing functionality used in **Plot** without the axes and labels. By default, the coordinate system is automatically inferred from the limits of child elements. This can be overridden with custom `xlim`/`ylim` specifications. The Elements that are passed to **Graph** can express their position and size information in this new coordinate system.
|
|
7
|
+
This is the core graphing functionality used in **Plot** without the axes and labels. By default, the coordinate system is automatically inferred from the limits of child elements. This can be overridden with custom `xlim`/`ylim`/`coord` specifications. The Elements that are passed to **Graph** can express their position and size information in this new coordinate system.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Unlike **Group**, **Graph** will automatically pass the given `coord` to all children, so they can express their position and size information in this new coordinate system. This is very useful for elements like **Line** or **Points**, which evaluate their `points` values based on their own coordinate system, not that of the container.
|
|
10
|
+
|
|
11
|
+
You'll often want to use **Graph** (directly or indirectly) to display mathematical curves, as they might otherwise come out looking upside down relative to what you expect (as higher y-values mean "down" in raw SVG).
|
|
10
12
|
|
|
11
13
|
Parameters:
|
|
12
|
-
- `xlim`/`ylim` = `
|
|
13
|
-
- `padding` = `0` —
|
|
14
|
-
- `coord` = `'auto'` — the coordinate system to use for the graph (overrides `xlim`/`ylim`)
|
|
14
|
+
- `xlim`/`ylim`/`coord` = `'auto'` — the coordinate system to use for the graph
|
|
15
|
+
- `padding` = `0` — proportional padding to add when limits are auto-detected from children
|
|
15
16
|
|
|
16
17
|
**Example**
|
|
17
18
|
|
|
@@ -33,6 +34,8 @@ Generated code:
|
|
|
33
34
|
|
|
34
35
|
Uses **Graph** to plot one or more elements over the desired limits and frame them with axes. If not specified by `xlim` and `ylim`, the limits of the plot will be computed from the bounding box of the constituent elements. By default, the `aspect` will be the ratio of the range of the `xlim` and `ylim`. See **Axis** for more details on how to customize the axes, ticks, and labels.
|
|
35
36
|
|
|
37
|
+
By default, the extent of **Plot** only includes the graphing area itself, not the axes, labels, or title. To include these, you can set the `margin` parameter to a non-zero value. However, it many cases it makes more sense to enclose **Plot** in a **Frame** or **Box** element and set the `margin` parameter on that instead. This is useful if you want to add border that exactly encloses the graphing area.
|
|
38
|
+
|
|
36
39
|
Parameters:
|
|
37
40
|
- `xlim`/`ylim` = `[0, 1]` — the range over which to graph
|
|
38
41
|
- `xanchor`/`yanchor` — the value at which to place the respective axis. Note that the `xanchor` is a y-value and vice versa. Defaults to `xmin`/`ymin`
|
|
@@ -51,6 +54,16 @@ Subunits:
|
|
|
51
54
|
- `label`/`xlabel`/`ylabel` — the axis label elements
|
|
52
55
|
- `title` — the plot title element
|
|
53
56
|
|
|
57
|
+
Title:
|
|
58
|
+
- `title-size` = `0.075` — the size of the title element
|
|
59
|
+
- `title-offset` = `0.05` — the offset of the title element from the top of the plot
|
|
60
|
+
|
|
61
|
+
Labels:
|
|
62
|
+
- `label-size` = `0.05` — the size of the label elements
|
|
63
|
+
- `label-offset` = `0.125` — the offset of the label elements from the axis
|
|
64
|
+
- `xlabel-size`/`ylabel-size` — the size of the x/y label element (overrides `label-size`)
|
|
65
|
+
- `xlabel-offset`/`ylabel-offset` — the offset of the x/y label element from the axis (overrides `label-offset`)
|
|
66
|
+
|
|
54
67
|
**Example**
|
|
55
68
|
|
|
56
69
|
Prompt: plot an inverted sine wave with ticks labeled in multiples of π. There is a faint dashed grid. The x-axis is labeled "phase" and the y-axis is labeled "amplitude". The title is "Inverted Sine Wave".
|
|
@@ -8,11 +8,11 @@ Displays text and other elements. Note that you will typically not set the font
|
|
|
8
8
|
|
|
9
9
|
If `wrap` is specified, the text will be wrapped to the specified width. In either case, single newlines will be respected, though whitespace will be compressed. There are two wrapper elements related to text:
|
|
10
10
|
|
|
11
|
-
There are two default fonts that are always provided: `sans = 'IBM Plex Sans'` and `mono ='IBM Plex Mono'`. You can use these global variables anywhere.
|
|
12
|
-
|
|
13
11
|
- **TextBox** / **TextFrame** can handle text with a border and background
|
|
14
12
|
- **TextStack** can handle multiple lines of text that are passed in as an array
|
|
15
13
|
|
|
14
|
+
There are two default fonts that are always provided: `sans = 'IBM Plex Sans'` and `mono ='IBM Plex Mono'`. There are three availabe font weights: `light = 300`, `regular = 400`, and `bold = 700`. The default weight is `light`. You can use these global variables anywhere.
|
|
15
|
+
|
|
16
16
|
Parameters:
|
|
17
17
|
- `children` — the text to display
|
|
18
18
|
- `wrap` = `null` — the width (in ems) to wrap the text at (if `null`, the text will not be wrapped)
|
|
@@ -20,7 +20,7 @@ Parameters:
|
|
|
20
20
|
- `justify` = `'left'` — the horizontal justification of the text
|
|
21
21
|
- `color` = `black` — sets the text color using both stroke and fill (this is the usual way)
|
|
22
22
|
- `font-family` = `sans` — the font family (for display and size calculations)
|
|
23
|
-
- `font-weight` = `
|
|
23
|
+
- `font-weight` = `300` — the font weight (for display and size calculations)
|
|
24
24
|
|
|
25
25
|
**Example**
|
|
26
26
|
|
package/src/elems/core.ts
CHANGED
|
@@ -258,7 +258,7 @@ function props_repr(d: Attrs, prec: number): string {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
// reserved keys
|
|
261
|
-
const SPEC_KEYS = [ 'rect', 'aspect', 'expand', 'align', 'upright', '
|
|
261
|
+
const SPEC_KEYS = [ 'rect', 'coord', 'aspect', 'aspect0', 'expand', 'align', 'upright', 'offset', 'rotate', 'rotate_adjust', 'rotate_invar' ]
|
|
262
262
|
const HELP_KEYS = [ 'pos', 'size', 'xsize', 'ysize', 'flex', 'spin', 'orient' ]
|
|
263
263
|
const EXTR_KEYS = [ 'stack_size' ]
|
|
264
264
|
const RESERVED_KEYS = [ ...SPEC_KEYS, ...HELP_KEYS, ...EXTR_KEYS ]
|
|
@@ -282,6 +282,7 @@ interface SpecArgs {
|
|
|
282
282
|
rect?: Rect
|
|
283
283
|
coord?: Rect | 'auto'
|
|
284
284
|
aspect?: number | 'auto'
|
|
285
|
+
aspect0?: number
|
|
285
286
|
expand?: boolean
|
|
286
287
|
align?: Align
|
|
287
288
|
upright?: boolean
|
|
@@ -345,8 +346,8 @@ class Element {
|
|
|
345
346
|
if (flex === true) this.spec.aspect = undefined
|
|
346
347
|
|
|
347
348
|
// adjust aspect for rotation
|
|
348
|
-
this.spec.aspect0
|
|
349
|
-
this.spec.aspect = this.spec.rotate_invar ? this.spec.aspect0 : rotate_aspect(this.spec.
|
|
349
|
+
this.spec.aspect0 ??= this.spec.aspect
|
|
350
|
+
this.spec.aspect = this.spec.rotate_invar ? this.spec.aspect0 : rotate_aspect(this.spec.aspect0, this.spec.rotate)
|
|
350
351
|
|
|
351
352
|
// warn if children are passed
|
|
352
353
|
if (children != null) console.error(`Got children in ${this.constructor.name}`)
|
|
@@ -493,16 +494,16 @@ class Group extends Element {
|
|
|
493
494
|
children.push(...irects, ...orects)
|
|
494
495
|
}
|
|
495
496
|
|
|
496
|
-
// make
|
|
497
|
+
// make clip path
|
|
497
498
|
let clip_path: string | undefined
|
|
498
499
|
if (clip != null) {
|
|
499
500
|
const clip_id = makeUID('clip')
|
|
500
501
|
clip_path = `url(#${clip_id})`
|
|
501
|
-
const
|
|
502
|
-
children.push(
|
|
502
|
+
const clip_elem = new ClipPath({ children: [ clip ], id: clip_id })
|
|
503
|
+
children.push(clip_elem)
|
|
503
504
|
}
|
|
504
505
|
|
|
505
|
-
//
|
|
506
|
+
// make mask
|
|
506
507
|
let mask: string | undefined
|
|
507
508
|
if (mask0 != null) {
|
|
508
509
|
const mask_id = makeUID('mask')
|
package/src/elems/layout.ts
CHANGED
|
@@ -150,8 +150,7 @@ function computeStackLayout(direc: string, children: Element[], { spacing = 0, e
|
|
|
150
150
|
// adjust for direction (invert aspect if horizontal)
|
|
151
151
|
const items = children.map(c => {
|
|
152
152
|
const size = c.attr.stack_size ?? (even ? 1 / children.length : null)
|
|
153
|
-
const
|
|
154
|
-
const aspect = expd ? c.spec.aspect : undefined
|
|
153
|
+
const aspect = c.attr.stack_size == null ? c.spec.aspect : null
|
|
155
154
|
return { size, aspect } as StackChild
|
|
156
155
|
})
|
|
157
156
|
|
package/src/elems/plot.ts
CHANGED
|
@@ -599,15 +599,15 @@ interface PlotArgs extends BoxArgs {
|
|
|
599
599
|
ylabel_offset?: number
|
|
600
600
|
xtick_size?: number
|
|
601
601
|
ytick_size?: number
|
|
602
|
-
padding?: number
|
|
603
602
|
margin?: number
|
|
603
|
+
padding?: number
|
|
604
604
|
debug?: boolean
|
|
605
605
|
}
|
|
606
606
|
|
|
607
607
|
class Plot extends Box {
|
|
608
608
|
constructor(args: PlotArgs = {}) {
|
|
609
609
|
let {
|
|
610
|
-
children: children0, xlim, ylim, axis = true, xaxis, yaxis, xticks = 5, yticks = 5, xanchor, yanchor, grid, xgrid, ygrid, xlabel, ylabel, title, tick_size = 0.015, label_size = 0.05, label_offset =
|
|
610
|
+
children: children0, xlim, ylim, axis = true, xaxis, yaxis, xticks = 5, yticks = 5, xanchor, yanchor, grid, xgrid, ygrid, xlabel, ylabel, title, tick_size = 0.015, label_size = 0.05, label_offset = 0.125, title_size = 0.075, title_offset = 0.05, xlabel_size, ylabel_size, xlabel_offset, ylabel_offset, xtick_label_offset = 0.75, ytick_label_offset = 0.25, xtick_size, ytick_size, padding, margin, aspect: aspect0 = 'auto', clip, debug = false, ...attr0
|
|
611
611
|
} = THEME(args, 'Plot')
|
|
612
612
|
const children = ensure_children(children0)
|
|
613
613
|
|
package/src/elems/text.ts
CHANGED
|
@@ -223,7 +223,7 @@ class Text extends VStack {
|
|
|
223
223
|
)
|
|
224
224
|
|
|
225
225
|
// pass to VStack
|
|
226
|
-
super({ children: lines, spacing, even: true,
|
|
226
|
+
super({ children: lines, spacing, even: true, ...spec })
|
|
227
227
|
this.args = args
|
|
228
228
|
|
|
229
229
|
// additional props
|
|
@@ -236,11 +236,7 @@ class Text extends VStack {
|
|
|
236
236
|
//
|
|
237
237
|
|
|
238
238
|
interface TextStackArgs extends StackArgs {
|
|
239
|
-
wrap?: number
|
|
240
|
-
font_family?: string
|
|
241
|
-
font_weight?: number
|
|
242
|
-
text_wrap?: number
|
|
243
|
-
text_justify?: string
|
|
239
|
+
wrap?: number
|
|
244
240
|
}
|
|
245
241
|
|
|
246
242
|
class TextStack extends VStack {
|
|
@@ -268,11 +264,11 @@ interface TextBoxArgs extends BoxArgs {
|
|
|
268
264
|
|
|
269
265
|
class TextBox extends Box {
|
|
270
266
|
constructor(args: TextBoxArgs = {}) {
|
|
271
|
-
const { children, padding = 0.1, justify, wrap,
|
|
267
|
+
const { children, padding = 0.1, justify, wrap, ...attr0 } = THEME(args, 'TextBox')
|
|
272
268
|
const [ font_attr0, text_attr, attr ] = prefix_split([ 'font', 'text' ], attr0)
|
|
273
269
|
const font_attr = prefix_join('font', font_attr0)
|
|
274
|
-
const text = new Text({ children, justify, wrap,
|
|
275
|
-
super({ children: [ text ], padding,
|
|
270
|
+
const text = new Text({ children, justify, wrap, ...text_attr, ...font_attr })
|
|
271
|
+
super({ children: [ text ], padding, ...attr })
|
|
276
272
|
this.args = args
|
|
277
273
|
}
|
|
278
274
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/fonts/fonts.ts
CHANGED
|
@@ -1,11 +1,37 @@
|
|
|
1
1
|
import { parse as parseFont, type Font } from 'opentype.js'
|
|
2
|
-
import { is_browser,
|
|
2
|
+
import { is_browser, is_string } from '../lib/utils'
|
|
3
3
|
import { sans, mono, moji } from '../lib/const'
|
|
4
4
|
|
|
5
|
+
//
|
|
6
|
+
// object utils
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
function map_object<T, U>(obj: Record<string, T>, fn: (v: T) => U): Record<string, U> {
|
|
10
|
+
return Object.fromEntries(
|
|
11
|
+
Object.entries(obj).map(([ k, v ]) => [ k, fn(v) ])
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function map_object_async<T, U>(obj: Record<string, T>, fn: (v: T) => Promise<U>): Promise<Record<string, U>> {
|
|
16
|
+
return Object.fromEntries(
|
|
17
|
+
await Promise.all(
|
|
18
|
+
Object.entries(obj).map(
|
|
19
|
+
async ([ k, v ]) => [ k, await fn(v) ]
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
//
|
|
6
26
|
// load font data as arraybuffer
|
|
7
27
|
//
|
|
8
28
|
|
|
29
|
+
type FontWeight = 'light' | 'regular' | 'bold'
|
|
30
|
+
type FontPath = string | Record<FontWeight, string>
|
|
31
|
+
type FontData = ArrayBuffer | Record<FontWeight, ArrayBuffer>
|
|
32
|
+
type FontSet = Record<FontWeight, Font>
|
|
33
|
+
type FontEntry = Font | FontSet
|
|
34
|
+
|
|
9
35
|
async function loadFont(path: string): Promise<ArrayBuffer> {
|
|
10
36
|
if (is_browser()) {
|
|
11
37
|
const response = await fetch(path)
|
|
@@ -18,15 +44,51 @@ async function loadFont(path: string): Promise<ArrayBuffer> {
|
|
|
18
44
|
}
|
|
19
45
|
}
|
|
20
46
|
|
|
47
|
+
async function loadFontFamily(path: FontPath): Promise<FontData> {
|
|
48
|
+
if (is_string(path)) {
|
|
49
|
+
return await loadFont(path)
|
|
50
|
+
} else {
|
|
51
|
+
return {
|
|
52
|
+
light: await loadFont(path.light),
|
|
53
|
+
regular: await loadFont(path.regular),
|
|
54
|
+
bold: await loadFont(path.bold),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseFontFamily(data: FontData): FontEntry {
|
|
60
|
+
if (data instanceof ArrayBuffer) {
|
|
61
|
+
return parseFont(data)
|
|
62
|
+
} else {
|
|
63
|
+
return {
|
|
64
|
+
light: parseFont(data.light),
|
|
65
|
+
regular: parseFont(data.regular),
|
|
66
|
+
bold: parseFont(data.bold),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
21
71
|
//
|
|
22
72
|
// load core fonts (vite resolves these as assets via static string analysis)
|
|
23
73
|
//
|
|
24
74
|
|
|
25
|
-
const FONT_PATHS: Record<string,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
75
|
+
const FONT_PATHS: Record<string, FontPath> = {
|
|
76
|
+
[sans]: {
|
|
77
|
+
// @ts-ignore
|
|
78
|
+
light: (await import('./IBMPlexSans-Light.ttf')).default,
|
|
79
|
+
// @ts-ignore
|
|
80
|
+
regular: (await import('./IBMPlexSans-Regular.ttf')).default,
|
|
81
|
+
// @ts-ignore
|
|
82
|
+
bold: (await import('./IBMPlexSans-Bold.ttf')).default,
|
|
83
|
+
},
|
|
84
|
+
[mono]: {
|
|
85
|
+
// @ts-ignore
|
|
86
|
+
light: (await import('./IBMPlexMono-Light.ttf')).default,
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
regular: (await import('./IBMPlexMono-Regular.ttf')).default,
|
|
89
|
+
// @ts-ignore
|
|
90
|
+
bold: (await import('./IBMPlexMono-Bold.ttf')).default,
|
|
91
|
+
},
|
|
30
92
|
// @ts-ignore
|
|
31
93
|
[moji]: (await import('./NotoEmoji-Variable.ttf')).default,
|
|
32
94
|
// @ts-ignore
|
|
@@ -45,13 +107,9 @@ const FONT_PATHS: Record<string, string> = {
|
|
|
45
107
|
'KaTeX_Size4': (await import('katex/dist/fonts/KaTeX_Size4-Regular.ttf')).default,
|
|
46
108
|
}
|
|
47
109
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
const FONTS: Record<string, Font> = map_object(FONT_DATA,
|
|
53
|
-
(_name, data) => parseFont(data)
|
|
54
|
-
)
|
|
110
|
+
// load and parse font data
|
|
111
|
+
const FONT_DATA: Record<string, FontData> = await map_object_async(FONT_PATHS, loadFontFamily)
|
|
112
|
+
const FONTS: Record<string, FontEntry> = map_object(FONT_DATA, parseFontFamily)
|
|
55
113
|
|
|
56
114
|
//
|
|
57
115
|
// allow additional fonts to be loaded
|
|
@@ -59,12 +117,12 @@ const FONTS: Record<string, Font> = map_object(FONT_DATA,
|
|
|
59
117
|
|
|
60
118
|
async function registerFont(name: string, path: string) {
|
|
61
119
|
FONT_PATHS[name] = path
|
|
62
|
-
FONT_DATA[name] = await
|
|
63
|
-
FONTS[name] =
|
|
120
|
+
FONT_DATA[name] = await loadFontFamily(path)
|
|
121
|
+
FONTS[name] = parseFontFamily(FONT_DATA[name])
|
|
64
122
|
}
|
|
65
123
|
|
|
66
124
|
//
|
|
67
125
|
// exports
|
|
68
126
|
//
|
|
69
127
|
|
|
70
|
-
export { FONT_PATHS, FONT_DATA, FONTS, registerFont }
|
|
128
|
+
export { FONT_PATHS, FONT_DATA, FONTS, registerFont, FontWeight, FontSet, FontEntry }
|
package/src/gum.ts
CHANGED
|
@@ -5,7 +5,7 @@ import './types/linebreak.d.ts'
|
|
|
5
5
|
import './types/katex.d.ts'
|
|
6
6
|
|
|
7
7
|
import { setTheme } from './lib/theme'
|
|
8
|
-
import { sans, mono, moji, cmoji, light, bold, none, black, white, gray, blue, red, green, yellow, purple, lightgray, darkgray, e, pi, phi, r2d, d2r } from './lib/const'
|
|
8
|
+
import { sans, mono, moji, cmoji, light, regular, bold, none, black, white, gray, blue, red, green, yellow, purple, lightgray, darkgray, e, pi, phi, r2d, d2r } from './lib/const'
|
|
9
9
|
import { is_scalar, is_string, is_boolean, is_object, is_function, is_array, zip, reshape, split, concat, slice, sum, prod, mean, cumsum, norm, range, linspace, enumerate, repeat, meshgrid, lingrid, exp, log, log10, sin, cos, tan, abs, pow, sqrt, sign, floor, ceil, round, atan, atan2, minimum, maximum, min, max, clamp, rescale, sigmoid, logit, smoothstep, setSeed, random, uniform, normal, integer, interp, palette, polar, rounder, add2, sub2, mul2, div2, normalize } from './lib/utils'
|
|
10
10
|
import { registerFont } from './fonts/fonts'
|
|
11
11
|
|
|
@@ -27,7 +27,7 @@ const Rect = Rectangle
|
|
|
27
27
|
type ElementConstructor = new (args: ElementArgs) => Element
|
|
28
28
|
|
|
29
29
|
const CONST = {
|
|
30
|
-
e, pi, phi, r2d, d2r, none, white, black, blue, red, green, yellow, purple, gray, lightgray, darkgray, sans, mono, moji, cmoji, light, bold,
|
|
30
|
+
e, pi, phi, r2d, d2r, none, white, black, blue, red, green, yellow, purple, gray, lightgray, darkgray, sans, mono, moji, cmoji, light, regular, bold,
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const UTILS = {
|
|
@@ -48,7 +48,7 @@ export {
|
|
|
48
48
|
ELEMS, CONTEXT, Context,
|
|
49
49
|
setTheme, registerFont, calcPngAspect, parseTable,
|
|
50
50
|
is_string, is_boolean, is_array, is_object, is_function, is_element, is_scalar,
|
|
51
|
-
e, pi, phi, r2d, d2r, none, white, black, blue, red, green, yellow, purple, gray, lightgray, darkgray, sans, mono, moji, cmoji, light, bold,
|
|
51
|
+
e, pi, phi, r2d, d2r, none, white, black, blue, red, green, yellow, purple, gray, lightgray, darkgray, sans, mono, moji, cmoji, light, regular, bold,
|
|
52
52
|
range, linspace, enumerate, repeat, meshgrid, lingrid, zip, reshape, split, concat, slice, sum, prod, mean, cumsum, min, max, minimum, maximum, norm, clamp, rescale, normalize, exp, log, log10, sin, cos, tan, abs, pow, sqrt, sign, floor, ceil, round, atan, atan2, sigmoid, logit, smoothstep, polar, rounder, interp, palette, add2, sub2, mul2, div2, spline1d, spline2d,
|
|
53
53
|
setSeed, random, uniform, normal, integer,
|
|
54
54
|
Element, Group, Svg, Box, Frame, Stack, HWrap, VStack, HStack, Grid, Points, Anchor, Attach, Absolute, Spacer, Ray, Line, UnitLine, HLine, VLine, Rectangle, Rect, RoundedRect, RoundedLine, Square, Ellipse, Arc, Circle, Dot, Shape, Path, Spline, Triangle, Fill, Arrow, Field, Span, TextLine, Text, TextBox, TextFrame, TextStack, Bold, Italic, LabelBox, TitleBox, TitleFrame, ArrowHead, Node, Edge, Network, SymPoints, SymLine, SymSpline, SymShape, SymFill, SymField, Bar, VBar, HBar, Bars, VBars, HBars, Scale, VScale, HScale, Label, HLabel, VLabel, Labels, HLabels, VLabels, Axis, HAxis, VAxis, OuterLabel, Mesh, HMesh, VMesh, Mesh2D, Graph, Plot, BarPlot, Legend, Slide, Latex, MathSpan, MathSymbol, MathSpacer, MathRow, MathCol, MathBox, MathRule, MathText, SupSub, Frac, Sqrt, Accent, Bracket, PngImage, SvgImage,
|
package/src/lib/const.ts
CHANGED
|
@@ -12,9 +12,12 @@ const mono = 'IBM Plex Mono'
|
|
|
12
12
|
const moji = 'Noto Emoji'
|
|
13
13
|
const cmoji = 'Noto Color Emoji'
|
|
14
14
|
|
|
15
|
-
// font
|
|
15
|
+
// font weights
|
|
16
16
|
const light = 300
|
|
17
|
-
const
|
|
17
|
+
const regular = 400
|
|
18
|
+
const bold = 700
|
|
19
|
+
|
|
20
|
+
// font metrics
|
|
18
21
|
const vtext = -0.15
|
|
19
22
|
|
|
20
23
|
// colors
|
|
@@ -52,4 +55,4 @@ const DEFAULTS = {
|
|
|
52
55
|
calc_size: 16,
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
export { DEFAULTS, svgns, htmlns, sans, mono, moji, cmoji, light, bold, vtext, none, black, white, gray, blue, red, green, yellow, purple, lightgray, darkgray, e, pi, phi, r2d, d2r }
|
|
58
|
+
export { DEFAULTS, svgns, htmlns, sans, mono, moji, cmoji, light, regular, bold, vtext, none, black, white, gray, blue, red, green, yellow, purple, lightgray, darkgray, e, pi, phi, r2d, d2r }
|
package/src/lib/text.ts
CHANGED
|
@@ -2,30 +2,75 @@
|
|
|
2
2
|
|
|
3
3
|
import EMOJI_REGEX from 'emojibase-regex'
|
|
4
4
|
import LineBreaker from 'linebreak'
|
|
5
|
+
import type { Font } from 'opentype.js'
|
|
5
6
|
|
|
6
|
-
import { DEFAULTS as D, sans, moji } from './const
|
|
7
|
-
import { is_string, compress_whitespace, sum, zip, max, min } from './utils
|
|
8
|
-
import { wrapWidths } from './wrap
|
|
9
|
-
import { FONTS } from '../fonts/fonts
|
|
7
|
+
import { DEFAULTS as D, sans, moji, light, regular } from './const'
|
|
8
|
+
import { is_string, compress_whitespace, sum, zip, max, min } from './utils'
|
|
9
|
+
import { wrapWidths } from './wrap'
|
|
10
|
+
import { FONTS, type FontSet, type FontEntry, type FontWeight } from '../fonts/fonts'
|
|
10
11
|
|
|
11
|
-
import type { Limit } from './types
|
|
12
|
+
import type { Limit } from './types'
|
|
12
13
|
|
|
13
14
|
//
|
|
14
15
|
// create text sizer
|
|
15
16
|
//
|
|
16
17
|
|
|
18
|
+
function is_font_set(font: FontEntry): font is FontSet {
|
|
19
|
+
return 'light' in font && 'regular' in font && 'bold' in font
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
function is_emoji(text: string): boolean {
|
|
18
23
|
return EMOJI_REGEX.test(text)
|
|
19
24
|
}
|
|
20
25
|
|
|
26
|
+
type TextRun = {
|
|
27
|
+
text: string
|
|
28
|
+
emoji: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const EMOJI_GLOBAL_REGEX = new RegExp(EMOJI_REGEX.source, EMOJI_REGEX.flags.includes('g') ? EMOJI_REGEX.flags : `${EMOJI_REGEX.flags}g`)
|
|
32
|
+
|
|
33
|
+
function splitEmojiRuns(text: string): TextRun[] {
|
|
34
|
+
const runs: TextRun[] = []
|
|
35
|
+
let last = 0
|
|
36
|
+
EMOJI_GLOBAL_REGEX.lastIndex = 0
|
|
37
|
+
|
|
38
|
+
let match: RegExpExecArray | null
|
|
39
|
+
while ((match = EMOJI_GLOBAL_REGEX.exec(text)) != null) {
|
|
40
|
+
const emoji = match[0]
|
|
41
|
+
const start = match.index
|
|
42
|
+
const end = start + emoji.length
|
|
43
|
+
|
|
44
|
+
if (start > last) runs.push({ text: text.slice(last, start), emoji: false })
|
|
45
|
+
if (emoji.length > 0) runs.push({ text: emoji, emoji: true })
|
|
46
|
+
|
|
47
|
+
last = end
|
|
48
|
+
if (EMOJI_GLOBAL_REGEX.lastIndex == start) EMOJI_GLOBAL_REGEX.lastIndex++
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (last < text.length) runs.push({ text: text.slice(last), emoji: false })
|
|
52
|
+
EMOJI_GLOBAL_REGEX.lastIndex = 0
|
|
53
|
+
return runs
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const medium = 500
|
|
57
|
+
|
|
58
|
+
// match the browser result for the bundled 300/400/700 static faces
|
|
59
|
+
function closest_weight(weight: number): FontWeight {
|
|
60
|
+
if (weight < regular) return 'light'
|
|
61
|
+
if (weight <= medium) return 'regular'
|
|
62
|
+
return 'bold'
|
|
63
|
+
}
|
|
64
|
+
|
|
21
65
|
function arrayEquals(a: number[], b: number[]): boolean {
|
|
22
66
|
return a.length == b.length && a.every((x, i) => x == b[i])
|
|
23
67
|
}
|
|
24
68
|
|
|
25
69
|
function emojiSizer(text: string): number {
|
|
26
70
|
// get emoji font
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
71
|
+
const font0 = FONTS[moji]
|
|
72
|
+
if (font0 == null) return 1.25
|
|
73
|
+
const font = is_font_set(font0) ? font0.light : font0
|
|
29
74
|
|
|
30
75
|
// get glyphs
|
|
31
76
|
const { unitsPerEm } = font
|
|
@@ -55,19 +100,30 @@ function emojiSizer(text: string): number {
|
|
|
55
100
|
|
|
56
101
|
type TextSizerArgs = {
|
|
57
102
|
font_family?: string
|
|
103
|
+
font_weight?: number
|
|
58
104
|
calc_size?: number
|
|
59
105
|
}
|
|
60
106
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (is_emoji(text)) return emojiSizer(text)
|
|
107
|
+
function textFont(font_family: string, font_weight: number): Font {
|
|
108
|
+
// get font info
|
|
64
109
|
const font = FONTS[font_family]
|
|
65
|
-
|
|
66
|
-
|
|
110
|
+
|
|
111
|
+
// match the static face browser font matching would select
|
|
112
|
+
if (!is_font_set(font)) return font
|
|
113
|
+
const weight = closest_weight(font_weight)
|
|
114
|
+
return font[weight]
|
|
67
115
|
}
|
|
68
116
|
|
|
69
|
-
function
|
|
70
|
-
const font =
|
|
117
|
+
function textSizer(text: string, { font_family = sans, font_weight = light, calc_size = D.calc_size }: TextSizerArgs = {}): number {
|
|
118
|
+
const font = textFont(font_family, font_weight)
|
|
119
|
+
const runs = splitEmojiRuns(text)
|
|
120
|
+
return sum(runs.map(run =>
|
|
121
|
+
run.emoji ? emojiSizer(run.text) :
|
|
122
|
+
(font.getAdvanceWidth(run.text, calc_size) / calc_size)
|
|
123
|
+
))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function fontVertical(font: Font, text: string): Limit {
|
|
71
127
|
const glyphs = font.stringToGlyphs(text)
|
|
72
128
|
const [yMins = [], yMaxs = []] = zip(...glyphs.map(g => [ g.yMin, g.yMax ]))
|
|
73
129
|
const units = font.unitsPerEm ?? 1000
|
|
@@ -76,6 +132,11 @@ function textVertical(text: string, { font_family = sans }: TextSizerArgs = {}):
|
|
|
76
132
|
return [ yMin / units, yMax / units ]
|
|
77
133
|
}
|
|
78
134
|
|
|
135
|
+
function textVertical(text: string, { font_family = sans, font_weight = light }: TextSizerArgs = {}): Limit {
|
|
136
|
+
const font = textFont(font_family, font_weight)
|
|
137
|
+
return fontVertical(font, text)
|
|
138
|
+
}
|
|
139
|
+
|
|
79
140
|
type TextMetrics = {
|
|
80
141
|
advance: number
|
|
81
142
|
vrange: Limit
|
package/src/lib/utils.ts
CHANGED
|
@@ -272,16 +272,6 @@ function map_object<T, U>(obj: Record<string, T>, fn: (k: string, v: T) => U): R
|
|
|
272
272
|
)
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
async function map_object_async<T, U>(obj: Record<string, T>, fn: (k: string, v: T) => Promise<U>): Promise<Record<string, U>> {
|
|
276
|
-
return Object.fromEntries(
|
|
277
|
-
await Promise.all(
|
|
278
|
-
Object.entries(obj).map(
|
|
279
|
-
async ([ k, v ]) => [ k, await fn(k, v) ]
|
|
280
|
-
)
|
|
281
|
-
)
|
|
282
|
-
)
|
|
283
|
-
}
|
|
284
|
-
|
|
285
275
|
function filter_object<T>(obj: Record<string, T>, fn: (k: string, v: T) => boolean): Record<string, T> {
|
|
286
276
|
return Object.fromEntries(
|
|
287
277
|
Object.entries(obj).filter(([ k, v ]) => fn(k, v))
|
|
@@ -915,4 +905,4 @@ function binary_search(arr: number[], t: number): number {
|
|
|
915
905
|
// export
|
|
916
906
|
//
|
|
917
907
|
|
|
918
|
-
export { is_browser, is_boolean, is_scalar, is_string, is_number, is_object, is_function, is_array, is_singleton, is_point, ensure_vector, ensure_singleton, ensure_function, check_singleton, check_array, check_string, gzip, zip, reshape, split, concat, squeeze, slice, intersperse, sum, prod, mean, all, any, cumsum, norm, normalize, range, linspace, enumerate, repeat, padvec, meshgrid, lingrid, map_object,
|
|
908
|
+
export { is_browser, is_boolean, is_scalar, is_string, is_number, is_object, is_function, is_array, is_singleton, is_point, ensure_vector, ensure_singleton, ensure_function, check_singleton, check_array, check_string, gzip, zip, reshape, split, concat, squeeze, slice, intersperse, sum, prod, mean, all, any, cumsum, norm, normalize, range, linspace, enumerate, repeat, padvec, meshgrid, lingrid, map_object, filter_object, compress_whitespace, exp, log, log10, sin, cos, tan, cot, abs, pow, sqrt, sign, floor, ceil, round, atan, atan2, isNan, isInf, minimum, maximum, heaviside, heavisign, abs_min, abs_max, min, max, clamp, rescale, sigmoid, logit, smoothstep, identity, invert, setSeed, random, uniform, normal, integer, broadcast_point, add2, sub2, mul2, div2, ensure_number, ensure_point, ensure_mnumber, ensure_mpoint, addm, subm, add2m, sub2m, make_mpoint, squeeze_mnumber, squeeze_mpoint, rect_size, rect_dims, rect_center, rect_radius, rect_aspect, rect_radial, norm_angle, split_limits, vector_angle, angle_direc, polar, side_direc, unit_direc, norm_side, rgba_repr, interp, palette, detect_coords, resolve_limits, join_limits, invert_orient, aspect_invariant, flip_rect, radial_rect, box_rect, rect_box, cbox_rect, rect_cbox, merge_rects, merge_points, merge_limits, merge_values, expand_limits, expand_rect, upright_rect, upright_limits, rounder, remap_rect, resizer, rescaler, rotate_aspect, prefix_split, prefix_join, binary_search }
|
package/src/meta.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { readFileSync, readdirSync } from 'fs'
|
|
4
4
|
|
|
5
5
|
// replace links with bold and push headings
|
|
6
|
-
function prepareText(text: string, sub: boolean =
|
|
6
|
+
function prepareText(text: string, sub: boolean = false): string {
|
|
7
7
|
text = text.replace(/\[(.*?)\]\((.*?)\)/g, '**$1**') // links to bold
|
|
8
8
|
if (sub) text = text.replace(/^# (.*?)$/mg, '## $1') // headings to sub-headings
|
|
9
9
|
return text.trim()
|
|
@@ -22,12 +22,12 @@ function prepareGalaCode(text: string): string {
|
|
|
22
22
|
return `**Code**\n\n${code}`
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
function prepareDocsPage(text: string, code: string): string {
|
|
26
|
-
return `${prepareText(text,
|
|
25
|
+
function prepareDocsPage(text: string, code: string, sub: boolean = false): string {
|
|
26
|
+
return `${prepareText(text, sub)}\n\n${prepareDocsCode(code)}`
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function prepareGalaPage(text: string, code: string): string {
|
|
30
|
-
return `${prepareText(text,
|
|
29
|
+
function prepareGalaPage(text: string, code: string, sub: boolean = false): string {
|
|
30
|
+
return `${prepareText(text, sub)}\n\n${prepareGalaCode(code)}`
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// index directory contents
|
package/src/render.ts
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
// Rasterize SVG to PNG via node-canvas
|
|
2
2
|
|
|
3
|
-
import { createCanvas, loadImage, registerFont } from 'canvas'
|
|
3
|
+
import { createCanvas, loadImage, registerFont, type ImageData as CanvasImageData } from 'canvas'
|
|
4
4
|
|
|
5
5
|
import type { Size } from './lib/types'
|
|
6
6
|
import { FONT_PATHS } from './fonts/fonts'
|
|
7
|
+
import { light, regular, bold } from './lib/const'
|
|
7
8
|
|
|
8
9
|
// register bundled fonts so SVG <text> resolves consistently
|
|
9
10
|
for (const [ family, path ] of Object.entries(FONT_PATHS)) {
|
|
10
|
-
|
|
11
|
+
if (typeof path == 'string') {
|
|
12
|
+
registerFont(path, { family })
|
|
13
|
+
} else {
|
|
14
|
+
registerFont(path.light, { family, weight: String(light) })
|
|
15
|
+
registerFont(path.regular, { family, weight: String(regular) })
|
|
16
|
+
registerFont(path.bold, { family, weight: String(bold) })
|
|
17
|
+
}
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
interface
|
|
20
|
+
interface RasterizeBaseArgs {
|
|
14
21
|
size?: Size
|
|
15
22
|
width?: number
|
|
16
23
|
height?: number
|
|
17
24
|
background?: string
|
|
18
25
|
}
|
|
19
26
|
|
|
27
|
+
interface RasterizePngArgs extends RasterizeBaseArgs {
|
|
28
|
+
pixelData?: false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface RasterizePixelArgs extends RasterizeBaseArgs {
|
|
32
|
+
pixelData: true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type RasterizeArgs = RasterizePngArgs | RasterizePixelArgs
|
|
36
|
+
|
|
20
37
|
interface FormatImageArgs {
|
|
21
38
|
imageId?: number | null
|
|
22
39
|
placementId?: number | null
|
|
@@ -26,7 +43,9 @@ interface FormatImageArgs {
|
|
|
26
43
|
cursorMovement?: boolean
|
|
27
44
|
}
|
|
28
45
|
|
|
29
|
-
async function rasterizeSvg(svg: string | Buffer,
|
|
46
|
+
async function rasterizeSvg(svg: string | Buffer, args: RasterizePixelArgs): Promise<CanvasImageData>
|
|
47
|
+
async function rasterizeSvg(svg: string | Buffer, args?: RasterizePngArgs): Promise<Buffer>
|
|
48
|
+
async function rasterizeSvg(svg: string | Buffer, { size, width, height, background, pixelData }: RasterizeArgs = {}): Promise<Buffer | CanvasImageData> {
|
|
30
49
|
const buf = typeof svg === 'string' ? Buffer.from(svg) : svg
|
|
31
50
|
const img = await loadImage(buf)
|
|
32
51
|
const w0 = size?.[0] ?? img.width
|
|
@@ -63,9 +82,9 @@ async function rasterizeSvg(svg: string | Buffer, { size, width, height, backgro
|
|
|
63
82
|
ctx.fillRect(0, 0, outW, outH)
|
|
64
83
|
}
|
|
65
84
|
|
|
66
|
-
// draw image to canvas and return
|
|
85
|
+
// draw image to canvas and return the requested raster representation
|
|
67
86
|
ctx.drawImage(img, 0, 0, outW, outH)
|
|
68
|
-
return canvas.toBuffer('image/png')
|
|
87
|
+
return pixelData ? ctx.getImageData(0, 0, outW, outH) : canvas.toBuffer('image/png')
|
|
69
88
|
}
|
|
70
89
|
|
|
71
90
|
// kitty image protocol
|
|
@@ -107,4 +126,4 @@ async function readStdin(): Promise<string> {
|
|
|
107
126
|
}
|
|
108
127
|
|
|
109
128
|
export { rasterizeSvg, formatImage, readStdin }
|
|
110
|
-
export type { RasterizeArgs, FormatImageArgs }
|
|
129
|
+
export type { RasterizeBaseArgs, RasterizePngArgs, RasterizePixelArgs, RasterizeArgs, FormatImageArgs }
|
package/scripts/claude.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// upload skill files for anthropic
|
|
2
|
-
|
|
3
|
-
import { createReadStream } from 'fs'
|
|
4
|
-
import Anthropic, { toFile } from '@anthropic-ai/sdk'
|
|
5
|
-
|
|
6
|
-
// create anthropic client
|
|
7
|
-
const apiKey = process.env.ANTHROPIC_API_KEY
|
|
8
|
-
const client = new Anthropic({ apiKey })
|
|
9
|
-
|
|
10
|
-
// directory paths
|
|
11
|
-
const skill_name = 'gum-jsx'
|
|
12
|
-
const skill_file = `${skill_name}.skill`
|
|
13
|
-
const skill_path = `skills/${skill_file}`
|
|
14
|
-
|
|
15
|
-
// upload skill files
|
|
16
|
-
const skill = await client.beta.skills.create({
|
|
17
|
-
files: [
|
|
18
|
-
await toFile(createReadStream(skill_path), skill_file),
|
|
19
|
-
],
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
// output env info
|
|
23
|
-
console.log(`
|
|
24
|
-
GUM_JSX_SKILL_ID=${skill.id}
|
|
25
|
-
`)
|
|
Binary file
|