@inkra/react 0.0.0-stage → 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 +21 -0
- package/README.md +316 -2
- package/THIRD_PARTY_NOTICES.md +39 -0
- package/Yozai-OFL.txt +94 -0
- package/dist/Button.d.ts +36 -0
- package/dist/Button.js +89 -0
- package/dist/PencilIcon.d.ts +22 -0
- package/dist/PencilIcon.js +210 -0
- package/dist/PencilLine.d.ts +10 -0
- package/dist/PencilLine.js +91 -0
- package/dist/PencilSurface.d.ts +19 -0
- package/dist/PencilSurface.js +19 -0
- package/dist/PencilText.d.ts +16 -0
- package/dist/PencilText.js +338 -0
- package/dist/accordion/Accordion.d.ts +27 -0
- package/dist/accordion/Accordion.js +40 -0
- package/dist/accordion/index.d.ts +2 -0
- package/dist/accordion/index.js +1 -0
- package/dist/alert-dialog/index.d.ts +64 -0
- package/dist/alert-dialog/index.js +109 -0
- package/dist/assets/ui-hand-yozai.woff2 +0 -0
- package/dist/audio-player/index.d.ts +60 -0
- package/dist/audio-player/index.js +101 -0
- package/dist/breadcrumb/index.d.ts +16 -0
- package/dist/breadcrumb/index.js +25 -0
- package/dist/calendar/index.d.ts +21 -0
- package/dist/calendar/index.js +41 -0
- package/dist/carousel/index.d.ts +51 -0
- package/dist/carousel/index.js +290 -0
- package/dist/choice-grid/index.d.ts +38 -0
- package/dist/choice-grid/index.js +85 -0
- package/dist/color-picker/color.d.ts +15 -0
- package/dist/color-picker/color.js +55 -0
- package/dist/color-picker/index.d.ts +31 -0
- package/dist/color-picker/index.js +69 -0
- package/dist/command/index.d.ts +23 -0
- package/dist/command/index.js +39 -0
- package/dist/content/index.d.ts +106 -0
- package/dist/content/index.js +80 -0
- package/dist/context-menu/index.d.ts +76 -0
- package/dist/context-menu/index.js +240 -0
- package/dist/data-table/index.d.ts +47 -0
- package/dist/data-table/index.js +91 -0
- package/dist/date-picker/index.d.ts +50 -0
- package/dist/date-picker/index.js +180 -0
- package/dist/day-agenda/index.d.ts +43 -0
- package/dist/day-agenda/index.js +47 -0
- package/dist/day-agenda/layout.d.ts +20 -0
- package/dist/day-agenda/layout.js +60 -0
- package/dist/display/ProgressRing.d.ts +6 -0
- package/dist/display/ProgressRing.js +146 -0
- package/dist/display/index.d.ts +59 -0
- package/dist/display/index.js +75 -0
- package/dist/dockable-panel/index.d.ts +41 -0
- package/dist/dockable-panel/index.js +253 -0
- package/dist/dockable-panel/model.d.ts +21 -0
- package/dist/dockable-panel/model.js +61 -0
- package/dist/drawer/index.d.ts +63 -0
- package/dist/drawer/index.js +86 -0
- package/dist/dropdown-menu/index.d.ts +34 -0
- package/dist/dropdown-menu/index.js +40 -0
- package/dist/editable/index.d.ts +53 -0
- package/dist/editable/index.js +162 -0
- package/dist/empty/index.d.ts +21 -0
- package/dist/empty/index.js +23 -0
- package/dist/flip/index.d.ts +49 -0
- package/dist/flip/index.js +138 -0
- package/dist/form/EditablePencilText.d.ts +13 -0
- package/dist/form/EditablePencilText.js +323 -0
- package/dist/form/Field.d.ts +13 -0
- package/dist/form/Field.js +26 -0
- package/dist/form/Input.d.ts +27 -0
- package/dist/form/Input.js +97 -0
- package/dist/form/InputAction.d.ts +7 -0
- package/dist/form/InputAction.js +24 -0
- package/dist/form/Textarea.d.ts +17 -0
- package/dist/form/Textarea.js +102 -0
- package/dist/form/context.d.ts +12 -0
- package/dist/form/context.js +10 -0
- package/dist/form/controlProps.d.ts +23 -0
- package/dist/form/controlProps.js +20 -0
- package/dist/form/index.d.ts +7 -0
- package/dist/form/index.js +3 -0
- package/dist/form/types.d.ts +6 -0
- package/dist/form/types.js +1 -0
- package/dist/heatmap/index.d.ts +59 -0
- package/dist/heatmap/index.js +248 -0
- package/dist/hotkey/index.d.ts +37 -0
- package/dist/hotkey/index.js +130 -0
- package/dist/hotkey/model.d.ts +27 -0
- package/dist/hotkey/model.js +97 -0
- package/dist/hover-card/index.d.ts +33 -0
- package/dist/hover-card/index.js +141 -0
- package/dist/image-cropper/index.d.ts +82 -0
- package/dist/image-cropper/index.js +261 -0
- package/dist/image-cropper/model.d.ts +42 -0
- package/dist/image-cropper/model.js +94 -0
- package/dist/image-viewer/index.d.ts +79 -0
- package/dist/image-viewer/index.js +220 -0
- package/dist/image-viewer/model.d.ts +9 -0
- package/dist/image-viewer/model.js +18 -0
- package/dist/images/layers-2x.png +0 -0
- package/dist/images/layers.png +0 -0
- package/dist/images/marker-icon.png +0 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +69 -0
- package/dist/input-number/index.d.ts +16 -0
- package/dist/input-number/index.js +77 -0
- package/dist/internal/InputActionContext.d.ts +1 -0
- package/dist/internal/InputActionContext.js +3 -0
- package/dist/internal/cache.d.ts +14 -0
- package/dist/internal/cache.js +54 -0
- package/dist/internal/line.d.ts +4 -0
- package/dist/internal/line.js +21 -0
- package/dist/internal/paper.d.ts +1 -0
- package/dist/internal/paper.js +65 -0
- package/dist/internal/random.d.ts +3 -0
- package/dist/internal/random.js +30 -0
- package/dist/internal/recovery.d.ts +1 -0
- package/dist/internal/recovery.js +52 -0
- package/dist/internal/renderTokens.d.ts +10 -0
- package/dist/internal/renderTokens.js +13 -0
- package/dist/internal/stroke.d.ts +24 -0
- package/dist/internal/stroke.js +144 -0
- package/dist/internal/surfaceRenderer.d.ts +41 -0
- package/dist/internal/surfaceRenderer.js +178 -0
- package/dist/internal/textQueue.d.ts +1 -0
- package/dist/internal/textQueue.js +37 -0
- package/dist/internal/textRenderer.d.ts +28 -0
- package/dist/internal/textRenderer.js +239 -0
- package/dist/internal/travelIcons.d.ts +24 -0
- package/dist/internal/travelIcons.js +14 -0
- package/dist/internal/useBrowserLayoutEffect.d.ts +2 -0
- package/dist/internal/useBrowserLayoutEffect.js +3 -0
- package/dist/internal/usePencilSurface.d.ts +3 -0
- package/dist/internal/usePencilSurface.js +116 -0
- package/dist/internal/useSelectionDrawing.d.ts +2 -0
- package/dist/internal/useSelectionDrawing.js +90 -0
- package/dist/internal/wash.d.ts +16 -0
- package/dist/internal/wash.js +149 -0
- package/dist/journey-map/MapScale.d.ts +4 -0
- package/dist/journey-map/MapScale.js +54 -0
- package/dist/journey-map/index.d.ts +39 -0
- package/dist/journey-map/index.js +197 -0
- package/dist/journey-map/terrain.d.ts +8 -0
- package/dist/journey-map/terrain.js +127 -0
- package/dist/keypad/index.d.ts +23 -0
- package/dist/keypad/index.js +63 -0
- package/dist/keypad/model.d.ts +20 -0
- package/dist/keypad/model.js +33 -0
- package/dist/layout/index.d.ts +63 -0
- package/dist/layout/index.js +62 -0
- package/dist/log/index.d.ts +45 -0
- package/dist/log/index.js +159 -0
- package/dist/log/model.d.ts +16 -0
- package/dist/log/model.js +13 -0
- package/dist/node-graph/index.d.ts +123 -0
- package/dist/node-graph/index.js +304 -0
- package/dist/number-ticker/index.d.ts +29 -0
- package/dist/number-ticker/index.js +132 -0
- package/dist/number-ticker/model.d.ts +8 -0
- package/dist/number-ticker/model.js +34 -0
- package/dist/overflow-list/index.d.ts +39 -0
- package/dist/overflow-list/index.js +230 -0
- package/dist/overflow-list/layout.d.ts +19 -0
- package/dist/overflow-list/layout.js +44 -0
- package/dist/overlay/Overlay.d.ts +65 -0
- package/dist/overlay/Overlay.js +77 -0
- package/dist/overlay/Popover.d.ts +43 -0
- package/dist/overlay/Popover.js +45 -0
- package/dist/overlay/index.d.ts +4 -0
- package/dist/overlay/index.js +2 -0
- package/dist/overlay/themeBridge.d.ts +8 -0
- package/dist/overlay/themeBridge.js +98 -0
- package/dist/pagination/index.d.ts +23 -0
- package/dist/pagination/index.js +41 -0
- package/dist/pagination/model.d.ts +4 -0
- package/dist/pagination/model.js +42 -0
- package/dist/pan-zoom/index.d.ts +71 -0
- package/dist/pan-zoom/index.js +175 -0
- package/dist/pencil.d.ts +10 -0
- package/dist/pencil.js +39 -0
- package/dist/radar/index.d.ts +54 -0
- package/dist/radar/index.js +220 -0
- package/dist/range-slider/index.d.ts +16 -0
- package/dist/range-slider/index.js +22 -0
- package/dist/rating/index.d.ts +28 -0
- package/dist/rating/index.js +127 -0
- package/dist/resizable/index.d.ts +32 -0
- package/dist/resizable/index.js +50 -0
- package/dist/scroll-area/index.d.ts +36 -0
- package/dist/scroll-area/index.js +44 -0
- package/dist/segmented-progress/SegmentedProgress.d.ts +17 -0
- package/dist/segmented-progress/SegmentedProgress.js +42 -0
- package/dist/select/MultipleSelect.d.ts +44 -0
- package/dist/select/MultipleSelect.js +124 -0
- package/dist/select/SearchableSelect.d.ts +45 -0
- package/dist/select/SearchableSelect.js +50 -0
- package/dist/select/Select.d.ts +109 -0
- package/dist/select/Select.js +205 -0
- package/dist/select/index.d.ts +4 -0
- package/dist/select/index.js +2 -0
- package/dist/selection/Checkbox.d.ts +9 -0
- package/dist/selection/Checkbox.js +37 -0
- package/dist/selection/RadioGroup.d.ts +23 -0
- package/dist/selection/RadioGroup.js +82 -0
- package/dist/selection/Switch.d.ts +8 -0
- package/dist/selection/Switch.js +36 -0
- package/dist/selection/index.d.ts +6 -0
- package/dist/selection/index.js +3 -0
- package/dist/selection/useNativeChecked.d.ts +13 -0
- package/dist/selection/useNativeChecked.js +27 -0
- package/dist/slider/index.d.ts +11 -0
- package/dist/slider/index.js +45 -0
- package/dist/slot/index.d.ts +24 -0
- package/dist/slot/index.js +29 -0
- package/dist/sortable/index.d.ts +50 -0
- package/dist/sortable/index.js +278 -0
- package/dist/sortable/model.d.ts +30 -0
- package/dist/sortable/model.js +64 -0
- package/dist/sparkline/index.d.ts +19 -0
- package/dist/sparkline/index.js +240 -0
- package/dist/sparkline/model.d.ts +16 -0
- package/dist/sparkline/model.js +28 -0
- package/dist/steps/index.d.ts +16 -0
- package/dist/steps/index.js +37 -0
- package/dist/styles.css +8226 -0
- package/dist/tabs/index.d.ts +22 -0
- package/dist/tabs/index.js +42 -0
- package/dist/tag-input/index.d.ts +69 -0
- package/dist/tag-input/index.js +325 -0
- package/dist/tag-input/model.d.ts +14 -0
- package/dist/tag-input/model.js +38 -0
- package/dist/time-picker/index.d.ts +15 -0
- package/dist/time-picker/index.js +168 -0
- package/dist/timeline/index.d.ts +14 -0
- package/dist/timeline/index.js +16 -0
- package/dist/toast/index.d.ts +27 -0
- package/dist/toast/index.js +36 -0
- package/dist/toggle-group/index.d.ts +17 -0
- package/dist/toggle-group/index.js +26 -0
- package/dist/tokens.css +356 -0
- package/dist/tooltip/Tooltip.d.ts +24 -0
- package/dist/tooltip/Tooltip.js +32 -0
- package/dist/tooltip/index.d.ts +2 -0
- package/dist/tooltip/index.js +1 -0
- package/dist/transfer/index.d.ts +64 -0
- package/dist/transfer/index.js +174 -0
- package/dist/transfer/model.d.ts +12 -0
- package/dist/transfer/model.js +33 -0
- package/dist/tree/index.d.ts +39 -0
- package/dist/tree/index.js +36 -0
- package/dist/tree-select/index.d.ts +59 -0
- package/dist/tree-select/index.js +142 -0
- package/dist/tree-select/model.d.ts +31 -0
- package/dist/tree-select/model.js +135 -0
- package/dist/typewriter/index.d.ts +21 -0
- package/dist/typewriter/index.js +101 -0
- package/dist/typewriter/model.d.ts +7 -0
- package/dist/typewriter/model.js +45 -0
- package/dist/upload/index.d.ts +59 -0
- package/dist/upload/index.js +230 -0
- package/dist/virtual-list/index.d.ts +44 -0
- package/dist/virtual-list/index.js +128 -0
- package/dist/virtual-list/model.d.ts +7 -0
- package/dist/virtual-list/model.js +27 -0
- package/package.json +119 -4
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { pencilPaper } from './paper.js';
|
|
2
|
+
import { pencilStroke } from './stroke.js';
|
|
3
|
+
import { drawPencilWash } from './wash.js';
|
|
4
|
+
import { pencilRenderDefaults } from './renderTokens.js';
|
|
5
|
+
export const PENCIL_ACTION_WASH = Object.freeze({
|
|
6
|
+
lightStrength: 0.2,
|
|
7
|
+
darkStrength: 0.12,
|
|
8
|
+
});
|
|
9
|
+
export const PENCIL_QUIET_WASH = Object.freeze({
|
|
10
|
+
lightStrength: 0.12,
|
|
11
|
+
darkStrength: 0.08,
|
|
12
|
+
});
|
|
13
|
+
const surfaceStyles = {
|
|
14
|
+
paper: { radius: 8, width: 1.2, passes: 3 },
|
|
15
|
+
action: {
|
|
16
|
+
radius: 7,
|
|
17
|
+
width: 1.35,
|
|
18
|
+
passes: 3,
|
|
19
|
+
themeStrength: { light: PENCIL_ACTION_WASH.lightStrength, dark: PENCIL_ACTION_WASH.darkStrength },
|
|
20
|
+
},
|
|
21
|
+
quiet: {
|
|
22
|
+
radius: 7,
|
|
23
|
+
width: 0.75,
|
|
24
|
+
passes: 2,
|
|
25
|
+
themeStrength: { light: PENCIL_QUIET_WASH.lightStrength, dark: PENCIL_QUIET_WASH.darkStrength },
|
|
26
|
+
},
|
|
27
|
+
badge: { radius: 6, width: 0.9, passes: 2, strength: 0.25 },
|
|
28
|
+
outline: { radius: 7, width: 1.2, passes: 3 },
|
|
29
|
+
danger: { radius: 7, width: 0.75, passes: 2, strength: 0.18 },
|
|
30
|
+
};
|
|
31
|
+
export const PENCIL_FILL_WASH = Object.freeze({
|
|
32
|
+
angle: pencilRenderDefaults.washAngle,
|
|
33
|
+
spacing: pencilRenderDefaults.washSpacing,
|
|
34
|
+
roughness: pencilRenderDefaults.washRoughness,
|
|
35
|
+
inset: 2,
|
|
36
|
+
edgeReach: 0,
|
|
37
|
+
});
|
|
38
|
+
export const PENCIL_SELECTION_WASH = Object.freeze({
|
|
39
|
+
...PENCIL_FILL_WASH,
|
|
40
|
+
lightStrength: PENCIL_ACTION_WASH.lightStrength,
|
|
41
|
+
darkStrength: PENCIL_ACTION_WASH.darkStrength,
|
|
42
|
+
radius: 8,
|
|
43
|
+
});
|
|
44
|
+
function outline(width, height, radius, seed) {
|
|
45
|
+
const left = 2.5;
|
|
46
|
+
const top = 2.5;
|
|
47
|
+
const right = width - 2.5;
|
|
48
|
+
const bottom = height - 2.5;
|
|
49
|
+
const points = [];
|
|
50
|
+
for (let corner = 0; corner < 4; corner += 1) {
|
|
51
|
+
const variation = radius === 0 ? 0 : ((seed >>> (corner * 4)) & 7) * 0.12;
|
|
52
|
+
const resolvedRadius = Math.min(radius + variation, (right - left) / 2, (bottom - top) / 2);
|
|
53
|
+
const centerX = corner < 2 ? right - resolvedRadius : left + resolvedRadius;
|
|
54
|
+
const centerY = corner === 0 || corner === 3 ? top + resolvedRadius : bottom - resolvedRadius;
|
|
55
|
+
for (let step = 0; step <= 6; step += 1) {
|
|
56
|
+
const angle = (corner - 1 + step / 6) * Math.PI / 2;
|
|
57
|
+
points.push([
|
|
58
|
+
centerX + Math.cos(angle) * resolvedRadius,
|
|
59
|
+
centerY + Math.sin(angle) * resolvedRadius,
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return points;
|
|
64
|
+
}
|
|
65
|
+
function paperPattern(context, color, dark, dpr) {
|
|
66
|
+
const pattern = context.createPattern(pencilPaper(color, dark, dpr), 'repeat');
|
|
67
|
+
if (!pattern)
|
|
68
|
+
return color;
|
|
69
|
+
if (typeof pattern.setTransform === 'function' && typeof DOMMatrix !== 'undefined') {
|
|
70
|
+
pattern.setTransform(new DOMMatrix().scale(1 / dpr));
|
|
71
|
+
}
|
|
72
|
+
return pattern;
|
|
73
|
+
}
|
|
74
|
+
export function drawPencilSurface(canvas, borderCanvas, width, height, variant, seed, dpr, theme, radius) {
|
|
75
|
+
const bleed = variant === 'wash' ? 12 : 0;
|
|
76
|
+
canvas.width = Math.ceil((width + bleed * 2) * dpr);
|
|
77
|
+
canvas.height = Math.ceil((height + bleed * 2) * dpr);
|
|
78
|
+
const context = canvas.getContext('2d');
|
|
79
|
+
if (!context)
|
|
80
|
+
throw new Error('Canvas 2D is unavailable');
|
|
81
|
+
context.setTransform(canvas.width / (width + bleed * 2), 0, 0, canvas.height / (height + bleed * 2), 0, 0);
|
|
82
|
+
if (variant === 'wash') {
|
|
83
|
+
if (borderCanvas) {
|
|
84
|
+
borderCanvas.width = canvas.width;
|
|
85
|
+
borderCanvas.height = canvas.height;
|
|
86
|
+
}
|
|
87
|
+
context.translate(bleed, bleed);
|
|
88
|
+
drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
|
|
89
|
+
strength: theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48,
|
|
90
|
+
spacing: theme.washSettings?.spacing ?? pencilRenderDefaults.washSpacing,
|
|
91
|
+
roughness: theme.washSettings?.roughness ?? pencilRenderDefaults.washRoughness,
|
|
92
|
+
angle: (theme.washSettings?.angle ?? pencilRenderDefaults.washAngle) + 35,
|
|
93
|
+
inset: 2,
|
|
94
|
+
radius: 8,
|
|
95
|
+
edgeReach: 8,
|
|
96
|
+
dpr,
|
|
97
|
+
});
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
if (variant === 'selection') {
|
|
101
|
+
if (borderCanvas) {
|
|
102
|
+
borderCanvas.width = canvas.width;
|
|
103
|
+
borderCanvas.height = canvas.height;
|
|
104
|
+
}
|
|
105
|
+
drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
|
|
106
|
+
strength: (theme.dark ? PENCIL_SELECTION_WASH.darkStrength : PENCIL_SELECTION_WASH.lightStrength) * (theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48),
|
|
107
|
+
spacing: theme.washSettings?.spacing ?? PENCIL_SELECTION_WASH.spacing,
|
|
108
|
+
roughness: theme.washSettings?.roughness ?? PENCIL_SELECTION_WASH.roughness,
|
|
109
|
+
inset: PENCIL_SELECTION_WASH.inset,
|
|
110
|
+
radius: PENCIL_SELECTION_WASH.radius,
|
|
111
|
+
edgeReach: PENCIL_SELECTION_WASH.edgeReach,
|
|
112
|
+
angle: theme.washSettings?.angle ?? PENCIL_SELECTION_WASH.angle,
|
|
113
|
+
dpr,
|
|
114
|
+
});
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
if (variant === 'ghost') {
|
|
118
|
+
if (borderCanvas) {
|
|
119
|
+
borderCanvas.width = canvas.width;
|
|
120
|
+
borderCanvas.height = canvas.height;
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
if (variant === 'full') {
|
|
125
|
+
if (borderCanvas) {
|
|
126
|
+
borderCanvas.width = canvas.width;
|
|
127
|
+
borderCanvas.height = canvas.height;
|
|
128
|
+
}
|
|
129
|
+
context.fillStyle = paperPattern(context, theme.paper, theme.dark, dpr);
|
|
130
|
+
context.fillRect(0, 0, width, height);
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
const style = surfaceStyles[variant];
|
|
134
|
+
const strength = style.themeStrength
|
|
135
|
+
? (theme.dark ? style.themeStrength.dark : style.themeStrength.light)
|
|
136
|
+
: style.strength;
|
|
137
|
+
const resolvedRadius = radius !== undefined && Number.isFinite(radius) ? Math.max(0, Math.min(radius, 8)) : style.radius;
|
|
138
|
+
const points = outline(width, height, resolvedRadius, seed);
|
|
139
|
+
if (variant !== 'outline') {
|
|
140
|
+
context.save();
|
|
141
|
+
context.beginPath();
|
|
142
|
+
context.moveTo(...points[0]);
|
|
143
|
+
for (const point of points.slice(1))
|
|
144
|
+
context.lineTo(...point);
|
|
145
|
+
context.closePath();
|
|
146
|
+
context.clip();
|
|
147
|
+
context.fillStyle = paperPattern(context, theme.paper, theme.dark, dpr);
|
|
148
|
+
context.fillRect(0, 0, width, height);
|
|
149
|
+
if (strength) {
|
|
150
|
+
drawPencilWash(context, { x: 0, y: 0, width, height }, theme.wash, seed, {
|
|
151
|
+
...PENCIL_FILL_WASH,
|
|
152
|
+
...theme.washSettings,
|
|
153
|
+
strength: strength * (theme.washSettings?.strength ?? pencilRenderDefaults.washStrength / 0.48),
|
|
154
|
+
radius: resolvedRadius,
|
|
155
|
+
dpr,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
context.restore();
|
|
159
|
+
}
|
|
160
|
+
let borderContext = context;
|
|
161
|
+
if (borderCanvas) {
|
|
162
|
+
borderCanvas.width = canvas.width;
|
|
163
|
+
borderCanvas.height = canvas.height;
|
|
164
|
+
const separateContext = borderCanvas.getContext('2d');
|
|
165
|
+
if (separateContext) {
|
|
166
|
+
separateContext.setTransform(canvas.width / width, 0, 0, canvas.height / height, 0, 0);
|
|
167
|
+
borderContext = separateContext;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
pencilStroke(borderContext, points, theme.ink, style.width * ((theme.strokeWidth ?? pencilRenderDefaults.strokeWidth) / 1.2), seed, theme.strokeRoughness ?? pencilRenderDefaults.strokeRoughness, style.passes, true, {
|
|
171
|
+
variation: 0.82,
|
|
172
|
+
breaks: variant === 'paper' ? 0.2 : 0.25,
|
|
173
|
+
grain: 0.7,
|
|
174
|
+
gain: variant === 'action' ? 2.5 : 2.1,
|
|
175
|
+
step: 0.8,
|
|
176
|
+
});
|
|
177
|
+
return points;
|
|
178
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function queueTextPaint(job: () => void): () => void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const jobs = new Set();
|
|
2
|
+
let pending = null;
|
|
3
|
+
function schedule() {
|
|
4
|
+
if (pending !== null || !jobs.size)
|
|
5
|
+
return;
|
|
6
|
+
const run = (deadline) => {
|
|
7
|
+
pending = null;
|
|
8
|
+
const start = performance.now();
|
|
9
|
+
do {
|
|
10
|
+
const job = jobs.values().next().value;
|
|
11
|
+
if (!job)
|
|
12
|
+
break;
|
|
13
|
+
jobs.delete(job);
|
|
14
|
+
job();
|
|
15
|
+
} while (jobs.size && performance.now() - start < 6 && (deadline?.timeRemaining() ?? 3) > 2);
|
|
16
|
+
schedule();
|
|
17
|
+
};
|
|
18
|
+
const idleWindow = window;
|
|
19
|
+
pending = idleWindow.requestIdleCallback
|
|
20
|
+
? idleWindow.requestIdleCallback(run, { timeout: 300 })
|
|
21
|
+
: window.setTimeout(run, 16);
|
|
22
|
+
}
|
|
23
|
+
export function queueTextPaint(job) {
|
|
24
|
+
jobs.add(job);
|
|
25
|
+
schedule();
|
|
26
|
+
return () => {
|
|
27
|
+
jobs.delete(job);
|
|
28
|
+
if (!jobs.size && pending !== null) {
|
|
29
|
+
const idleWindow = window;
|
|
30
|
+
if (idleWindow.cancelIdleCallback)
|
|
31
|
+
idleWindow.cancelIdleCallback(pending);
|
|
32
|
+
else
|
|
33
|
+
window.clearTimeout(pending);
|
|
34
|
+
pending = null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface PencilLetteringSettings {
|
|
2
|
+
variation: number;
|
|
3
|
+
breaks: number;
|
|
4
|
+
outline: number;
|
|
5
|
+
fill: number;
|
|
6
|
+
grain: number;
|
|
7
|
+
}
|
|
8
|
+
export interface PencilTextSprite {
|
|
9
|
+
ink: HTMLCanvasElement;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
advance: number;
|
|
13
|
+
ascent: number;
|
|
14
|
+
descent: number;
|
|
15
|
+
padding: number;
|
|
16
|
+
}
|
|
17
|
+
export declare const pencilLettering: PencilLetteringSettings;
|
|
18
|
+
export declare const pencilHandwriting = "\"Trip Map Hand\",\"HanziPen SC\",\"Hannotate SC\",\"Kaiti SC\",\"STKaiti\",cursive";
|
|
19
|
+
export declare function renderPencilText(text: string, options: {
|
|
20
|
+
size: number;
|
|
21
|
+
family?: string;
|
|
22
|
+
color: string;
|
|
23
|
+
seed: number;
|
|
24
|
+
dpr: number;
|
|
25
|
+
settings?: PencilLetteringSettings;
|
|
26
|
+
cache?: boolean;
|
|
27
|
+
glyphSeed?: number;
|
|
28
|
+
}): PencilTextSprite;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { BoundedCache, PENCIL_RENDERER_VERSION, canvasByteCost, canvasIsUsable } from './cache.js';
|
|
2
|
+
import { seededRandom } from './random.js';
|
|
3
|
+
export const pencilLettering = Object.freeze({
|
|
4
|
+
variation: 0.2,
|
|
5
|
+
breaks: 0.5,
|
|
6
|
+
outline: 0.9,
|
|
7
|
+
fill: 0.9,
|
|
8
|
+
grain: 0.6,
|
|
9
|
+
});
|
|
10
|
+
export const pencilHandwriting = '"Trip Map Hand","HanziPen SC","Hannotate SC","Kaiti SC","STKaiti",cursive';
|
|
11
|
+
const TEXT_PIXEL_LIMIT = 524288;
|
|
12
|
+
const textCache = new BoundedCache(160, 16 * 1024 * 1024);
|
|
13
|
+
const glyphCache = new BoundedCache(512, 8 * 1024 * 1024);
|
|
14
|
+
const clamp = (value) => Math.max(0, Math.min(1, value));
|
|
15
|
+
function hash(x, y, seed) {
|
|
16
|
+
let value = Math.imul(x ^ seed, 374761393) ^ Math.imul(y, 668265263);
|
|
17
|
+
value = Math.imul(value ^ (value >>> 13), 1274126177);
|
|
18
|
+
return ((value ^ (value >>> 16)) >>> 0) / 4294967296;
|
|
19
|
+
}
|
|
20
|
+
function field(x, y, seed) {
|
|
21
|
+
const integerX = Math.floor(x);
|
|
22
|
+
const integerY = Math.floor(y);
|
|
23
|
+
const fractionX = x - integerX;
|
|
24
|
+
const fractionY = y - integerY;
|
|
25
|
+
const smoothX = fractionX * fractionX * (3 - 2 * fractionX);
|
|
26
|
+
const smoothY = fractionY * fractionY * (3 - 2 * fractionY);
|
|
27
|
+
return (hash(integerX, integerY, seed) * (1 - smoothX)
|
|
28
|
+
+ hash(integerX + 1, integerY, seed) * smoothX) * (1 - smoothY)
|
|
29
|
+
+ (hash(integerX, integerY + 1, seed) * (1 - smoothX)
|
|
30
|
+
+ hash(integerX + 1, integerY + 1, seed) * smoothX) * smoothY;
|
|
31
|
+
}
|
|
32
|
+
function distanceTo(mask, width, height, inside) {
|
|
33
|
+
const distance = new Float32Array(width * height);
|
|
34
|
+
for (let index = 0; index < distance.length; index += 1) {
|
|
35
|
+
distance[index] = (mask[index * 4 + 3] > 100) === inside ? 0 : 1000;
|
|
36
|
+
}
|
|
37
|
+
const relax = (index, neighbor, cost) => {
|
|
38
|
+
distance[index] = Math.min(distance[index], distance[neighbor] + cost);
|
|
39
|
+
};
|
|
40
|
+
for (let y = 0; y < height; y += 1) {
|
|
41
|
+
for (let x = 0; x < width; x += 1) {
|
|
42
|
+
const index = y * width + x;
|
|
43
|
+
if (x)
|
|
44
|
+
relax(index, index - 1, 1);
|
|
45
|
+
if (y) {
|
|
46
|
+
relax(index, index - width, 1);
|
|
47
|
+
if (x)
|
|
48
|
+
relax(index, index - width - 1, 1.414);
|
|
49
|
+
if (x + 1 < width)
|
|
50
|
+
relax(index, index - width + 1, 1.414);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (let y = height - 1; y >= 0; y -= 1) {
|
|
55
|
+
for (let x = width - 1; x >= 0; x -= 1) {
|
|
56
|
+
const index = y * width + x;
|
|
57
|
+
if (x + 1 < width)
|
|
58
|
+
relax(index, index + 1, 1);
|
|
59
|
+
if (y + 1 < height) {
|
|
60
|
+
relax(index, index + width, 1);
|
|
61
|
+
if (x)
|
|
62
|
+
relax(index, index + width - 1, 1.414);
|
|
63
|
+
if (x + 1 < width)
|
|
64
|
+
relax(index, index + width + 1, 1.414);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return distance;
|
|
69
|
+
}
|
|
70
|
+
export function renderPencilText(text, options) {
|
|
71
|
+
const { size, color, seed, dpr } = options;
|
|
72
|
+
const family = options.family ?? pencilHandwriting;
|
|
73
|
+
const settings = options.settings ?? pencilLettering;
|
|
74
|
+
const cache = options.cache ?? true;
|
|
75
|
+
if (text.length > 256 || !Number.isFinite(size) || size <= 0 || size > 128) {
|
|
76
|
+
throw new RangeError('Pencil text exceeds the bounded renderer input');
|
|
77
|
+
}
|
|
78
|
+
const resolution = Math.max(2, Math.min(4, Math.ceil(dpr * 2)));
|
|
79
|
+
const font = `400 ${size}px ${family}`;
|
|
80
|
+
const fontReady = document.fonts?.check(font, text) ?? false;
|
|
81
|
+
const key = cache ? JSON.stringify([
|
|
82
|
+
PENCIL_RENDERER_VERSION,
|
|
83
|
+
text,
|
|
84
|
+
size,
|
|
85
|
+
family,
|
|
86
|
+
color,
|
|
87
|
+
seed,
|
|
88
|
+
dpr,
|
|
89
|
+
resolution,
|
|
90
|
+
settings,
|
|
91
|
+
fontReady,
|
|
92
|
+
options.glyphSeed,
|
|
93
|
+
]) : '';
|
|
94
|
+
if (cache) {
|
|
95
|
+
const cached = textCache.get(key);
|
|
96
|
+
if (cached && canvasIsUsable(cached.ink))
|
|
97
|
+
return cached;
|
|
98
|
+
}
|
|
99
|
+
const mask = document.createElement('canvas');
|
|
100
|
+
const measure = mask.getContext('2d', { willReadFrequently: true });
|
|
101
|
+
if (!measure)
|
|
102
|
+
throw new Error('Canvas 2D is unavailable');
|
|
103
|
+
measure.fillStyle = color;
|
|
104
|
+
measure.fillRect(0, 0, 1, 1);
|
|
105
|
+
const rgb = measure.getImageData(0, 0, 1, 1).data;
|
|
106
|
+
measure.font = font;
|
|
107
|
+
measure.fontKerning = 'none';
|
|
108
|
+
const characters = Array.from(text);
|
|
109
|
+
const widths = characters.map(character => measure.measureText(character).width);
|
|
110
|
+
const advance = Math.ceil(widths.reduce((sum, width) => sum + width, 0));
|
|
111
|
+
const padding = 5;
|
|
112
|
+
const ascent = Math.ceil(size * 1.1);
|
|
113
|
+
const descent = Math.ceil(size * 0.35);
|
|
114
|
+
const width = advance + padding * 2;
|
|
115
|
+
const height = ascent + descent + padding * 2;
|
|
116
|
+
if (width * height * resolution ** 2 > TEXT_PIXEL_LIMIT) {
|
|
117
|
+
throw new RangeError('Pencil text exceeds the bounded pixel budget');
|
|
118
|
+
}
|
|
119
|
+
mask.width = width * resolution;
|
|
120
|
+
mask.height = height * resolution;
|
|
121
|
+
const context = mask.getContext('2d', { willReadFrequently: true });
|
|
122
|
+
if (!context)
|
|
123
|
+
throw new Error('Canvas 2D is unavailable');
|
|
124
|
+
context.scale(resolution, resolution);
|
|
125
|
+
// Plain Latin labels can reuse bounded character sprites. Complex scripts and
|
|
126
|
+
// private editable text retain the original whole-line rendering path.
|
|
127
|
+
if (cache && options.glyphSeed !== undefined && /^[\x20-\x7e]+$/.test(text)) {
|
|
128
|
+
let left = 0;
|
|
129
|
+
characters.forEach((character, index) => {
|
|
130
|
+
if (character !== ' ') {
|
|
131
|
+
const characterSeed = (options.glyphSeed ^ Math.imul(character.charCodeAt(0), 16777619)
|
|
132
|
+
^ Math.imul(index % 3, 374761393)) >>> 0;
|
|
133
|
+
const glyphKey = JSON.stringify([
|
|
134
|
+
PENCIL_RENDERER_VERSION, character, size, family, color, characterSeed,
|
|
135
|
+
resolution, settings, document.fonts?.check(font, character),
|
|
136
|
+
]);
|
|
137
|
+
let glyph = glyphCache.get(glyphKey);
|
|
138
|
+
if (!glyph || !canvasIsUsable(glyph.ink)) {
|
|
139
|
+
glyph = renderPencilText(character, { ...options, seed: characterSeed, glyphSeed: undefined, cache: false });
|
|
140
|
+
glyphCache.set(glyphKey, glyph, canvasByteCost(glyph.ink));
|
|
141
|
+
}
|
|
142
|
+
context.drawImage(glyph.ink, left, 0, glyph.width, glyph.height);
|
|
143
|
+
}
|
|
144
|
+
left += widths[index];
|
|
145
|
+
});
|
|
146
|
+
const sprite = { ink: mask, width, height, advance, ascent, descent, padding };
|
|
147
|
+
return textCache.set(key, sprite, canvasByteCost(mask));
|
|
148
|
+
}
|
|
149
|
+
context.font = font;
|
|
150
|
+
context.fontKerning = 'none';
|
|
151
|
+
context.fillStyle = color;
|
|
152
|
+
context.strokeStyle = color;
|
|
153
|
+
context.lineWidth = size >= 16 ? 0.48 : 0.24;
|
|
154
|
+
const random = seededRandom(seed);
|
|
155
|
+
let x = padding;
|
|
156
|
+
characters.forEach((character, index) => {
|
|
157
|
+
const center = x + widths[index] / 2;
|
|
158
|
+
const baseline = padding + ascent + (random() - 0.5) * 0.55;
|
|
159
|
+
context.save();
|
|
160
|
+
context.translate(center, baseline);
|
|
161
|
+
context.rotate((random() - 0.5) * 0.035);
|
|
162
|
+
context.globalAlpha = 1;
|
|
163
|
+
context.fillText(character, -widths[index] / 2, 0);
|
|
164
|
+
context.strokeText(character, -widths[index] / 2, 0);
|
|
165
|
+
context.restore();
|
|
166
|
+
x += widths[index];
|
|
167
|
+
});
|
|
168
|
+
const image = context.getImageData(0, 0, mask.width, mask.height);
|
|
169
|
+
const shape = new Uint8ClampedArray(image.data);
|
|
170
|
+
const outside = distanceTo(shape, mask.width, mask.height, true);
|
|
171
|
+
const inside = distanceTo(shape, mask.width, mask.height, false);
|
|
172
|
+
const cuts = [];
|
|
173
|
+
let characterLeft = padding;
|
|
174
|
+
for (let character = 0; character < characters.length; character += 1) {
|
|
175
|
+
for (let attempt = 0, found = 0; attempt < 80 && found < 2; attempt += 1) {
|
|
176
|
+
const centerX = characterLeft + random() * widths[character];
|
|
177
|
+
const centerY = padding + ascent - size + random() * size;
|
|
178
|
+
const pixel = Math.floor(centerY * resolution) * mask.width + Math.floor(centerX * resolution);
|
|
179
|
+
if (shape[pixel * 4 + 3] < 130)
|
|
180
|
+
continue;
|
|
181
|
+
const angle = random() * Math.PI;
|
|
182
|
+
cuts.push({
|
|
183
|
+
x: centerX,
|
|
184
|
+
y: centerY,
|
|
185
|
+
cosine: Math.cos(angle),
|
|
186
|
+
sine: Math.sin(angle),
|
|
187
|
+
long: 1.1 + random() * 0.7,
|
|
188
|
+
short: 0.22 + random() * 0.2,
|
|
189
|
+
});
|
|
190
|
+
found += 1;
|
|
191
|
+
}
|
|
192
|
+
characterLeft += widths[character];
|
|
193
|
+
}
|
|
194
|
+
const columnCuts = Array.from({ length: mask.width }, (_, column) => cuts.filter(cut => Math.abs(column / resolution - cut.x)
|
|
195
|
+
<= Math.hypot(cut.cosine * cut.long, cut.sine * cut.short) * 1.55));
|
|
196
|
+
for (let y = 0; y < mask.height; y += 1) {
|
|
197
|
+
for (let xIndex = 0; xIndex < mask.width; xIndex += 1) {
|
|
198
|
+
const index = (y * mask.width + xIndex) * 4;
|
|
199
|
+
const pixel = y * mask.width + xIndex;
|
|
200
|
+
if (outside[pixel] > resolution * 1.1) {
|
|
201
|
+
image.data[index + 3] = 0;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const pixelX = xIndex / resolution;
|
|
205
|
+
const pixelY = y / resolution;
|
|
206
|
+
const force = field(pixelX / 2.7, pixelY / 2.4, seed + 31);
|
|
207
|
+
const pressure = 1 - settings.variation * 0.94
|
|
208
|
+
* (1 - clamp((force - 0.23) / 0.46) ** 0.85);
|
|
209
|
+
const paperTooth = field(pixelX * 2.3, pixelY * 2.1, seed + 113);
|
|
210
|
+
const tooth = 1 - settings.grain * 0.76
|
|
211
|
+
* (1 - clamp((paperTooth - 0.2) / 0.53));
|
|
212
|
+
let contact = 1;
|
|
213
|
+
for (const cut of columnCuts[xIndex]) {
|
|
214
|
+
const deltaX = pixelX - cut.x;
|
|
215
|
+
const deltaY = pixelY - cut.y;
|
|
216
|
+
const radius = Math.hypot((deltaX * cut.cosine + deltaY * cut.sine) / cut.long, (-deltaX * cut.sine + deltaY * cut.cosine) / cut.short);
|
|
217
|
+
contact = Math.min(contact, clamp((radius - 0.85) / 0.7));
|
|
218
|
+
}
|
|
219
|
+
const distance = shape[index + 3] > 100
|
|
220
|
+
? -(inside[pixel] - 0.5) / resolution
|
|
221
|
+
: (outside[pixel] - 0.5) / resolution;
|
|
222
|
+
const edgeWidth = 0.34 + settings.outline * 0.5;
|
|
223
|
+
const edgeWobble = (field(pixelX * 0.9, pixelY * 0.9, seed + 209) - 0.5)
|
|
224
|
+
* 0.32 * settings.outline;
|
|
225
|
+
const rim = clamp(1 - Math.abs(distance + edgeWobble) / edgeWidth);
|
|
226
|
+
const core = (shape[index + 3] / 255) * (0.28 + 0.64 * settings.fill);
|
|
227
|
+
const lift = 1 - settings.breaks * 1.75 * (1 - contact);
|
|
228
|
+
const pigment = clamp(Math.max(core, rim * (0.48 + 0.52 * settings.outline))
|
|
229
|
+
* pressure * tooth * Math.max(0.08, lift) * 1.38);
|
|
230
|
+
image.data[index] = rgb[0];
|
|
231
|
+
image.data[index + 1] = rgb[1];
|
|
232
|
+
image.data[index + 2] = rgb[2];
|
|
233
|
+
image.data[index + 3] = Math.round(255 * pigment);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
context.putImageData(image, 0, 0);
|
|
237
|
+
const sprite = { ink: mask, width, height, advance, ascent, descent, padding };
|
|
238
|
+
return cache ? textCache.set(key, sprite, canvasByteCost(mask)) : sprite;
|
|
239
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare const paths: {
|
|
2
|
+
plus: {
|
|
3
|
+
d: string;
|
|
4
|
+
}[];
|
|
5
|
+
minus: {
|
|
6
|
+
d: string;
|
|
7
|
+
}[];
|
|
8
|
+
reset: {
|
|
9
|
+
d: string;
|
|
10
|
+
}[];
|
|
11
|
+
previous: {
|
|
12
|
+
d: string;
|
|
13
|
+
}[];
|
|
14
|
+
next: {
|
|
15
|
+
d: string;
|
|
16
|
+
}[];
|
|
17
|
+
info: {
|
|
18
|
+
d: string;
|
|
19
|
+
}[];
|
|
20
|
+
};
|
|
21
|
+
export declare function TravelIcon({ name }: {
|
|
22
|
+
name: keyof typeof paths;
|
|
23
|
+
}): import("react").JSX.Element;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PencilIcon } from '../PencilIcon.js';
|
|
3
|
+
// Lucide ISC geometry rendered through the same pencil renderer as the trip tools.
|
|
4
|
+
const paths = {
|
|
5
|
+
plus: [{ d: 'M5 12h14' }, { d: 'M12 5v14' }],
|
|
6
|
+
minus: [{ d: 'M5 12h14' }],
|
|
7
|
+
reset: [{ d: 'M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8' }, { d: 'M3 3v5h5' }],
|
|
8
|
+
previous: [{ d: 'm12 19-7-7 7-7' }, { d: 'M19 12H5' }],
|
|
9
|
+
next: [{ d: 'M5 12h14' }, { d: 'm12 5 7 7-7 7' }],
|
|
10
|
+
info: [{ d: 'M22 12a10 10 0 1 1-20 0 10 10 0 0 1 20 0' }, { d: 'M12 16v-4' }, { d: 'M12 8h.01' }],
|
|
11
|
+
};
|
|
12
|
+
export function TravelIcon({ name }) {
|
|
13
|
+
return _jsx(PencilIcon, { paths: paths[name], sourceSize: 24, size: 30 });
|
|
14
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { PencilSurfaceVariant } from './surfaceRenderer.js';
|
|
3
|
+
export declare function usePencilSurface(rootRef: RefObject<HTMLElement | null>, canvasRef: RefObject<HTMLCanvasElement | null>, borderRef: RefObject<HTMLCanvasElement | null>, variant: PencilSurfaceVariant, seed: string | number, clipContent: boolean, radius?: number): void;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { useBrowserLayoutEffect } from './useBrowserLayoutEffect.js';
|
|
2
|
+
import { PENCIL_RENDERER_VERSION, effectiveDpr } from './cache.js';
|
|
3
|
+
import { observeCanvasRecovery } from './recovery.js';
|
|
4
|
+
import { seedToNumber } from './random.js';
|
|
5
|
+
import { drawPencilSurface } from './surfaceRenderer.js';
|
|
6
|
+
import { maxStrokeRoughness, pencilRenderDefaults, readRenderToken } from './renderTokens.js';
|
|
7
|
+
function token(style, name, fallback) {
|
|
8
|
+
return style.getPropertyValue(name).trim() || fallback;
|
|
9
|
+
}
|
|
10
|
+
export function usePencilSurface(rootRef, canvasRef, borderRef, variant, seed, clipContent, radius) {
|
|
11
|
+
useBrowserLayoutEffect(() => {
|
|
12
|
+
const root = rootRef.current;
|
|
13
|
+
const canvas = canvasRef.current;
|
|
14
|
+
if (!root || !canvas)
|
|
15
|
+
return undefined;
|
|
16
|
+
let frame = 0;
|
|
17
|
+
let lastPaint = '';
|
|
18
|
+
let disposed = false;
|
|
19
|
+
const paint = () => {
|
|
20
|
+
frame = 0;
|
|
21
|
+
if (disposed)
|
|
22
|
+
return;
|
|
23
|
+
const width = root.clientWidth;
|
|
24
|
+
const height = root.clientHeight;
|
|
25
|
+
if (width < 6 || height < 6)
|
|
26
|
+
return;
|
|
27
|
+
const dpr = effectiveDpr(2);
|
|
28
|
+
const style = getComputedStyle(root);
|
|
29
|
+
const paper = token(style, '--inkra-paper', '#fcfcfa');
|
|
30
|
+
const ink = variant === 'action' || variant === 'outline'
|
|
31
|
+
? token(style, '--inkra-accent-strong', '#234e39')
|
|
32
|
+
: variant === 'danger'
|
|
33
|
+
? token(style, '--inkra-danger', '#873e36')
|
|
34
|
+
: token(style, '--inkra-border-strong', '#778575');
|
|
35
|
+
const dark = style.colorScheme.includes('dark');
|
|
36
|
+
const strokeWidth = readRenderToken(style, '--inkra-stroke-width', pencilRenderDefaults.strokeWidth, 0.3, 3);
|
|
37
|
+
const strokeRoughness = readRenderToken(style, '--inkra-stroke-amplitude', pencilRenderDefaults.strokeRoughness, 0, maxStrokeRoughness);
|
|
38
|
+
const washSettings = {
|
|
39
|
+
strength: readRenderToken(style, '--inkra-wash-strength', pencilRenderDefaults.washStrength, 0, 1) / 0.48,
|
|
40
|
+
spacing: readRenderToken(style, '--inkra-wash-spacing', pencilRenderDefaults.washSpacing, 1, 6),
|
|
41
|
+
roughness: readRenderToken(style, '--inkra-wash-roughness', pencilRenderDefaults.washRoughness, 0, 6),
|
|
42
|
+
angle: readRenderToken(style, '--inkra-wash-angle', pencilRenderDefaults.washAngle, -85, 85),
|
|
43
|
+
};
|
|
44
|
+
const wash = variant === 'selection'
|
|
45
|
+
? token(style, '--inkra-selection-pigment', dark ? '#c6b873' : '#769566')
|
|
46
|
+
: variant === 'danger'
|
|
47
|
+
? token(style, '--inkra-danger-soft', '#d49b8b')
|
|
48
|
+
: variant === 'badge'
|
|
49
|
+
? token(style, '--inkra-coral-soft', '#f8ebe7')
|
|
50
|
+
: variant === 'quiet'
|
|
51
|
+
? token(style, '--inkra-accent-soft', '#e6efe8')
|
|
52
|
+
: token(style, '--inkra-accent', '#2e634a');
|
|
53
|
+
const numericSeed = seedToNumber(seed);
|
|
54
|
+
const paintKey = JSON.stringify([
|
|
55
|
+
PENCIL_RENDERER_VERSION,
|
|
56
|
+
variant,
|
|
57
|
+
numericSeed,
|
|
58
|
+
width,
|
|
59
|
+
height,
|
|
60
|
+
dpr,
|
|
61
|
+
paper,
|
|
62
|
+
ink,
|
|
63
|
+
wash,
|
|
64
|
+
dark,
|
|
65
|
+
clipContent,
|
|
66
|
+
radius,
|
|
67
|
+
strokeWidth, strokeRoughness, washSettings,
|
|
68
|
+
]);
|
|
69
|
+
if (paintKey === lastPaint && !canvas.getContext('2d')?.isContextLost?.())
|
|
70
|
+
return;
|
|
71
|
+
try {
|
|
72
|
+
const points = drawPencilSurface(canvas, borderRef.current, width, height, variant, numericSeed, dpr, { paper, dark, ink, wash, strokeWidth, strokeRoughness, washSettings }, radius);
|
|
73
|
+
if (clipContent && points) {
|
|
74
|
+
root.style.setProperty('--inkra-pencil-clip', `polygon(${points.map(([x, y]) => `${x / width * 100}% ${y / height * 100}%`).join(',')})`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
root.style.removeProperty('--inkra-pencil-clip');
|
|
78
|
+
}
|
|
79
|
+
delete root.dataset.pencilFallback;
|
|
80
|
+
lastPaint = paintKey;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
root.dataset.pencilFallback = 'true';
|
|
84
|
+
root.style.removeProperty('--inkra-pencil-clip');
|
|
85
|
+
lastPaint = '';
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const schedule = () => {
|
|
89
|
+
if (!frame)
|
|
90
|
+
frame = requestAnimationFrame(paint);
|
|
91
|
+
};
|
|
92
|
+
const resize = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(schedule);
|
|
93
|
+
resize?.observe(root);
|
|
94
|
+
window.addEventListener('resize', schedule, { passive: true });
|
|
95
|
+
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
|
96
|
+
const inheritedStyle = new MutationObserver(schedule);
|
|
97
|
+
for (let element = root; element; element = element.parentElement) {
|
|
98
|
+
inheritedStyle.observe(element, { attributes: true, attributeFilter: ['style', 'class', 'data-theme', 'data-inkra-theme'] });
|
|
99
|
+
}
|
|
100
|
+
colorScheme.addEventListener('change', schedule);
|
|
101
|
+
const stopRecovery = observeCanvasRecovery(() => {
|
|
102
|
+
lastPaint = '';
|
|
103
|
+
schedule();
|
|
104
|
+
});
|
|
105
|
+
paint();
|
|
106
|
+
return () => {
|
|
107
|
+
disposed = true;
|
|
108
|
+
stopRecovery();
|
|
109
|
+
resize?.disconnect();
|
|
110
|
+
inheritedStyle.disconnect();
|
|
111
|
+
colorScheme.removeEventListener('change', schedule);
|
|
112
|
+
window.removeEventListener('resize', schedule);
|
|
113
|
+
cancelAnimationFrame(frame);
|
|
114
|
+
};
|
|
115
|
+
}, [variant, seed, clipContent, radius, rootRef, canvasRef, borderRef]);
|
|
116
|
+
}
|