@nowline/renderer 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +94 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/svg/icons.d.ts +26 -0
- package/dist/svg/icons.d.ts.map +1 -0
- package/dist/svg/icons.js +61 -0
- package/dist/svg/icons.js.map +1 -0
- package/dist/svg/ids.d.ts +7 -0
- package/dist/svg/ids.d.ts.map +1 -0
- package/dist/svg/ids.js +15 -0
- package/dist/svg/ids.js.map +1 -0
- package/dist/svg/render.d.ts +28 -0
- package/dist/svg/render.d.ts.map +1 -0
- package/dist/svg/render.js +1737 -0
- package/dist/svg/render.js.map +1 -0
- package/dist/svg/sanitize.d.ts +6 -0
- package/dist/svg/sanitize.d.ts.map +1 -0
- package/dist/svg/sanitize.js +396 -0
- package/dist/svg/sanitize.js.map +1 -0
- package/dist/svg/shadow.d.ts +5 -0
- package/dist/svg/shadow.d.ts.map +1 -0
- package/dist/svg/shadow.js +27 -0
- package/dist/svg/shadow.js.map +1 -0
- package/dist/svg/xml.d.ts +8 -0
- package/dist/svg/xml.d.ts.map +1 -0
- package/dist/svg/xml.js +54 -0
- package/dist/svg/xml.js.map +1 -0
- package/package.json +35 -0
- package/src/index.ts +7 -0
- package/src/svg/icons.ts +107 -0
- package/src/svg/ids.ts +12 -0
- package/src/svg/render.ts +2154 -0
- package/src/svg/sanitize.ts +395 -0
- package/src/svg/shadow.ts +38 -0
- package/src/svg/xml.ts +58 -0
package/dist/svg/xml.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Minimal, dependency-free XML string helpers. Every output SVG string runs
|
|
2
|
+
// through here; characters are escaped consistently so identical inputs
|
|
3
|
+
// produce identical bytes.
|
|
4
|
+
const AMP = /&/g;
|
|
5
|
+
const LT = /</g;
|
|
6
|
+
const GT = />/g;
|
|
7
|
+
const QUOTE = /"/g;
|
|
8
|
+
const APOS = /'/g;
|
|
9
|
+
export function escText(value) {
|
|
10
|
+
return value.replace(AMP, '&').replace(LT, '<').replace(GT, '>');
|
|
11
|
+
}
|
|
12
|
+
export function escAttr(value) {
|
|
13
|
+
return value
|
|
14
|
+
.replace(AMP, '&')
|
|
15
|
+
.replace(LT, '<')
|
|
16
|
+
.replace(GT, '>')
|
|
17
|
+
.replace(QUOTE, '"')
|
|
18
|
+
.replace(APOS, ''');
|
|
19
|
+
}
|
|
20
|
+
export function attrs(values) {
|
|
21
|
+
const keys = Object.keys(values).sort();
|
|
22
|
+
const parts = [];
|
|
23
|
+
for (const key of keys) {
|
|
24
|
+
const v = values[key];
|
|
25
|
+
if (v === null || v === undefined || v === false)
|
|
26
|
+
continue;
|
|
27
|
+
if (v === true) {
|
|
28
|
+
parts.push(key);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
parts.push(`${key}="${escAttr(String(v))}"`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return parts.length ? ` ${parts.join(' ')}` : '';
|
|
35
|
+
}
|
|
36
|
+
export function tag(name, attributes, inner) {
|
|
37
|
+
if (inner === undefined || inner === '') {
|
|
38
|
+
return `<${name}${attrs(attributes)}/>`;
|
|
39
|
+
}
|
|
40
|
+
return `<${name}${attrs(attributes)}>${inner}</${name}>`;
|
|
41
|
+
}
|
|
42
|
+
export function textTag(attributes, content) {
|
|
43
|
+
return `<text${attrs(attributes)}>${escText(content)}</text>`;
|
|
44
|
+
}
|
|
45
|
+
// Fixed-precision number formatter — avoids locale-dependent toFixed quirks
|
|
46
|
+
// and guarantees deterministic output across Node versions.
|
|
47
|
+
export function num(n) {
|
|
48
|
+
if (!Number.isFinite(n))
|
|
49
|
+
return '0';
|
|
50
|
+
if (Number.isInteger(n))
|
|
51
|
+
return n.toString();
|
|
52
|
+
return (Math.round(n * 100) / 100).toString();
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=xml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xml.js","sourceRoot":"","sources":["../../src/svg/xml.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,wEAAwE;AACxE,2BAA2B;AAE3B,MAAM,GAAG,GAAG,IAAI,CAAC;AACjB,MAAM,EAAE,GAAG,IAAI,CAAC;AAChB,MAAM,EAAE,GAAG,IAAI,CAAC;AAChB,MAAM,KAAK,GAAG,IAAI,CAAC;AACnB,MAAM,IAAI,GAAG,IAAI,CAAC;AAElB,MAAM,UAAU,OAAO,CAAC,KAAa;IACjC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAa;IACjC,OAAO,KAAK;SACP,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC;SACrB,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC;SACnB,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC;SACxB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAChC,CAAC;AAID,MAAM,UAAU,KAAK,CAAC,MAAiC;IACnD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IACxC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,KAAK;YAAE,SAAS;QAC3D,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACb,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;aAAM,CAAC;YACJ,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACjD,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,UAAqC,EAAE,KAAc;IACnF,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACtC,OAAO,IAAI,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;IAC5C,CAAC;IACD,OAAO,IAAI,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,KAAK,IAAI,GAAG,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,UAAqC,EAAE,OAAe;IAC1E,OAAO,QAAQ,KAAK,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;AAClE,CAAC;AAED,4EAA4E;AAC5E,4DAA4D;AAC5D,MAAM,UAAU,GAAG,CAAC,CAAS;IACzB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,GAAG,CAAC;IACpC,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC7C,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC;AAClD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nowline/renderer",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Nowline SVG renderer — positioned model → SVG string",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/",
|
|
17
|
+
"src/"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@nowline/layout": "0.2.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.0.0",
|
|
24
|
+
"langium": "~4.2.2",
|
|
25
|
+
"typescript": "~5.7.0",
|
|
26
|
+
"vitest": "^3.1.0",
|
|
27
|
+
"@nowline/core": "0.2.0"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -b tsconfig.json",
|
|
31
|
+
"watch": "tsc -b tsconfig.json --watch",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
package/src/svg/icons.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Built-in link icon paths. All drawn to a 16x16 box; renderer positions them.
|
|
2
|
+
|
|
3
|
+
export const LINK_ICON_PATHS: Record<string, string> = {
|
|
4
|
+
// Generic link/chain icon.
|
|
5
|
+
generic:
|
|
6
|
+
'M7 3h2a4 4 0 0 1 0 8H7a4 4 0 0 1 0-8Zm0 2a2 2 0 1 0 0 4h2a2 2 0 1 0 0-4H7Zm6 0h2a4 4 0 0 1 0 8h-2a4 4 0 0 1 0-8Z',
|
|
7
|
+
// Abstract Linear triangle.
|
|
8
|
+
linear: 'M2 8l6-6 6 6-6 6Z',
|
|
9
|
+
// GitHub octocat silhouette (simplified).
|
|
10
|
+
github: 'M8 1C4.14 1 1 4.14 1 8c0 3.1 2 5.7 4.8 6.6.35.07.48-.15.48-.34v-1.2c-1.95.43-2.36-.83-2.36-.83-.32-.82-.78-1.04-.78-1.04-.63-.43.05-.42.05-.42.71.05 1.08.73 1.08.73.62 1.06 1.63.75 2.03.57.06-.45.24-.75.44-.92-1.56-.18-3.2-.78-3.2-3.48 0-.77.27-1.4.72-1.9-.07-.18-.31-.9.07-1.88 0 0 .59-.19 1.93.72.56-.16 1.16-.24 1.76-.24.6 0 1.2.08 1.76.24 1.34-.9 1.93-.72 1.93-.72.38.98.14 1.7.07 1.88.45.5.72 1.13.72 1.9 0 2.7-1.65 3.3-3.22 3.48.25.21.47.63.47 1.27v1.88c0 .19.13.41.49.34C13 13.7 15 11.1 15 8c0-3.86-3.14-7-7-7Z',
|
|
11
|
+
// Jira-ish blue diamond.
|
|
12
|
+
jira: 'M8 1 1 8l7 7 7-7Zm0 3.5L11.5 8 8 11.5 4.5 8Z',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// --- Capacity-icon library ---
|
|
16
|
+
//
|
|
17
|
+
// Built-in named glyphs for the `capacity-icon:` style property and (where
|
|
18
|
+
// reused) the `icon:` style property. Each glyph is a small inline SVG fragment
|
|
19
|
+
// drawn on a 24x24 viewBox using `currentColor` so the renderer can paint it in
|
|
20
|
+
// the resolved entity text color and at any pixel size by setting the wrapping
|
|
21
|
+
// `<svg>` element's `width` / `height`.
|
|
22
|
+
//
|
|
23
|
+
// Adapted from Lucide (https://lucide.dev) under the ISC License — paths are
|
|
24
|
+
// transcribed verbatim; only the wrapping `<svg>` element differs (we set
|
|
25
|
+
// width / height at render time and bind colors to currentColor).
|
|
26
|
+
//
|
|
27
|
+
// Why a curated SVG library instead of Unicode glyphs? Per spec, built-in
|
|
28
|
+
// capacity-icon names render identically across every output platform (web,
|
|
29
|
+
// CLI export, embedded SVG, etc.). Unicode emoji (`👤`, `⏱`) render in
|
|
30
|
+
// platform-specific fonts (Apple, Google, Microsoft, Linux), so the same DSL
|
|
31
|
+
// would produce visually inconsistent output. SVG paths are pixel-deterministic.
|
|
32
|
+
// Authors who *want* the host-platform emoji can use an inline literal
|
|
33
|
+
// (`capacity-icon:"👤"`) or declare a custom symbol via the `symbol` keyword.
|
|
34
|
+
//
|
|
35
|
+
// `multiplier` is intentionally absent from this map: U+00D7 MULTIPLICATION
|
|
36
|
+
// SIGN is a stable typographic operator with consistent rendering across every
|
|
37
|
+
// system font, so it renders as a `<text>` element instead of an SVG path.
|
|
38
|
+
export interface CapacityIconSvg {
|
|
39
|
+
/** SVG viewBox attribute. All glyphs are normalized to a 24x24 box. */
|
|
40
|
+
viewBox: string;
|
|
41
|
+
/**
|
|
42
|
+
* Inline SVG fragment — paths/circles/lines using `currentColor` for stroke
|
|
43
|
+
* and `none` (or `currentColor`) for fill. The renderer wraps this in an
|
|
44
|
+
* `<svg>` element whose `width`/`height` set the rendered size and whose
|
|
45
|
+
* `color` (or an enclosing `text`/`g` color) drives the glyph color.
|
|
46
|
+
*/
|
|
47
|
+
body: string;
|
|
48
|
+
/** Visible 1-3 ASCII character fallback used when SVG output is constrained
|
|
49
|
+
* to ASCII (e.g. terminal text-mode export). Must match the `Built-in glyph
|
|
50
|
+
* table` in specs/rendering.md. */
|
|
51
|
+
ascii: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const CAPACITY_ICON_SVG: Record<string, CapacityIconSvg> = {
|
|
55
|
+
// Lucide `user` — a single figure (head + shoulders).
|
|
56
|
+
person: {
|
|
57
|
+
viewBox: '0 0 24 24',
|
|
58
|
+
body:
|
|
59
|
+
'<circle cx="12" cy="8" r="4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' +
|
|
60
|
+
'<path d="M4 21a8 8 0 0 1 16 0" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
61
|
+
ascii: 'p',
|
|
62
|
+
},
|
|
63
|
+
// Lucide `users` — paired figures (foreground + smaller silhouette behind).
|
|
64
|
+
people: {
|
|
65
|
+
viewBox: '0 0 24 24',
|
|
66
|
+
body:
|
|
67
|
+
'<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' +
|
|
68
|
+
'<circle cx="9" cy="7" r="4" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' +
|
|
69
|
+
'<path d="M22 21v-2a4 4 0 0 0-3-3.87" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' +
|
|
70
|
+
'<path d="M16 3.13a4 4 0 0 1 0 7.75" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
71
|
+
ascii: 'P',
|
|
72
|
+
},
|
|
73
|
+
// Lucide `star` — five-pointed star, filled with currentColor for visual weight.
|
|
74
|
+
points: {
|
|
75
|
+
viewBox: '0 0 24 24',
|
|
76
|
+
body: '<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" fill="currentColor" stroke="currentColor" stroke-width="1" stroke-linejoin="round"/>',
|
|
77
|
+
ascii: '*',
|
|
78
|
+
},
|
|
79
|
+
// Lucide `timer` — stopwatch silhouette (face circle, top crown line, hand).
|
|
80
|
+
time: {
|
|
81
|
+
viewBox: '0 0 24 24',
|
|
82
|
+
body:
|
|
83
|
+
'<line x1="10" x2="14" y1="2" y2="2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' +
|
|
84
|
+
'<line x1="12" x2="15" y1="14" y2="11" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>' +
|
|
85
|
+
'<circle cx="12" cy="14" r="8" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
86
|
+
ascii: 't',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Returns true when `name` is a renderer-curated capacity glyph. Useful for
|
|
91
|
+
* differentiating built-ins from custom glyph declarations and inline literals
|
|
92
|
+
* in the upcoming layout/render passes (m6/m7). */
|
|
93
|
+
export function hasCapacityIconSvg(name: string): boolean {
|
|
94
|
+
return Object.hasOwn(CAPACITY_ICON_SVG, name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** ASCII fallbacks for every built-in capacity-icon name, including the ones
|
|
98
|
+
* that render as text (`multiplier`) or render nothing (`none`). The spec's
|
|
99
|
+
* `Built-in glyph table` is the source of truth — keep these in sync. */
|
|
100
|
+
export const CAPACITY_ICON_ASCII: Record<string, string> = {
|
|
101
|
+
none: '',
|
|
102
|
+
multiplier: 'x',
|
|
103
|
+
person: CAPACITY_ICON_SVG.person.ascii,
|
|
104
|
+
people: CAPACITY_ICON_SVG.people.ascii,
|
|
105
|
+
points: CAPACITY_ICON_SVG.points.ascii,
|
|
106
|
+
time: CAPACITY_ICON_SVG.time.ascii,
|
|
107
|
+
};
|
package/src/svg/ids.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Deterministic id generator. A single counter per renderSvg() call yields
|
|
2
|
+
// ids like `nl-0`, `nl-1`, ... so identical inputs emit identical SVGs.
|
|
3
|
+
// Never uses Math.random, Date.now, or ambient state.
|
|
4
|
+
|
|
5
|
+
export class IdGenerator {
|
|
6
|
+
private counter = 0;
|
|
7
|
+
constructor(private readonly prefix: string = 'nl') {}
|
|
8
|
+
next(label?: string): string {
|
|
9
|
+
const id = `${this.prefix}-${this.counter++}`;
|
|
10
|
+
return label ? `${id}-${label}` : id;
|
|
11
|
+
}
|
|
12
|
+
}
|