X11 Work Bench Toolkit  1.0
draw_text.c
Go to the documentation of this file.
1 
2 // _ _ _ //
3 // __| | _ __ __ _ __ __ | |_ ___ __ __| |_ ___ //
4 // / _` || '__|/ _` |\ \ /\ / / | __|/ _ \\ \/ /| __| / __| //
5 // | (_| || | | (_| | \ V V / | |_| __/ > < | |_ _| (__ //
6 // \__,_||_| \__,_| \_/\_/_____\__|\___|/_/\_\ \__|(_)\___| //
7 // |_____| //
8 // //
9 // text draw, multi-line and tabbed text //
10 // //
12 
13 /*****************************************************************************
14 
15  X11workbench - X11 programmer's 'work bench' application and toolkit
16  Copyright (c) 2010-2016 by Bob Frazier (aka 'Big Bad Bombastic Bob')
17  all rights reserved
18 
19  DISCLAIMER: The X11workbench application and toolkit software are supplied
20  'as-is', with no warranties, either implied or explicit.
21  Any claims to alleged functionality or features should be
22  considered 'preliminary', and might not function as advertised.
23 
24  BSD-like license:
25 
26  There is no restriction as to what you can do with this software, so long
27  as you include the above copyright notice and DISCLAIMER for any distributed
28  work that is equal to or derived from this one, along with this paragraph
29  that explains the terms of the license if the source is also being made
30  available. A "derived work" describes a work that uses a significant portion
31  of the source files or algorithms that are included with this one.
32  Specifically excluded from this are files that were generated by the software,
33  or anything that is included with the software that is part of another package
34  (such as files that were created or added during the 'configure' process).
35  Specifically included is the use of part or all of any of the X11 workbench
36  toolkit source or header files in your distributed application. If you do not
37  ship the source, the above copyright statement is still required to be placed
38  in a reasonably prominent place, such as documentation, splash screens, and/or
39  'about the application' dialog boxes.
40 
41  Use and distribution are in accordance with GPL, LGPL, and/or the above
42  BSD-like license. See COPYING and README files for more information.
43 
44 
45  Additional information at http://sourceforge.net/projects/X11workbench
46 
47 ******************************************************************************/
48 
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <unistd.h>
59 #include <memory.h>
60 #include <string.h>
61 #include <strings.h>
62 
63 #include "window_helper.h" // this also includes 'font_helper.h'
64 
65 #include "draw_text.h"
66 
67 
68 
69 // function prototypes
70 
71 
94 static int InternalCalcIdealBounds(Display *pDisplay, XFontSet fontSet, DT_WORDS *pWords, int iTabWidth, unsigned int iTabOrigin,
95  const WB_RECT *prcSource, WB_RECT *prcDest, int iAlignment, int iStartLine, int iEndLine);
96 
97 
98 static void InternalDebugDumpWords(DT_WORDS *pWords);
99 
100 
101 
102 
103 // *******************
104 // DRAW TEXT UTILITIES
105 // *******************
106 
107 int DTGetTextWidth0(XFontStruct *pFont, const char *szUTF8, int nLength)
108 {
109 //XRectangle rctInk, rctLog;
110 XFontSet fSet;
111 int iRval;//, iLen;
112 
113  if(nLength <= 0)
114  {
115  return 0;
116  }
117 
118  fSet = WBFontSetFromFont(WBGetDefaultDisplay(), pFont);
119 
120  if(!fSet)
121  {
122  WB_ERROR_PRINT("Unable to get font set!\n");
123 
124  return XTextWidth(pFont, szUTF8, nLength);
125  }
126 
127  iRval = DTGetTextWidth(fSet, szUTF8, nLength); // WB_TEXT_ESCAPEMENT(fSet, szUTF8, nLength);
128 
129  XFreeFontSet(WBGetDefaultDisplay(), fSet);
130 
131  return iRval;
132 }
133 
134 int DTGetTextWidth(XFontSet fontSet, const char *szUTF8, int nLength)
135 {
136 int iRval;
137 
138  if(nLength <= 0)
139  {
140  return 0;
141  }
142 
143  if(fontSet == None)
144  {
146 
147  if(fontSet == None)
148  {
149  WB_ERROR_PRINT("ERROR: %s - fSet is None\n", __FUNCTION__);
150 
151  return 0;
152  }
153  }
154 
155  iRval = WBTextWidth(fontSet, szUTF8, nLength); // WB_TEXT_ESCAPEMENT(fSet, szUTF8, nLength);
156 
157  // TODO: any debug output
158 
159  return iRval;
160 }
161 
162 
163 
164 // determine ideal font size from desired text and geometry
165 
166 XFontStruct *DTCalcIdealFont(XFontStruct *pRefFont, const char *szText, WB_GEOM *geomBounds)
167 {
168  if(!pRefFont)
169  {
170  pRefFont = WBGetDefaultFont();
171  }
172 
173 
174 
175 
176  return NULL; // for now
177 }
178 
179 
180 XFontSet DTCalcIdealFontSet(Display *pDisplay, XFontSet fontSet, const char *szText, WB_GEOM *geomBounds)
181 {
182  if(fontSet == None)
183  {
184  fontSet = WBGetDefaultFontSet(pDisplay);
185  }
186 
187 
188  return None; // for now
189 }
190 
191 
192 #define INITIAL_DT_WORDS_COUNT 256
193 
194 static int CheckReAllocWords(DT_WORDS **ppWords)
195 {
196 DT_WORDS *pRval = *ppWords;
197 
198  if(pRval->nCount + 1 >= pRval->nMax)
199  {
200  int iNewLen = sizeof(*pRval)
201  + ((pRval->nMax & 0xffffff80) + 127) * sizeof(struct __DT_WORD__);
202 
203  void *p3 = WBReAlloc(pRval, iNewLen);
204 
205  if(!p3)
206  {
207  WB_ERROR_PRINT("%s - ERROR: not enough memory to allocate new struct (%d required)\n",
208  __FUNCTION__, iNewLen);
209 
210  return 0;
211  }
212 
213  pRval = (DT_WORDS *)p3;
214 //#ifdef HAVE_MALLOC_USABLE_SIZE
215  pRval->nMax = (WBAllocUsableSize(pRval) - sizeof(DT_WORDS))
216  / sizeof(struct __DT_WORD__)
217  + 1; // because DT_WORDS contains one struct __DT_WORD__
218 //#else // HAVE_MALLOC_USABLE_SIZE
219  pRval->nMax = (pRval->nMax & 0xffffff80) + 128;
220 //#endif // HAVE_MALLOC_USABLE_SIZE
221  }
222 
223  *ppWords = pRval;
224  return 1; // OK
225 }
226 
227 // NOTES ON UTF-8 AND INTERNATIONAL CHARACTERS
228 // notes from http://en.wikipedia.org/wiki/UTF-8
229 //
230 // # of bits highest Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6
231 // -----------------------------------------------------------------------------
232 // 7 bits U+007F 0xxxxxxx
233 // 11 bits U+07FF 110xxxxx 10xxxxxx
234 // 16 bits U+FFFF 1110xxxx 10xxxxxx 10xxxxxx
235 // 21 bits U+1FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
236 // 26 bits U+3FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
237 // 31 bits U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
238 //
239 // Because of THIS, it is STILL SAFE to look for <= ' ' for white space, and make some assumptions
240 // about 0-byte, \n, \r, \t, and other control characters (for now, anyway).
241 //
242 // RFC 3629 states "Implementations of the decoding algorithm MUST protect against decoding invalid sequences."
243 // invalid sequences could (in theory) cause burping to the point of security risk.
244 
245 // There are also some "print direction" override characters 202C 202D and 202E .
246 // http://www.unicodemap.org/details/0x202C/index.html (POP Directional Formatting)
247 // http://www.unicodemap.org/details/0x202D/index.html (LTR Override)
248 // http://www.unicodemap.org/details/0x202E/index.html (RTL Override)
249 //
250 // also http://www.unicodemap.org/details/0x200E/index.html (LTR mark)
251 // http://www.unicodemap.org/details/0x200E/index.html (RTL mark)
252 // http://www.unicodemap.org/details/0x202A/index.html (LTR embedding)
253 // http://www.unicodemap.org/details/0x202B/index.html (RTL embedding)
254 //
255 // and these:
256 // http://www.unicodemap.org/details/0x2028/index.html (line separator - alternate LF?)
257 // http://www.unicodemap.org/details/0x202A/index.html (paragraph separator - alternate to CTRL+L ?)
258 //
259 // There are also a handful of various width 'spacing' including NO WIDTH SPACE
260 // 206A through 206F apparently affect "shaping". 205F through 2069 apparently do nothing
261 //
262 // If I pay attention to these, it could put some serious strain on everything. If I don't, it's not really UTF-8
263 // However the focus of THIS project is "only implement what is necessary" (and forget the rest).
264 
265 
266 DT_WORDS * DTGetWordsFromText0(XFontStruct *pFont, const char *szText, int iAlignment)
267 {
268 XFontSet fSet;
269 DT_WORDS *pRval = NULL;
270 
271 
272  fSet = WBFontSetFromFont(WBGetDefaultDisplay(), pFont);
273 
274  if(!fSet)
275  {
276  WB_ERROR_PRINT("ERROR: %s - Unable to get font set!\n", __FUNCTION__);
277 
278  return NULL;
279  }
280 
281  pRval = DTGetWordsFromText(WBGetDefaultDisplay(), fSet, szText, iAlignment);
282 
283  XFreeFontSet(WBGetDefaultDisplay(), fSet);
284 
285  return pRval;
286 }
287 
288 
289 DT_WORDS * DTGetWordsFromText(Display *pDisplay, XFontSet fontSet, const char *szText, int iAlignment)
290 {
291  const char *p1, *p2;
292 
293  DT_WORDS *pRval = WBAlloc(sizeof(DT_WORDS) + sizeof(struct __DT_WORD__) * (INITIAL_DT_WORDS_COUNT - 1));
294 
295  if(!pRval)
296  {
297  return NULL;
298  }
299 
300  pRval->nCount = 0;
301  pRval->szText = szText;
302 //#ifdef HAVE_MALLOC_USABLE_SIZE
303  pRval->nMax = (WBAllocUsableSize(pRval) - sizeof(DT_WORDS)) // calculate actual # of structs it contains
304  / sizeof(struct __DT_WORD__)
305  + 1; // because DT_WORDS contains one struct __DT_WORD__
306 //#else // HAVE_MALLOC_USABLE_SIZE
307  pRval->nMax = INITIAL_DT_WORDS_COUNT;
308 //#endif // HAVE_MALLOC_USABLE_SIZE
309 
310  if(!szText || !*szText)
311  {
312  return pRval;
313  }
314 
315  p1 = szText;
316  while(*p1)
317  {
318  // find non-white-space and keep 'tabs' on the count/type of white space as well
319  // adding entries to 'words' as needed
320 
321  while(*p1 && (unsigned char)*p1 <= ' ')
322  {
323  // TODO: check for high bit set aka UTF-8 multi-byte chars
324 
325  if(*p1 == '\t') // tab character
326  {
327  if(!CheckReAllocWords(&pRval))
328  {
329  WBFree(pRval);
330  return NULL; // ERROR
331  }
332 
333  pRval->aWords[pRval->nCount].pText = NULL;
334  pRval->aWords[pRval->nCount].nLength = 1;
335  pRval->aWords[pRval->nCount].iIsWhiteSpace = 0;
336  pRval->aWords[pRval->nCount].iIsLineFeed = 0;
337  pRval->aWords[pRval->nCount].iIsTab = 1;
338  pRval->aWords[pRval->nCount].iX = -1;
339  pRval->aWords[pRval->nCount].iY = -1;
340 
341  while(*(p1 + 1) == '\t')
342  {
343  p1++;
344  pRval->aWords[pRval->nCount].nLength++;
345  }
346 
347  // use white space string for width
348  pRval->aWords[pRval->nCount].iWidth = 0; // NA (calc on the fly)
349  pRval->aWords[pRval->nCount].iHeight = 0; // NA (calc on the fly)
350 
351  pRval->nCount++;
352  }
353  else if(*p1 == '\n' || *p1 == '\r')
354  {
355  if(!CheckReAllocWords(&pRval))
356  {
357  WBFree(pRval);
358  return NULL; // ERROR
359  }
360 
361  pRval->aWords[pRval->nCount].pText = NULL;
362  pRval->aWords[pRval->nCount].nLength = 1;
363 
364  if(iAlignment & DTAlignment_SINGLELINE) // treat line feed as white space
365  {
366  pRval->aWords[pRval->nCount].iIsWhiteSpace = 1;
367  pRval->aWords[pRval->nCount].iIsLineFeed = 0;
368  }
369  else
370  {
371  pRval->aWords[pRval->nCount].iIsWhiteSpace = 0;
372  pRval->aWords[pRval->nCount].iIsLineFeed = 1;
373  }
374 
375  pRval->aWords[pRval->nCount].iIsTab = 0;
376  pRval->aWords[pRval->nCount].iX = -1;
377  pRval->aWords[pRval->nCount].iY = -1;
378 
379  while(1)
380  {
381  if(*p1 == '\n' && p1[1] == '\r')
382  {
383  p1++;
384  }
385  else if(*p1 == '\r' && p1[1] == '\n')
386  {
387  p1++;
388  }
389 
390 // pRval->aWords[pRval->nCount].nLength++;
391 
392  if(p1[1] != '\r' && p1[1] != '\n')
393  {
394  break;
395  }
396 
397  p1++; // loop consecutive CR,LF
398  pRval->aWords[pRval->nCount].nLength++;
399  }
400 
401  // use white space string for width
402  pRval->aWords[pRval->nCount].iWidth = 0; // NA
403  pRval->aWords[pRval->nCount].iHeight = 0; // NA
404 
405  pRval->nCount++;
406  }
407  else if((unsigned char)*p1 <= ' ')
408  {
409  if(!CheckReAllocWords(&pRval))
410  {
411  WBFree(pRval);
412  return NULL; // ERROR
413  }
414 
415  pRval->aWords[pRval->nCount].pText = NULL;
416  pRval->aWords[pRval->nCount].nLength = 1;
417  pRval->aWords[pRval->nCount].iIsWhiteSpace = 1;
418  pRval->aWords[pRval->nCount].iIsLineFeed = 0;
419  pRval->aWords[pRval->nCount].iIsTab = 0;
420  pRval->aWords[pRval->nCount].iX = -1;
421  pRval->aWords[pRval->nCount].iY = -1;
422 
423  p2 = p1;
424 
425  while(*(p1 + 1) && (unsigned char)*(p1 + 1) <= ' ' && *(p1 + 1) != '\t' && *(p1 + 1) != '\r' && *(p1 + 1) != '\n')
426  {
427  p1++;
428  pRval->aWords[pRval->nCount].nLength++;
429  }
430 
431  // use white space string for width
432  pRval->aWords[pRval->nCount].iWidth = DTGetTextWidth(fontSet, p2, pRval->aWords[pRval->nCount].nLength);
433  pRval->aWords[pRval->nCount].iHeight = WBFontSetHeight(pDisplay, fontSet); // total height of font
434 
435  pRval->nCount++;
436  }
437 
438  p1++;
439  }
440 
441  if(*p1)
442  {
443  int iUnderscoreFlag = 0;
444 
445  // get length of word
446 
447  p2 = p1;
448  while(/* *p1 && */ (unsigned char)*p1 > ' ')
449  {
450  if(*p1 == '_')
451  {
452  iUnderscoreFlag++;
453  }
454 
455  p1++;
456  }
457 
458  if(!CheckReAllocWords(&pRval))
459  {
460  WBFree(pRval);
461  return NULL; // ERROR
462  }
463 
464  pRval->aWords[pRval->nCount].pText = p2;
465  pRval->aWords[pRval->nCount].nLength = p1 - p2;
466  pRval->aWords[pRval->nCount].iIsWhiteSpace = 0;
467  pRval->aWords[pRval->nCount].iIsTab = 0;
468  pRval->aWords[pRval->nCount].iIsLineFeed = 0;
469  pRval->aWords[pRval->nCount].iX = -1;
470  pRval->aWords[pRval->nCount].iY = -1;
471 
472  if(p1 > p2)
473  {
474  if(!(iAlignment & DTAlignment_UNDERSCORE) || !iUnderscoreFlag)
475  {
476  pRval->aWords[pRval->nCount].iWidth = DTGetTextWidth(fontSet, p2, p1 - p2);
477  }
478  else
479  {
480  // fast and dirty, subtract "length of underscores" from text length
481  // assume kerning doesn't alter this at all
482 
483  // TODO: the hard way?
484 
485  pRval->aWords[pRval->nCount].iWidth = DTGetTextWidth(fontSet, p2, p1 - p2)
486  - iUnderscoreFlag * DTGetTextWidth(fontSet, "_", 1);
487  }
488  }
489  else
490  {
491  pRval->aWords[pRval->nCount].iWidth = 0;
492  }
493 
494  pRval->aWords[pRval->nCount].iHeight = WBFontSetHeight(pDisplay, fontSet);//pFont->ascent + pFont->descent; // for now just do this
495 
496  pRval->nCount++;
497  }
498  }
499 
500  return pRval;
501 }
502 
503 int DTCalcIdealBounds(Display *pDisplay, XFontSet fontSet, const char *szText, int iTabWidth, unsigned int iTabOrigin,
504  const WB_RECT *prcSource, WB_RECT *prcDest, int iAlignment)
505 {
506 int iRval;
507 DT_WORDS *pWords;
508 
509 
510  pWords = DTGetWordsFromText(pDisplay, fontSet, szText, iAlignment);
511 
512  if(!pWords)
513  {
514  if(prcDest)
515  prcDest->left = prcDest->right = prcDest->top = prcDest->bottom = 0;
516 
517  return -1; // error
518  }
519 
520 // InternalDebugDumpWords(pWords);
521 
522  iRval = InternalCalcIdealBounds(pDisplay, fontSet, pWords, iTabWidth, iTabOrigin, prcSource, prcDest, iAlignment, 0, -1);
523 
524  WBFree(pWords);
525 
526  return iRval;
527 }
528 
529 
530 static void DoHCenterWrappedLines(DT_WORDS *pWords, int iWidth)
531 {
532 int i1, i2, i3, iMax;
533 DT_WORD *pW;
534 
535 
536  // For a multi-line string, I need to make sure each line centers
537  // correctly. If the lines break, I allow them to break and wrap
538  // but each line that is COMPLETELY within the centered rectangle
539  // without wrapping MUST BE RE-CENTERED!!!
540 
541  i1 = 0; // this will be my 'word counter'
542 
543  do
544  {
545  i2 = i1; // start of line
546  iMax = 0;
547 
548  // step 1: find the end of the current line and calculate
549  // the max width of the line (including 'wrap')
550 
551  while(i1 < pWords->nCount)
552  {
553  pW = &(pWords->aWords[i1]);
554  i1++;
555 
556  if(pW->iIsLineFeed)
557  {
558  break; // i1 already incremented, so it will skip Mr. Line Feed on the next iteration
559  }
560 
561  if(!pW->iIsTab && !pW->iIsWhiteSpace)
562  {
563  i3 = pW->iX + pW->iWidth; // max extent
564 
565  if(i3 > iMax)
566  {
567  iMax = i3;
568  }
569  }
570  }
571 
572  // if 'iMax' is less than 'iWidth', add half the delta
573  // to all of the 'iX' values
574 
575  i3 = (iWidth - iMax) / 2;
576 
577  if(i3 > 0)
578  {
579  for(; i2 < i1; i2++)
580  {
581  pW = &(pWords->aWords[i2]);
582 
583  if(pW->iX >= 0) // only for the 'assigned' ones
584  {
585  pW->iX += i3;
586  }
587  }
588  }
589 
590  } while(i1 < pWords->nCount);
591 
592 
593 // InternalDebugDumpWords(pWords);
594 }
595 
596 
597 
599 // * parameters to this function
600 // *
601 // * pFont A pointer to an XFontStruct (NULL implies system default font)
602 // * pWords The DT_WORDS structure for the text
603 // * iTabWidth A positive integer in 'characters', or negative integer in pixels, indicating tab width
604 // * iTabOrgin An unsigned integer indicating the tab origin, using the same units as iTabWidth, corresponding to the first character.
605 // * prcSource A pointer to the 'source' bounding rectangle in which the text is intended to fit
606 // * prcDest A pointer to the 'destination' bounding rectangle, based on the actual text size
607 // * iAlignment The desired text alignment, one or more of the DTAlignment bit flags
608 // * iStartLine A zero-based index for the first line on which to start calculating things
609 // * iEndLine A zero-based index for the last line on which to start calculating things (-1 for 'all')
610 // * zero if the text will fit within prcSource, -1 error, or 1 to indicate that prcDest is larger than prcSource
611 
612 static int InternalCalcIdealBounds(Display *pDisplay, XFontSet fontSet, DT_WORDS *pWords, int iTabWidth, unsigned int iTabOrigin,
613  const WB_RECT *prcSource, WB_RECT *prcDest, int iAlignment, int iStartLine, int iEndLine)
614 {
615 int iFontWidth, iFontHeight, iFontAscent, iFontDescent, iLineSpacing; // average font width/height and line spacing
616 int iHPos, iHPos0, iMaxLen, iLines;
617 int i1, i2, i3;
618 //const char *p1;
619 WB_RECT rctBounds, rctSource;
620 DT_WORD *pW, *pW2;
621 
622 
623  if(!pWords || (!prcSource && !prcDest))
624  {
625  WB_ERROR_PRINT("%s - returns ERROR (bad values pWords=%p, prcSource=%p, prcDest=%p)\n",
626  __FUNCTION__, pWords, prcSource, prcDest);
627  return -1;
628  }
629  else if(prcSource)
630  {
631  rctSource.left = prcSource->left;
632  rctSource.top = prcSource->top;
633  rctSource.right = prcSource->right;
634  rctSource.bottom = prcSource->bottom;
635  }
636  else
637  {
638  bzero(&rctSource, sizeof(rctSource));
639  }
640 
641  memcpy(&rctBounds, &rctSource, sizeof(rctBounds)); // make copy of it as 'rctSource' (for now)
642 
643  if(fontSet == None)
644  {
645  fontSet = WBGetDefaultFontSet(pDisplay);
646  if(fontSet == None)
647  {
648  WB_ERROR_PRINT("%s - returns ERROR (WBGetDefaultFontSet returns None)\n", __FUNCTION__);
649  return -1;
650  }
651  }
652 
653 // // TEMPORARY
654 // WB_ERROR_PRINT("%s - TEMPORARY: bounds rectangle is INITIALLY %d,%d,%d,%d\n",
655 // __FUNCTION__, rctBounds.left, rctBounds.top, rctBounds.right, rctBounds.bottom);
656 
657  // TODO: make use of iStartLine and iEndLine - for now "do all" every time so I can avoid a re-write
658 
659  // get a few things straight 'round here
660 
661  iFontWidth = WBFontSetAvgCharWidth(WBGetDefaultDisplay(), fontSet);
662 
663  if(!iFontWidth)
664  {
665  iFontWidth = DTGetTextWidth(fontSet, " ", 1); // width of a single space
666  }
667 
668  iFontAscent = WBFontSetAscent(pDisplay, fontSet);
669  iFontDescent = WBFontSetDescent(pDisplay, fontSet);
670  iFontHeight = iFontAscent + iFontDescent; // cache for performance
671 
672  // for now, line spacing equals 1/2 descent or 2 pixels
673  iLineSpacing = iFontDescent / 2;
674  if(iLineSpacing < 2)
675  {
676  iLineSpacing = 2;
677  }
678 
679  if(iTabWidth < 0)
680  {
681  iTabWidth = -iTabWidth; // in pixels
682  }
683  else
684  {
685  iTabOrigin *= iFontWidth;
686  iTabWidth *= iFontWidth; // convert chars to pixels
687  }
688 
689  // step 1: trial fit within the current rectangle
690 
691  iHPos0 = iHPos = rctSource.left;
692 
693  iMaxLen = 0;
694  iLines = 0;
695 
696  // initial text width and # of lines calculation.
697 
698  for(i1=0; i1 < pWords->nCount; i1++)
699  {
700  pW = &(pWords->aWords[i1]);
701 
702  if(pW->iIsWhiteSpace)
703  {
704  pW->iY = pW->iX = -1; // NA
705 
706  // before I start adding white space, see if there is
707  // any NON-WHITE SPACE following this entry...
708 
709  if(iAlignment & DTAlignment_ALLOW_TRAILING)
710  {
711  iHPos += pW->iWidth; // it's ok to use the TOTAL width of this white space
712  }
713  else
714  {
715  for(i2=i1 + 1; i2 < pWords->nCount; i2++)
716  {
717  pW2 = &(pWords->aWords[i2]);
718 
719  if(!pW2->iIsWhiteSpace && !pW2->iIsTab && !pW2->iIsLineFeed)
720  {
721  iHPos += pW->iWidth; // it's ok to use the width of this white space
722  break;
723  }
724  else if(pW2->iIsLineFeed)
725  {
726  break; // end of line (trailing white space will be ignored)
727  }
728  }
729  }
730  }
731  else if(pW->iIsTab)
732  {
733  pW->iY = pW->iX = -1; // NA
734 
735  // before I start adding white space for tabs, see if there is
736  // any NON-WHITE SPACE following this entry...
737 
738  if(iAlignment & DTAlignment_ALLOW_TRAILING)
739  {
740 do_the_tab:
741  if(iAlignment & (DTAlignment_HCENTER | DTAlignment_HJUSTIFY)) // no tabbing for center/justify
742  {
743  iHPos += pW->nLength * iFontWidth; // treat it as regular white space
744  }
745  else
746  {
747  i3 = (iHPos - iHPos0 + iTabOrigin) % iTabWidth; // remaining space to tab over
748 
749  if(!i3)
750  {
751  iHPos += iTabWidth; // tab out the entire distance
752  }
753  else
754  {
755  iHPos += i3; // tab out "the remainder"
756  }
757 
758  if(pW->nLength > 1) // check for multiple tabs
759  {
760  iHPos += iTabWidth * (pW->nLength - 1); // additional tabs
761  }
762  }
763  }
764  else
765  {
766  for(i2=i1 + 1; i2 < pWords->nCount; i2++)
767  {
768  pW2 = &(pWords->aWords[i2]);
769  pW2->iX = pW2->iY = -1; // because I'm skipping them
770 
771  if(!pW2->iIsWhiteSpace && !pW2->iIsTab && !pW2->iIsLineFeed)
772  {
773  goto do_the_tab; // cleaner code with label
774  }
775  else if(pW2->iIsLineFeed)
776  {
777  break; // end of line (trailing tabs will be ignored)
778  }
779  }
780 
781  // if I get here, only white space remains at the end of the DT_WORD array. Ignore it.
782  break;
783  }
784  }
785  else if(pW->iIsLineFeed)
786  {
787  pW->iY = pW->iX = -1; // NA
788 
789  if(iMaxLen < iHPos - iHPos0)
790  {
791  iMaxLen = iHPos - iHPos0;
792  }
793 
794  if(iHPos > iHPos0 && // there is actual text present on this line
795  (i1 + 1) == pWords->nCount) // the last entry? The text ends in one or more line feeds
796  {
797  // TODO: see if it's just a bunch of blank lines following the text
798  // and consider ignoring them ALL if it makes sense to do so
799 
800  iLines += pW->nLength - 1; // at the end, the final line feed doesn't "count" unless it's by itself
801  }
802  else
803  {
804  iLines += pW->nLength; // add ALL of the line feeds to the line count (more to come)
805  }
806 
807  iHPos = iHPos0;
808  }
809  else
810  {
811  if(!iLines) // this handles blank text, which will show up with "zero lines"
812  {
813  iLines++; // indicate at least one line of printable stuff
814  }
815 
816  pW->iX = iHPos - iHPos0; // relative X position
817  pW->iY = (iLines - 1) * iFontHeight + iFontAscent; // relative Y position for 'base of text'
818 
819  iHPos += pW->iWidth;
820  }
821 
822  if(iHPos > iMaxLen)
823  {
824  iMaxLen = iHPos;
825  }
826  }
827 
828  // Now I know the current (unmodified) text extents. Let's see what they SHOULD be
829 
830 // WB_ERROR_PRINT("TEMPORARY: %s - iHPos=%d, iMaxLen=%d, iLines=%d, iFontHeight=%d, *=%d\n", __FUNCTION__,
831 // iHPos, iMaxLen, iLines, iFontHeight, (iLines * iFontHeight));
832 
833  rctBounds.left = rctSource.left;
834  rctBounds.top = rctSource.top;
835  rctBounds.right = /*rctBounds.left +*/ iMaxLen;
836  rctBounds.bottom = rctBounds.top + (iLines * iFontHeight);
837 
838 // WB_ERROR_PRINT("TEMPORARY: %s - rctSource %d,%d,%d,%d rctBounds: %d,%d,%d,%d\n", __FUNCTION__,
839 // rctSource.left, rctSource.top, rctSource.right, rctSource.bottom,
840 // rctBounds.left, rctBounds.top, rctBounds.right, rctBounds.bottom);
841 
842  if(iLines > 1)
843  {
844  rctBounds.bottom += (iLines - 1) * iLineSpacing; // inter-line spacing
845  }
846 
847 #ifndef NO_DEBUG
848 
849  if(rctBounds.top < rctSource.top || rctBounds.bottom > rctSource.bottom)
850  {
851  WB_ERROR_PRINT("WARNING: %s - source rectangle too small, alignment flags may not work\n", __FUNCTION__);
852  }
853 
854 #endif // NO_DEBUG
855 
856  if(rctBounds.right <= rctSource.right &&
857  rctBounds.bottom <= rctSource.bottom)
858  {
859 it_fits:
860 
861 // // TEMPORARY
862 // WB_ERROR_PRINT("%s - TEMPORARY: 'it fits' bounds rectangle is %d,%d,%d,%d\n",
863 // __FUNCTION__, rctBounds.left, rctBounds.top, rctBounds.right, rctBounds.bottom);
864 
865  // it looks like it can fit properly without any additional effort.
866  // based on the alignment flags, I will return now with the results.
867 
868  // TODO: check if any alignment flags might prevent me from returning now...
869 
870  if(prcDest) // caller wants the actual rectangle
871  {
872 // WB_ERROR_PRINT("%s - TEMPORARY: 'it fits' bounds rectangle is %d,%d,%d,%d iAlignment=%d (%08xH)\n",
873 // __FUNCTION__, rctBounds.left, rctBounds.top, rctBounds.right, rctBounds.bottom, iAlignment, iAlignment);
874 
875  if(iAlignment & DTAlignment_NO_SHRINK)
876  {
877  rctBounds.right = rctSource.right;
878  rctBounds.bottom = rctSource.bottom;
879  }
880  else
881  {
882  switch(iAlignment & DTAlignment_HMASK)
883  {
884  case DTAlignment_HJUSTIFY: // treat like 'center' for now
885  case DTAlignment_HCENTER:
886 // WB_ERROR_PRINT("TEMPORARY: %s - here I am, %d, %d, %d, %d\n", __FUNCTION__,
887 // rctSource.left, rctSource.right, rctBounds.left, rctBounds.right);
888 
889  i1 = rctSource.right - rctBounds.right;
890  i1 -= (i1 >> 1); // so that it's balanced properly, do it THIS way
891  rctBounds.left += i1;
892  rctBounds.right += i1;
893 
894  // For a multi-line string, I need to make sure each line centers
895  // correctly. If the lines break, I allow them to break and wrap
896  // but each line that is COMPLETELY within the centered rectangle
897  // without wrapping MUST BE RE-CENTERED!!!
898 
899  DoHCenterWrappedLines(pWords, rctBounds.right - rctBounds.left);
900 
901  break;
902 
903  case DTAlignment_HLEFT:
904  break; // no changes
905  case DTAlignment_HRIGHT:
906  rctBounds.left += rctSource.right - rctBounds.right;
907  rctBounds.right = rctSource.right;
908  break;
909  }
910  }
911 
912  switch(iAlignment & DTAlignment_VMASK)
913  {
914  case DTAlignment_VTOP:
915  break;
916  case DTAlignment_VBOTTOM:
917  rctBounds.top += rctSource.bottom - rctBounds.bottom;
918  rctBounds.bottom = rctSource.bottom;
919  break;
920  case DTAlignment_VCENTER:
921  i1 = ((rctSource.bottom - rctBounds.bottom + 1) >> 1) - 1; // favors being slightly closer to top
922  rctBounds.top += i1;
923  rctBounds.bottom += i1;
924  break;
925 
926  case DTAlignment_VCENTERASCENT: // only works properly with single line
927  i2 = iFontDescent >> 1; // 1/2 ascent is top + a / 2; 1/2 font is top + (a + d) / 2; diff is d / 2
928  i1 = ((rctSource.bottom - rctBounds.bottom + 1) >> 1) - 1; // favors being slightly closer to top
929  i1 -= i2;
930  if(i1 > 0) // subtracting i2 from i1 didn't make it negative
931  {
932  rctBounds.top += i1;
933  rctBounds.bottom += i1;
934  }
935  break;
936 
938  i2 = (iFontAscent - iFontDescent) >> 1; // ascent is top + a; 1/2 font is top + (a + d) / 2; diff is (a - d) / 2
939  i1 = ((rctSource.bottom - rctBounds.bottom + 1) >> 1) - 1; // favors being slightly closer to top
940  i1 += i2;
941  if(i1 < rctSource.bottom) // adding i2 won't exceed 'bottom'
942  {
943  rctBounds.top += i1;
944  rctBounds.bottom += i1;
945  }
946  else // adding i2 exceeds bottom, so just use bottom
947  {
948  rctBounds.top += rctSource.bottom - rctBounds.bottom;
949  rctBounds.bottom = rctSource.bottom;
950  }
951  break;
952 
955  break; // reserved
956  }
957 
958 // WB_ERROR_PRINT("%s - TEMPORARY: 'aligned' bounds rectangle is %d,%d,%d,%d\n",
959 // __FUNCTION__, rctBounds.left, rctBounds.top, rctBounds.right, rctBounds.bottom);
960 
961  memcpy(prcDest, &rctBounds, sizeof(*prcDest));
962  }
963 
964  return 0;
965  }
966 
967  // the simplest solutions are usually the best. If the text extends past the horizontal
968  // border but does not extend past the vertical border, attempt to wrap it to make it fit.
969  // This is probably the most common solution.
970 
971  if(rctSource.right > 0 && // if prcSource is NULL, rctSource.right will be zero
972  rctBounds.right > rctSource.right &&
973  rctBounds.bottom <= rctSource.bottom &&
974  !(iAlignment & DTAlignment_NO_WORD_WRAP)) // disable this if I'm bit allowing word wrap
975  {
976  iMaxLen = 0;
977  iHPos0 = iHPos = rctSource.left;
978 
979  // TEMPORARY
980 // WB_ERROR_PRINT("%s - TEMPORARY: 'needs text wrap' bounds rectangle is %d,%d,%d,%d\n",
981 // __FUNCTION__, rctBounds.left, rctBounds.top, rctBounds.right, rctBounds.bottom);
982 
983 
984 
985  for(i1=0; i1 < pWords->nCount; i1++)
986  {
987  pW = &(pWords->aWords[i1]);
988 
989  if(pW->iIsWhiteSpace)
990  {
991  pW->iY = pW->iX = -1; // NA
992 
993  // before I start adding white space, see if there is
994  // any NON-WHITE SPACE following this entry...
995 
996  if(iAlignment & DTAlignment_ALLOW_TRAILING)
997  {
998  iHPos += pW->iWidth; // it's ok to use the width of this white space
999  }
1000  else
1001  {
1002  for(i2=i1 + 1; i2 < pWords->nCount; i2++)
1003  {
1004  pW2 = &(pWords->aWords[i2]);
1005 
1006  if(!pW2->iIsWhiteSpace && !pW2->iIsTab && !pW2->iIsLineFeed)
1007  {
1008  iHPos += pW->iWidth; // it's ok to use the width of this white space
1009  break;
1010  }
1011  else if(pW2->iIsLineFeed)
1012  {
1013  break; // end of line (trailing white space will be ignored)
1014  }
1015  }
1016  }
1017  }
1018  else if(pW->iIsTab)
1019  {
1020  pW->iY = pW->iX = -1; // NA
1021 
1022  // before I start adding white space for tabs, see if there is
1023  // any NON-WHITE SPACE following this entry...
1024 
1025  if(iAlignment & DTAlignment_ALLOW_TRAILING) // all bets are off when line-wrap happens
1026  {
1027 do_the_tab2:
1028  if(iAlignment & (DTAlignment_HCENTER | DTAlignment_HJUSTIFY)) // no tabbing for center/justify
1029  {
1030  iHPos += pW->nLength * iFontWidth; // treat it as regular white space
1031  }
1032  else
1033  {
1034  i3 = (iHPos - iHPos0 + iTabOrigin) % iTabWidth;
1035 
1036  if(!i3)
1037  {
1038  iHPos += iTabWidth; // tab out the entire distance
1039  }
1040  else
1041  {
1042  iHPos += i3; // tab out "the remainder"
1043  }
1044 
1045  if(pW->nLength > 1) // check for multiple tabs
1046  {
1047  iHPos += iTabWidth * (pW->nLength - 1); // additional tabs
1048  }
1049  }
1050 
1051  if(iHPos > rctSource.right) // tab goes past the end
1052  {
1053  // treat it like a line feed, but 'trim off' any additional white space first
1054 
1055  while((i1 + 1) < pWords->nCount)
1056  {
1057  pW2 = &(pWords->aWords[i1]);
1058  pW2->iX = pW2->iY = -1; // because I'm probably skipping them
1059 
1060  if(!pW2->iIsWhiteSpace && !pW2->iIsTab)
1061  {
1062  break; // if next item isn't white space, bust out of the loop
1063  }
1064  else if(pW2->iIsLineFeed)
1065  {
1066  i1++; // absorb THIS line feed since I'm doing one now
1067  break;
1068  }
1069 
1070  i1++; // keep skipping white space
1071  }
1072 
1073  // Do a line feed first (nearly the same as 'line feed' code, below)
1074 //do_a_line_feed2:
1075  if(iMaxLen < iHPos - iHPos0)
1076  {
1077  iMaxLen = iHPos - iHPos0;
1078  }
1079 
1080  if(iHPos > iHPos0 && // there is actual text present on this line
1081  (i1 + 1) == pWords->nCount) // the last entry? The text ends in one or more line feeds
1082  {
1083  // TODO: see if it's just a bunch of blank lines following the text
1084  // and consider ignoring them ALL if it makes sense to do so
1085 
1086  // at the end, the final line feed doesn't "count" unless it's by itself
1087  }
1088  else
1089  {
1090  iLines++; // only ONE line feed here...
1091  }
1092 
1093  iHPos = iHPos0;
1094  }
1095  }
1096  else
1097  {
1098  for(i2=i1 + 1; i2 < pWords->nCount; i2++)
1099  {
1100  pW2 = &(pWords->aWords[i2]);
1101  pW2->iX = pW2->iY = -1; // because I'm going to skip them
1102 
1103  if(!pW2->iIsWhiteSpace && !pW2->iIsTab && !pW2->iIsLineFeed)
1104  {
1105  goto do_the_tab2; // cleaner code with label
1106  }
1107  else if(pW2->iIsLineFeed)
1108  {
1109  i1 = i2 - 1; // skip all of the white space
1110  break; // this will cause the line feed to become "the next thing" and I'll end the line
1111  }
1112  }
1113 
1114  // if I get here, only white space remains at the end of the current 'line' (so ignore it)
1115  break;
1116  }
1117  }
1118  else if(pW->iIsLineFeed)
1119  {
1120  pW->iY = pW->iX = -1; // NA
1121 
1122  if(iMaxLen < iHPos - iHPos0)
1123  {
1124  iMaxLen = iHPos - iHPos0;
1125  }
1126 
1127  if(iHPos > iHPos0 && // there is actual text present on this line
1128  (i1 + 1) == pWords->nCount) // the last entry? The text ends in one or more line feeds
1129  {
1130  // TODO: see if it's just a bunch of blank lines following the text
1131  // and consider ignoring them ALL if it makes sense to do so
1132 
1133  iLines += pW->nLength - 1; // at the end, the final line feed doesn't "count" unless it's by itself
1134  }
1135  else
1136  {
1137  iLines += pW->nLength; // add ALL of the line feeds to the line count (more to come)
1138  }
1139 
1140  iHPos = iHPos0;
1141  }
1142  else
1143  {
1144  if(!iLines) // this handles blank text, which will show up with "zero lines"
1145  {
1146  iLines++; // indicate at least one line of printable stuff
1147 
1148  if(iHPos + pW->iWidth > rctSource.right) // word is "too wide"
1149  {
1150  goto will_not_fit; // generic bailout point, doesn't attempt to gerrymander the boundary rectangle
1151  }
1152  }
1153 
1154  pW->iX = iHPos - iHPos0; // relative X position
1155  pW->iY = (iLines - 1) * iFontHeight + iFontAscent; // relative Y position for 'base of text'
1156 
1157  if(iHPos + pW->iWidth > rctSource.right) // word is "too wide"
1158  {
1159  // Do a line feed first (nearly the same as 'line feed' code, above)
1160 
1161  if(iMaxLen < iHPos - iHPos0)
1162  {
1163  iMaxLen = iHPos - iHPos0;
1164  }
1165 
1166  if(iHPos > iHPos0 && // there is actual text present on this line
1167  (i1 + 1) == pWords->nCount) // the last entry? The text ends in one or more line feeds
1168  {
1169  // TODO: see if it's just a bunch of blank lines following the text
1170  // and consider ignoring them ALL if it makes sense to do so
1171 
1172  // at the end, the final line feed doesn't "count" unless it's by itself
1173  }
1174  else
1175  {
1176  iLines++; // only ONE line feed here...
1177  }
1178 
1179  iHPos = iHPos0;
1180  i1--; // "repeat this word"
1181  continue;
1182  }
1183 
1184  iHPos += pW->iWidth;
1185  }
1186  }
1187 
1188  if(iHPos > iMaxLen)
1189  {
1190  iMaxLen = iHPos;
1191  }
1192  }
1193 
1194  // re-calc boundary, see if it fits NOW
1195 
1196  rctBounds.left = rctSource.left;
1197  rctBounds.top = rctSource.top;
1198  rctBounds.right = rctBounds.left + iMaxLen;
1199  rctBounds.bottom = rctBounds.top + (iLines * iFontHeight);
1200 
1201  if(iLines > 1)
1202  {
1203  rctBounds.bottom += (iLines - 1) * iLineSpacing; // inter-line spacing
1204  }
1205 
1206  if(rctBounds.right <= rctSource.right &&
1207  rctBounds.bottom <= rctSource.bottom)
1208  {
1209  goto it_fits;
1210  }
1211 
1212 will_not_fit:
1213 
1214 // // TEMPORARY
1215 // WB_ERROR_PRINT("%s - TEMPORARY: 'it does not fit' bounds rectangle is %d,%d,%d,%d\n",
1216 // __FUNCTION__, rctBounds.left, rctBounds.top, rctBounds.right, rctBounds.bottom);
1217 
1218 
1219  if(!(iAlignment & DTAlignment_PRINTING))
1220  {
1221  // At this point we know it's not going to fit in the specified rectangle. It will be
1222  // necessary to ESTIMATE the "ideal" rectangle for the text.
1223 
1224  // re-sizing the rectangle, ideal dimensions vs actual dimensions
1225 
1226  // since wrapped text looks SO much better when it maintains a reasonable height/width ratio,
1227  // try squeezing the text around to make this happen. The most reasonable first trial will
1228  // be to wrap the text at 3/4 iMaxLen, then 2/3 iMaxLen, then 1/2 iMaxLen, etc. until I end
1229  // up with a decent height/width ratio. To facilitate this I should throw out anything that
1230  // has a font pixel ratio of more than 16:1 (this would be appx 20 characters x 1 line) or
1231  // less than 4 : 1 (this would be appx 5 characters x 1 line). If I can't achieve either of
1232  // these goals, leave 'prcDest' as it is right now.
1233 
1234  // if rctSource is an empty rectangle, I'll need to create a set of dimensions that makes
1235  // good visual sense, based on the contents of the string.
1236 
1237  // First step: 'ideal' dimensions
1238 
1239 
1240  // Since the above didn't work, try half-split convergence
1241 
1242  // TODO: implement these things
1243  }
1244 
1245  if(prcDest)
1246  {
1247  memcpy(prcDest, &rctBounds, sizeof(*prcDest));
1248  }
1249 
1250  return 1; // for now (until I complete writing this)
1251 }
1252 
1253 
1254 
1255 void DTDrawSingleLineText0(XFontStruct *pFont, const char *szText, Display *pDisplay, GC gc, Drawable dw,
1256  int iTabWidth, int iTabOrigin, const WB_RECT *prcBounds, int iAlignment)
1257 {
1258 #if 0
1259 XGCValues xgc;
1260 int iAvgChar
1261 
1262  iAvgChar = WBFontAvgCharWidth(pDisplay, pFont); // was DTGetTextWidth(pFont, " ", 1); // width of 1 space for text border (TODO: RTL text)
1263 
1264  // height pFont->max_bounds.ascent + pFont->max_bounds.descent + 2;
1265 
1266 
1267  iU1 = DTGetTextWidth(pFont, tbuf, p1 - tbuf);
1268 
1269  WB_DRAW_STRING(pDisplay, wID, fSet, gc, iHPos, iVPos, szText, strlen(szText));
1270 
1271  xgc.font = pOldFont->fid;
1273  XChangeGC(pDisplay, gc, GCFont, &xgc);
1275 #endif // 0
1276  // TODO: implement
1277 
1278  DTDrawMultiLineText0(pFont, szText, pDisplay, gc, dw, iTabWidth, iTabOrigin, prcBounds,
1280 }
1281 
1282 void DTDrawSingleLineText(XFontSet fontSet, const char *szText, Display *pDisplay, GC gc, Drawable dw,
1283  int iTabWidth, int iTabOrigin, const WB_RECT *prcBounds, int iAlignment)
1284 {
1285 #if 0
1286 XGCValues xgc;
1287 int iAvgChar
1288 
1289  iAvgChar = WBFontAvgCharWidth(pDisplay, pFont); // was DTGetTextWidth(pFont, " ", 1); // width of 1 space for text border (TODO: RTL text)
1290 
1291  // height pFont->max_bounds.ascent + pFont->max_bounds.descent + 2;
1292 
1293 
1294  iU1 = DTGetTextWidth(pFont, tbuf, p1 - tbuf);
1295 
1296  WB_DRAW_STRING(pDisplay, wID, fSet, gc, iHPos, iVPos, szText, strlen(szText));
1297 
1298  xgc.font = pOldFont->fid;
1300  XChangeGC(pDisplay, gc, GCFont, &xgc);
1302 #endif // 0
1303  // TODO: implement
1304 
1305  DTDrawMultiLineText(fontSet, szText, pDisplay, gc, dw, iTabWidth, iTabOrigin, prcBounds,
1307 }
1308 
1309 void DTDrawMultiLineText0(XFontStruct *pFont, const char *szText, Display *pDisplay, GC gc, Drawable dw,
1310  int iTabWidth, int iTabOrigin, const WB_RECT *prcBounds, int iAlignment)
1311 {
1312 XFontSet fSet;
1313 
1314  if(!pFont)
1315  {
1316  pFont = WBGetDefaultFont();
1317  if(!pFont)
1318  {
1319  WB_ERROR_PRINT("%s - ERROR: WBGetDefaultFont returns NULL\n",
1320  __FUNCTION__);
1321 
1322  return; // bad
1323  }
1324  }
1325 
1326  fSet = WBFontSetFromFont(pDisplay, pFont);
1327 
1328  if(fSet == None)
1329  {
1330  WB_ERROR_PRINT("Unable to get font set!\n");
1331 
1332  return;
1333  }
1334 
1335  DTDrawMultiLineText(fSet, szText, pDisplay, gc, dw, iTabWidth, iTabOrigin, prcBounds, iAlignment);
1336 }
1337 
1338 void DTDrawMultiLineText(XFontSet fSet, const char *szText, Display *pDisplay, GC gc, Drawable dw,
1339  int iTabWidth, int iTabOrigin, const WB_RECT *prcBounds, int iAlignment)
1340 {
1341 int i1, i2, i3, iH, iH2, iFontDescent; //, iW2, iFontWidth, iFontHeight;
1342 DT_WORDS *pWords;
1343 DT_WORD *pW;
1344 WB_RECT rcDest;
1345 XPoint xpt[3];
1346 XCharStruct xMaxBounds;
1347 
1348 
1349  if(fSet == None)
1350  {
1351  fSet = WBGetDefaultFontSet(pDisplay);
1352  if(fSet == None)
1353  {
1354  WB_ERROR_PRINT("%s - ERROR: WBGetDefaultFontSet returns None\n",
1355  __FUNCTION__);
1356 
1357  return; // bad
1358  }
1359  }
1360 
1361  if(iAlignment & DTAlignment_SINGLELINE)
1362  {
1363  iAlignment |= DTAlignment_NO_WORD_WRAP; // make sure
1364  }
1365 
1366 
1367  // get a few things straight 'round here
1368 
1369 // NOTE: iFontWidth and iFontHeight not being used; commented out because of linux gcc warnings
1370 // iFontWidth = WBFontSetAvgCharWidth(pDisplay, pFont); // was DTGetTextWidth(pFont, " ", 1); // width of a single space
1371 // iFontHeight = pFont->ascent + pFont->descent;
1372 
1373  pWords = DTGetWordsFromText(pDisplay, fSet, szText, iAlignment);
1374 
1375  if(!pWords)
1376  {
1377  WB_ERROR_PRINT("%s - ERROR: DTGetWordsFromText returns NULL\n",
1378  __FUNCTION__);
1379 
1380  return; // error
1381  }
1382 
1383  memcpy(&rcDest, prcBounds, sizeof(rcDest));
1385 // WB_ERROR_PRINT("%s - TEMPORARY: bounds rectangle is INITIALLY %d,%d,%d,%d\n",
1386 // __FUNCTION__, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom);
1387 
1388  if(InternalCalcIdealBounds(pDisplay, fSet, pWords, iTabWidth, iTabOrigin, prcBounds, &rcDest,
1389  iAlignment | DTAlignment_PRINTING, 0, -1)
1390  < 0)
1391  {
1392  InternalDebugDumpWords(pWords);
1393 
1394  WBFree(pWords);
1395 
1396  WB_ERROR_PRINT("%s - ERROR: InternalCalcIdealBounds returns error\n",
1397  __FUNCTION__);
1398 
1399  return; // bad (error)
1400  }
1401 
1402  iFontDescent = WBFontSetDescent(pDisplay, fSet);
1403  xMaxBounds = WBFontSetMaxBounds(pDisplay, fSet); // font's 'max_bounds' structure member, maximized for all of them
1404 
1405 // InternalDebugDumpWords(pWords);
1406 
1408 // WB_ERROR_PRINT("%s - TEMPORARY: bounds rectangle is NOW %d,%d,%d,%d\n",
1409 // __FUNCTION__, rcDest.left, rcDest.top, rcDest.right, rcDest.bottom);
1410 
1411  // this function assumes I do not erase the background. TODO: flag for that?
1412 
1413  for(i1=0; i1 < pWords->nCount; i1++)
1414  {
1415  pW = &(pWords->aWords[i1]);
1416 
1417  if(pW->iX >= 0 && pW->iY >= 0 && pW->iWidth > 0)
1418  {
1419  if(!(iAlignment & DTAlignment_UNDERSCORE) ||
1420  !memchr(pW->pText, '_', pW->nLength))
1421  {
1422  // normal string drawing - no underscores to deal with
1423 
1425 // WB_ERROR_PRINT("%s - TEMPORARY (a), WB_DRAW_STRING at %d,%d %d chars of %s\n",
1426 // __FUNCTION__, rcDest.left + pW->iX, rcDest.top + pW->iY, pW->nLength, pW->pText);
1427 
1428  // draw the entire string. 'pW->iY' is the BASE of the font
1429  WB_DRAW_STRING(pDisplay, dw, fSet, gc, rcDest.left + pW->iX, rcDest.top + pW->iY,
1430  pW->pText, pW->nLength);
1431  }
1432  else
1433  {
1434  iH = rcDest.left + pW->iX;
1435  iH2 = -1; // horizontal position of NEXT underscore to display (corresponds to character index 'i3')
1436 
1437  for(i2=0, i3=0; i2 < pW->nLength; )
1438  {
1439  if(pW->pText[i2] == '_')
1440  {
1441  if((i2 + 1) >= pW->nLength)
1442  {
1443  break; // this will print the '_' normally
1444  }
1445  else if(i3 && i3 == i2) // a double-underscore
1446  {
1447  i2++;
1448  continue; // i'm going to print this as-is
1449  }
1450 
1452 // WB_ERROR_PRINT("%s - TEMPORARY (b), WB_DRAW_STRING at %d,%d %d chars of %s\n",
1453 // __FUNCTION__, iH, rcDest.top + pW->iY, pW->nLength - i3, (char *)(pW->pText) + i3);
1454 
1455  // print i3 through i2 - 1. 'pW->iY' is the BASE of the font
1456  WB_DRAW_STRING(pDisplay, dw, fSet, gc, iH, rcDest.top + pW->iY,
1457  (char *)(pW->pText) + i3, i2 - i3);
1458 
1459  // draw the preceding underscore
1460  if(iH2 >= 0)
1461  {
1462  xpt[0].x=iH2;
1463  xpt[0].y=rcDest.top + pW->iY + iFontDescent + 1;// + pFont->max_bounds.ascent;
1464  xpt[1].x=iH + DTGetTextWidth(fSet, (char *)(pW->pText) + i3, 1); // width of character
1465  xpt[1].y=xpt[0].y;
1466 
1467  XDrawLines(pDisplay, dw, gc, xpt, 2, CoordModeOrigin);
1468  }
1469 
1470  // advance the pointer
1471  iH += DTGetTextWidth(fSet, (char *)(pW->pText) + i3, i2 - i3);
1472 
1473  iH2 = iH; // next underscore's position
1474 
1475  i2++;
1476  i3 = i2; // the next text character
1477  }
1478  else
1479  {
1480  i2++;
1481  }
1482  }
1483 
1484  if(i3 < pW->nLength)
1485  {
1486  // draw the last part of the string. 'pW->iY' is the BASE of the font
1487  WB_DRAW_STRING(pDisplay, dw, fSet, gc, iH, rcDest.top + pW->iY,
1488  (char *)(pW->pText) + i3, pW->nLength - i3);
1489 
1490  // draw the preceding underscore
1491  if(iH >= 0)
1492  {
1493  xpt[0].x=iH;
1494  xpt[0].y=rcDest.top + pW->iY + xMaxBounds.descent + 1;// + pFont->max_bounds.ascent;
1495  xpt[1].x=iH + DTGetTextWidth(fSet, (char *)(pW->pText) + i3, 1); // width of character
1496  xpt[1].y=xpt[0].y;
1497 
1498  XDrawLines(pDisplay, dw, gc, xpt, 2, CoordModeOrigin);
1499  }
1500  }
1501  }
1502  }
1503  }
1504 
1505  WBFree(pWords);
1506 }
1507 
1508 
1509 // TODO: consider whether either of these is really necessary. each line could stand on its own,
1510 // and it would be more efficient. where it MIGHT help is a simple 'edit window' with limited
1511 // text length, which could be stored as a 'DT_WORDS'. Probably NOT practical.
1512 // (so perhaps these should be deprecated?)
1513 
1514 
1515 void DTPreRender(Display *pDisplay, XFontSet fontSet, DT_WORDS *pWords, int iTabWidth, int iTabOrigin,
1516  WB_RECT *prcBounds, int iAlignment, int iStartLine, int iEndLine)
1517 {
1518 WB_RECT rcDest;
1519 
1520 
1521  if(!pDisplay)
1522  {
1523  pDisplay = WBGetDefaultDisplay();
1524  }
1525 
1526  if(fontSet == None)
1527  {
1528  fontSet = WBGetDefaultFontSet(pDisplay);
1529  if(fontSet)
1530  {
1531  WB_ERROR_PRINT("%s - ERROR: WBGetDefaultFont returns NULL\n",
1532  __FUNCTION__);
1533 
1534  return; // bad
1535  }
1536  }
1537 
1538  if(iAlignment & DTAlignment_SINGLELINE)
1539  {
1540  iAlignment |= DTAlignment_NO_WORD_WRAP; // make sure
1541  }
1542 
1543  memcpy(&rcDest, prcBounds, sizeof(rcDest));
1544 
1545  if(InternalCalcIdealBounds(pDisplay, fontSet, pWords, iTabWidth, iTabOrigin, prcBounds, &rcDest,
1546  iAlignment | DTAlignment_PRINTING, iStartLine, iEndLine)
1547  < 0)
1548  {
1549  InternalDebugDumpWords(pWords);
1550 
1551  WB_ERROR_PRINT("%s - ERROR: InternalCalcIdealBounds returns error\n",
1552  __FUNCTION__);
1553 
1554  return; // bad (error)
1555  }
1556 
1557 
1558  // TODO: implement
1559 }
1560 
1561 void DTRender(Display *pDisplay, XFontSet fontSet, const DT_WORDS *pWords, GC gc, Drawable dw,
1562  int iHScrollBy, int iVScrollBy, const WB_RECT *prcBounds, const WB_RECT *prcViewport, int iAlignment)
1563 {
1564  // TODO: implement
1565 }
1566 
1567 
1568 
1569 
1570 
1571 
1572 static void InternalDebugDumpWords(DT_WORDS *pWords)
1573 {
1574  char tbuf[1024]; // for now max size of 'word'
1575  int i1, i2;
1576 
1577  WBDebugPrint("===================================================\n"
1578  "%s - DUMPING pWords array\n ", __FUNCTION__);
1579 
1580  for(i1=0; i1 < pWords->nCount; i1++)
1581  {
1582  DT_WORD *pW = &(pWords->aWords[i1]);
1583 
1584  if(pW->iIsLineFeed)
1585  {
1586  for(i2=0; i2 < pW->nLength; i2++)
1587  {
1588  WBDebugPrint("<LF>\n ");
1589  }
1590  }
1591  else if(pW->iIsTab)
1592  {
1593  for(i2=0; i2 < pW->nLength; i2++)
1594  {
1595  WBDebugPrint("<TAB>");
1596  }
1597  }
1598  else if(pW->iIsWhiteSpace)
1599  {
1600  for(i2=0; i2 < pW->nLength; i2++)
1601  {
1602  WBDebugPrint("<W>");
1603  }
1604  }
1605  else
1606  {
1607  i2 = pW->nLength > sizeof(tbuf) - 1 ? sizeof(tbuf) - 1: pW->nLength;
1608  memcpy(tbuf, pW->pText, i2);
1609  tbuf[i2] = 0;
1610  WBDebugPrint("%s", tbuf);
1611  }
1612  }
1613 
1614  WBDebugPrint("\n\n");
1615 
1616  // now do it again and indicate the length/width/height for each
1617 
1618  for(i1=0; i1 < pWords->nCount; i1++)
1619  {
1620  DT_WORD *pW = &(pWords->aWords[i1]);
1621 
1622  if(pW->iIsLineFeed)
1623  {
1624  WBDebugPrint(" LF: length=%d,width=%d,height=%d X=%d,Y=%d\n",
1625  pW->nLength, pW->iWidth, pW->iHeight, pW->iX, pW->iY);
1626  }
1627  else if(pW->iIsTab)
1628  {
1629  WBDebugPrint(" TAB: length=%d,width=%d,height=%d X=%d,Y=%d\n",
1630  pW->nLength, pW->iWidth, pW->iHeight, pW->iX, pW->iY);
1631  }
1632  else if(pW->iIsWhiteSpace)
1633  {
1634  WBDebugPrint(" WS: length=%d,width=%d,height=%d X=%d,Y=%d\n",
1635  pW->nLength, pW->iWidth, pW->iHeight, pW->iX, pW->iY);
1636  }
1637  else
1638  {
1639  i2 = pW->nLength > sizeof(tbuf) - 1 ? sizeof(tbuf) - 1: pW->nLength;
1640  memcpy(tbuf, pW->pText, i2);
1641  tbuf[i2] = 0;
1642  WBDebugPrint(" STR: length=%d,width=%d,height=%d X=%d,Y=%d \"%s\"\n",
1643  pW->nLength, pW->iWidth, pW->iHeight, pW->iX, pW->iY, tbuf);
1644  }
1645  }
1646 
1647  WBDebugPrint("\n===================================================\n");
1648 }
1649 
1650