@yassimba/pi-loom-mermaid 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.
@@ -0,0 +1,466 @@
1
+ import type { Role, Span } from './types.ts'
2
+ import { measured } from './width.ts'
3
+
4
+ /**
5
+ * Sentinel occupying the trailing column of a wide glyph. Never emitted: the
6
+ * line builder skips it so a CJK character claims two cells of layout but
7
+ * contributes one character of output.
8
+ */
9
+ export const CONT = String.fromCharCode(0)
10
+
11
+ /** Connection direction bits, combined into a box-drawing glyph by `maskChar`. */
12
+ export const U = 1
13
+ export const D = 2
14
+ export const L = 4
15
+ export const R = 8
16
+
17
+ /** Line styles, tracked per cell so crossing edges keep their own stroke. */
18
+ export const STY_DOT = 1
19
+ export const STY_THICK = 2
20
+ export const STY_SOLID = 4
21
+
22
+ /**
23
+ * A grid of cells. Edges accumulate as direction bits rather than glyphs so
24
+ * that crossings and junctions resolve correctly whatever order they are drawn
25
+ * in; `finalizeMask` turns the accumulated bits into characters at the end.
26
+ *
27
+ * `occupied` marks cells claimed by a box, which edge bits must not overwrite.
28
+ *
29
+ * `pass` remembers how each cell was reached: a vertical run passing
30
+ * through, a horizontal run passing through, or a turn, end or junction.
31
+ * A cell crossed by one vertical and one horizontal run and nothing else
32
+ * is two edges crossing, drawn as a hop (`╫`) rather than a junction
33
+ * (`┼`), so an edge can be followed through a dense band.
34
+ */
35
+ const PASS_V = 1
36
+ const PASS_H = 2
37
+ const JOINED = 4
38
+ const HOP = '╫'
39
+ export class Canvas {
40
+ readonly w: number
41
+ readonly h: number
42
+ ch: string[]
43
+ role: Role[]
44
+ /** Space-joined author classes per cell, or undefined; see `Span.classes`. */
45
+ tag: (string | undefined)[]
46
+ /** Link target per cell, or undefined; see `Span.href`. */
47
+ href: (string | undefined)[]
48
+ mask: Uint8Array
49
+ style: Uint8Array
50
+ occupied: Uint8Array
51
+ pass: Uint8Array
52
+ /** Edge labels queued by the layout, written after every line. */
53
+ labels: { label: string; row: number; x: number }[] = []
54
+ curStyle: number = STY_SOLID
55
+ /** Author classes stamped on cells painted while set, like `curStyle`. */
56
+ curTag: string | undefined
57
+ /** Link target stamped on cells painted while set, like `curTag`. */
58
+ curHref: string | undefined
59
+
60
+ constructor(w: number, h: number) {
61
+ const n = w * h
62
+ this.w = w
63
+ this.h = h
64
+ this.ch = new Array(n).fill(' ')
65
+ this.role = new Array(n).fill('none')
66
+ this.tag = new Array(n).fill(undefined)
67
+ this.href = new Array(n).fill(undefined)
68
+ this.mask = new Uint8Array(n)
69
+ this.style = new Uint8Array(n)
70
+ this.occupied = new Uint8Array(n)
71
+ this.pass = new Uint8Array(n)
72
+ }
73
+
74
+ idx(x: number, y: number): number {
75
+ return y * this.w + x
76
+ }
77
+
78
+ set(x: number, y: number, c: string, role: Role): void {
79
+ if (x >= this.w || y >= this.h) return
80
+ const i = this.idx(x, y)
81
+ // A literal tab measures one cell here but jumps to the terminal's tab
82
+ // stop there, desyncing every column after it (the source box expands
83
+ // tabs for the same reason). Same width, safe glyph.
84
+ this.ch[i] = c === '\t' ? ' ' : c
85
+ this.role[i] = role
86
+ if (this.curTag !== undefined) this.tag[i] = this.curTag
87
+ if (this.curHref !== undefined) this.href[i] = this.curHref
88
+ }
89
+
90
+ /**
91
+ * Accumulate direction bits on a free cell.
92
+ *
93
+ * `role` is the role to claim the cell for; `border` cells are never
94
+ * reclassified, so a connector meeting a box keeps the box's styling.
95
+ */
96
+ addBits(x: number, y: number, bits: number, role: Role = 'edge'): void {
97
+ if (x >= this.w || y >= this.h) return
98
+ const i = this.idx(x, y)
99
+ if (this.occupied[i]) return
100
+ this.mask[i] |= bits
101
+ this.pass[i] |= bits === (U | D) ? PASS_V : bits === (L | R) ? PASS_H : JOINED
102
+ this.style[i] |= this.curStyle
103
+ if (this.role[i] !== 'border') this.role[i] = role
104
+ if (this.curTag !== undefined) this.tag[i] = this.curTag
105
+ if (this.curHref !== undefined) this.href[i] = this.curHref
106
+ }
107
+
108
+ /** Stamp a finished sub-canvas (a subgraph frame's contents) at an offset. */
109
+ blit(sub: Canvas, ox: number, oy: number): void {
110
+ for (let sy = 0; sy < sub.h; sy++) {
111
+ for (let sx = 0; sx < sub.w; sx++) {
112
+ const x = ox + sx
113
+ const y = oy + sy
114
+ if (x >= this.w || y >= this.h) continue
115
+ const si = sub.idx(sx, sy)
116
+ const di = this.idx(x, y)
117
+ this.ch[di] = sub.ch[si]
118
+ this.role[di] = sub.role[si]
119
+ this.tag[di] = sub.tag[si]
120
+ this.href[di] = sub.href[si]
121
+ this.style[di] = sub.style[si]
122
+ this.pass[di] = sub.pass[si]
123
+ this.occupied[di] = 1
124
+ }
125
+ }
126
+ }
127
+
128
+ /** Add direction bits even to an occupied cell, so an edge can meet a border. */
129
+ junction(x: number, y: number, bits: number): void {
130
+ if (x >= this.w || y >= this.h) return
131
+ const i = this.idx(x, y)
132
+ this.mask[i] |= bits
133
+ this.pass[i] |= JOINED
134
+ if (this.role[i] !== 'border') this.role[i] = 'edge'
135
+ }
136
+
137
+ segV(x: number, y0: number, y1: number): void {
138
+ const a = Math.min(y0, y1)
139
+ const b = Math.max(y0, y1)
140
+ for (let y = a; y <= b; y++) {
141
+ let bits = 0
142
+ if (y > a) bits |= U
143
+ if (y < b) bits |= D
144
+ this.addBits(x, y, bits)
145
+ }
146
+ }
147
+
148
+ segH(y: number, x0: number, x1: number): void {
149
+ const a = Math.min(x0, x1)
150
+ const b = Math.max(x0, x1)
151
+ for (let x = a; x <= b; x++) {
152
+ let bits = 0
153
+ if (x > a) bits |= L
154
+ if (x < b) bits |= R
155
+ this.addBits(x, y, bits)
156
+ }
157
+ }
158
+
159
+ /** Resolve accumulated direction bits into glyphs, honouring line style. */
160
+ finalizeMask(): void {
161
+ for (let i = 0; i < this.ch.length; i++) {
162
+ if (this.mask[i] === 0) continue
163
+ if (this.ch[i] === ' ') {
164
+ const c = this.pass[i] === (PASS_V | PASS_H) ? HOP : maskChar(this.mask[i])
165
+ this.ch[i] =
166
+ this.style[i] === STY_DOT ? dottedChar(c) : this.style[i] === STY_THICK ? thickChar(c) : c
167
+ } else if (this.ch[i] === '═' || this.ch[i] === '║') {
168
+ this.ch[i] = doubleTee(this.ch[i], this.mask[i])
169
+ }
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Mirror top-to-bottom for `BT`. Rows reorder but within-row text does not,
175
+ * so labels stay readable; box-drawing glyphs flip to match.
176
+ */
177
+ flipVertical(): void {
178
+ for (let y = 0; y < Math.floor(this.h / 2); y++) {
179
+ const y2 = this.h - 1 - y
180
+ for (let x = 0; x < this.w; x++) {
181
+ const i = this.idx(x, y)
182
+ const j = this.idx(x, y2)
183
+ ;[this.ch[i], this.ch[j]] = [this.ch[j], this.ch[i]]
184
+ ;[this.role[i], this.role[j]] = [this.role[j], this.role[i]]
185
+ ;[this.tag[i], this.tag[j]] = [this.tag[j], this.tag[i]]
186
+ ;[this.href[i], this.href[j]] = [this.href[j], this.href[i]]
187
+ }
188
+ }
189
+ for (let i = 0; i < this.ch.length; i++) {
190
+ if (!textRole(this.role[i])) this.ch[i] = flipGlyphV(this.ch[i])
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Mirror left-to-right for `RL`. Mirroring reverses each row, so after
196
+ * flipping glyphs each text/label run is reversed back to reading order.
197
+ */
198
+ flipHorizontal(): void {
199
+ for (let y = 0; y < this.h; y++) {
200
+ for (let x = 0; x < Math.floor(this.w / 2); x++) {
201
+ const x2 = this.w - 1 - x
202
+ const i = this.idx(x, y)
203
+ const j = this.idx(x2, y)
204
+ ;[this.ch[i], this.ch[j]] = [this.ch[j], this.ch[i]]
205
+ ;[this.role[i], this.role[j]] = [this.role[j], this.role[i]]
206
+ ;[this.tag[i], this.tag[j]] = [this.tag[j], this.tag[i]]
207
+ ;[this.href[i], this.href[j]] = [this.href[j], this.href[i]]
208
+ }
209
+ }
210
+ for (let i = 0; i < this.ch.length; i++) {
211
+ if (!textRole(this.role[i])) this.ch[i] = flipGlyphH(this.ch[i])
212
+ }
213
+ for (let y = 0; y < this.h; y++) {
214
+ let x = 0
215
+ while (x < this.w) {
216
+ const role = this.role[this.idx(x, y)]
217
+ if (role === 'text' || role === 'edgeLabel') {
218
+ const start = this.idx(x, y)
219
+ while (x < this.w && this.role[this.idx(x, y)] === role) x++
220
+ const end = this.idx(x, y)
221
+ reverseSlice(this.ch, start, end)
222
+ } else {
223
+ x++
224
+ }
225
+ }
226
+ }
227
+ }
228
+
229
+ /** Group each row into runs of one role and tag, dropping continuations. */
230
+ toLines(): { plain: string[]; styled: Span[][]; width: number } {
231
+ const plain: string[] = []
232
+ const styled: Span[][] = []
233
+ let width = 0
234
+ for (let y = 0; y < this.h; y++) {
235
+ // A trailing CONT counts as painted: it is the second cell of a wide
236
+ // glyph, so the row really does reach that column.
237
+ let last = 0
238
+ for (let x = this.w - 1; x >= 0; x--) {
239
+ if (this.ch[this.idx(x, y)] !== ' ') {
240
+ last = x + 1
241
+ break
242
+ }
243
+ }
244
+ width = Math.max(width, last)
245
+ const spans: Span[] = []
246
+ const push = (text: string, role: Role, tag: string | undefined, href?: string): void => {
247
+ if (text === '') return
248
+ const span: Span = { text, role }
249
+ if (tag !== undefined) span.classes = tag.split(' ')
250
+ if (href !== undefined) span.href = href
251
+ spans.push(span)
252
+ }
253
+ let plainRow = ''
254
+ let run = ''
255
+ let runRole: Role = 'none'
256
+ let runTag: string | undefined
257
+ let runHref: string | undefined
258
+ for (let x = 0; x < last; x++) {
259
+ const i = this.idx(x, y)
260
+ const c = this.ch[i]
261
+ if (c === CONT) continue
262
+ plainRow += c
263
+ if (
264
+ (this.role[i] !== runRole || this.tag[i] !== runTag || this.href[i] !== runHref) &&
265
+ run !== ''
266
+ ) {
267
+ push(run, runRole, runTag, runHref)
268
+ run = ''
269
+ }
270
+ runRole = this.role[i]
271
+ runTag = this.tag[i]
272
+ runHref = this.href[i]
273
+ run += c
274
+ }
275
+ push(run, runRole, runTag, runHref)
276
+ styled.push(spans)
277
+ // Only ASCII spaces, which is all a blank cell ever holds. Trimming `\s`
278
+ // would eat a trailing NBSP that `styled` keeps, desyncing the two.
279
+ // (A ` +$` regex backtracks quadratically on a row of mostly spaces:
280
+ // it was more than half the render time of a 500-edge diagram.)
281
+ let cut = plainRow.length
282
+ while (cut > 0 && plainRow[cut - 1] === ' ') cut--
283
+ plain.push(plainRow.slice(0, cut))
284
+ }
285
+ let first = 0
286
+ while (first < plain.length && plain[first] === '') first++
287
+ let end = plain.length
288
+ while (end > first && plain[end - 1] === '') end--
289
+ return { plain: plain.slice(first, end), styled: styled.slice(first, end), width }
290
+ }
291
+ }
292
+
293
+ function reverseSlice(arr: string[], start: number, end: number): void {
294
+ for (let i = start, j = end - 1; i < j; i++, j--) {
295
+ ;[arr[i], arr[j]] = [arr[j], arr[i]]
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Paint `text` at `x, y`, one grapheme cluster per cell.
301
+ *
302
+ * A wide cluster claims a second cell, marked with `CONT` so the line builder
303
+ * emits one character for it rather than a stray space.
304
+ */
305
+ export function drawText(canvas: Canvas, text: string, x: number, y: number, role: Role): void {
306
+ let cur = x
307
+ for (const [cluster, cw] of measured(text)) {
308
+ if (cw === 0) continue
309
+ canvas.set(cur, y, cluster, role)
310
+ for (let k = 1; k < cw; k++) canvas.set(cur + k, y, CONT, role)
311
+ cur += cw
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Paint `text` at `x, y`, clearing any edge bits underneath first.
317
+ *
318
+ * Used where text sits on top of a drawn line (sequence messages, dividers,
319
+ * compartment rows) and must win over it.
320
+ */
321
+ export function drawTextOverEdges(
322
+ canvas: Canvas,
323
+ text: string,
324
+ x: number,
325
+ y: number,
326
+ role: Role,
327
+ ): void {
328
+ let cur = x
329
+ for (const [cluster, cw] of measured(text)) {
330
+ if (cw === 0) continue
331
+ for (let k = 0; k < cw; k++) {
332
+ if (cur + k < canvas.w && y < canvas.h) canvas.mask[canvas.idx(cur + k, y)] = 0
333
+ canvas.set(cur + k, y, k === 0 ? cluster : CONT, role)
334
+ }
335
+ cur += cw
336
+ }
337
+ }
338
+
339
+ function maskChar(mask: number): string {
340
+ switch (mask) {
341
+ case 0:
342
+ return ' '
343
+ case U:
344
+ case D:
345
+ case U | D:
346
+ return '│'
347
+ case L:
348
+ case R:
349
+ case L | R:
350
+ return '─'
351
+ case D | R:
352
+ return '┌'
353
+ case D | L:
354
+ return '┐'
355
+ case U | R:
356
+ return '└'
357
+ case U | L:
358
+ return '┘'
359
+ case U | D | R:
360
+ return '├'
361
+ case U | D | L:
362
+ return '┤'
363
+ case D | L | R:
364
+ return '┬'
365
+ case U | L | R:
366
+ return '┴'
367
+ default:
368
+ return '┼'
369
+ }
370
+ }
371
+
372
+ /** An edge teeing into a double-line border: the mixed single/double glyphs. */
373
+ function doubleTee(c: string, mask: number): string {
374
+ if (c === '═') {
375
+ if (mask & U && mask & D) return '╪'
376
+ if (mask & D) return '╤'
377
+ if (mask & U) return '╧'
378
+ } else {
379
+ if (mask & L && mask & R) return '╫'
380
+ if (mask & R) return '╟'
381
+ if (mask & L) return '╢'
382
+ }
383
+ return c
384
+ }
385
+
386
+ const DOTTED: Record<string, string> = { '─': '╌', '│': '╎' }
387
+
388
+ const THICK: Record<string, string> = {
389
+ '─': '━',
390
+ '│': '┃',
391
+ '┌': '┏',
392
+ '┐': '┓',
393
+ '└': '┗',
394
+ '┘': '┛',
395
+ '├': '┣',
396
+ '┤': '┫',
397
+ '┬': '┳',
398
+ '┴': '┻',
399
+ '┼': '╋',
400
+ }
401
+
402
+ const FLIP_V: Record<string, string> = {
403
+ '╔': '╚',
404
+ '╚': '╔',
405
+ '╗': '╝',
406
+ '╝': '╗',
407
+ '╤': '╧',
408
+ '╧': '╤',
409
+ '┌': '└',
410
+ '└': '┌',
411
+ '┐': '┘',
412
+ '┘': '┐',
413
+ '┏': '┗',
414
+ '┗': '┏',
415
+ '┓': '┛',
416
+ '┛': '┓',
417
+ '╭': '╰',
418
+ '╰': '╭',
419
+ '╮': '╯',
420
+ '╯': '╮',
421
+ '┬': '┴',
422
+ '┴': '┬',
423
+ '┳': '┻',
424
+ '┻': '┳',
425
+ '▼': '▲',
426
+ '▲': '▼',
427
+ '▽': '△',
428
+ '△': '▽',
429
+ }
430
+
431
+ const FLIP_H: Record<string, string> = {
432
+ '╔': '╗',
433
+ '╗': '╔',
434
+ '╚': '╝',
435
+ '╝': '╚',
436
+ '╟': '╢',
437
+ '╢': '╟',
438
+ '┌': '┐',
439
+ '┐': '┌',
440
+ '└': '┘',
441
+ '┘': '└',
442
+ '┏': '┓',
443
+ '┓': '┏',
444
+ '┗': '┛',
445
+ '┛': '┗',
446
+ '╭': '╮',
447
+ '╮': '╭',
448
+ '╰': '╯',
449
+ '╯': '╰',
450
+ '├': '┤',
451
+ '┤': '├',
452
+ '┣': '┫',
453
+ '┫': '┣',
454
+ '▶': '◄',
455
+ '◄': '▶',
456
+ '▷': '◁',
457
+ '◁': '▷',
458
+ }
459
+
460
+ const dottedChar = (c: string): string => DOTTED[c] ?? c
461
+ const thickChar = (c: string): string => THICK[c] ?? c
462
+ const flipGlyphV = (c: string): string => FLIP_V[c] ?? c
463
+ const flipGlyphH = (c: string): string => FLIP_H[c] ?? c
464
+
465
+ /** User-authored cells: flips reorder them but must not remap their glyphs. */
466
+ const textRole = (r: Role): boolean => r === 'text' || r === 'edgeLabel' || r === 'title'
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Best-effort interpretation of `classDef` styles for a cell grid.
3
+ *
4
+ * A terminal cell can express a foreground, a background and boldness —
5
+ * nothing else. `fill` is the node background, `stroke` its border,
6
+ * `color` its text; every other property is silently ignored.
7
+ */
8
+
9
+ import { NAMED_COLORS } from './css-colors.ts'
10
+
11
+ /** The terminal-expressible subset of a classDef; colors as `#rrggbb`. */
12
+ export interface ClassStyle {
13
+ fill?: string
14
+ stroke?: string
15
+ color?: string
16
+ bold?: boolean
17
+ }
18
+
19
+ /** `#rgb`, `#rrggbb`, `rgb(r,g,b)` or a CSS color name → `#rrggbb`; else null. */
20
+ function normalizeColor(v: string): string | null {
21
+ const s = v.trim().toLowerCase()
22
+ if (/^#[0-9a-f]{6}$/.test(s)) return s
23
+ if (/^#[0-9a-f]{3}$/.test(s)) return `#${s[1]}${s[1]}${s[2]}${s[2]}${s[3]}${s[3]}`
24
+ const rgb = s.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/)
25
+ if (rgb !== null) {
26
+ const hex = (n: string) => Math.min(255, Number(n)).toString(16).padStart(2, '0')
27
+ return `#${hex(rgb[1])}${hex(rgb[2])}${hex(rgb[3])}`
28
+ }
29
+ return NAMED_COLORS[s] ?? null
30
+ }
31
+
32
+ /**
33
+ * The merged style of a span's classes (later classes win), or null when
34
+ * nothing terminal-expressible was declared.
35
+ */
36
+ export function resolveClassStyle(
37
+ classes: string[] | undefined,
38
+ classDefs: Record<string, Record<string, string>>,
39
+ ): ClassStyle | null {
40
+ if (classes === undefined) return null
41
+ const out: ClassStyle = {}
42
+ for (const name of classes) {
43
+ const props = classDefs[name]
44
+ if (props === undefined) continue
45
+ for (const [k, v] of Object.entries(props)) {
46
+ if (k === 'fill' || k === 'stroke' || k === 'color') {
47
+ const c = normalizeColor(v)
48
+ if (c !== null) out[k] = c
49
+ } else if (k === 'font-weight') {
50
+ out.bold = v.trim() === 'bold' || v.trim() === 'bolder'
51
+ }
52
+ }
53
+ }
54
+ return Object.keys(out).length > 0 ? out : null
55
+ }
56
+
57
+ /**
58
+ * Black or white, whichever reads on the given `#rrggbb` background — the
59
+ * guard that keeps `fill:#eee` legible on a dark terminal theme.
60
+ */
61
+ export function contrastOn(fill: string): '#000000' | '#ffffff' {
62
+ const ch = (i: number) => Number.parseInt(fill.slice(i, i + 2), 16)
63
+ const yiq = (ch(1) * 299 + ch(3) * 587 + ch(5) * 114) / 1000
64
+ return yiq >= 128 ? '#000000' : '#ffffff'
65
+ }
@@ -0,0 +1,153 @@
1
+ // Generated by scripts/gen-css-colors.ts from https://drafts.csswg.org/css-color-4/ — do not edit.
2
+
3
+ /** The CSS Color 4 named-color table: name → `#rrggbb`. */
4
+ export const NAMED_COLORS: Record<string, string> = {
5
+ aliceblue: '#f0f8ff',
6
+ antiquewhite: '#faebd7',
7
+ aqua: '#00ffff',
8
+ aquamarine: '#7fffd4',
9
+ azure: '#f0ffff',
10
+ beige: '#f5f5dc',
11
+ bisque: '#ffe4c4',
12
+ black: '#000000',
13
+ blanchedalmond: '#ffebcd',
14
+ blue: '#0000ff',
15
+ blueviolet: '#8a2be2',
16
+ brown: '#a52a2a',
17
+ burlywood: '#deb887',
18
+ cadetblue: '#5f9ea0',
19
+ chartreuse: '#7fff00',
20
+ chocolate: '#d2691e',
21
+ coral: '#ff7f50',
22
+ cornflowerblue: '#6495ed',
23
+ cornsilk: '#fff8dc',
24
+ crimson: '#dc143c',
25
+ cyan: '#00ffff',
26
+ darkblue: '#00008b',
27
+ darkcyan: '#008b8b',
28
+ darkgoldenrod: '#b8860b',
29
+ darkgray: '#a9a9a9',
30
+ darkgreen: '#006400',
31
+ darkgrey: '#a9a9a9',
32
+ darkkhaki: '#bdb76b',
33
+ darkmagenta: '#8b008b',
34
+ darkolivegreen: '#556b2f',
35
+ darkorange: '#ff8c00',
36
+ darkorchid: '#9932cc',
37
+ darkred: '#8b0000',
38
+ darksalmon: '#e9967a',
39
+ darkseagreen: '#8fbc8f',
40
+ darkslateblue: '#483d8b',
41
+ darkslategray: '#2f4f4f',
42
+ darkslategrey: '#2f4f4f',
43
+ darkturquoise: '#00ced1',
44
+ darkviolet: '#9400d3',
45
+ deeppink: '#ff1493',
46
+ deepskyblue: '#00bfff',
47
+ dimgray: '#696969',
48
+ dimgrey: '#696969',
49
+ dodgerblue: '#1e90ff',
50
+ firebrick: '#b22222',
51
+ floralwhite: '#fffaf0',
52
+ forestgreen: '#228b22',
53
+ fuchsia: '#ff00ff',
54
+ gainsboro: '#dcdcdc',
55
+ ghostwhite: '#f8f8ff',
56
+ gold: '#ffd700',
57
+ goldenrod: '#daa520',
58
+ gray: '#808080',
59
+ green: '#008000',
60
+ greenyellow: '#adff2f',
61
+ grey: '#808080',
62
+ honeydew: '#f0fff0',
63
+ hotpink: '#ff69b4',
64
+ indianred: '#cd5c5c',
65
+ indigo: '#4b0082',
66
+ ivory: '#fffff0',
67
+ khaki: '#f0e68c',
68
+ lavender: '#e6e6fa',
69
+ lavenderblush: '#fff0f5',
70
+ lawngreen: '#7cfc00',
71
+ lemonchiffon: '#fffacd',
72
+ lightblue: '#add8e6',
73
+ lightcoral: '#f08080',
74
+ lightcyan: '#e0ffff',
75
+ lightgoldenrodyellow: '#fafad2',
76
+ lightgray: '#d3d3d3',
77
+ lightgreen: '#90ee90',
78
+ lightgrey: '#d3d3d3',
79
+ lightpink: '#ffb6c1',
80
+ lightsalmon: '#ffa07a',
81
+ lightseagreen: '#20b2aa',
82
+ lightskyblue: '#87cefa',
83
+ lightslategray: '#778899',
84
+ lightslategrey: '#778899',
85
+ lightsteelblue: '#b0c4de',
86
+ lightyellow: '#ffffe0',
87
+ lime: '#00ff00',
88
+ limegreen: '#32cd32',
89
+ linen: '#faf0e6',
90
+ magenta: '#ff00ff',
91
+ maroon: '#800000',
92
+ mediumaquamarine: '#66cdaa',
93
+ mediumblue: '#0000cd',
94
+ mediumorchid: '#ba55d3',
95
+ mediumpurple: '#9370db',
96
+ mediumseagreen: '#3cb371',
97
+ mediumslateblue: '#7b68ee',
98
+ mediumspringgreen: '#00fa9a',
99
+ mediumturquoise: '#48d1cc',
100
+ mediumvioletred: '#c71585',
101
+ midnightblue: '#191970',
102
+ mintcream: '#f5fffa',
103
+ mistyrose: '#ffe4e1',
104
+ moccasin: '#ffe4b5',
105
+ navajowhite: '#ffdead',
106
+ navy: '#000080',
107
+ oldlace: '#fdf5e6',
108
+ olive: '#808000',
109
+ olivedrab: '#6b8e23',
110
+ orange: '#ffa500',
111
+ orangered: '#ff4500',
112
+ orchid: '#da70d6',
113
+ palegoldenrod: '#eee8aa',
114
+ palegreen: '#98fb98',
115
+ paleturquoise: '#afeeee',
116
+ palevioletred: '#db7093',
117
+ papayawhip: '#ffefd5',
118
+ peachpuff: '#ffdab9',
119
+ peru: '#cd853f',
120
+ pink: '#ffc0cb',
121
+ plum: '#dda0dd',
122
+ powderblue: '#b0e0e6',
123
+ purple: '#800080',
124
+ rebeccapurple: '#663399',
125
+ red: '#ff0000',
126
+ rosybrown: '#bc8f8f',
127
+ royalblue: '#4169e1',
128
+ saddlebrown: '#8b4513',
129
+ salmon: '#fa8072',
130
+ sandybrown: '#f4a460',
131
+ seagreen: '#2e8b57',
132
+ seashell: '#fff5ee',
133
+ sienna: '#a0522d',
134
+ silver: '#c0c0c0',
135
+ skyblue: '#87ceeb',
136
+ slateblue: '#6a5acd',
137
+ slategray: '#708090',
138
+ slategrey: '#708090',
139
+ snow: '#fffafa',
140
+ springgreen: '#00ff7f',
141
+ steelblue: '#4682b4',
142
+ tan: '#d2b48c',
143
+ teal: '#008080',
144
+ thistle: '#d8bfd8',
145
+ tomato: '#ff6347',
146
+ turquoise: '#40e0d0',
147
+ violet: '#ee82ee',
148
+ wheat: '#f5deb3',
149
+ white: '#ffffff',
150
+ whitesmoke: '#f5f5f5',
151
+ yellow: '#ffff00',
152
+ yellowgreen: '#9acd32',
153
+ }