@zseven-w/pen-renderer 0.5.2 → 0.7.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.
@@ -1,8 +1,8 @@
1
- import type { CanvasKit } from 'canvaskit-wasm'
2
- import type { PenFill, PenStroke } from '@zseven-w/pen-types'
3
- import { DEFAULT_FILL, DEFAULT_STROKE_WIDTH } from '@zseven-w/pen-core'
1
+ import type { CanvasKit } from 'canvaskit-wasm';
2
+ import type { PenFill, PenStroke } from '@zseven-w/pen-types';
3
+ import { DEFAULT_FILL, DEFAULT_STROKE_WIDTH } from '@zseven-w/pen-core';
4
4
 
5
- export { cssFontFamily } from '@zseven-w/pen-core'
5
+ export { cssFontFamily } from '@zseven-w/pen-core';
6
6
 
7
7
  // ---------------------------------------------------------------------------
8
8
  // Color parsing — ck.Color4f takes 0-1 floats for all channels (r, g, b, a)
@@ -10,57 +10,61 @@ export { cssFontFamily } from '@zseven-w/pen-core'
10
10
 
11
11
  export function parseColor(ck: CanvasKit, color: string): Float32Array {
12
12
  if (color.startsWith('#')) {
13
- const hex = color.slice(1)
13
+ const hex = color.slice(1);
14
14
  if (hex.length === 8) {
15
- const r = parseInt(hex.slice(0, 2), 16) / 255
16
- const g = parseInt(hex.slice(2, 4), 16) / 255
17
- const b = parseInt(hex.slice(4, 6), 16) / 255
18
- const a = parseInt(hex.slice(6, 8), 16) / 255
19
- return ck.Color4f(r, g, b, a)
15
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
16
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
17
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
18
+ const a = parseInt(hex.slice(6, 8), 16) / 255;
19
+ return ck.Color4f(r, g, b, a);
20
20
  }
21
21
  if (hex.length === 6) {
22
- const r = parseInt(hex.slice(0, 2), 16) / 255
23
- const g = parseInt(hex.slice(2, 4), 16) / 255
24
- const b = parseInt(hex.slice(4, 6), 16) / 255
25
- return ck.Color4f(r, g, b, 1)
22
+ const r = parseInt(hex.slice(0, 2), 16) / 255;
23
+ const g = parseInt(hex.slice(2, 4), 16) / 255;
24
+ const b = parseInt(hex.slice(4, 6), 16) / 255;
25
+ return ck.Color4f(r, g, b, 1);
26
26
  }
27
27
  if (hex.length === 3) {
28
- const r = parseInt(hex[0] + hex[0], 16) / 255
29
- const g = parseInt(hex[1] + hex[1], 16) / 255
30
- const b = parseInt(hex[2] + hex[2], 16) / 255
31
- return ck.Color4f(r, g, b, 1)
28
+ const r = parseInt(hex[0] + hex[0], 16) / 255;
29
+ const g = parseInt(hex[1] + hex[1], 16) / 255;
30
+ const b = parseInt(hex[2] + hex[2], 16) / 255;
31
+ return ck.Color4f(r, g, b, 1);
32
32
  }
33
33
  }
34
- if (color === 'transparent') return ck.Color4f(0, 0, 0, 0)
35
- if (color === 'white') return ck.Color4f(1, 1, 1, 1)
36
- if (color === 'black') return ck.Color4f(0, 0, 0, 1)
34
+ if (color === 'transparent') return ck.Color4f(0, 0, 0, 0);
35
+ if (color === 'white') return ck.Color4f(1, 1, 1, 1);
36
+ if (color === 'black') return ck.Color4f(0, 0, 0, 1);
37
37
  // rgba() parsing
38
- const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/)
38
+ const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
39
39
  if (rgbaMatch) {
40
40
  return ck.Color4f(
41
41
  parseInt(rgbaMatch[1]) / 255,
42
42
  parseInt(rgbaMatch[2]) / 255,
43
43
  parseInt(rgbaMatch[3]) / 255,
44
44
  rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1,
45
- )
45
+ );
46
46
  }
