@guinetik/gcanvas 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/.github/workflows/release.yaml +70 -0
- package/.jshintrc +4 -0
- package/.vscode/settings.json +22 -0
- package/CLAUDE.md +310 -0
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +1 -0
- package/demos/animations.html +31 -0
- package/demos/basic.html +38 -0
- package/demos/baskara.html +31 -0
- package/demos/bezier.html +35 -0
- package/demos/beziersignature.html +29 -0
- package/demos/blackhole.html +28 -0
- package/demos/blob.html +35 -0
- package/demos/demos.css +289 -0
- package/demos/easing.html +28 -0
- package/demos/events.html +195 -0
- package/demos/fluent.html +647 -0
- package/demos/fractals.html +36 -0
- package/demos/genart.html +26 -0
- package/demos/gendream.html +26 -0
- package/demos/group.html +36 -0
- package/demos/home.html +587 -0
- package/demos/index.html +364 -0
- package/demos/isometric.html +34 -0
- package/demos/js/animations.js +452 -0
- package/demos/js/basic.js +204 -0
- package/demos/js/baskara.js +751 -0
- package/demos/js/bezier.js +692 -0
- package/demos/js/beziersignature.js +241 -0
- package/demos/js/blackhole/accretiondisk.obj.js +379 -0
- package/demos/js/blackhole/blackhole.obj.js +318 -0
- package/demos/js/blackhole/index.js +409 -0
- package/demos/js/blackhole/particle.js +56 -0
- package/demos/js/blackhole/starfield.obj.js +218 -0
- package/demos/js/blob.js +2263 -0
- package/demos/js/easing.js +477 -0
- package/demos/js/fluent.js +183 -0
- package/demos/js/fractals.js +931 -0
- package/demos/js/fractalworker.js +93 -0
- package/demos/js/genart.js +268 -0
- package/demos/js/gendream.js +209 -0
- package/demos/js/group.js +140 -0
- package/demos/js/info-toggle.js +25 -0
- package/demos/js/isometric.js +863 -0
- package/demos/js/kerr.js +1556 -0
- package/demos/js/lavalamp.js +590 -0
- package/demos/js/layout.js +354 -0
- package/demos/js/mondrian.js +285 -0
- package/demos/js/opacity.js +275 -0
- package/demos/js/painter.js +484 -0
- package/demos/js/particles-showcase.js +514 -0
- package/demos/js/particles.js +299 -0
- package/demos/js/patterns.js +397 -0
- package/demos/js/penrose/artifact.js +69 -0
- package/demos/js/penrose/blackhole.js +121 -0
- package/demos/js/penrose/constants.js +73 -0
- package/demos/js/penrose/game.js +943 -0
- package/demos/js/penrose/lore.js +278 -0
- package/demos/js/penrose/penrosescene.js +892 -0
- package/demos/js/penrose/ship.js +216 -0
- package/demos/js/penrose/sounds.js +211 -0
- package/demos/js/penrose/voidparticle.js +55 -0
- package/demos/js/penrose/voidscene.js +258 -0
- package/demos/js/penrose/voidship.js +144 -0
- package/demos/js/penrose/wormhole.js +46 -0
- package/demos/js/pipeline.js +555 -0
- package/demos/js/scene.js +304 -0
- package/demos/js/scenes.js +320 -0
- package/demos/js/schrodinger.js +410 -0
- package/demos/js/schwarzschild.js +1023 -0
- package/demos/js/shapes.js +628 -0
- package/demos/js/space/alien.js +171 -0
- package/demos/js/space/boom.js +98 -0
- package/demos/js/space/boss.js +353 -0
- package/demos/js/space/buff.js +73 -0
- package/demos/js/space/bullet.js +102 -0
- package/demos/js/space/constants.js +85 -0
- package/demos/js/space/game.js +1884 -0
- package/demos/js/space/hud.js +112 -0
- package/demos/js/space/laserbeam.js +179 -0
- package/demos/js/space/lightning.js +277 -0
- package/demos/js/space/minion.js +192 -0
- package/demos/js/space/missile.js +212 -0
- package/demos/js/space/player.js +430 -0
- package/demos/js/space/powerup.js +90 -0
- package/demos/js/space/starfield.js +58 -0
- package/demos/js/space/starpower.js +90 -0
- package/demos/js/spacetime.js +559 -0
- package/demos/js/svgtween.js +204 -0
- package/demos/js/tde/accretiondisk.js +418 -0
- package/demos/js/tde/blackhole.js +219 -0
- package/demos/js/tde/blackholescene.js +209 -0
- package/demos/js/tde/config.js +59 -0
- package/demos/js/tde/index.js +695 -0
- package/demos/js/tde/jets.js +290 -0
- package/demos/js/tde/lensedstarfield.js +147 -0
- package/demos/js/tde/tdestar.js +317 -0
- package/demos/js/tde/tidalstream.js +356 -0
- package/demos/js/tde_old/blackhole.obj.js +354 -0
- package/demos/js/tde_old/debris.obj.js +791 -0
- package/demos/js/tde_old/flare.obj.js +239 -0
- package/demos/js/tde_old/index.js +448 -0
- package/demos/js/tde_old/star.obj.js +812 -0
- package/demos/js/tiles.js +312 -0
- package/demos/js/tweendemo.js +79 -0
- package/demos/js/visibility.js +102 -0
- package/demos/kerr.html +28 -0
- package/demos/lavalamp.html +27 -0
- package/demos/layouts.html +37 -0
- package/demos/logo.svg +4 -0
- package/demos/loop.html +84 -0
- package/demos/mondrian.html +32 -0
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +36 -0
- package/demos/painter.html +39 -0
- package/demos/particles-showcase.html +28 -0
- package/demos/particles.html +24 -0
- package/demos/patterns.html +33 -0
- package/demos/penrose-game.html +31 -0
- package/demos/pipeline.html +737 -0
- package/demos/scene.html +33 -0
- package/demos/scenes.html +96 -0
- package/demos/schrodinger.html +27 -0
- package/demos/schwarzschild.html +27 -0
- package/demos/shapes.html +16 -0
- package/demos/space.html +85 -0
- package/demos/spacetime.html +27 -0
- package/demos/svgtween.html +29 -0
- package/demos/tde.html +28 -0
- package/demos/tiles.html +28 -0
- package/demos/transforms.html +400 -0
- package/demos/tween.html +45 -0
- package/demos/visibility.html +33 -0
- package/disk_example.png +0 -0
- package/docs/README.md +222 -0
- package/docs/concepts/architecture-overview.md +204 -0
- package/docs/concepts/lifecycle.md +255 -0
- package/docs/concepts/rendering-pipeline.md +279 -0
- package/docs/concepts/tde-zorder.md +106 -0
- package/docs/concepts/two-layer-architecture.md +229 -0
- package/docs/getting-started/first-game.md +354 -0
- package/docs/getting-started/hello-world.md +269 -0
- package/docs/getting-started/installation.md +157 -0
- package/docs/modules/collision/README.md +453 -0
- package/docs/modules/fluent/README.md +1075 -0
- package/docs/modules/game/README.md +303 -0
- package/docs/modules/isometric-camera.md +210 -0
- package/docs/modules/isometric.md +275 -0
- package/docs/modules/painter/README.md +328 -0
- package/docs/modules/particle/README.md +559 -0
- package/docs/modules/shapes/README.md +221 -0
- package/docs/modules/shapes/base/euclidian.md +123 -0
- package/docs/modules/shapes/base/geometry2d.md +204 -0
- package/docs/modules/shapes/base/renderable.md +215 -0
- package/docs/modules/shapes/base/shape.md +262 -0
- package/docs/modules/shapes/base/transformable.md +243 -0
- package/docs/modules/shapes/hierarchy.md +218 -0
- package/docs/modules/state/README.md +577 -0
- package/docs/modules/util/README.md +99 -0
- package/docs/modules/util/camera3d.md +412 -0
- package/docs/modules/util/scene3d.md +395 -0
- package/index.html +17 -0
- package/jsdoc.json +50 -0
- package/package.json +55 -0
- package/readme.md +599 -0
- package/scripts/build-demo.js +69 -0
- package/scripts/bundle4llm.js +276 -0
- package/scripts/clearconsole.js +48 -0
- package/src/collision/collision-system.js +332 -0
- package/src/collision/collision.js +303 -0
- package/src/collision/index.js +10 -0
- package/src/fluent/fluent-game.js +430 -0
- package/src/fluent/fluent-go.js +1060 -0
- package/src/fluent/fluent-layer.js +152 -0
- package/src/fluent/fluent-scene.js +291 -0
- package/src/fluent/index.js +98 -0
- package/src/fluent/sketch.js +380 -0
- package/src/game/game.js +467 -0
- package/src/game/index.js +49 -0
- package/src/game/objects/go.js +220 -0
- package/src/game/objects/imagego.js +30 -0
- package/src/game/objects/index.js +54 -0
- package/src/game/objects/isometric-scene.js +260 -0
- package/src/game/objects/layoutscene.js +549 -0
- package/src/game/objects/scene.js +175 -0
- package/src/game/objects/scene3d.js +118 -0
- package/src/game/objects/text.js +221 -0
- package/src/game/objects/wrapper.js +232 -0
- package/src/game/pipeline.js +243 -0
- package/src/game/ui/button.js +396 -0
- package/src/game/ui/cursor.js +93 -0
- package/src/game/ui/fps.js +91 -0
- package/src/game/ui/index.js +5 -0
- package/src/game/ui/togglebutton.js +93 -0
- package/src/game/ui/tooltip.js +249 -0
- package/src/index.js +25 -0
- package/src/io/events.js +20 -0
- package/src/io/index.js +86 -0
- package/src/io/input.js +70 -0
- package/src/io/keys.js +152 -0
- package/src/io/mouse.js +61 -0
- package/src/io/touch.js +39 -0
- package/src/logger/debugtab.js +138 -0
- package/src/logger/index.js +3 -0
- package/src/logger/loggable.js +47 -0
- package/src/logger/logger.js +113 -0
- package/src/math/complex.js +37 -0
- package/src/math/constants.js +1 -0
- package/src/math/fractal.js +1271 -0
- package/src/math/gr.js +201 -0
- package/src/math/heat.js +202 -0
- package/src/math/index.js +12 -0
- package/src/math/noise.js +433 -0
- package/src/math/orbital.js +191 -0
- package/src/math/patterns.js +1339 -0
- package/src/math/penrose.js +259 -0
- package/src/math/quantum.js +115 -0
- package/src/math/random.js +195 -0
- package/src/math/tensor.js +1009 -0
- package/src/mixins/anchor.js +131 -0
- package/src/mixins/draggable.js +72 -0
- package/src/mixins/index.js +2 -0
- package/src/motion/bezier.js +132 -0
- package/src/motion/bounce.js +58 -0
- package/src/motion/easing.js +349 -0
- package/src/motion/float.js +130 -0
- package/src/motion/follow.js +125 -0
- package/src/motion/hop.js +52 -0
- package/src/motion/index.js +82 -0
- package/src/motion/motion.js +1124 -0
- package/src/motion/orbit.js +49 -0
- package/src/motion/oscillate.js +39 -0
- package/src/motion/parabolic.js +141 -0
- package/src/motion/patrol.js +147 -0
- package/src/motion/pendulum.js +48 -0
- package/src/motion/pulse.js +88 -0
- package/src/motion/shake.js +83 -0
- package/src/motion/spiral.js +144 -0
- package/src/motion/spring.js +150 -0
- package/src/motion/swing.js +47 -0
- package/src/motion/tween.js +92 -0
- package/src/motion/tweenetik.js +139 -0
- package/src/motion/waypoint.js +210 -0
- package/src/painter/index.js +8 -0
- package/src/painter/painter.colors.js +331 -0
- package/src/painter/painter.effects.js +230 -0
- package/src/painter/painter.img.js +229 -0
- package/src/painter/painter.js +295 -0
- package/src/painter/painter.lines.js +189 -0
- package/src/painter/painter.opacity.js +41 -0
- package/src/painter/painter.shapes.js +277 -0
- package/src/painter/painter.text.js +273 -0
- package/src/particle/emitter.js +124 -0
- package/src/particle/index.js +11 -0
- package/src/particle/particle-system.js +322 -0
- package/src/particle/particle.js +71 -0
- package/src/particle/updaters.js +170 -0
- package/src/shapes/arc.js +43 -0
- package/src/shapes/arrow.js +33 -0
- package/src/shapes/bezier.js +42 -0
- package/src/shapes/circle.js +62 -0
- package/src/shapes/clouds.js +56 -0
- package/src/shapes/cone.js +219 -0
- package/src/shapes/cross.js +70 -0
- package/src/shapes/cube.js +244 -0
- package/src/shapes/cylinder.js +254 -0
- package/src/shapes/diamond.js +48 -0
- package/src/shapes/euclidian.js +111 -0
- package/src/shapes/figure.js +115 -0
- package/src/shapes/geometry.js +220 -0
- package/src/shapes/group.js +375 -0
- package/src/shapes/heart.js +42 -0
- package/src/shapes/hexagon.js +26 -0
- package/src/shapes/image.js +192 -0
- package/src/shapes/index.js +111 -0
- package/src/shapes/line.js +29 -0
- package/src/shapes/pattern.js +90 -0
- package/src/shapes/pin.js +44 -0
- package/src/shapes/poly.js +31 -0
- package/src/shapes/prism.js +226 -0
- package/src/shapes/rect.js +35 -0
- package/src/shapes/renderable.js +333 -0
- package/src/shapes/ring.js +26 -0
- package/src/shapes/roundrect.js +95 -0
- package/src/shapes/shape.js +117 -0
- package/src/shapes/slice.js +26 -0
- package/src/shapes/sphere.js +314 -0
- package/src/shapes/sphere3d.js +537 -0
- package/src/shapes/square.js +15 -0
- package/src/shapes/star.js +99 -0
- package/src/shapes/svg.js +408 -0
- package/src/shapes/text.js +553 -0
- package/src/shapes/traceable.js +83 -0
- package/src/shapes/transform.js +357 -0
- package/src/shapes/transformable.js +172 -0
- package/src/shapes/triangle.js +26 -0
- package/src/sound/index.js +17 -0
- package/src/sound/sound.js +473 -0
- package/src/sound/synth.analyzer.js +149 -0
- package/src/sound/synth.effects.js +207 -0
- package/src/sound/synth.envelope.js +59 -0
- package/src/sound/synth.js +229 -0
- package/src/sound/synth.musical.js +160 -0
- package/src/sound/synth.noise.js +85 -0
- package/src/sound/synth.oscillators.js +293 -0
- package/src/state/index.js +10 -0
- package/src/state/state-machine.js +371 -0
- package/src/util/camera3d.js +438 -0
- package/src/util/index.js +6 -0
- package/src/util/isometric-camera.js +235 -0
- package/src/util/layout.js +317 -0
- package/src/util/position.js +147 -0
- package/src/util/tasks.js +47 -0
- package/src/util/zindex.js +287 -0
- package/src/webgl/index.js +9 -0
- package/src/webgl/shaders/sphere-shaders.js +994 -0
- package/src/webgl/webgl-renderer.js +388 -0
- package/tde.png +0 -0
- package/test/math/orbital.test.js +61 -0
- package/test/math/tensor.test.js +114 -0
- package/test/particle/emitter.test.js +204 -0
- package/test/particle/particle-system.test.js +310 -0
- package/test/particle/particle.test.js +116 -0
- package/test/particle/updaters.test.js +386 -0
- package/test/setup.js +120 -0
- package/test/shapes/euclidian.test.js +44 -0
- package/test/shapes/geometry.test.js +86 -0
- package/test/shapes/group.test.js +86 -0
- package/test/shapes/rectangle.test.js +64 -0
- package/test/shapes/transform.test.js +379 -0
- package/test/util/camera3d.test.js +428 -0
- package/test/util/scene3d.test.js +352 -0
- package/types/collision.d.ts +249 -0
- package/types/common.d.ts +155 -0
- package/types/game.d.ts +497 -0
- package/types/index.d.ts +309 -0
- package/types/io.d.ts +188 -0
- package/types/logger.d.ts +127 -0
- package/types/math.d.ts +268 -0
- package/types/mixins.d.ts +92 -0
- package/types/motion.d.ts +678 -0
- package/types/painter.d.ts +378 -0
- package/types/shapes.d.ts +864 -0
- package/types/sound.d.ts +672 -0
- package/types/state.d.ts +251 -0
- package/types/util.d.ts +253 -0
- package/vite.config.js +50 -0
- package/vitest.config.js +13 -0
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import { Shape } from "./shape.js";
|
|
2
|
+
import { Painter } from "../painter/painter.js";
|
|
3
|
+
/**
|
|
4
|
+
* TextShape - A drawable text shape that supports rotation, scaling, and grouping.
|
|
5
|
+
* Intended for use inside a Group.
|
|
6
|
+
*
|
|
7
|
+
* @extends Shape
|
|
8
|
+
*/
|
|
9
|
+
export class TextShape extends Shape {
|
|
10
|
+
/**
|
|
11
|
+
* Create a text shape
|
|
12
|
+
*
|
|
13
|
+
* @param {string} text - The text content
|
|
14
|
+
* @param {Object} [options={}] - Configuration options
|
|
15
|
+
* @param {string} [options.font="12px monospace"] - CSS font string
|
|
16
|
+
* @param {string} [options.color="#000"] - Text color
|
|
17
|
+
* @param {string} [options.align="center"] - Text alignment (left, center, right)
|
|
18
|
+
* @param {string} [options.baseline="middle"] - Text baseline (top, middle, bottom)
|
|
19
|
+
*/
|
|
20
|
+
constructor(text, options = {}) {
|
|
21
|
+
super(options);
|
|
22
|
+
this._text = text;
|
|
23
|
+
this._font = options.font || "12px monospace";
|
|
24
|
+
this._color = options.color || "yellow";
|
|
25
|
+
this._align = options.align || "center";
|
|
26
|
+
this._baseline = options.baseline || "middle";
|
|
27
|
+
|
|
28
|
+
// Calculate initial bounds
|
|
29
|
+
this._calculateBounds();
|
|
30
|
+
this._calculateAlignmentOffsets();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Draw the text using Painter
|
|
35
|
+
*/
|
|
36
|
+
draw() {
|
|
37
|
+
super.draw();
|
|
38
|
+
this.logger.log("draw", this.font, this.color, this.opacity);
|
|
39
|
+
Painter.text.setFont(this.font);
|
|
40
|
+
Painter.text.setTextAlign(this.align);
|
|
41
|
+
Painter.text.setTextBaseline(this.baseline);
|
|
42
|
+
Painter.text.fillText(this.text, 0, 0, this.color);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_calculateAlignmentOffsets() {
|
|
46
|
+
// Save current canvas context
|
|
47
|
+
if (!Painter.text) return;
|
|
48
|
+
// Measure text dimensions
|
|
49
|
+
const metrics = Painter.text.measureTextDimensions(this.text, this.font);
|
|
50
|
+
// Calculate horizontal center point offset
|
|
51
|
+
switch (this._align) {
|
|
52
|
+
case "left":
|
|
53
|
+
this._centerOffsetX = metrics.width / 2;
|
|
54
|
+
break;
|
|
55
|
+
case "center":
|
|
56
|
+
this._centerOffsetX = 0;
|
|
57
|
+
break;
|
|
58
|
+
case "right":
|
|
59
|
+
this._centerOffsetX = -metrics.width / 2 - 5;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
// Calculate vertical center point offset
|
|
63
|
+
switch (this._baseline) {
|
|
64
|
+
case "top":
|
|
65
|
+
this._centerOffsetY = metrics.height/4;
|
|
66
|
+
break;
|
|
67
|
+
case "middle":
|
|
68
|
+
this._centerOffsetY = -2;
|
|
69
|
+
break;
|
|
70
|
+
case "bottom":
|
|
71
|
+
this._centerOffsetY = -metrics.height;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
//console.log("calculateAlignmentOffsets", this._centerOffsetY, this._centerOffsetX);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getTextBounds() {
|
|
78
|
+
if (Painter.text) {
|
|
79
|
+
// Measure the text dimensions
|
|
80
|
+
const metrics = Painter.text.measureTextDimensions(this.text, this.font);
|
|
81
|
+
// Add padding
|
|
82
|
+
const padding = 2;
|
|
83
|
+
return {
|
|
84
|
+
x: this._centerOffsetX - metrics.width / 2,
|
|
85
|
+
y: this._centerOffsetY - metrics.height / 2,
|
|
86
|
+
width: metrics.width + padding * 2,
|
|
87
|
+
height: metrics.height + padding * 2,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Fallback
|
|
91
|
+
return {
|
|
92
|
+
x: this._centerOffsetX,
|
|
93
|
+
y: this._centerOffsetY,
|
|
94
|
+
width: this._width,
|
|
95
|
+
height: this._height,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Overridden _calculateBounds to include alignment offsets
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
_calculateBounds() {
|
|
104
|
+
if (Painter.text) {
|
|
105
|
+
// Measure the text dimensions
|
|
106
|
+
const metrics = Painter.text.measureTextDimensions(this.text, this.font);
|
|
107
|
+
|
|
108
|
+
// Set dimensions based on measurements
|
|
109
|
+
this._width = metrics.width;
|
|
110
|
+
this._height = metrics.height;
|
|
111
|
+
|
|
112
|
+
// Calculate alignment offsets
|
|
113
|
+
this._calculateAlignmentOffsets();
|
|
114
|
+
} else {
|
|
115
|
+
// Fallback if Painter not available
|
|
116
|
+
this._width = this.text ? this.text.length * 8 : 0;
|
|
117
|
+
this._height = 16;
|
|
118
|
+
}
|
|
119
|
+
this.trace(
|
|
120
|
+
"TextShape.calculateBounds: " + this._width + "x" + this._height
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Debug bounds should match text bounds
|
|
126
|
+
* @returns {Object} Debug bounds with width and height
|
|
127
|
+
*/
|
|
128
|
+
getDebugBounds() {
|
|
129
|
+
const textBounds = this.getTextBounds();
|
|
130
|
+
return {
|
|
131
|
+
x: textBounds.x,
|
|
132
|
+
y: textBounds.y,
|
|
133
|
+
width: textBounds.width,
|
|
134
|
+
height: textBounds.height,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if a property has changed and update bounds if needed
|
|
140
|
+
* @param {*} value - New value
|
|
141
|
+
* @param {*} oldValue - Previous value
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
checkDirty(value, oldValue) {
|
|
145
|
+
if (value !== oldValue) {
|
|
146
|
+
this._boundsDirty = true;
|
|
147
|
+
this._calculateBounds();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Getters and setters
|
|
152
|
+
|
|
153
|
+
get text() {
|
|
154
|
+
return this._text;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
set text(value) {
|
|
158
|
+
this.checkDirty(value, this._text);
|
|
159
|
+
this._text = value;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
get font() {
|
|
163
|
+
return this._font;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
set font(value) {
|
|
167
|
+
this.checkDirty(value, this._font);
|
|
168
|
+
this._font = value;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
get color() {
|
|
172
|
+
return this._color;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
set color(value) {
|
|
176
|
+
this._color = value;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
get align() {
|
|
180
|
+
return this._align;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
set align(value) {
|
|
184
|
+
this.checkDirty(value, this._align);
|
|
185
|
+
this._align = value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
get baseline() {
|
|
189
|
+
return this._baseline;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
set baseline(value) {
|
|
193
|
+
this.checkDirty(value, this._baseline);
|
|
194
|
+
this._baseline = value;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* OutlinedText - A text shape with a stroke outline.
|
|
200
|
+
*
|
|
201
|
+
* Draws text with both fill and stroke for an outlined effect.
|
|
202
|
+
*/
|
|
203
|
+
export class OutlinedText extends Shape {
|
|
204
|
+
/**
|
|
205
|
+
* @param {number} x - X coordinate (or center X if centered)
|
|
206
|
+
* @param {number} y - Y coordinate (or center Y if centered)
|
|
207
|
+
* @param {string} text - Text content
|
|
208
|
+
* @param {Object} [options] - Shape rendering options
|
|
209
|
+
* @param {boolean} [options.centered=false] - Whether the text is positioned from its center
|
|
210
|
+
* @param {string} [options.color='#000000'] - Text fill color
|
|
211
|
+
* @param {string} [options.stroke='#FFFFFF'] - Text stroke color
|
|
212
|
+
* @param {number} [options.lineWidth=1] - Width of the text outline
|
|
213
|
+
* @param {string} [options.font] - Font specification
|
|
214
|
+
* @param {string} [options.align='left'] - Text alignment ('left', 'center', 'right')
|
|
215
|
+
* @param {string} [options.baseline='alphabetic'] - Text baseline
|
|
216
|
+
*/
|
|
217
|
+
constructor(x, y, text, options = {}) {
|
|
218
|
+
super(x, y, options);
|
|
219
|
+
this.text = text;
|
|
220
|
+
// Text-specific options
|
|
221
|
+
this.centered = options.centered || false;
|
|
222
|
+
this.color = options.color || "#000000";
|
|
223
|
+
this.stroke = options.stroke || "#FFFFFF";
|
|
224
|
+
this.lineWidth = options.lineWidth || 1;
|
|
225
|
+
this.font = options.font || null;
|
|
226
|
+
this.align = options.align || "left";
|
|
227
|
+
this.baseline = options.baseline || "alphabetic";
|
|
228
|
+
// Calculate text dimensions
|
|
229
|
+
this.calculateDimensions();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Calculate the dimensions of the text
|
|
234
|
+
* @private
|
|
235
|
+
*/
|
|
236
|
+
calculateDimensions() {
|
|
237
|
+
if (!Painter.ctx) {
|
|
238
|
+
console.warn(
|
|
239
|
+
"Painter context not initialized. Cannot calculate text dimensions."
|
|
240
|
+
);
|
|
241
|
+
this.width = 0;
|
|
242
|
+
this.height = 0;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Save current context settings
|
|
247
|
+
const currentFont = Painter.text.font();
|
|
248
|
+
|
|
249
|
+
// Apply font if provided
|
|
250
|
+
if (this.font) Painter.text.setFont(this.font);
|
|
251
|
+
|
|
252
|
+
// Measure the text
|
|
253
|
+
const metrics = Painter.text.measureText(this.text);
|
|
254
|
+
|
|
255
|
+
// Set dimensions
|
|
256
|
+
this.width = metrics.width;
|
|
257
|
+
|
|
258
|
+
// Approximate height from font size if available
|
|
259
|
+
if (this.font) {
|
|
260
|
+
const fontSize = parseInt(this.font);
|
|
261
|
+
this.height = isNaN(fontSize) ? 20 : fontSize; // Default to 20 if parsing fails
|
|
262
|
+
} else {
|
|
263
|
+
// Try to get height from metrics (newer browsers) or estimate
|
|
264
|
+
this.height =
|
|
265
|
+
metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent ||
|
|
266
|
+
20;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Add a bit of padding for the stroke
|
|
270
|
+
this.width += this.lineWidth * 2;
|
|
271
|
+
this.height += this.lineWidth * 2;
|
|
272
|
+
|
|
273
|
+
// Restore font
|
|
274
|
+
Painter.text.setFont(currentFont);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Update the text content
|
|
279
|
+
* @param {string} text - New text content
|
|
280
|
+
*/
|
|
281
|
+
setText(text) {
|
|
282
|
+
this.text = text;
|
|
283
|
+
this.calculateDimensions();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Renders the outlined text
|
|
288
|
+
*/
|
|
289
|
+
draw() {
|
|
290
|
+
super.draw();
|
|
291
|
+
|
|
292
|
+
// Handle case where Painter context isn't initialized yet
|
|
293
|
+
if (!Painter.ctx) {
|
|
294
|
+
console.warn("Painter context not initialized. Cannot draw text.");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Calculate the starting position
|
|
299
|
+
let xPos = 0;
|
|
300
|
+
let yPos = 0;
|
|
301
|
+
|
|
302
|
+
// Apply text settings
|
|
303
|
+
if (this.font) Painter.text.setFont(this.font);
|
|
304
|
+
Painter.text.setTextAlign(this.align);
|
|
305
|
+
Painter.text.setTextBaseline(this.baseline);
|
|
306
|
+
|
|
307
|
+
// Adjust for centered positioning
|
|
308
|
+
if (this.centered) {
|
|
309
|
+
// No adjustment needed as textAlign will handle this
|
|
310
|
+
if (this.baseline === "middle" || this.baseline === "alphabetic") {
|
|
311
|
+
yPos = 0;
|
|
312
|
+
} else if (this.baseline === "top") {
|
|
313
|
+
yPos = this.height / 2;
|
|
314
|
+
} else if (this.baseline === "bottom") {
|
|
315
|
+
yPos = -this.height / 2;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Draw the outlined text using Painter's outlinedText method
|
|
320
|
+
Painter.outlinedText(
|
|
321
|
+
this.text,
|
|
322
|
+
xPos,
|
|
323
|
+
yPos,
|
|
324
|
+
this.color,
|
|
325
|
+
this.stroke,
|
|
326
|
+
this.lineWidth,
|
|
327
|
+
this.font
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
getBounds() {
|
|
332
|
+
// Use the canvas context directly for precision
|
|
333
|
+
if (!Painter.ctx) {
|
|
334
|
+
return super.getBounds(); // fallback
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Save current font and apply the text style
|
|
338
|
+
const prevFont = Painter.text.font();
|
|
339
|
+
Painter.text.setFont(this.font);
|
|
340
|
+
|
|
341
|
+
const metrics = Painter.text.measureText(this.text);
|
|
342
|
+
const width = metrics.width;
|
|
343
|
+
const height =
|
|
344
|
+
metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent ||
|
|
345
|
+
parseInt(this.font) ||
|
|
346
|
+
20;
|
|
347
|
+
|
|
348
|
+
Painter.text.setFont(prevFont);
|
|
349
|
+
|
|
350
|
+
this.width = width;
|
|
351
|
+
this.height = height;
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
x: this.x,
|
|
355
|
+
y: this.y,
|
|
356
|
+
width,
|
|
357
|
+
height,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* WrappedText - A text shape that automatically wraps to fit within a specified width.
|
|
364
|
+
*
|
|
365
|
+
* Draws text that wraps to new lines when it exceeds a maximum width.
|
|
366
|
+
*/
|
|
367
|
+
export class WrappedText extends Shape {
|
|
368
|
+
/**
|
|
369
|
+
* @param {number} x - Left X coordinate (or center if centered=true)
|
|
370
|
+
* @param {number} y - Top Y coordinate (or center if centered=true)
|
|
371
|
+
* @param {string} text - Text content
|
|
372
|
+
* @param {number} maxWidth - Maximum width before wrapping
|
|
373
|
+
* @param {number} [lineHeight=20] - Line height for wrapped text
|
|
374
|
+
* @param {Object} [options] - Shape rendering options
|
|
375
|
+
* @param {boolean} [options.centered=false] - Whether the text is positioned from its center
|
|
376
|
+
* @param {string} [options.color='#000000'] - Text fill color
|
|
377
|
+
* @param {string} [options.font] - Font specification
|
|
378
|
+
* @param {string} [options.align='left'] - Text alignment ('left', 'center', 'right')
|
|
379
|
+
* @param {string} [options.baseline='top'] - Text baseline
|
|
380
|
+
*/
|
|
381
|
+
constructor(x, y, text, maxWidth, lineHeight = 20, options = {}) {
|
|
382
|
+
super(x, y, options);
|
|
383
|
+
|
|
384
|
+
this.text = text;
|
|
385
|
+
this.maxWidth = maxWidth;
|
|
386
|
+
this.lineHeight = lineHeight;
|
|
387
|
+
|
|
388
|
+
// Text-specific options
|
|
389
|
+
this.centered = options.centered || false;
|
|
390
|
+
this.color = options.color || "#000000";
|
|
391
|
+
this.font = options.font || null;
|
|
392
|
+
this.align = options.align || "left";
|
|
393
|
+
this.baseline = options.baseline || "top";
|
|
394
|
+
|
|
395
|
+
// For outlined text
|
|
396
|
+
this.outlineColor = options.outlineColor || null;
|
|
397
|
+
this.outlineWidth = options.outlineWidth || 1;
|
|
398
|
+
|
|
399
|
+
// Calculate wrapped text dimensions
|
|
400
|
+
this.calculateDimensions();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Calculate the dimensions of the wrapped text
|
|
405
|
+
* @private
|
|
406
|
+
*/
|
|
407
|
+
calculateDimensions() {
|
|
408
|
+
if (!Painter.ctx) {
|
|
409
|
+
console.warn(
|
|
410
|
+
"Painter context not initialized. Cannot calculate text dimensions."
|
|
411
|
+
);
|
|
412
|
+
this.width = this.maxWidth;
|
|
413
|
+
this.height = this.lineHeight;
|
|
414
|
+
this.lines = [this.text];
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Save current context settings
|
|
419
|
+
const currentFont = Painter.text.font();
|
|
420
|
+
const currentAlign = Painter.text.textAlign();
|
|
421
|
+
const currentBaseline = Painter.text.textBaseline();
|
|
422
|
+
|
|
423
|
+
// Apply text settings
|
|
424
|
+
if (this.font) Painter.text.setFont(this.font);
|
|
425
|
+
Painter.text.setTextAlign("left"); // Always left-align for measurement
|
|
426
|
+
Painter.text.setTextBaseline("top");
|
|
427
|
+
|
|
428
|
+
// Calculate wrapped lines and dimensions
|
|
429
|
+
const words = this.text.split(" ");
|
|
430
|
+
let line = "";
|
|
431
|
+
let testLine = "";
|
|
432
|
+
this.lines = [];
|
|
433
|
+
this.width = 0;
|
|
434
|
+
|
|
435
|
+
for (let i = 0; i < words.length; i++) {
|
|
436
|
+
testLine = line + words[i] + " ";
|
|
437
|
+
const metrics = Painter.text.measureText(testLine);
|
|
438
|
+
const testWidth = metrics.width;
|
|
439
|
+
|
|
440
|
+
if (testWidth > this.maxWidth && i > 0) {
|
|
441
|
+
this.lines.push(line);
|
|
442
|
+
this.width = Math.max(this.width, Painter.text.measureText(line).width);
|
|
443
|
+
line = words[i] + " ";
|
|
444
|
+
} else {
|
|
445
|
+
line = testLine;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Add the last line
|
|
450
|
+
this.lines.push(line);
|
|
451
|
+
this.width = Math.max(this.width, Painter.text.measureText(line).width);
|
|
452
|
+
this.height = this.lines.length * this.lineHeight;
|
|
453
|
+
|
|
454
|
+
// Restore context settings
|
|
455
|
+
Painter.text.setFont(currentFont);
|
|
456
|
+
Painter.text.setTextAlign(currentAlign);
|
|
457
|
+
Painter.text.setTextBaseline(currentBaseline);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Update the text content
|
|
462
|
+
* @param {string} text - New text content
|
|
463
|
+
*/
|
|
464
|
+
setText(text) {
|
|
465
|
+
this.text = text;
|
|
466
|
+
this.calculateDimensions();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Renders the wrapped text
|
|
471
|
+
*/
|
|
472
|
+
draw() {
|
|
473
|
+
super.draw();
|
|
474
|
+
|
|
475
|
+
// Handle case where Painter context isn't initialized yet
|
|
476
|
+
if (!Painter.ctx) {
|
|
477
|
+
console.warn("Painter context not initialized. Cannot draw text.");
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Calculate the starting position
|
|
482
|
+
let xPos = 0;
|
|
483
|
+
let yPos = 0;
|
|
484
|
+
|
|
485
|
+
// Adjust for centered positioning
|
|
486
|
+
if (this.centered) {
|
|
487
|
+
xPos = -this.width / 2;
|
|
488
|
+
yPos = -this.height / 2;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Apply text settings
|
|
492
|
+
if (this.font) Painter.text.setFont(this.font);
|
|
493
|
+
Painter.text.setTextAlign(this.align);
|
|
494
|
+
Painter.text.setTextBaseline(this.baseline);
|
|
495
|
+
|
|
496
|
+
// Adjust x based on alignment
|
|
497
|
+
let alignmentX = xPos;
|
|
498
|
+
if (this.align === "center") {
|
|
499
|
+
alignmentX = 0;
|
|
500
|
+
} else if (this.align === "right") {
|
|
501
|
+
alignmentX = xPos + this.width;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Draw each line
|
|
505
|
+
for (let i = 0; i < this.lines.length; i++) {
|
|
506
|
+
const lineY = yPos + i * this.lineHeight;
|
|
507
|
+
|
|
508
|
+
if (this.outlineColor) {
|
|
509
|
+
// Draw outlined text
|
|
510
|
+
Painter.outlinedText(
|
|
511
|
+
this.lines[i],
|
|
512
|
+
alignmentX,
|
|
513
|
+
lineY,
|
|
514
|
+
this.color,
|
|
515
|
+
this.outlineColor,
|
|
516
|
+
this.outlineWidth,
|
|
517
|
+
this.font
|
|
518
|
+
);
|
|
519
|
+
} else {
|
|
520
|
+
// Draw regular text
|
|
521
|
+
Painter.text.fillText(
|
|
522
|
+
this.lines[i],
|
|
523
|
+
alignmentX,
|
|
524
|
+
lineY,
|
|
525
|
+
this.color,
|
|
526
|
+
this.font
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Returns the bounding box
|
|
534
|
+
* @returns {{x: number, y: number, width: number, height: number}}
|
|
535
|
+
*/
|
|
536
|
+
getBounds() {
|
|
537
|
+
if (this.centered) {
|
|
538
|
+
return {
|
|
539
|
+
x: this.x,
|
|
540
|
+
y: this.y,
|
|
541
|
+
width: this.width,
|
|
542
|
+
height: this.height,
|
|
543
|
+
};
|
|
544
|
+
} else {
|
|
545
|
+
return {
|
|
546
|
+
x: this.x + this.width / 2,
|
|
547
|
+
y: this.y + this.height / 2,
|
|
548
|
+
width: this.width,
|
|
549
|
+
height: this.height,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Painter } from "../painter/painter";
|
|
2
|
+
import { Geometry2d } from "./geometry.js";
|
|
3
|
+
|
|
4
|
+
export class Traceable extends Geometry2d {
|
|
5
|
+
constructor(options = {}) {
|
|
6
|
+
super(options);
|
|
7
|
+
this._debug = Boolean(options.debug);
|
|
8
|
+
this._debugColor =
|
|
9
|
+
typeof options.debugColor === "string" ? options.debugColor : "#0f0";
|
|
10
|
+
this.logger.log("Traceable", this.x, this.y, this.width, this.height);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Draws debug bounding box in local space (after translation and transforms).
|
|
15
|
+
* Should be called from within the transformed context.
|
|
16
|
+
*/
|
|
17
|
+
drawDebug() {
|
|
18
|
+
if (!this._debug) return;
|
|
19
|
+
|
|
20
|
+
// Get the debug bounds in local space
|
|
21
|
+
const debugBounds = this.getDebugBounds();
|
|
22
|
+
this.logger.log(
|
|
23
|
+
this.constructor.name,
|
|
24
|
+
"drawDebug",
|
|
25
|
+
debugBounds.x,
|
|
26
|
+
debugBounds.y,
|
|
27
|
+
debugBounds.width,
|
|
28
|
+
debugBounds.height
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// Draw debug rectangle in local space (already translated and transformed)
|
|
32
|
+
Painter.shapes.outlineRect(
|
|
33
|
+
debugBounds.x,
|
|
34
|
+
debugBounds.y,
|
|
35
|
+
debugBounds.width,
|
|
36
|
+
debugBounds.height,
|
|
37
|
+
this._debugColor,
|
|
38
|
+
2
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Returns debug bounds in local space (centered at origin).
|
|
44
|
+
* Override in subclasses for custom debug bounds.
|
|
45
|
+
* @returns {{x: number, y: number, width: number, height: number}}
|
|
46
|
+
*/
|
|
47
|
+
getDebugBounds() {
|
|
48
|
+
// Return bounds centered at local origin (0, 0)
|
|
49
|
+
return {
|
|
50
|
+
width: this.width,
|
|
51
|
+
height: this.height,
|
|
52
|
+
x: -this.width / 2,
|
|
53
|
+
y: -this.height / 2,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Logs the object's render state (debug only).
|
|
59
|
+
* @param {string} [msg]
|
|
60
|
+
*/
|
|
61
|
+
trace(msg = "render") {
|
|
62
|
+
this.logger.log(
|
|
63
|
+
this.name == null ? this.constructor.name : this.name,
|
|
64
|
+
msg,
|
|
65
|
+
"x",
|
|
66
|
+
this.x,
|
|
67
|
+
"y",
|
|
68
|
+
this.y,
|
|
69
|
+
"w",
|
|
70
|
+
this.width,
|
|
71
|
+
"h",
|
|
72
|
+
this.height,
|
|
73
|
+
"opacity",
|
|
74
|
+
this._opacity,
|
|
75
|
+
"visible",
|
|
76
|
+
this._visible,
|
|
77
|
+
"active",
|
|
78
|
+
this._active,
|
|
79
|
+
"debug",
|
|
80
|
+
this.debug
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|