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