47
- return ck.Color4f(0.82, 0.835, 0.858, 1) // fallback #d1d5db
47
+ return ck.Color4f(0.82, 0.835, 0.858, 1); // fallback #d1d5db
48
48
  }
49
49
 
50
50
  // ---------------------------------------------------------------------------
51
51
  // Corner radius helpers
52
52
  // ---------------------------------------------------------------------------
53
53
 
54
- export function cornerRadiusValue(cr: number | [number, number, number, number] | undefined): number {
55
- if (cr === undefined) return 0
56
- if (typeof cr === 'number') return cr
57
- return cr[0]
54
+ export function cornerRadiusValue(
55
+ cr: number | [number, number, number, number] | undefined,
56
+ ): number {
57
+ if (cr === undefined) return 0;
58
+ if (typeof cr === 'number') return cr;
59
+ return cr[0];
58
60
  }
59
61
 
60
- export function cornerRadii(cr: number | [number, number, number, number] | undefined): [number, number, number, number] {
61
- if (cr === undefined) return [0, 0, 0, 0]
62
- if (typeof cr === 'number') return [cr, cr, cr, cr]
63
- return cr
62
+ export function cornerRadii(
63
+ cr: number | [number, number, number, number] | undefined,
64
+ ): [number, number, number, number] {
65
+ if (cr === undefined) return [0, 0, 0, 0];
66
+ if (typeof cr === 'number') return [cr, cr, cr, cr];
67
+ return cr;
64
68
  }
65
69
 
66
70
  // ---------------------------------------------------------------------------
@@ -68,28 +72,44 @@ export function cornerRadii(cr: number | [number, number, number, number] | unde
68
72
  // ---------------------------------------------------------------------------
69
73
 
70
74
  export function resolveFillColor(fills?: PenFill[] | string): string {
71
- if (typeof fills === 'string') return fills
72
- if (!fills || fills.length === 0) return DEFAULT_FILL
73
- const first = fills[0]
74
- if (first.type === 'solid') return first.color
75
+ if (typeof fills === 'string') return fills;
76
+ if (!fills || fills.length === 0) return DEFAULT_FILL;
77
+ const first = fills[0];
78
+ if (!first) return DEFAULT_FILL;
79
+ if (first.type === 'solid') return first.color;
75
80
  if (first.type === 'linear_gradient' || first.type === 'radial_gradient') {
76
- return first.stops[0]?.color ?? DEFAULT_FILL
81
+ return first.stops[0]?.color ?? DEFAULT_FILL;
77
82
  }
78
- return DEFAULT_FILL
83
+ return DEFAULT_FILL;
79
84
  }
80
85
 
81
86
  export function resolveStrokeColor(stroke?: PenStroke): string | undefined {
82
- if (!stroke) return undefined
83
- if (typeof stroke.fill === 'string') return stroke.fill
84
- if (stroke.fill && stroke.fill.length > 0) return resolveFillColor(stroke.fill)
85
- return undefined
87
+ if (!stroke) return undefined;
88
+ if (typeof stroke === 'string') return stroke;
89
+ if (typeof stroke.fill === 'string') return stroke.fill;
90
+ if (stroke.fill && stroke.fill.length > 0) return resolveFillColor(stroke.fill);
91
+ if ('color' in stroke && typeof (stroke as any).color === 'string') return (stroke as any).color;
92
+ return undefined;
86
93
  }
87
94
 
88
95
  export function resolveStrokeWidth(stroke?: PenStroke): number {
89
- if (!stroke) return 0
90
- if (typeof stroke.thickness === 'number') return stroke.thickness
91
- if (typeof stroke.thickness === 'object' && !Array.isArray(stroke.thickness)) return 0
92
- return stroke.thickness?.[0] ?? DEFAULT_STROKE_WIDTH
96
+ if (!stroke) return 0;
97
+ if (typeof stroke.thickness === 'number') return stroke.thickness;
98
+ if (typeof stroke.thickness === 'object' && !Array.isArray(stroke.thickness)) return 0;
99
+ return stroke.thickness?.[0] ?? DEFAULT_STROKE_WIDTH;
100
+ }
101
+
102
+ export function hasVisibleStroke(stroke?: PenStroke): boolean {
103
+ return resolveStrokeWidth(stroke) > 0 && !!resolveStrokeColor(stroke);
104
+ }
105
+
106
+ export function shouldUseTransparentFallbackFill(
107
+ fills: PenFill[] | string | undefined,
108
+ stroke?: PenStroke,
109
+ isContainer = false,
110
+ ): boolean {
111
+ const hasExplicitFill = typeof fills === 'string' ? fills.length > 0 : !!fills?.length;
112
+ return !hasExplicitFill && (isContainer || hasVisibleStroke(stroke));
93
113
  }
94
114
 
95
115
  // ---------------------------------------------------------------------------
@@ -98,51 +118,59 @@ export function resolveStrokeWidth(stroke?: PenStroke): number {
98
118
 
99
119
  /** CJK character range check (for character-level line breaking). */
100
120
  function isCJK(ch: string): boolean {
101
- const c = ch.charCodeAt(0)
102
- return (c >= 0x4E00 && c <= 0x9FFF) || (c >= 0x3400 && c <= 0x4DBF) ||
103
- (c >= 0x3000 && c <= 0x303F) || (c >= 0xFF00 && c <= 0xFFEF) ||
104
- (c >= 0x2E80 && c <= 0x2FDF)
121
+ const c = ch.charCodeAt(0);
122
+ return (
123
+ (c >= 0x4e00 && c <= 0x9fff) ||
124
+ (c >= 0x3400 && c <= 0x4dbf) ||
125
+ (c >= 0x3000 && c <= 0x303f) ||
126
+ (c >= 0xff00 && c <= 0xffef) ||
127
+ (c >= 0x2e80 && c <= 0x2fdf)
128
+ );
105
129
  }
106
130
 
107
131
  /** Word-wrap a single line of text, appending wrapped lines to `out`. */
108
132
  export function wrapLine(ctx: CanvasRenderingContext2D, text: string, maxW: number, out: string[]) {
109
- if (ctx.measureText(text).width <= maxW) { out.push(text); return }
133
+ if (ctx.measureText(text).width <= maxW) {
134
+ out.push(text);
135
+ return;
136
+ }
110
137
 
111
- let current = ''
112
- let i = 0
138
+ let current = '';
139
+ let i = 0;
113
140
  while (i < text.length) {
114
- const ch = text[i]
141
+ const ch = text[i];
115
142
  if (isCJK(ch)) {
116
- const test = current + ch
143
+ const test = current + ch;
117
144
  if (ctx.measureText(test).width > maxW && current) {
118
- out.push(current)
119
- current = ch
145
+ out.push(current);
146
+ current = ch;
120
147
  } else {
121
- current = test
148
+ current = test;
122
149
  }
123
- i++
150
+ i++;
124
151
  } else if (ch === ' ') {
125
- const test = current + ch
152
+ const test = current + ch;
126
153
  if (ctx.measureText(test).width > maxW && current) {
127
- out.push(current)
128
- current = ''
154
+ out.push(current);
155
+ current = '';
129
156
  } else {
130
- current = test
157
+ current = test;
131
158
  }
132
- i++
159
+ i++;
133
160
  } else {
134
- let word = ''
161
+ let word = '';
135
162
  while (i < text.length && text[i] !== ' ' && !isCJK(text[i])) {
136
- word += text[i]; i++
163
+ word += text[i];
164
+ i++;
137
165
  }
138
- const test = current + word
166
+ const test = current + word;
139
167
  if (ctx.measureText(test).width > maxW && current) {
140
- out.push(current)
141
- current = word
168
+ out.push(current);
169
+ current = word;
142
170
  } else {
143
- current = test
171
+ current = test;
144
172
  }
145
173
  }
146
174
  }
147
- if (current) out.push(current)
175
+ if (current) out.push(current);
148
176
  }