Skip to content

Commit 750a786

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

File tree

7 files changed

+340
-0
lines changed

7 files changed

+340
-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;
+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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+
let texture;
28+
const mapper = vtkMapper2D.newInstance();
29+
const plane = vtkPlaneSource.newInstance({
30+
xResolution: 1,
31+
yResolution: 1,
32+
});
33+
34+
function createTextTexture(text) {
35+
const fontSizeScale = publicAPI.getProperty().getFontSizeScale();
36+
const fontStyle = publicAPI.getProperty().getFontStyle();
37+
const fontFamily = publicAPI.getProperty().getFontFamily();
38+
const fontColor = publicAPI.getProperty().getFontColor();
39+
const shadowColor = publicAPI.getProperty().getShadowColor();
40+
const shadowOffset = publicAPI.getProperty().getShadowOffset();
41+
const shadowBlur = publicAPI.getProperty().getShadowBlur();
42+
const resolution = publicAPI.getProperty().getResolution();
43+
const backgroundColor = publicAPI.getProperty().getBackgroundColor();
44+
45+
const dpr = Math.max(window.devicePixelRatio || 1, 1);
46+
const canvas = document.createElement('canvas');
47+
const ctx = canvas.getContext('2d');
48+
49+
// Set the text properties to measure
50+
const textSize = fontSizeScale(resolution) * dpr;
51+
52+
ctx.font = `${fontStyle} ${textSize}px "${fontFamily}"`;
53+
ctx.textBaseline = 'middle';
54+
ctx.textAlign = 'center';
55+
56+
// Measure the text
57+
const metrics = ctx.measureText(text);
58+
const textWidth = metrics.width / dpr;
59+
60+
const {
61+
actualBoundingBoxLeft,
62+
actualBoundingBoxRight,
63+
actualBoundingBoxAscent,
64+
actualBoundingBoxDescent,
65+
} = metrics;
66+
const hAdjustment = (actualBoundingBoxLeft - actualBoundingBoxRight) / 2;
67+
const vAdjustment =
68+
(actualBoundingBoxAscent - actualBoundingBoxDescent) / 2;
69+
70+
const textHeight = textSize / dpr - vAdjustment;
71+
72+
// Update canvas size to fit text
73+
const [width, height] = [textWidth * dpr, textHeight * dpr];
74+
canvas.width = width;
75+
canvas.height = height;
76+
77+
// vertical flip
78+
ctx.translate(0, height);
79+
ctx.scale(1, -1);
80+
81+
// Clear the canvas
82+
ctx.clearRect(0, 0, width, height);
83+
84+
if (backgroundColor) {
85+
ctx.fillStyle = vtkMath.floatRGB2HexCode(backgroundColor);
86+
ctx.fillRect(0, 0, width, height);
87+
}
88+
89+
// Reset context after resize and prepare for rendering
90+
ctx.imageSmoothingEnabled = true;
91+
ctx.imageSmoothingQuality = 'high';
92+
ctx.font = `${fontStyle} ${textSize}px "${fontFamily}"`;
93+
ctx.fillStyle = vtkMath.floatRGB2HexCode(fontColor);
94+
ctx.textBaseline = 'middle';
95+
ctx.textAlign = 'center';
96+
97+
// set shadow
98+
ctx.shadowColor = shadowColor;
99+
ctx.shadowOffsetX = shadowOffset[0];
100+
ctx.shadowOffsetY = shadowOffset[1];
101+
ctx.shadowBlur = shadowBlur;
102+
103+
ctx.fillText(text, width / 2 + hAdjustment, height / 2 + vAdjustment);
104+
105+
const tex = vtkTexture.newInstance();
106+
const vtkImage = ImageHelper.canvasToImageData(canvas);
107+
tex.setInputData(vtkImage, 0);
108+
109+
// Update plane dimensions to match text size
110+
plane.set({
111+
point1: [width, 0, 0],
112+
point2: [0, height, 0],
113+
});
114+
115+
return tex;
116+
}
117+
118+
mapper.setInputConnection(plane.getOutputPort());
119+
120+
publicAPI.setMapper(mapper);
121+
122+
publicAPI.setInput = (input) => {
123+
if (input !== model.input && input) {
124+
model.input = input;
125+
publicAPI.removeTexture(texture);
126+
texture = createTextTexture(model.input);
127+
publicAPI.addTexture(texture);
128+
}
129+
};
130+
}
131+
132+
// Default property values
133+
const DEFAULT_VALUES = {
134+
mapper: null,
135+
property: null,
136+
};
137+
138+
export function extend(publicAPI, model, initialValues = {}) {
139+
Object.assign(model, DEFAULT_VALUES, initialValues);
140+
141+
// Inheritance
142+
vtkActor2D.extend(publicAPI, model, initialValues);
143+
144+
// Build VTK API
145+
macro.setGet(publicAPI, model, ['input']);
146+
147+
// Object methods
148+
vtkTextActor(publicAPI, model);
149+
}
150+
151+
export const newInstance = macro.newInstance(extend, 'vtkTextActor');
152+
153+
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)