Skip to content

Commit dbd2b9a

Browse files
committed
feat(TextActor): add vtkTextActor
1 parent 05d7cf7 commit dbd2b9a

File tree

7 files changed

+339
-0
lines changed

7 files changed

+339
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<table>
2+
<tr>
3+
<td>Type your text</td>
4+
<td>
5+
<input id="text" class='text' type="text" value="Hello World!" />
6+
</td>
7+
</tr>
8+
</table>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import '@kitware/vtk.js/favicon';
2+
3+
// Load the rendering pieces we want to use (for both WebGL and WebGPU)
4+
import '@kitware/vtk.js/Rendering/Profiles/Geometry';
5+
6+
import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow';
7+
import vtkTextActor from '@kitware/vtk.js/Rendering/Core/TextActor';
8+
9+
import controlPanel from './controlPanel.html';
10+
11+
// ----------------------------------------------------------------------------
12+
// Standard rendering code setup
13+
// ----------------------------------------------------------------------------
14+
15+
const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance();
16+
const renderer = fullScreenRenderer.getRenderer();
17+
const renderWindow = fullScreenRenderer.getRenderWindow();
18+
19+
// ----------------------------------------------------------------------------
20+
// Example code
21+
// ----------------------------------------------------------------------------
22+
23+
const actor = vtkTextActor.newInstance();
24+
actor.setInput('Hello World!');
25+
actor.setDisplayPosition(20, 30);
26+
27+
renderer.addActor(actor);
28+
renderer.resetCamera();
29+
renderWindow.render();
30+
31+
// -----------------------------------------------------------
32+
// UI control handling
33+
// -----------------------------------------------------------
34+
35+
fullScreenRenderer.addController(controlPanel);
36+
37+
// -----------------------------------------------------------
38+
// Make some variables global so that you can inspect and
39+
// modify objects in your browser's developer console:
40+
// -----------------------------------------------------------
41+
42+
const textInput = document.getElementById('text');
43+
textInput.addEventListener('input', (event) => {
44+
const value = event.target.value;
45+
actor.setInput(value);
46+
renderWindow.render();
47+
});
48+
global.actor = actor;
49+
global.renderer = renderer;
50+
global.renderWindow = renderWindow;
+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import macro from 'vtk.js/Sources/macros';
2+
import vtkPlaneSource from 'vtk.js/Sources/Filters/Sources/PlaneSource';
3+
import vtkTexture from 'vtk.js/Sources/Rendering/Core/Texture';
4+
import vtkActor2D from 'vtk.js/Sources/Rendering/Core/Actor2D';
5+
import vtkMapper2D from 'vtk.js/Sources/Rendering/Core/Mapper2D';
6+
import vtkTextProperty from 'vtk.js/Sources/Rendering/Core/TextProperty';
7+
import ImageHelper from 'vtk.js/Sources/Common/Core/ImageHelper';
8+
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
9+
10+
// ----------------------------------------------------------------------------
11+
// vtkTextActor methods
12+
// ----------------------------------------------------------------------------
13+
14+
function vtkTextActor(publicAPI, model) {
15+
// Set our className
16+
model.classHierarchy.push('vtkTextActor');
17+
18+
publicAPI.makeProperty = vtkTextProperty.newInstance;
19+
20+
publicAPI.getProperty = () => {
21+
if (model.property === null) {
22+
model.property = publicAPI.makeProperty();
23+
}
24+
return model.property;
25+
};
26+
27+
const texture = vtkTexture.newInstance({
28+
resizable: true,
29+
});
30+
const canvas = new OffscreenCanvas(1, 1);
31+
const mapper = vtkMapper2D.newInstance();
32+
const plane = vtkPlaneSource.newInstance({
33+
xResolution: 1,
34+
yResolution: 1,
35+
});
36+
37+
function createImageData(text) {
38+
const fontSizeScale = publicAPI.getProperty().getFontSizeScale();
39+
const fontStyle = publicAPI.getProperty().getFontStyle();
40+
const fontFamily = publicAPI.getProperty().getFontFamily();
41+
const fontColor = publicAPI.getProperty().getFontColor();
42+
const shadowColor = publicAPI.getProperty().getShadowColor();
43+
const shadowOffset = publicAPI.getProperty().getShadowOffset();
44+
const shadowBlur = publicAPI.getProperty().getShadowBlur();
45+
const resolution = publicAPI.getProperty().getResolution();
46+
const backgroundColor = publicAPI.getProperty().getBackgroundColor();
47+
48+
const dpr = Math.max(window.devicePixelRatio || 1, 1);
49+
const ctx = canvas.getContext('2d');
50+
51+
// Set the text properties to measure
52+
const textSize = fontSizeScale(resolution) * dpr;
53+
54+
ctx.font = `${fontStyle} ${textSize}px "${fontFamily}"`;
55+
ctx.textBaseline = 'middle';
56+
ctx.textAlign = 'center';
57+
58+
// Measure the text
59+
const metrics = ctx.measureText(text);
60+
const textWidth = metrics.width / dpr;
61+
62+
const {
63+
actualBoundingBoxLeft,
64+
actualBoundingBoxRight,
65+
actualBoundingBoxAscent,
66+
actualBoundingBoxDescent,
67+
} = metrics;
68+
const hAdjustment = (actualBoundingBoxLeft - actualBoundingBoxRight) / 2;
69+
const vAdjustment =
70+
(actualBoundingBoxAscent - actualBoundingBoxDescent) / 2;
71+
72+
const textHeight = textSize / dpr - vAdjustment;
73+
74+
// Update canvas size to fit text
75+
const [width, height] = [textWidth * dpr, textHeight * dpr];
76+
canvas.width = width;
77+
canvas.height = height;
78+
79+
// Vertical flip
80+
ctx.translate(0, height);
81+
ctx.scale(1, -1);
82+
83+
// Clear the canvas
84+
ctx.clearRect(0, 0, width, height);
85+
86+
if (backgroundColor) {
87+
ctx.fillStyle = vtkMath.floatRGB2HexCode(backgroundColor);
88+
ctx.fillRect(0, 0, width, height);
89+
}
90+
91+
// Reset context after resize and prepare for rendering
92+
ctx.imageSmoothingEnabled = true;
93+
ctx.imageSmoothingQuality = 'high';
94+
ctx.font = `${fontStyle} ${textSize}px "${fontFamily}"`;
95+
ctx.fillStyle = vtkMath.floatRGB2HexCode(fontColor);
96+
ctx.textBaseline = 'middle';
97+
ctx.textAlign = 'center';
98+
99+
// Set shadow
100+
ctx.shadowColor = vtkMath.floatRGB2HexCode(shadowColor);
101+
ctx.shadowOffsetX = shadowOffset[0];
102+
ctx.shadowOffsetY = shadowOffset[1];
103+
ctx.shadowBlur = shadowBlur;
104+
105+
// Draw the text
106+
ctx.fillText(text, width / 2 + hAdjustment, height / 2 + vAdjustment);
107+
108+
// Update plane dimensions to match text size
109+
plane.set({
110+
point1: [width, 0, 0],
111+
point2: [0, height, 0],
112+
});
113+
114+
return ImageHelper.canvasToImageData(canvas);
115+
}
116+
117+
mapper.setInputConnection(plane.getOutputPort());
118+
119+
publicAPI.setMapper(mapper);
120+
publicAPI.addTexture(texture);
121+
122+
publicAPI.setInput = (input) => {
123+
if (input !== model.input && input) {
124+
model.input = input;
125+
const image = createImageData(model.input);
126+
texture.setInputData(image, 0);
127+
}
128+
};
129+
}
130+
131+
// Default property values
132+
const DEFAULT_VALUES = {
133+
mapper: null,
134+
property: null,
135+
};
136+
137+
export function extend(publicAPI, model, initialValues = {}) {
138+
Object.assign(model, DEFAULT_VALUES, initialValues);
139+
140+
// Inheritance
141+
vtkActor2D.extend(publicAPI, model, initialValues);
142+
143+
// Build VTK API
144+
macro.setGet(publicAPI, model, ['input']);
145+
146+
// Object methods
147+
vtkTextActor(publicAPI, model);
148+
}
149+
150+
export const newInstance = macro.newInstance(extend, 'vtkTextActor');
151+
152+
export default { newInstance, extend };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import test from 'tape';
2+
import testUtils from 'vtk.js/Sources/Testing/testUtils';
3+
4+
import vtkTextActor from 'vtk.js/Sources/Rendering/Core/TextActor';
5+
import 'vtk.js/Sources/Rendering/Misc/RenderingAPIs';
6+
import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer';
7+
import vtkRenderWindow from 'vtk.js/Sources/Rendering/Core/RenderWindow';
8+
9+
import baseline from './testTextActor.png';
10+
11+
test.onlyIfWebGL('Test TextActor', (t) => {
12+
const gc = testUtils.createGarbageCollector();
13+
t.ok('rendering', 'vtkTextActor');
14+
15+
// Create some control UI
16+
const container = document.querySelector('body');
17+
const renderWindowContainer = gc.registerDOMElement(
18+
document.createElement('div')
19+
);
20+
container.appendChild(renderWindowContainer);
21+
22+
// create what we will view
23+
const renderWindow = gc.registerResource(vtkRenderWindow.newInstance());
24+
const renderer = gc.registerResource(vtkRenderer.newInstance());
25+
renderWindow.addRenderer(renderer);
26+
renderer.setBackground(0.32, 0.34, 0.43);
27+
28+
// ----------------------------------------------------------------------------
29+
// Test code
30+
// ----------------------------------------------------------------------------
31+
32+
const actor = gc.registerResource(vtkTextActor.newInstance());
33+
actor.getProperty().setResolution(100);
34+
actor.setDisplayPosition(20, 30);
35+
actor.setInput('vtk.js');
36+
37+
renderer.addActor(actor);
38+
renderer.resetCamera();
39+
40+
// -----------------------------------------------------------
41+
// Make some variables global so that you can inspect and
42+
// modify objects in your browser's developer console:
43+
// -----------------------------------------------------------
44+
45+
// create something to view it
46+
const glwindow = gc.registerResource(renderWindow.newAPISpecificView());
47+
glwindow.setContainer(renderWindowContainer);
48+
renderWindow.addView(glwindow);
49+
glwindow.setSize(400, 400);
50+
51+
const promise = glwindow
52+
.captureNextImage()
53+
.then((image) =>
54+
testUtils.compareImages(
55+
image,
56+
[baseline],
57+
'Rendering/Core/TextActor',
58+
t,
59+
1
60+
)
61+
)
62+
.finally(gc.releaseResources);
63+
renderWindow.render();
64+
return promise;
65+
});
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import macro from 'vtk.js/Sources/macros';
2+
import vtkProperty2D from 'vtk.js/Sources/Rendering/Core/Property2D';
3+
4+
// ----------------------------------------------------------------------------
5+
// vtkTextProperty methods
6+
// ----------------------------------------------------------------------------
7+
8+
function vtkTextProperty(publicAPI, model) {
9+
// Set our className
10+
model.classHierarchy.push('vtkTextProperty');
11+
}
12+
13+
// ----------------------------------------------------------------------------
14+
// Object factory
15+
// ----------------------------------------------------------------------------
16+
17+
const DEFAULT_VALUES = {
18+
fontFamily: 'Arial',
19+
fontColor: [0, 0, 0],
20+
fontStyle: 'normal',
21+
backgroundColor: [1, 1, 1],
22+
fontSizeScale: (resolution) => resolution / 1.8,
23+
resolution: 200,
24+
shadowColor: [1, 1, 0],
25+
shadowOffset: [1, -1],
26+
shadowBlur: 0,
27+
};
28+
29+
// ----------------------------------------------------------------------------
30+
export function extend(publicAPI, model, initialValues = {}) {
31+
Object.assign(model, DEFAULT_VALUES, initialValues);
32+
33+
// Inheritance
34+
vtkProperty2D.extend(publicAPI, model, initialValues);
35+
36+
// Build VTK API
37+
38+
macro.setGet(publicAPI, model, [
39+
'fontFamily',
40+
'fontColor',
41+
'fontStyle',
42+
'fillStyle',
43+
'fontSizeScale',
44+
'resolution',
45+
'shadowBlur',
46+
]);
47+
macro.setGetArray(publicAPI, model, ['shadowOffset'], 2);
48+
macro.setGetArray(publicAPI, model, ['backgroundColor', 'shadowColor'], 3);
49+
50+
// Object methods
51+
vtkTextProperty(publicAPI, model);
52+
}
53+
54+
// ----------------------------------------------------------------------------
55+
56+
export const newInstance = macro.newInstance(extend, 'vtkTextProperty');
57+
58+
// ----------------------------------------------------------------------------
59+
60+
export default { newInstance, extend };

Sources/Rendering/Core/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ import vtkSkybox from './Skybox';
4141
import vtkSphereMapper from './SphereMapper';
4242
import vtkStickMapper from './StickMapper';
4343
import vtkTexture from './Texture';
44+
import vtkTextActor from './TextActor';
45+
import vtkTextProperty from './TextProperty';
4446
import vtkViewport from './Viewport';
4547
import vtkVolume from './Volume';
4648
import vtkVolumeMapper from './VolumeMapper';
@@ -86,6 +88,8 @@ export default {
8688
vtkSphereMapper,
8789
vtkStickMapper,
8890
vtkTexture,
91+
vtkTextActor,
92+
vtkTextProperty,
8993
vtkViewport,
9094
vtkVolume,
9195
vtkVolumeMapper,

0 commit comments

Comments
 (0)