@svg-gen/tech-svg-generator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Satishkumar Dhule
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,320 @@
1
+ # Tech SVG Generator
2
+
3
+ Generate clean, professional SVG illustrations for technical content. Perfect for blogs, documentation, presentations, and README files.
4
+
5
+ [![CI](https://github.com/open-tech-svg-gen/tech-svg-generator/actions/workflows/ci.yml/badge.svg)](https://github.com/open-tech-svg-gen/tech-svg-generator/actions/workflows/ci.yml)
6
+ [![npm version](https://img.shields.io/npm/v/tech-svg-generator)](https://www.npmjs.com/package/tech-svg-generator)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)
9
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-green.svg)](https://nodejs.org/)
10
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/open-tech-svg-gen/tech-svg-generator/pulls)
11
+
12
+ ## Features
13
+
14
+ - 🎨 **14 Scene Types** - Architecture, scaling, database, deployment, security, debugging, testing, performance, API, monitoring, frontend, success, error, and default
15
+ - 🗨️ **Cartoon Strips** - Generate comic-style strips with characters, emotions, and speech bubbles
16
+ - 📝 **YAML/JSON Support** - Define scenes and cartoons in declarative format
17
+ - 🔍 **Auto-Detection** - Automatically selects the best scene based on title keywords
18
+ - 🌙 **Multiple Themes** - GitHub Dark (default), Dracula, Nord, One Dark
19
+ - 👤 **6 Character Presets** - Diverse developer characters with customizable styles
20
+ - 😀 **9 Emotions** - Neutral, happy, sad, angry, surprised, thinking, confused, excited, worried
21
+ - 📦 **Minimal Dependencies** - Only js-yaml for YAML parsing
22
+ - 🖼️ **Consistent Style** - Professional, clean illustrations every time
23
+ - ✏️ **Monospace Fonts** - All text uses developer-friendly monospace fonts
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install tech-svg-generator
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```typescript
34
+ import { generateSVG } from 'tech-svg-generator';
35
+
36
+ // Auto-detect scene from title
37
+ const result = generateSVG('Database Replication Strategies');
38
+ console.log(result.svg); // SVG string
39
+ console.log(result.scene); // 'database'
40
+
41
+ // Save to file
42
+ import fs from 'fs';
43
+ fs.writeFileSync('illustration.svg', result.svg);
44
+ ```
45
+
46
+ ## API
47
+
48
+ ### `generateSVG(title, content?, options?)`
49
+
50
+ Generate an SVG illustration.
51
+
52
+ **Parameters:**
53
+ - `title` (string) - The title/topic for the illustration
54
+ - `content` (string, optional) - Additional content for better scene detection
55
+ - `options` (object, optional):
56
+ - `width` (number) - SVG width (default: 700)
57
+ - `height` (number) - SVG height (default: 420)
58
+ - `theme` (string) - Theme name: 'github-dark', 'dracula', 'nord', 'one-dark'
59
+ - `scene` (string) - Force a specific scene type
60
+
61
+ **Returns:** `GenerateResult`
62
+ - `svg` (string) - The generated SVG string
63
+ - `scene` (string) - The detected/used scene type
64
+ - `width` (number) - SVG width
65
+ - `height` (number) - SVG height
66
+
67
+ ### `detectScene(title, content?)`
68
+
69
+ Detect the best scene type for given text.
70
+
71
+ ```typescript
72
+ import { detectScene } from 'tech-svg-generator';
73
+
74
+ detectScene('Kubernetes Pod Scheduling'); // 'scaling'
75
+ detectScene('JWT Authentication Flow'); // 'security'
76
+ detectScene('React Performance Tips'); // 'frontend'
77
+ ```
78
+
79
+ ### `getAvailableScenes()`
80
+
81
+ Get list of all available scene types.
82
+
83
+ ```typescript
84
+ import { getAvailableScenes } from 'tech-svg-generator';
85
+
86
+ console.log(getAvailableScenes());
87
+ // ['architecture', 'scaling', 'database', 'deployment', 'security', ...]
88
+ ```
89
+
90
+ ## Scene Types
91
+
92
+ | Scene | Keywords | Description |
93
+ |-------|----------|-------------|
94
+ | `architecture` | architecture, design, pattern, system, microservice | System architecture diagrams |
95
+ | `scaling` | scale, kubernetes, docker, cluster, load | Horizontal scaling visualization |
96
+ | `database` | database, sql, postgres, redis, cache | Database with replication |
97
+ | `deployment` | deploy, ci, cd, pipeline, release | CI/CD pipeline flow |
98
+ | `security` | security, auth, jwt, oauth, firewall | Security flow diagram |
99
+ | `debugging` | debug, bug, error, fix, trace | Code debugging scene |
100
+ | `testing` | test, jest, coverage, unit, e2e | Test results dashboard |
101
+ | `performance` | performance, optimize, latency, cpu | Performance metrics |
102
+ | `api` | api, rest, graphql, endpoint, http | API request/response |
103
+ | `monitoring` | monitor, metric, log, alert, grafana | Monitoring dashboard |
104
+ | `frontend` | frontend, react, vue, css, component | Web vitals metrics |
105
+ | `success` | success, complete, launch, shipped | Success celebration |
106
+ | `error` | fail, crash, outage, incident, 503 | Error/incident scene |
107
+ | `default` | - | Generic system overview |
108
+
109
+ ## Themes
110
+
111
+ ```typescript
112
+ import { generateSVG, THEMES } from 'tech-svg-generator';
113
+
114
+ // Use a specific theme
115
+ const result = generateSVG('API Gateway Design', '', { theme: 'dracula' });
116
+
117
+ // Available themes
118
+ console.log(Object.keys(THEMES)); // ['github-dark', 'dracula', 'nord', 'one-dark']
119
+ ```
120
+
121
+ ## Examples
122
+
123
+ ### Blog Post Illustration
124
+
125
+ ```typescript
126
+ import { generateSVG } from 'tech-svg-generator';
127
+ import fs from 'fs';
128
+
129
+ const posts = [
130
+ 'How Netflix Handles Database Sharding',
131
+ 'Implementing Zero Trust Security',
132
+ 'React Server Components Deep Dive',
133
+ ];
134
+
135
+ posts.forEach((title, i) => {
136
+ const { svg, scene } = generateSVG(title);
137
+ fs.writeFileSync(`post-${i + 1}-${scene}.svg`, svg);
138
+ });
139
+ ```
140
+
141
+ ### Force Specific Scene
142
+
143
+ ```typescript
144
+ const result = generateSVG('My Custom Topic', '', { scene: 'architecture' });
145
+ ```
146
+
147
+ ### Custom Dimensions
148
+
149
+ ```typescript
150
+ const result = generateSVG('Wide Banner', '', {
151
+ width: 1200,
152
+ height: 300
153
+ });
154
+ ```
155
+
156
+ ## Output Example
157
+
158
+ The generated SVGs feature:
159
+ - Clean, fixed-grid layouts with no overlapping elements
160
+ - Professional GitHub-inspired dark theme
161
+ - Contextual icons and metrics
162
+ - Multi-line title support for long titles
163
+ - Consistent monospace typography
164
+
165
+ ## Cartoon Strips
166
+
167
+ Generate comic-style strips with characters having conversations!
168
+
169
+ ### Quick Start
170
+
171
+ ```typescript
172
+ import { generateCartoonStrip } from 'tech-svg-generator';
173
+ import fs from 'fs';
174
+
175
+ const svg = generateCartoonStrip({
176
+ title: 'The Daily Standup',
177
+ theme: 'github-dark',
178
+ width: 800,
179
+ height: 400,
180
+ layout: '2x1',
181
+ characters: {
182
+ dev: { name: 'Developer', preset: 'dev1' },
183
+ pm: { name: 'PM', preset: 'dev2' }
184
+ },
185
+ panels: [
186
+ {
187
+ characters: ['dev', 'pm'],
188
+ caption: 'Monday morning...',
189
+ dialogue: [
190
+ { character: 'pm', text: 'How is the feature going?', emotion: 'neutral' },
191
+ { character: 'dev', text: 'Almost done!', emotion: 'happy' }
192
+ ]
193
+ },
194
+ {
195
+ characters: ['dev', 'pm'],
196
+ caption: 'Friday...',
197
+ dialogue: [
198
+ { character: 'dev', text: 'Shipped!', emotion: 'excited' },
199
+ { character: 'pm', text: 'Great work!', emotion: 'happy' }
200
+ ]
201
+ }
202
+ ]
203
+ });
204
+
205
+ fs.writeFileSync('standup.svg', svg);
206
+ ```
207
+
208
+ ### Character Presets
209
+
210
+ | Preset | Description |
211
+ |--------|-------------|
212
+ | `alex` | Indigo short hair, blue hoodie, glasses |
213
+ | `sam` | Purple curly hair, green t-shirt |
214
+ | `jordan` | Blonde long hair, red shirt, headphones |
215
+ | `casey` | Dark spiky hair, purple hoodie |
216
+ | `riley` | Pink ponytail, indigo t-shirt |
217
+ | `morgan` | Teal mohawk, orange hoodie, sunglasses |
218
+ | `taylor` | Red short hair, dark formal attire |
219
+ | `robot` | Gray robot character with blue accents |
220
+
221
+ Legacy aliases (`dev1`-`dev5`) are also supported for backward compatibility.
222
+
223
+ ### Emotions
224
+
225
+ Characters can express: `neutral`, `happy`, `sad`, `angry`, `surprised`, `thinking`, `confused`, `excited`, `worried`
226
+
227
+ ### Speech Bubble Types
228
+
229
+ - `speech` (default) - Regular speech bubble
230
+ - `thought` - Cloud-style thought bubble
231
+ - `shout` - Spiky exclamation bubble
232
+
233
+ ### Layout Options
234
+
235
+ - `auto` - Automatically arrange panels
236
+ - `1x2`, `2x1` - Single row/column
237
+ - `2x2` - 2x2 grid
238
+ - `3x1`, `1x3` - Three panels in row/column
239
+ - `2x3`, `3x2` - Six panel layouts
240
+
241
+ ## YAML/JSON Scene Descriptions
242
+
243
+ Define scenes and cartoons in declarative YAML or JSON format!
244
+
245
+ ### Scene from YAML
246
+
247
+ ```typescript
248
+ import { generateFromYAML } from 'tech-svg-generator';
249
+
250
+ const yaml = `
251
+ type: scene
252
+ title: "Database Migration Strategy"
253
+ content: "PostgreSQL replication and failover"
254
+ scene: database
255
+ theme: github-dark
256
+ width: 700
257
+ height: 420
258
+ `;
259
+
260
+ const svg = generateFromYAML(yaml);
261
+ ```
262
+
263
+ ### Cartoon from YAML
264
+
265
+ ```typescript
266
+ import { generateFromYAML } from 'tech-svg-generator';
267
+
268
+ const yaml = `
269
+ type: cartoon
270
+ title: "Code Review"
271
+ theme: dracula
272
+ layout: "2x1"
273
+
274
+ characters:
275
+ alice:
276
+ name: Alice
277
+ preset: dev1
278
+ bob:
279
+ name: Bob
280
+ preset: dev2
281
+
282
+ panels:
283
+ - characters: [alice, bob]
284
+ dialogue:
285
+ - character: alice
286
+ text: "Can you review my PR?"
287
+ emotion: happy
288
+ - character: bob
289
+ text: "Sure!"
290
+ emotion: neutral
291
+ `;
292
+
293
+ const svg = generateFromYAML(yaml);
294
+ ```
295
+
296
+ ### JSON Support
297
+
298
+ ```typescript
299
+ import { generateFromJSON } from 'tech-svg-generator';
300
+
301
+ const json = JSON.stringify({
302
+ type: 'cartoon',
303
+ title: 'Debugging',
304
+ characters: {
305
+ dev: { name: 'Dev', preset: 'dev1' }
306
+ },
307
+ panels: [{
308
+ characters: ['dev'],
309
+ dialogue: [
310
+ { character: 'dev', text: 'Found it!', emotion: 'excited' }
311
+ ]
312
+ }]
313
+ });
314
+
315
+ const svg = generateFromJSON(json);
316
+ ```
317
+
318
+ ## License
319
+
320
+ MIT © Satishkumar Dhule
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Cartoon strip generator with characters and speech bubbles
3
+ */
4
+ import type { ThemeColors } from './themes.js';
5
+ import { type Emotion } from './characters.js';
6
+ export interface DialogLine {
7
+ /** Character ID speaking */
8
+ character: string;
9
+ /** The dialogue text */
10
+ text: string;
11
+ /** Emotion for this line */
12
+ emotion?: Emotion;
13
+ /** Type of speech bubble */
14
+ type?: 'speech' | 'thought' | 'shout';
15
+ }
16
+ export interface CartoonPanel {
17
+ /** Characters in this panel (by ID) */
18
+ characters: string[];
19
+ /** Dialogue lines in order */
20
+ dialogue: DialogLine[];
21
+ /** Optional panel title/caption */
22
+ caption?: string;
23
+ }
24
+ export interface CartoonStripConfig {
25
+ /** Title of the strip */
26
+ title?: string;
27
+ /** Character definitions */
28
+ characters: Record<string, {
29
+ name: string;
30
+ preset?: string;
31
+ style?: any;
32
+ }>;
33
+ /** Panels in the strip */
34
+ panels: CartoonPanel[];
35
+ /** Grid layout: 'auto' | '1x2' | '2x1' | '2x2' | '1x3' | '3x1' | '2x3' | '3x2' */
36
+ layout?: string;
37
+ /** Theme name */
38
+ theme?: string;
39
+ /** Total width */
40
+ width?: number;
41
+ /** Total height */
42
+ height?: number;
43
+ }
44
+ /**
45
+ * Render a speech bubble
46
+ */
47
+ declare function speechBubble(x: number, y: number, text: string, colors: ThemeColors, type?: 'speech' | 'thought' | 'shout', tailDirection?: 'left' | 'right' | 'down', maxWidth?: number): string;
48
+ /**
49
+ * Generate a cartoon strip SVG
50
+ */
51
+ export declare function generateCartoonStrip(config: CartoonStripConfig): string;
52
+ export { speechBubble };
53
+ //# sourceMappingURL=cartoon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cartoon.d.ts","sourceRoot":"","sources":["../src/cartoon.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAoD,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAKjG,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,4BAA4B;IAC5B,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;CACvC;AAED,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,8BAA8B;IAC9B,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IAC3E,0BAA0B;IAC1B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,iBAAS,YAAY,CACnB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,IAAI,GAAE,QAAQ,GAAG,SAAS,GAAG,OAAkB,EAC/C,aAAa,GAAE,MAAM,GAAG,OAAO,GAAG,MAAe,EACjD,QAAQ,GAAE,MAAY,GACrB,MAAM,CAuGR;AAkHD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAqDvE;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Cartoon strip generator with characters and speech bubbles
3
+ */
4
+ import { getTheme } from './themes.js';
5
+ import { renderCharacter, createCharacter } from './characters.js';
6
+ import { escapeHtml } from './primitives.js';
7
+ const FONT = "'SF Mono', Menlo, Monaco, 'Courier New', monospace";
8
+ /**
9
+ * Render a speech bubble
10
+ */
11
+ function speechBubble(x, y, text, colors, type = 'speech', tailDirection = 'down', maxWidth = 180) {
12
+ // Word wrap text
13
+ const words = text.split(' ');
14
+ const lines = [];
15
+ let currentLine = '';
16
+ const charsPerLine = Math.floor(maxWidth / 7);
17
+ for (const word of words) {
18
+ if ((currentLine + ' ' + word).trim().length <= charsPerLine) {
19
+ currentLine = (currentLine + ' ' + word).trim();
20
+ }
21
+ else {
22
+ if (currentLine)
23
+ lines.push(currentLine);
24
+ currentLine = word;
25
+ }
26
+ }
27
+ if (currentLine)
28
+ lines.push(currentLine);
29
+ // Limit lines
30
+ if (lines.length > 4) {
31
+ lines.length = 4;
32
+ lines[3] = lines[3].substring(0, charsPerLine - 3) + '...';
33
+ }
34
+ const lineHeight = 16;
35
+ const padding = 12;
36
+ const bubbleWidth = Math.min(maxWidth, Math.max(...lines.map(l => l.length * 7)) + padding * 2);
37
+ const bubbleHeight = lines.length * lineHeight + padding * 2;
38
+ let bubblePath;
39
+ let tailPath;
40
+ if (type === 'thought') {
41
+ // Thought bubble with cloud shape
42
+ bubblePath = `
43
+ <ellipse cx="${x}" cy="${y}" rx="${bubbleWidth / 2}" ry="${bubbleHeight / 2}" fill="${colors.card}" stroke="${colors.border}" stroke-width="2"/>
44
+ `;
45
+ const tailX = tailDirection === 'left' ? x - bubbleWidth / 2 - 5 : tailDirection === 'right' ? x + bubbleWidth / 2 + 5 : x;
46
+ const tailY = y + bubbleHeight / 2 + 10;
47
+ tailPath = `
48
+ <circle cx="${tailX}" cy="${tailY}" r="6" fill="${colors.card}" stroke="${colors.border}" stroke-width="2"/>
49
+ <circle cx="${tailX + (tailDirection === 'left' ? -8 : tailDirection === 'right' ? 8 : 0)}" cy="${tailY + 12}" r="4" fill="${colors.card}" stroke="${colors.border}" stroke-width="2"/>
50
+ `;
51
+ }
52
+ else if (type === 'shout') {
53
+ // Spiky shout bubble
54
+ const spikes = 8;
55
+ const innerR = Math.min(bubbleWidth, bubbleHeight) / 2;
56
+ const outerR = innerR * 1.3;
57
+ let points = '';
58
+ for (let i = 0; i < spikes * 2; i++) {
59
+ const angle = (i * Math.PI) / spikes;
60
+ const r = i % 2 === 0 ? outerR : innerR;
61
+ const px = x + r * Math.cos(angle - Math.PI / 2);
62
+ const py = y + r * Math.sin(angle - Math.PI / 2) * (bubbleHeight / bubbleWidth);
63
+ points += `${px},${py} `;
64
+ }
65
+ bubblePath = `<polygon points="${points.trim()}" fill="${colors.card}" stroke="${colors.orange}" stroke-width="2"/>`;
66
+ tailPath = '';
67
+ }
68
+ else {
69
+ // Regular speech bubble
70
+ const rx = 12;
71
+ bubblePath = `<rect x="${x - bubbleWidth / 2}" y="${y - bubbleHeight / 2}" width="${bubbleWidth}" height="${bubbleHeight}" rx="${rx}" fill="${colors.card}" stroke="${colors.border}" stroke-width="2"/>`;
72
+ // Tail pointing to speaker
73
+ const tailSize = 12;
74
+ let tx, ty, t1x, t1y, t2x, t2y;
75
+ if (tailDirection === 'left') {
76
+ tx = x - bubbleWidth / 2 - tailSize;
77
+ ty = y + bubbleHeight / 4;
78
+ t1x = x - bubbleWidth / 2;
79
+ t1y = y;
80
+ t2x = x - bubbleWidth / 2;
81
+ t2y = y + bubbleHeight / 4;
82
+ }
83
+ else if (tailDirection === 'right') {
84
+ tx = x + bubbleWidth / 2 + tailSize;
85
+ ty = y + bubbleHeight / 4;
86
+ t1x = x + bubbleWidth / 2;
87
+ t1y = y;
88
+ t2x = x + bubbleWidth / 2;
89
+ t2y = y + bubbleHeight / 4;
90
+ }
91
+ else {
92
+ tx = x;
93
+ ty = y + bubbleHeight / 2 + tailSize;
94
+ t1x = x - 8;
95
+ t1y = y + bubbleHeight / 2;
96
+ t2x = x + 8;
97
+ t2y = y + bubbleHeight / 2;
98
+ }
99
+ tailPath = `<polygon points="${t1x},${t1y} ${tx},${ty} ${t2x},${t2y}" fill="${colors.card}" stroke="${colors.border}" stroke-width="2"/>
100
+ <line x1="${t1x}" y1="${t1y}" x2="${t2x}" y2="${t2y}" stroke="${colors.card}" stroke-width="4"/>`;
101
+ }
102
+ const textContent = lines.map((line, i) => `<text x="${x}" y="${y - (lines.length - 1) * lineHeight / 2 + i * lineHeight + 5}" text-anchor="middle" fill="${colors.text}" font-size="12" font-family="${FONT}">${escapeHtml(line)}</text>`).join('');
103
+ return `
104
+ <g class="speech-bubble">
105
+ ${bubblePath}
106
+ ${tailPath}
107
+ ${textContent}
108
+ </g>
109
+ `;
110
+ }
111
+ /**
112
+ * Calculate grid layout dimensions
113
+ */
114
+ function calculateLayout(panelCount, layout, totalWidth, totalHeight) {
115
+ let cols, rows;
116
+ if (layout === 'auto') {
117
+ if (panelCount <= 2) {
118
+ cols = panelCount;
119
+ rows = 1;
120
+ }
121
+ else if (panelCount <= 4) {
122
+ cols = 2;
123
+ rows = Math.ceil(panelCount / 2);
124
+ }
125
+ else if (panelCount <= 6) {
126
+ cols = 3;
127
+ rows = Math.ceil(panelCount / 3);
128
+ }
129
+ else {
130
+ cols = 4;
131
+ rows = Math.ceil(panelCount / 4);
132
+ }
133
+ }
134
+ else {
135
+ const match = layout.match(/(\d+)x(\d+)/);
136
+ if (match) {
137
+ cols = parseInt(match[1]);
138
+ rows = parseInt(match[2]);
139
+ }
140
+ else {
141
+ cols = 2;
142
+ rows = Math.ceil(panelCount / 2);
143
+ }
144
+ }
145
+ const gap = 10;
146
+ const panelWidth = (totalWidth - gap * (cols + 1)) / cols;
147
+ const panelHeight = (totalHeight - gap * (rows + 1) - 40) / rows; // 40 for title
148
+ return { cols, rows, panelWidth, panelHeight };
149
+ }
150
+ /**
151
+ * Render a single panel
152
+ */
153
+ function renderPanel(panel, characters, x, y, width, height, colors, panelIndex) {
154
+ const panelChars = panel.characters.map(id => characters.get(id)).filter(Boolean);
155
+ const charCount = panelChars.length;
156
+ // Panel background
157
+ let content = `
158
+ <rect x="${x}" y="${y}" width="${width}" height="${height}" rx="8" fill="${colors.elevated}" stroke="${colors.border}" stroke-width="2"/>
159
+ `;
160
+ // Caption if present
161
+ if (panel.caption) {
162
+ content += `<text x="${x + width / 2}" y="${y + 18}" text-anchor="middle" fill="${colors.muted}" font-size="10" font-family="${FONT}">${escapeHtml(panel.caption)}</text>`;
163
+ }
164
+ // Position characters - adjusted for new larger character design
165
+ const charY = y + height - 100;
166
+ const charSpacing = width / (charCount + 1);
167
+ const charScale = Math.min(0.7, (height - 150) / 180); // Scale based on panel height
168
+ panelChars.forEach((char, i) => {
169
+ const charX = x + charSpacing * (i + 1);
170
+ const facing = i < charCount / 2 ? 'right' : 'left';
171
+ // Find emotion for this character in dialogue
172
+ const charDialogue = panel.dialogue.find(d => d.character === char.id);
173
+ const emotion = charDialogue?.emotion || 'neutral';
174
+ content += renderCharacter(charX, charY, char, emotion, charScale, facing);
175
+ });
176
+ // Render dialogue bubbles - positioned above characters
177
+ const dialogueStartY = y + (panel.caption ? 35 : 20);
178
+ const dialogueEndY = charY - 60 * charScale;
179
+ const bubbleSpacing = Math.min(70, (dialogueEndY - dialogueStartY) / Math.max(panel.dialogue.length, 1));
180
+ panel.dialogue.forEach((line, i) => {
181
+ const charIndex = panelChars.findIndex(c => c.id === line.character);
182
+ if (charIndex === -1)
183
+ return;
184
+ const charX = x + charSpacing * (charIndex + 1);
185
+ const bubbleX = charX;
186
+ const bubbleY = dialogueStartY + 25 + i * bubbleSpacing;
187
+ content += speechBubble(bubbleX, bubbleY, line.text, colors, line.type || 'speech', 'down', width * 0.65);
188
+ });
189
+ return content;
190
+ }
191
+ /**
192
+ * Generate a cartoon strip SVG
193
+ */
194
+ export function generateCartoonStrip(config) {
195
+ const { title = '', characters: charDefs, panels, layout = 'auto', theme: themeName, width = 800, height = 600, } = config;
196
+ const theme = getTheme(themeName);
197
+ const colors = theme.colors;
198
+ // Create character instances
199
+ const characters = new Map();
200
+ for (const [id, def] of Object.entries(charDefs)) {
201
+ characters.set(id, createCharacter(id, def.name, def.preset || def.style || 'dev1'));
202
+ }
203
+ // Calculate layout
204
+ const { cols, rows, panelWidth, panelHeight } = calculateLayout(panels.length, layout, width, height);
205
+ const gap = 10;
206
+ // Build SVG
207
+ let content = '';
208
+ // Title
209
+ if (title) {
210
+ content += `<text x="${width / 2}" y="28" text-anchor="middle" fill="${colors.text}" font-size="16" font-weight="bold" font-family="${FONT}">${escapeHtml(title)}</text>`;
211
+ }
212
+ // Render panels
213
+ panels.forEach((panel, i) => {
214
+ const col = i % cols;
215
+ const row = Math.floor(i / cols);
216
+ const px = gap + col * (panelWidth + gap);
217
+ const py = (title ? 45 : gap) + row * (panelHeight + gap);
218
+ content += renderPanel(panel, characters, px, py, panelWidth, panelHeight, colors, i);
219
+ });
220
+ return `<?xml version="1.0" encoding="UTF-8"?>
221
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}">
222
+ <defs>
223
+ <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
224
+ <stop offset="0%" stop-color="${colors.bg}"/>
225
+ <stop offset="100%" stop-color="${colors.card}"/>
226
+ </linearGradient>
227
+ </defs>
228
+ <rect width="${width}" height="${height}" fill="url(#bg)"/>
229
+ ${content}
230
+ </svg>`;
231
+ }
232
+ export { speechBubble };
233
+ //# sourceMappingURL=cartoon.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Character definitions for cartoon strips
3
+ * Improved cartoon-style characters with better proportions and expressions
4
+ */
5
+ export type Emotion = 'neutral' | 'happy' | 'sad' | 'angry' | 'surprised' | 'thinking' | 'confused' | 'excited' | 'worried';
6
+ export interface CharacterStyle {
7
+ /** Primary color (hair) */
8
+ primary: string;
9
+ /** Secondary color (shirt/clothing) */
10
+ secondary: string;
11
+ /** Skin tone */
12
+ skin: string;
13
+ /** Hair style */
14
+ hairStyle: 'short' | 'long' | 'bald' | 'spiky' | 'curly' | 'ponytail' | 'mohawk';
15
+ /** Accessory */
16
+ accessory: 'none' | 'glasses' | 'sunglasses' | 'hat' | 'headphones' | 'beanie';
17
+ /** Eye color */
18
+ eyeColor: string;
19
+ /** Shirt style */
20
+ shirtStyle: 'tshirt' | 'hoodie' | 'formal' | 'tank';
21
+ }
22
+ export interface Character {
23
+ id: string;
24
+ name: string;
25
+ style: CharacterStyle;
26
+ }
27
+ /**
28
+ * Predefined character styles - diverse and visually distinct
29
+ */
30
+ export declare const CHARACTER_PRESETS: Record<string, CharacterStyle>;
31
+ /**
32
+ * Render a character at position with emotion - completely redesigned
33
+ */
34
+ export declare function renderCharacter(x: number, y: number, character: Character, emotion?: Emotion, scale?: number, facing?: 'left' | 'right'): string;
35
+ /**
36
+ * Create a character from preset or custom style
37
+ */
38
+ export declare function createCharacter(id: string, name: string, presetOrStyle?: string | CharacterStyle): Character;
39
+ /**
40
+ * Get available character presets
41
+ */
42
+ export declare function getCharacterPresets(): string[];
43
+ /**
44
+ * Get available emotions
45
+ */
46
+ export declare function getEmotions(): Emotion[];
47
+ //# sourceMappingURL=characters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"characters.d.ts","sourceRoot":"","sources":["../src/characters.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,OAAO,GACf,SAAS,GACT,OAAO,GACP,KAAK,GACL,OAAO,GACP,WAAW,GACX,UAAU,GACV,UAAU,GACV,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,cAAc;IAC7B,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB;IACjB,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;IACjF,gBAAgB;IAChB,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,KAAK,GAAG,YAAY,GAAG,QAAQ,CAAC;IAC/E,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;CACrD;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,cAAc,CAAC;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAyE5D,CAAC;AAsRF;;GAEG;AACH,wBAAgB,eAAe,CAC7B,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,OAAmB,EAC5B,KAAK,GAAE,MAAU,EACjB,MAAM,GAAE,MAAM,GAAG,OAAiB,GACjC,MAAM,CAkDR;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,aAAa,GAAE,MAAM,GAAG,cAAuB,GAC9C,SAAS,CAMX;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,OAAO,EAAE,CAEvC"}