1 /**************************************************************************\
2 * Copyright (c) Kongsberg Oil & Gas Technologies AS
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * Redistributions of source code must retain the above copyright notice,
10 * this list of conditions and the following disclaimer.
11 *
12 * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * Neither the name of the copyright holder nor the names of its
17 * contributors may be used to endorse or promote products derived from
18 * this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32
33 /*!
34 \class SoText2 SoText2.h Inventor/nodes/SoText2.h
35 \brief The SoText2 class is a node type for visualizing 2D text aligned with the camera plane.
36
37 \ingroup nodes
38
39 SoText2 text is not scaled according to the distance from the
40 camera, and is not influenced by rotation or scaling as 3D
41 primitives are. If these are properties that you want the text to
42 have, you should instead use an SoText3 or SoAsciiText node.
43
44 Note that even though the size of the 2D text is not influenced by
45 the distance from the camera, the text is still subject to the usual
46 rules with regard to the depthbuffer, so it \e will be obscured by
47 graphics laying in front of it.
48
49 The text will be \e positioned according to the current transformation.
50 The x origin of the text is the first pixel of the leftmost character
51 of the text. The y origin of the text is the baseline of the first line
52 of text (the baseline being the imaginary line on which all upper case
53 characters are standing).
54
55 The size of the fonts on screen is decided from the SoFont::size
56 field of a preceding SoFont-node in the scene graph, which specifies
57 the size in pixel dimensions. This value sets the approximate
58 vertical dimension of the letters. The default value if no
59 SoFont-nodes are used, is 10.
60
61 One important issue about rendering performance: since the
62 positioning and rendering of an SoText2 node depends on the current
63 viewport and camera, having SoText2 nodes in the scene graph will
64 lead to a cache dependency on the previously encountered
65 SoCamera-node. This can have severe influence on the rendering
66 performance, since if the camera is above the SoText2's nearest
67 parent SoSeparator, the SoSeparator will not be able to cache the
68 geometry under it.
69
70 (Even worse rendering performance will be forced if the
71 SoSeparator::renderCaching flag is explicitly set to \c ON, as the
72 SoSeparator node will then continuously generate and destruct the
73 same cache as the camera moves.)
74
75 SoText2 nodes are therefore best positioned under their own
76 SoSeparator node, outside areas in the scene graph that otherwise
77 contains static geometry.
78
79 Also note that SoText2 nodes cache the ids and positions of each glyph
80 bitmap used to render \c string. This means that \c USE of a \c DEF'ed
81 SoText2 node, with a different font, will be noticeably slower than using
82 two separate SoText2 nodes, one for each font, since it will have to
83 recalculate glyph bitmap ids and positions for each call to \c GLrender().
84
85 SoScale nodes can not be used to influence the dimensions of the
86 rendering output of SoText2 nodes.
87
88 <b>FILE FORMAT/DEFAULTS:</b>
89 \code
90 Text2 {
91 string ""
92 spacing 1
93 justification LEFT
94 }
95 \endcode
96
97 \sa SoFont, SoFontStyle, SoText3, SoAsciiText
98 */
99
100 #include <Inventor/nodes/SoText2.h>
101 #include "coindefs.h"
102
103 #include <climits>
104 #include <cstring>
105
106 #ifdef HAVE_CONFIG_H
107 #include <config.h>
108 #endif // HAVE_CONFIG_H
109
110 #include <Inventor/SbBox2s.h>
111 #include <Inventor/SbLine.h>
112 #include <Inventor/SbPlane.h>
113 #include <Inventor/SbString.h>
114 #include <Inventor/SoPickedPoint.h>
115 #include <Inventor/actions/SoGLRenderAction.h>
116 #include <Inventor/actions/SoGetPrimitiveCountAction.h>
117 #include <Inventor/actions/SoRayPickAction.h>
118 #include <Inventor/bundles/SoMaterialBundle.h>
119 #include <Inventor/details/SoTextDetail.h>
120 #include <Inventor/elements/SoCullElement.h>
121 #include <Inventor/elements/SoFontNameElement.h>
122 #include <Inventor/elements/SoFontSizeElement.h>
123 #include <Inventor/elements/SoGLCacheContextElement.h>
124 #include <Inventor/elements/SoComplexityTypeElement.h>
125 #include <Inventor/elements/SoComplexityElement.h>
126 #include <Inventor/elements/SoLazyElement.h>
127 #include <Inventor/elements/SoModelMatrixElement.h>
128 #include <Inventor/elements/SoProjectionMatrixElement.h>
129 #include <Inventor/elements/SoViewingMatrixElement.h>
130 #include <Inventor/elements/SoViewVolumeElement.h>
131 #include <Inventor/elements/SoViewportRegionElement.h>
132 #include <Inventor/errors/SoDebugError.h>
133 #include <Inventor/sensors/SoFieldSensor.h>
134 #include <Inventor/elements/SoCacheElement.h>
135 #include <Inventor/elements/SoMultiTextureEnabledElement.h>
136 #include <Inventor/elements/SoGLMultiTextureEnabledElement.h>
137
138 #ifdef COIN_THREADSAFE
139 #include <Inventor/threads/SbMutex.h>
140 #endif // COIN_THREADSAFE
141
142 #include "nodes/SoSubNodeP.h"
143 #include "caches/SoGlyphCache.h"
144
145 // The "lean and mean" define is a workaround for a Cygwin bug: when
146 // windows.h is included _after_ one of the X11 or GLX headers above
147 // (as it is indirectly from Inventor/system/gl.h), compilation of
148 // winspool.h (included from windows.h) will bail out with an error
149 // message due to the use of "Status" as a struct member ("Status" is
150 // probably #defined somewhere in the X11 or GLX header files).
151 //
152 // The WIN32_LEAN_AND_MEAN causes windows.h to not include winspool.h.
153 //
154 // -mortene
155 #define WIN32_LEAN_AND_MEAN
156 #include <Inventor/system/gl.h>
157 #undef WIN32_LEAN_AND_MEAN
158 // UPDATE, FIXME: due to some reorganization of header files GL/glx.h
159 // should not be included anywhere for this source code file any
160 // more. This means the hack above should no longer be necessary. To
161 // test, try building this file with g++ on a Cygwin system where both
162 // windows.h and GL/glx.h are available. If that works fine, remove
163 // the "#define WIN32_LEAN_AND_MEAN" hack. 20030625 mortene.
164
165
166 #include "fonts/glyph2d.h"
167
168 /*!
169 \enum SoText2::Justification
170
171 Enum contains the various options for how the horizontal text layout
172 text should be done. Valid values are LEFT, RIGHT and CENTER.
173 */
174
175
176 /*!
177 \var SoMFString SoText2::string
178
179 The set of strings to render. Each string in the multiple value
180 field will be rendered on it's own line.
181
182 The default value of the field is a single empty string.
183 */
184 /*!
185 \var SoSFFloat SoText2::spacing
186
187 Spacing between each consecutive vertical line. Default value is
188 1.0, which means that the space between the uppermost line of each
189 rendered string will equal the vertical size of the highest
190 character in the bitmap alphabet.
191 */
192 /*!
193 \var SoSFEnum SoText2::justification
194
195 Decides how the horizontal layout of the text strings is done.
196 */
197
198
199 class SoText2P {
200 public:
SoText2P(SoText2 * textnode)201 SoText2P(SoText2 * textnode) : maxwidth(0), master(textnode)
202 {
203 this->bbox.makeEmpty();
204 }
205
206 SbBool getQuad(SoState * state, SbVec3f & v0, SbVec3f & v1,
207 SbVec3f & v2, SbVec3f & v3);
208 void flushGlyphCache();
209 void buildGlyphCache(SoState * state);
210 SbBool shouldBuildGlyphCache(SoState * state);
211 void dumpBuffer(unsigned char * buffer, SbVec2s size, SbVec2s pos, SbBool mono);
212 void computeBBox(SoAction * action, SbBox3f & box, SbVec3f & center);
213 static void setRasterPos3f(GLfloat x, GLfloat y, GLfloat z);
214
215
216 SbList <int> stringwidth;
217 int maxwidth;
218 SbList< SbList<SbVec2s> > positions;
219 SbBox2s bbox;
220
221 SoGlyphCache * cache;
222 SoFieldSensor * spacingsensor;
223 SoFieldSensor * stringsensor;
224 unsigned char * pixel_buffer;
225 int pixel_buffer_size;
226
sensor_cb(void * userdata,SoSensor * COIN_UNUSED_ARG (s))227 static void sensor_cb(void * userdata, SoSensor * COIN_UNUSED_ARG(s)) {
228 SoText2P * thisp = (SoText2P*) userdata;
229 thisp->lock();
230 if (thisp->cache) thisp->cache->invalidate();
231 thisp->unlock();
232 }
lock(void)233 void lock(void) {
234 #ifdef COIN_THREADSAFE
235 this->mutex.lock();
236 #endif // COIN_THREADSAFE
237 }
unlock(void)238 void unlock(void) {
239 #ifdef COIN_THREADSAFE
240 this->mutex.unlock();
241 #endif // COIN_THREADSAFE
242 }
243 private:
244 #ifdef COIN_THREADSAFE
245 // FIXME: a mutex for every instance seems a bit excessive,
246 // especially since MSWindows might have rather strict limits on the
247 // total amount of mutex resources a process (or even a user) can
248 // allocate. so consider making this a class-wide instance instead.
249 // -mortene.
250 SbMutex mutex;
251 #endif // COIN_THREADSAFE
252 SoText2 * master;
253 };
254
255 #define PRIVATE(p) (p->pimpl)
256 #define PUBLIC(p) (p->master)
257
258 // *************************************************************************
259
260 SO_NODE_SOURCE(SoText2);
261
262 /*!
263 Constructor.
264 */
SoText2(void)265 SoText2::SoText2(void)
266 {
267 PRIVATE(this) = new SoText2P(this);
268
269 SO_NODE_INTERNAL_CONSTRUCTOR(SoText2);
270
271 SO_NODE_ADD_FIELD(string, (""));
272 SO_NODE_ADD_FIELD(spacing, (1.0f));
273 SO_NODE_ADD_FIELD(justification, (SoText2::LEFT));
274
275 SO_NODE_DEFINE_ENUM_VALUE(Justification, LEFT);
276 SO_NODE_DEFINE_ENUM_VALUE(Justification, RIGHT);
277 SO_NODE_DEFINE_ENUM_VALUE(Justification, CENTER);
278 SO_NODE_SET_SF_ENUM_TYPE(justification, Justification);
279
280 PRIVATE(this)->stringsensor = new SoFieldSensor(SoText2P::sensor_cb, PRIVATE(this));
281 PRIVATE(this)->stringsensor->attach(&this->string);
282 PRIVATE(this)->stringsensor->setPriority(0);
283 PRIVATE(this)->spacingsensor = new SoFieldSensor(SoText2P::sensor_cb, PRIVATE(this));
284 PRIVATE(this)->spacingsensor->attach(&this->spacing);
285 PRIVATE(this)->spacingsensor->setPriority(0);
286 PRIVATE(this)->cache = NULL;
287 PRIVATE(this)->pixel_buffer = NULL;
288 PRIVATE(this)->pixel_buffer_size = 0;
289 }
290
291 /*!
292 Destructor.
293 */
~SoText2()294 SoText2::~SoText2()
295 {
296 if (PRIVATE(this)->cache) PRIVATE(this)->cache->unref();
297 delete[] PRIVATE(this)->pixel_buffer;
298 delete PRIVATE(this)->stringsensor;
299 delete PRIVATE(this)->spacingsensor;
300
301 PRIVATE(this)->flushGlyphCache();
302 delete PRIVATE(this);
303 }
304
305 // doc in super
306 void
initClass(void)307 SoText2::initClass(void)
308 {
309 SO_NODE_INTERNAL_INIT_CLASS(SoText2, SO_FROM_INVENTOR_2_1);
310 }
311
312 // **************************************************************************
313
314 // doc in super
315 void
GLRender(SoGLRenderAction * action)316 SoText2::GLRender(SoGLRenderAction * action)
317 {
318 if (!this->shouldGLRender(action)) return;
319
320 SoState * state = action->getState();
321
322 state->push();
323 SoLazyElement::setLightModel(state, SoLazyElement::BASE_COLOR);
324
325 PRIVATE(this)->lock();
326 PRIVATE(this)->buildGlyphCache(state);
327 SoCacheElement::addCacheDependency(state, PRIVATE(this)->cache);
328
329 const cc_font_specification * fontspec = PRIVATE(this)->cache->getCachedFontspec();
330
331 // Render only if bbox not outside cull planes.
332 SbBox3f box;
333 SbVec3f center;
334 PRIVATE(this)->computeBBox(action, box, center);
335 if (!SoCullElement::cullTest(state, box, TRUE)) {
336 SoMaterialBundle mb(action);
337 mb.sendFirst();
338 SbVec3f nilpoint(0.0f, 0.0f, 0.0f);
339 const SbMatrix & mat = SoModelMatrixElement::get(state);
340 const SbMatrix & projmatrix = (mat * SoViewingMatrixElement::get(state) *
341 SoProjectionMatrixElement::get(state));
342 const SbViewportRegion & vp = SoViewportRegionElement::get(state);
343 SbVec2s vpsize = vp.getViewportSizePixels();
344
345 projmatrix.multVecMatrix(nilpoint, nilpoint);
346 nilpoint[0] = (nilpoint[0] + 1.0f) * 0.5f * vpsize[0];
347 nilpoint[1] = (nilpoint[1] + 1.0f) * 0.5f * vpsize[1];
348
349 SbVec2s bbsize = PRIVATE(this)->bbox.getSize();
350 const SbVec2s& bbmin = PRIVATE(this)->bbox.getMin();
351 const SbVec2s& bbmax = PRIVATE(this)->bbox.getMax();
352
353 float textscreenoffsetx = nilpoint[0]+bbmin[0];
354 switch (this->justification.getValue()) {
355 case SoText2::LEFT:
356 break;
357 case SoText2::RIGHT:
358 textscreenoffsetx = nilpoint[0] + bbmin[0] - PRIVATE(this)->maxwidth;
359 break;
360 case SoText2::CENTER:
361 textscreenoffsetx = (nilpoint[0] + bbmin[0] - PRIVATE(this)->maxwidth / 2.0f);
362 break;
363 }
364
365 // Set new state.
366 glMatrixMode(GL_MODELVIEW);
367 glPushMatrix();
368 glLoadIdentity();
369 glMatrixMode(GL_PROJECTION);
370 glPushMatrix();
371 glLoadIdentity();
372 glOrtho(0, vpsize[0], 0, vpsize[1], -1.0f, 1.0f);
373 glPixelStorei(GL_UNPACK_ALIGNMENT,1);
374
375 float fontsize = SoFontSizeElement::get(state);
376 int xpos = 0;
377 int ypos = 0;
378 int rasterx, rastery;
379 int ix=0, iy=0;
380 int bitmappos[2];
381 int bitmapsize[2];
382 const unsigned char * buffer = NULL;
383 cc_glyph2d * prevglyph = NULL;
384
385 const int nrlines = this->string.getNum();
386
387 // get the current diffuse color
388 const SbColor & diffuse = SoLazyElement::getDiffuse(state, 0);
389 unsigned char red = (unsigned char) (diffuse[0] * 255.0f);
390 unsigned char green = (unsigned char) (diffuse[1] * 255.0f);
391 unsigned char blue = (unsigned char) (diffuse[2] * 255.0f);
392 const unsigned int alpha = (unsigned int)((1.0f - SoLazyElement::getTransparency(state, 0)) * 256);
393
394 state->push();
395
396 // disable textures for all units
397 SoGLMultiTextureEnabledElement::disableAll(state);
398
399 glPushAttrib(GL_ENABLE_BIT | GL_PIXEL_MODE_BIT | GL_COLOR_BUFFER_BIT);
400 glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT);
401
402 SbBool drawPixelBuffer = FALSE;
403
404 for (int i = 0; i < nrlines; i++) {
405 SbString str = this->string[i];
406 switch (this->justification.getValue()) {
407 case SoText2::LEFT:
408 xpos = 0;
409 break;
410 case SoText2::RIGHT:
411 xpos = PRIVATE(this)->maxwidth - PRIVATE(this)->stringwidth[i];
412 break;
413 case SoText2::CENTER:
414 xpos = (PRIVATE(this)->maxwidth - PRIVATE(this)->stringwidth[i]) / 2;
415 break;
416 }
417
418 int kerningx = 0;
419 int kerningy = 0;
420 int advancex = 0;
421 int advancey = 0;
422
423 const char * p = str.getString();
424 size_t length = cc_string_utf8_validate_length(p);
425
426 for (unsigned int strcharidx = 0; strcharidx < length; strcharidx++) {
427 uint32_t glyphidx = 0;
428
429 glyphidx = cc_string_utf8_get_char(p);
430 p = cc_string_utf8_next_char(p);
431
432 cc_glyph2d * glyph = cc_glyph2d_ref(glyphidx, fontspec, 0.0f);
433
434 buffer = cc_glyph2d_getbitmap(glyph, bitmapsize, bitmappos);
435
436 ix = bitmapsize[0];
437 iy = bitmapsize[1];
438
439 // Advance & Kerning
440 if (strcharidx > 0)
441 cc_glyph2d_getkerning(prevglyph, glyph, &kerningx, &kerningy);
442 cc_glyph2d_getadvance(glyph, &advancex, &advancey);
443
444 rasterx = xpos + kerningx + bitmappos[0];
445 rastery = ypos + (bitmappos[1] - bitmapsize[1]);
446
447 if (buffer) {
448 if (cc_glyph2d_getmono(glyph)) {
449 SoText2P::setRasterPos3f((float)rasterx + textscreenoffsetx, (float)rastery + (int)nilpoint[1], -nilpoint[2]);
450 glBitmap(ix,iy,0,0,0,0,(const GLubyte *)buffer);
451 }
452 else {
453 if (!drawPixelBuffer) {
454 int numpixels = bbsize[0] * bbsize[1];
455 if (numpixels > PRIVATE(this)->pixel_buffer_size) {
456 delete[] PRIVATE(this)->pixel_buffer;
457 PRIVATE(this)->pixel_buffer = new unsigned char[numpixels*4];
458 PRIVATE(this)->pixel_buffer_size = numpixels;
459 }
460 memset(PRIVATE(this)->pixel_buffer, 0, numpixels * 4);
461 drawPixelBuffer = TRUE;
462 }
463
464 int memx = rasterx - bbmin[0];
465 int memy = bbsize[1] - (bbmax[1] - rastery - 1) - 1;
466
467 if (memx >= 0 && memx + bitmapsize[0] <= bbsize[0] &&
468 memy >= 0 && memy + bitmapsize[1] <= bbsize[1]) {
469
470 unsigned char * dst = PRIVATE(this)->pixel_buffer + (memy * bbsize[0] + memx) * 4;
471 const unsigned char * src = buffer;
472 int nextlineoffset = (bbsize[0] - bitmapsize[0]) * 4;
473
474 // Ouch. This must lead to pretty slow rendering
475 for (int y = 0; y < iy; y++) {
476 for (int x = 0; x < ix; x++) {
477 *dst++ = red; *dst++ = green; *dst++ = blue;
478 // alpha from the gray level pixel value, blended with current value (because glyph bitmaps can overlap)
479 int srcval = *src;
480 int oldval = *dst;
481 *dst = ((oldval * (256 - srcval) + alpha * srcval) >> 8);
482 src++; dst++;
483 }
484 dst += nextlineoffset;
485 }
486 } else {
487 static SbBool once = TRUE;
488 if (once) {
489 SoDebugError::post("SoText2::GLRender",
490 "Unable to copy glyph to memory buffer. Position [%d,%d], size [%d,%d], buffer size [%d,%d]",
491 memx, memy, bitmapsize[0], bitmapsize[1], bbsize[0], bbsize[1]);
492 once = FALSE;
493 }
494 }
495 }
496 }
497
498 xpos += (advancex + kerningx);
499
500 if (prevglyph) {
501 // should be safe to unref here. SoGlyphCache will have a
502 // ref'ed instance
503 cc_glyph2d_unref(prevglyph);
504 }
505 prevglyph = glyph;
506 }
507
508 ypos -= (int)(((int) fontsize) * this->spacing.getValue());
509 }
510
511 if (prevglyph) {
512 // should be safe to unref here. SoGlyphCache will have a ref'ed
513 // instance
514 cc_glyph2d_unref(prevglyph);
515 }
516
517 if (drawPixelBuffer) {
518 glEnable(GL_BLEND);
519 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
520 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
521
522 rastery = (int)floor(nilpoint[1]+0.5) - bbsize[1] + bbmax[1];
523
524 SoText2P::setRasterPos3f((GLfloat)floor(textscreenoffsetx+0.5), (GLfloat)rastery, -nilpoint[2]);
525 glDrawPixels(bbsize[0], bbsize[1], GL_RGBA, GL_UNSIGNED_BYTE, (const GLubyte *)PRIVATE(this)->pixel_buffer);
526 }
527
528 // pop old state
529 glPopClientAttrib();
530 glPopAttrib();
531 state->pop();
532
533 glPixelStorei(GL_UNPACK_ALIGNMENT,4);
534 // Pop old GL matrix state.
535 glMatrixMode(GL_PROJECTION);
536 glPopMatrix();
537 glMatrixMode(GL_MODELVIEW);
538 glPopMatrix();
539 }
540
541 PRIVATE(this)->unlock();
542
543 state->pop();
544
545 // don't auto cache SoText2 nodes.
546 SoGLCacheContextElement::shouldAutoCache(action->getState(),
547 SoGLCacheContextElement::DONT_AUTO_CACHE);
548 }
549
550 // **************************************************************************
551
552
553 // doc in super
554 void
computeBBox(SoAction * action,SbBox3f & box,SbVec3f & center)555 SoText2::computeBBox(SoAction * action, SbBox3f & box, SbVec3f & center)
556 {
557 PRIVATE(this)->lock();
558 PRIVATE(this)->computeBBox(action, box, center);
559 SoCacheElement::addCacheDependency(action->getState(), PRIVATE(this)->cache);
560 PRIVATE(this)->unlock();
561 }
562
563 // doc in super
564 void
rayPick(SoRayPickAction * action)565 SoText2::rayPick(SoRayPickAction * action)
566 {
567 if (!this->shouldRayPick(action)) return;
568
569 PRIVATE(this)->lock();
570 PRIVATE(this)->buildGlyphCache(action->getState());
571 action->setObjectSpace();
572
573 SbVec3f v0, v1, v2, v3;
574 if (!PRIVATE(this)->getQuad(action->getState(), v0, v1, v2, v3)) {
575 PRIVATE(this)->unlock();
576 return; // empty
577 }
578
579 SbVec3f isect;
580 SbVec3f bary;
581 SbBool front;
582 SbBool hit = action->intersect(v0, v1, v2, isect, bary, front);
583 if (!hit) hit = action->intersect(v0, v2, v3, isect, bary, front);
584
585 if (hit && action->isBetweenPlanes(isect)) {
586 // find normalized 2D hitpoint on quad
587 float h = (v3-v0).length();
588 float w = (v1-v0).length();
589 SbLine horiz(v2,v3);
590 SbVec3f ptonline = horiz.getClosestPoint(isect);
591 float vdist = (ptonline-isect).length();
592 vdist /= h;
593
594 SbLine vert(v0,v3);
595 ptonline = vert.getClosestPoint(isect);
596 float hdist = (ptonline-isect).length();
597 hdist /= w;
598
599 // find which string was hit
600 const int numstr = this->string.getNum();
601 float fonth = 1.0f / float(numstr);
602 int stringidx = (numstr - SbClamp(int(vdist/fonth), 0, numstr-1)) - 1;
603
604 int maxlen = 0;
605 int i;
606 for (i = 0; i < numstr; i++) {
607 int len = this->string[i].getLength();
608 if (len > maxlen) maxlen = len;
609 }
610
611 // find the character
612 int charidx = -1;
613 int strlength = this->string[stringidx].getLength();
614 short minx, miny, maxx, maxy;
615 PRIVATE(this)->bbox.getBounds(minx, miny, maxx, maxy);
616 float bbwidth = (float)(maxx - minx);
617 float strleft = (bbwidth - PRIVATE(this)->stringwidth[stringidx]) / bbwidth;
618 float strright = 1.0;
619 switch (this->justification.getValue()) {
620 case LEFT:
621 strleft = 0.0;
622 strright = PRIVATE(this)->stringwidth[stringidx] / bbwidth;
623 break;
624 case RIGHT:
625 break;
626 case CENTER:
627 strleft /= 2.0;
628 strright = 1.0f - strleft;
629 break;
630 default:
631 assert(0 && "SoText2::rayPick: unknown justification");
632 }
633
634 float charleft, charright;
635 for (i=0; i<strlength; i++) {
636 charleft = strleft + PRIVATE(this)->positions[stringidx][i][0] / bbwidth;
637 charright = (i==strlength-1 ? strright : strleft + (PRIVATE(this)->positions[stringidx][i+1][0] / bbwidth));
638 if (hdist >= charleft && hdist <= charright) {
639 charidx = i;
640 i = strlength;
641 }
642 }
643
644
645 if (charidx >= 0 && charidx < strlength) { // we have a hit!
646 SoPickedPoint * pp = action->addIntersection(isect);
647 if (pp) {
648 SoTextDetail * detail = new SoTextDetail;
649 detail->setStringIndex(stringidx);
650 detail->setCharacterIndex(charidx);
651 pp->setDetail(detail, this);
652 pp->setMaterialIndex(0);
653 pp->setObjectNormal(SbVec3f(0.0f, 0.0f, 1.0f));
654 }
655 }
656 }
657 PRIVATE(this)->unlock();
658 }
659
660 // doc in super
661 void
getPrimitiveCount(SoGetPrimitiveCountAction * action)662 SoText2::getPrimitiveCount(SoGetPrimitiveCountAction *action)
663 {
664 if (!this->shouldPrimitiveCount(action))
665 return;
666
667 action->addNumText(this->string.getNum());
668 }
669
670 // doc in super
671 void
generatePrimitives(SoAction * COIN_UNUSED_ARG (action))672 SoText2::generatePrimitives(SoAction * COIN_UNUSED_ARG(action))
673 {
674 // This is supposed to be empty. There are no primitives.
675 }
676
677
678 // SoText2P methods below
679
680 void
flushGlyphCache()681 SoText2P::flushGlyphCache()
682 {
683 this->stringwidth.truncate(0);
684 this->maxwidth=0;
685 this->positions.truncate(0);
686 this->bbox.makeEmpty();
687 }
688
689
690 // Calculates a quad around the text in 3D.
691 // Return FALSE if the quad is empty.
692 SbBool
getQuad(SoState * state,SbVec3f & v0,SbVec3f & v1,SbVec3f & v2,SbVec3f & v3)693 SoText2P::getQuad(SoState * state, SbVec3f & v0, SbVec3f & v1,
694 SbVec3f & v2, SbVec3f & v3)
695 {
696 this->buildGlyphCache(state);
697
698 short xmin, ymin, xmax, ymax;
699 this->bbox.getBounds(xmin, ymin, xmax, ymax);
700
701 // FIXME: Why doesn't the SbBox2s have an 'isEmpty()' method as well?
702 // (20040308 handegar)
703 if (xmax < xmin) return FALSE;
704
705 SbVec3f nilpoint(0.0f, 0.0f, 0.0f);
706 const SbMatrix & mat = SoModelMatrixElement::get(state);
707 mat.multVecMatrix(nilpoint, nilpoint);
708
709 const SbViewVolume &vv = SoViewVolumeElement::get(state);
710
711 SbVec3f screenpoint;
712 vv.projectToScreen(nilpoint, screenpoint);
713
714 const SbViewportRegion & vp = SoViewportRegionElement::get(state);
715 SbVec2s vpsize = vp.getViewportSizePixels();
716
717 SbVec2f n0, n1, n2, n3, center;
718 SbVec2s sp((short) (screenpoint[0] * vpsize[0]), (short)(screenpoint[1] * vpsize[1]));
719
720 n0 = SbVec2f(float(sp[0] + xmin)/float(vpsize[0]),
721 float(sp[1] + ymax)/float(vpsize[1]));
722 n1 = SbVec2f(float(sp[0] + xmax)/float(vpsize[0]),
723 float(sp[1] + ymax)/float(vpsize[1]));
724 n2 = SbVec2f(float(sp[0] + xmax)/float(vpsize[0]),
725 float(sp[1] + ymin)/float(vpsize[1]));
726 n3 = SbVec2f(float(sp[0] + xmin)/float(vpsize[0]),
727 float(sp[1] + ymin)/float(vpsize[1]));
728
729 float w = n1[0]-n0[0];
730 float halfw = w*0.5f;
731 switch (PUBLIC(this)->justification.getValue()) {
732 case SoText2::LEFT:
733 break;
734 case SoText2::RIGHT:
735 n0[0] -= w;
736 n1[0] -= w;
737 n2[0] -= w;
738 n3[0] -= w;
739 break;
740 case SoText2::CENTER:
741 n0[0] -= halfw;
742 n1[0] -= halfw;
743 n2[0] -= halfw;
744 n3[0] -= halfw;
745 break;
746 default:
747 assert(0 && "unknown alignment");
748 break;
749 }
750
751 // get distance from nilpoint to camera plane
752 float dist = -vv.getPlane(0.0f).getDistance(nilpoint);
753
754 // find the four image points in the plane
755 v0 = vv.getPlanePoint(dist, n0);
756 v1 = vv.getPlanePoint(dist, n1);
757 v2 = vv.getPlanePoint(dist, n2);
758 v3 = vv.getPlanePoint(dist, n3);
759
760 // test if the quad is outside the view frustum, ignore it in that case
761 SbBox3f testbox;
762 testbox.extendBy(v0);
763 testbox.extendBy(v1);
764 testbox.extendBy(v2);
765 testbox.extendBy(v3);
766 if (!vv.intersect(testbox)) return FALSE;
767
768 // transform back to object space
769 SbMatrix inv = mat.inverse();
770 inv.multVecMatrix(v0, v0);
771 inv.multVecMatrix(v1, v1);
772 inv.multVecMatrix(v2, v2);
773 inv.multVecMatrix(v3, v3);
774
775 return TRUE;
776 }
777
778 // Debug convenience method.
779 void
dumpBuffer(unsigned char * buffer,SbVec2s size,SbVec2s pos,SbBool mono)780 SoText2P::dumpBuffer(unsigned char * buffer, SbVec2s size, SbVec2s pos, SbBool mono)
781 {
782 // FIXME: pure debug method, remove. preng 2003-03-18.
783 if (!buffer) {
784 fprintf(stderr,"bitmap error: buffer pointer NULL.\n");
785 } else {
786 int rows = size[1];
787 int bytes = mono ? size[0] >> 3 : size[0];
788 fprintf(stderr, "%s bitmap dump %d * %d bytes at %d, %d:\n",
789 mono ? "mono": "gray level", rows, bytes, pos[0], pos[1]);
790 for (int y=rows-1; y>=0; y--) {
791 for (int byte=0; byte<bytes; byte++) {
792 for (int bit=0; bit<8; bit++) {
793 fprintf(stderr, "%d", buffer[y*bytes + byte] & 0x80>>bit ? 1 : 0);
794 }
795 }
796 fprintf(stderr,"\n");
797 }
798 }
799 }
800
801 // FIXME: Use notify() mechanism to detect field changes. For
802 // Coin3. preng, 2003-03-10.
803 //
804 // UPDATE 20030408 mortene: that wouldn't be sufficient, as
805 // e.g. changes to SoFont and SoFontStyle nodes in the scene graph can
806 // also have an influence on which glyphs to render.
807 //
808 // The best solution would be to create a new cache; SoGlyphCache or
809 // something. This cache would automatically store SoFont and
810 // SoFontStyle elements and be marked as invalid when they change
811 // (that's what caches are for). pederb, 2003-04-08
812
813 SbBool
shouldBuildGlyphCache(SoState * state)814 SoText2P::shouldBuildGlyphCache(SoState * state)
815 {
816 if (this->cache == NULL) return TRUE;
817 return !this->cache->isValid(state);
818 }
819
820 void
buildGlyphCache(SoState * state)821 SoText2P::buildGlyphCache(SoState * state)
822 {
823 if (!this->shouldBuildGlyphCache(state)) {
824 return;
825 }
826
827 this->flushGlyphCache();
828
829 // don't unref the old cache until after we've created the new
830 // cache.
831 SoGlyphCache * oldcache = this->cache;
832
833 state->push();
834 SbBool storedinvalid = SoCacheElement::setInvalid(FALSE);
835 this->cache = new SoGlyphCache(state);
836 this->cache->ref();
837 SoCacheElement::set(state, this->cache);
838 this->cache->readFontspec(state);
839
840 float fontsize = SoFontSizeElement::get(state);
841 int ypos = 0;
842 int maxoverhang = INT_MIN;
843
844 const int nrlines = PUBLIC(this)->string.getNum();
845
846 const cc_font_specification * fontspec = this->cache->getCachedFontspec();
847
848 this->bbox.makeEmpty();
849
850 for (int i=0; i < nrlines; i++) {
851 SbString str = PUBLIC(this)->string[i];
852 this->positions.append(SbList<SbVec2s>());
853
854 SbBox2s linebbox;
855 int xpos = 0;
856 int actuallength = 0;
857 int kerningx = 0;
858 int kerningy = 0;
859 int advancex = 0;
860 int advancey = 0;
861 int bitmapsize[2];
862 int bitmappos[2];
863 const cc_glyph2d * prevglyph = NULL;
864 const char * p = str.getString();
865 size_t length = cc_string_utf8_validate_length(p);
866
867 // fetch all glyphs first
868 for (unsigned int strcharidx = 0; strcharidx < length; strcharidx++) {
869 uint32_t glyphidx = 0;
870
871 glyphidx = cc_string_utf8_get_char(p);
872 p = cc_string_utf8_next_char(p);
873
874 cc_glyph2d * glyph = cc_glyph2d_ref(glyphidx, fontspec, 0.0f);
875 // Should _always_ be able to get hold of a glyph -- if no
876 // glyph is available for a specific character, a default
877 // empty rectangle should be used. -mortene.
878 assert(glyph);
879
880 this->cache->addGlyph(glyph);
881
882 // Must fetch special modifiers so that heights for chars like
883 // 'q' and 'g' will be taken into account when creating a
884 // boundingbox.
885 (void) cc_glyph2d_getbitmap(glyph, bitmapsize, bitmappos);
886
887 // Advance & Kerning
888 if (strcharidx > 0)
889 cc_glyph2d_getkerning(prevglyph, glyph, &kerningx, &kerningy);
890 cc_glyph2d_getadvance(glyph, &advancex, &advancey);
891
892 SbVec2s pos;
893 pos[0] = xpos + kerningx + bitmappos[0];
894 pos[1] = ypos + (bitmappos[1] - bitmapsize[1]);
895
896 linebbox.extendBy(pos);
897 linebbox.extendBy(pos + SbVec2s(bitmapsize[0], bitmapsize[1]));
898 this->positions[i].append(pos);
899
900 actuallength += (advancex + kerningx);
901
902 xpos += (advancex + kerningx);
903 prevglyph = glyph;
904 }
905
906 this->bbox.extendBy(linebbox);
907 this->stringwidth.append(actuallength);
908 if (actuallength > this->maxwidth) this->maxwidth=actuallength;
909
910 // bitmap of last character can end before or beyond starting position of next character
911 if (!linebbox.isEmpty())
912 {
913 int overhang = linebbox.getMax()[0] - actuallength;
914 if (overhang > maxoverhang) maxoverhang = overhang;
915 }
916
917 ypos -= (int)(((int)fontsize) * PUBLIC(this)->spacing.getValue());
918
919 }
920
921 // extent bbox to include maxoverhang at the maxwidth string
922 // this is needed for right-aligned text which gets aligned at the maxwidth
923 // position, because there can be other strings with bitmaps going beyond
924 if (maxoverhang > INT_MIN)
925 {
926 this->bbox.extendBy(SbVec2s(this->maxwidth + maxoverhang, this->bbox.getMax()[1]));
927 }
928
929 state->pop();
930 SoCacheElement::setInvalid(storedinvalid);
931
932 if (oldcache) oldcache->unref();
933 }
934
935 void
computeBBox(SoAction * action,SbBox3f & box,SbVec3f & center)936 SoText2P::computeBBox(SoAction * action, SbBox3f & box, SbVec3f & center)
937 {
938 SbVec3f v0, v1, v2, v3;
939 // this will cause a cache dependency on the view volume,
940 // model matrix and viewport.
941 if (!this->getQuad(action->getState(), v0, v1, v2, v3)) {
942 return; // empty
943 }
944 box.makeEmpty();
945
946 box.extendBy(v0);
947 box.extendBy(v1);
948 box.extendBy(v2);
949 box.extendBy(v3);
950
951 center = box.getCenter();
952 }
953
954 // Sets the raster position for GL raster operations.
955 // Handles the special case where the x/y coordinates are negative
956 void
setRasterPos3f(GLfloat x,GLfloat y,GLfloat z)957 SoText2P::setRasterPos3f(GLfloat x, GLfloat y, GLfloat z)
958 {
959 float rpx = x >= 0 ? x : 0;
960 int offvp = x < 0 ? 1 : 0;
961 float offsetx = x >= 0 ? 0 : x;
962
963 float rpy = y >= 0 ? y : 0;
964 offvp = offvp || y < 0 ? 1 : 0;
965 float offsety = y >= 0 ? 0 : y;
966
967 glRasterPos3f(rpx,rpy,z);
968 if (offvp) { glBitmap(0, 0, 0, 0,offsetx,offsety, NULL); }
969 }
970
971 #undef PRIVATE
972 #undef PUBLIC
973