Release 950901
[wine] / objects / text.c
1 /*
2  * text functions
3  *
4  * Copyright 1993, 1994 Alexandre Julliard
5  *
6 static char Copyright[] = "Copyright  Alexandre Julliard, 1993, 1994";
7 */
8 #include <stdlib.h>
9 #include <X11/Xatom.h>
10 #include "windows.h"
11 #include "dc.h"
12 #include "gdi.h"
13 #include "callback.h"
14 #include "metafile.h"
15 #include "stddebug.h"
16 /* #define DEBUG_TEXT */
17 #include "debug.h"
18
19 #define TAB     9
20 #define LF     10
21 #define CR     13
22 #define SPACE  32
23 #define PREFIX 38
24
25 #define SWAP_INT(a,b)  { int t = a; a = b; b = t; }
26
27 static int tabstop = 8;
28 static int tabwidth;
29 static int spacewidth;
30 static int prefix_offset;
31
32
33 static char *TEXT_NextLine(HDC hdc, char *str, int *count, char *dest, 
34                            int *len, int width, WORD format)
35 {
36     /* Return next line of text from a string.
37      * 
38      * hdc - handle to DC.
39      * str - string to parse into lines.
40      * count - length of str.
41      * dest - destination in which to return line.
42      * len - length of resultant line in dest in chars.
43      * width - maximum width of line in pixels.
44      * format - format type passed to DrawText.
45      *
46      * Returns pointer to next char in str after end of the line
47      * or NULL if end of str reached.
48      */
49
50     int i = 0, j = 0, k;
51     int plen = 0;
52     int numspaces;
53     SIZE size;
54     int lasttab = 0;
55     int wb_i = 0, wb_j = 0, wb_count = 0;
56
57     while (*count)
58     {
59         switch (str[i])
60         {
61         case CR:
62         case LF:
63             if (!(format & DT_SINGLELINE))
64             {
65                 if (str[i] == CR && str[i+1] == LF)
66                     i++;
67                 i++;
68                 *len = j;
69                 (*count)--;
70                 return (&str[i]);
71             }
72             dest[j++] = str[i++];
73             if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) ||
74                 (format & DT_WORDBREAK))
75             {
76                 if (!GetTextExtentPoint(hdc, &dest[j-1], 1, &size))
77                     return NULL;
78                 plen += size.cx;
79             }
80             break;
81             
82         case PREFIX:
83             if (!(format & DT_NOPREFIX))
84             {
85                 prefix_offset = j;
86                 i++;
87             }
88             else
89             {
90                 dest[j++] = str[i++];
91                 if (!(format & DT_NOCLIP) || (format & DT_WORDBREAK))
92                 {
93                     if (!GetTextExtentPoint(hdc, &dest[j-1], 1, &size))
94                         return NULL;
95                     plen += size.cx;
96                 }
97             }
98             break;
99             
100         case TAB:
101             if (format & DT_EXPANDTABS)
102             {
103                 wb_i = ++i;
104                 wb_j = j;
105                 wb_count = *count;
106
107                 if (!GetTextExtentPoint(hdc, &dest[lasttab], j - lasttab,
108                                                                  &size))
109                     return NULL;
110
111                 numspaces = (tabwidth - size.cx) / spacewidth;
112                 for (k = 0; k < numspaces; k++)
113                     dest[j++] = SPACE;
114                 plen += tabwidth - size.cx;
115                 lasttab = wb_j + numspaces;
116             }
117             else
118             {
119                 dest[j++] = str[i++];
120                 if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) ||
121                     (format & DT_WORDBREAK))
122                 {
123                     if (!GetTextExtentPoint(hdc, &dest[j-1], 1, &size))
124                         return NULL;
125                     plen += size.cx;
126                 }
127             }
128             break;
129
130         case SPACE:
131             dest[j++] = str[i++];
132             if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) ||
133                 (format & DT_WORDBREAK))
134             {
135                 wb_i = i;
136                 wb_j = j - 1;
137                 wb_count = *count;
138                 if (!GetTextExtentPoint(hdc, &dest[j-1], 1, &size))
139                     return NULL;
140                 plen += size.cx;
141             }
142             break;
143
144         default:
145             dest[j++] = str[i++];
146             if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) ||
147                 (format & DT_WORDBREAK))
148             {
149                 if (!GetTextExtentPoint(hdc, &dest[j-1], 1, &size))
150                     return NULL;
151                 plen += size.cx;
152             }
153         }
154
155         (*count)--;
156         if (!(format & DT_NOCLIP) || (format & DT_WORDBREAK))
157         {
158             if (plen > width)
159             {
160                 if (format & DT_WORDBREAK)
161                 {
162                     if (wb_j)
163                     {
164                         *len = wb_j;
165                         *count = wb_count - 1;
166                         return (&str[wb_i]);
167                     }
168                 }
169                 else
170                 {
171                     *len = j;
172                     return (&str[i]);
173                 }
174             }
175         }
176     }
177     
178     *len = j;
179     return NULL;
180 }
181
182
183 /***********************************************************************
184  *           DrawText    (USER.85)
185  */
186 int DrawText( HDC hdc, LPSTR str, int count, LPRECT rect, WORD flags )
187 {
188     SIZE size;
189     char *strPtr;
190     static char line[1024];
191     int len, lh;
192     int prefix_x = 0;
193     int prefix_end = 0;
194     TEXTMETRIC tm;
195     int x = rect->left, y = rect->top;
196     int width = rect->right - rect->left;
197     int max_width = 0;
198
199     dprintf_text(stddeb,"DrawText: '%s', %d , [(%d,%d),(%d,%d)]\n", str, count,
200            rect->left, rect->top, rect->right, rect->bottom);
201     
202     if (count == -1) count = strlen(str);
203     strPtr = str;
204
205     GetTextMetrics(hdc, &tm);
206     if (flags & DT_EXTERNALLEADING)
207         lh = tm.tmHeight + tm.tmExternalLeading;
208     else
209         lh = tm.tmHeight;
210
211     if (flags & DT_TABSTOP)
212         tabstop = flags >> 8;
213
214     if (flags & DT_EXPANDTABS)
215     {
216         GetTextExtentPoint(hdc, " ", 1, &size);
217         spacewidth = size.cx;
218         GetTextExtentPoint(hdc, "o", 1, &size);
219         tabwidth = size.cx * tabstop;
220     }
221
222     do
223     {
224         prefix_offset = -1;
225         strPtr = TEXT_NextLine(hdc, strPtr, &count, line, &len, width, flags);
226
227         if (prefix_offset != -1)
228         {
229             GetTextExtentPoint(hdc, line, prefix_offset, &size);
230             prefix_x = size.cx;
231             GetTextExtentPoint(hdc, line, prefix_offset + 1, &size);
232             prefix_end = size.cx - 1;
233         }
234
235         if (!GetTextExtentPoint(hdc, line, len, &size)) return 0;
236         if (flags & DT_CENTER) x = (rect->left + rect->right -
237                                     size.cx) / 2;
238         else if (flags & DT_RIGHT) x = rect->right - size.cx;
239
240         if (flags & DT_SINGLELINE)
241         {
242             if (flags & DT_VCENTER) y = rect->top + 
243                 (rect->bottom - rect->top) / 2 - size.cy / 2;
244             else if (flags & DT_BOTTOM) y = rect->bottom - size.cy;
245         }
246         if (!(flags & DT_CALCRECT))
247         {
248             if (!ExtTextOut( hdc, x, y, (flags & DT_NOCLIP) ? 0 : ETO_CLIPPED,
249                              rect, line, len, NULL )) return 0;
250         }
251         else if (size.cx > max_width)
252             max_width = size.cx;
253
254         if (prefix_offset != -1)
255         {
256             HPEN hpen = CreatePen( PS_SOLID, 1, GetTextColor(hdc) );
257             HPEN oldPen = SelectObject( hdc, hpen );
258             MoveTo(hdc, x + prefix_x, y + tm.tmAscent + 1 );
259             LineTo(hdc, x + prefix_end, y + tm.tmAscent + 1 );
260             SelectObject( hdc, oldPen );
261             DeleteObject( hpen );
262         }
263
264         y += lh;
265         if (strPtr)
266         {
267             if (!(flags & DT_NOCLIP) && !(flags & DT_CALCRECT))
268             {
269                 if (y > rect->bottom - lh)
270                     break;
271             }
272         }
273     }
274     while (strPtr);
275     if (flags & DT_CALCRECT)
276     {
277         rect->right = rect->left + max_width;
278         rect->bottom = y;
279     }
280     return 1;
281 }
282
283
284 /***********************************************************************
285  *           ExtTextOut    (GDI.351)
286  */
287 BOOL ExtTextOut( HDC hdc, short x, short y, WORD flags, LPRECT lprect,
288                  LPSTR str, WORD count, LPINT lpDx )
289 {
290     int dir, ascent, descent, i;
291     XCharStruct info;
292     XFontStruct *font;
293     RECT rect;
294
295     DC * dc = (DC *) GDI_GetObjPtr( hdc, DC_MAGIC );
296     if (!dc) 
297     {
298         dc = (DC *)GDI_GetObjPtr( hdc, METAFILE_DC_MAGIC );
299         if (!dc) return FALSE;
300         MF_TextOut( dc, x, y, str, count );
301         return TRUE;
302     }
303
304     if (!DC_SetupGCForText( dc )) return TRUE;
305     font = dc->u.x.font.fstruct;
306
307     dprintf_text(stddeb,"ExtTextOut: %d,%d '%*.*s', %d  flags=%d\n",
308             x, y, count, count, str, count, flags);
309     if (lprect != NULL) {
310       dprintf_text(stddeb, "rect %d %d %d %d\n",
311                    lprect->left, lprect->top, lprect->right, lprect->bottom );
312     }
313
314       /* Setup coordinates */
315
316     if (dc->w.textAlign & TA_UPDATECP)
317     {
318         x = dc->w.CursPosX;
319         y = dc->w.CursPosY;
320     }
321     x = XLPTODP( dc, x );
322     y = YLPTODP( dc, y );
323     if (flags & (ETO_OPAQUE | ETO_CLIPPED))  /* There's a rectangle */
324     {
325         rect.left   = XLPTODP( dc, lprect->left );
326         rect.right  = XLPTODP( dc, lprect->right );
327         rect.top    = YLPTODP( dc, lprect->top );
328         rect.bottom = YLPTODP( dc, lprect->bottom );
329         if (rect.right < rect.left) SWAP_INT( rect.left, rect.right );
330         if (rect.bottom < rect.top) SWAP_INT( rect.top, rect.bottom );
331     }
332
333       /* Draw the rectangle */
334
335     if (flags & ETO_OPAQUE)
336     {
337         XSetForeground( display, dc->u.x.gc, dc->w.backgroundPixel );
338         XFillRectangle( display, dc->u.x.drawable, dc->u.x.gc,
339                         dc->w.DCOrgX + rect.left, dc->w.DCOrgY + rect.top,
340                         rect.right-rect.left, rect.bottom-rect.top );
341     }
342     if (!count) return TRUE;  /* Nothing more to do */
343
344       /* Compute text starting position */
345
346     XTextExtents( font, str, count, &dir, &ascent, &descent, &info );
347     info.width += count*dc->w.charExtra + dc->w.breakExtra*dc->w.breakCount;
348     if (lpDx) for (i = 0; i < count; i++) info.width += lpDx[i];
349
350     switch( dc->w.textAlign & (TA_LEFT | TA_RIGHT | TA_CENTER) )
351     {
352       case TA_LEFT:
353           if (dc->w.textAlign & TA_UPDATECP)
354               dc->w.CursPosX = XDPTOLP( dc, x + info.width );
355           break;
356       case TA_RIGHT:
357           x -= info.width;
358           if (dc->w.textAlign & TA_UPDATECP) dc->w.CursPosX = XDPTOLP( dc, x );
359           break;
360       case TA_CENTER:
361           x -= info.width / 2;
362           break;
363     }
364     switch( dc->w.textAlign & (TA_TOP | TA_BOTTOM | TA_BASELINE) )
365     {
366       case TA_TOP:
367           y += font->ascent;
368           break;
369       case TA_BOTTOM:
370           y -= font->descent;
371           break;
372       case TA_BASELINE:
373           break;
374     }
375
376       /* Set the clip region */
377
378     if (flags & ETO_CLIPPED)
379     {
380         SaveVisRgn( hdc );
381         IntersectVisRect( hdc, rect.left, rect.top, rect.right, rect.bottom );
382     }
383
384       /* Draw the text background if necessary */
385
386     if (dc->w.backgroundMode != TRANSPARENT)
387     {
388           /* If rectangle is opaque and clipped, do nothing */
389         if (!(flags & ETO_CLIPPED) || !(flags & ETO_OPAQUE))
390         {
391               /* Only draw if rectangle is not opaque or if some */
392               /* text is outside the rectangle */
393             if (!(flags & ETO_OPAQUE) ||
394                 (x < rect.left) ||
395                 (x + info.width >= rect.right) ||
396                 (y-font->ascent < rect.top) ||
397                 (y+font->descent >= rect.bottom))
398             {
399                 XSetForeground( display, dc->u.x.gc, dc->w.backgroundPixel );
400                 XFillRectangle( display, dc->u.x.drawable, dc->u.x.gc,
401                                 dc->w.DCOrgX + x,
402                                 dc->w.DCOrgY + y - font->ascent,
403                                 info.width,
404                                 font->ascent + font->descent );
405             }
406         }
407     }
408     
409       /* Draw the text */
410
411     XSetForeground( display, dc->u.x.gc, dc->w.textPixel );
412     if (!dc->w.charExtra && !dc->w.breakExtra && !lpDx)
413     {
414         XDrawString( display, dc->u.x.drawable, dc->u.x.gc, 
415                      dc->w.DCOrgX + x, dc->w.DCOrgY + y, str, count );
416     }
417     else  /* Now the fun begins... */
418     {
419         XTextItem *items, *pitem;
420
421         items = malloc( count * sizeof(XTextItem) );
422         for (i = 0, pitem = items; i < count; i++, pitem++)
423         {
424             pitem->chars  = str + i;
425             pitem->nchars = 1;
426             pitem->font   = None;
427             if (i == 0)
428             {
429                 pitem->delta = 0;
430                 continue;  /* First iteration -> no delta */
431             }
432             pitem->delta = dc->w.charExtra;
433             if (str[i] == (char)dc->u.x.font.metrics.tmBreakChar)
434                 pitem->delta += dc->w.breakExtra;
435             if (lpDx)
436             {
437                 INT width;
438                 GetCharWidth( hdc, str[i], str[i], &width );
439                 pitem->delta += lpDx[i-1] - width;
440             }
441         }
442         XDrawText( display, dc->u.x.drawable, dc->u.x.gc,
443                    dc->w.DCOrgX + x, dc->w.DCOrgY + y, items, count );
444         free( items );
445     }
446
447       /* Draw underline and strike-out if needed */
448
449     if (dc->u.x.font.metrics.tmUnderlined)
450     {
451         long linePos, lineWidth;       
452         if (!XGetFontProperty( font, XA_UNDERLINE_POSITION, &linePos ))
453             linePos = font->descent-1;
454         if (!XGetFontProperty( font, XA_UNDERLINE_THICKNESS, &lineWidth ))
455             lineWidth = 0;
456         else if (lineWidth == 1) lineWidth = 0;
457         XSetLineAttributes( display, dc->u.x.gc, lineWidth,
458                             LineSolid, CapRound, JoinBevel ); 
459         XDrawLine( display, dc->u.x.drawable, dc->u.x.gc,
460                    dc->w.DCOrgX + x, dc->w.DCOrgY + y + linePos,
461                    dc->w.DCOrgX + x + info.width, dc->w.DCOrgY + y + linePos );
462     }
463     if (dc->u.x.font.metrics.tmStruckOut)
464     {
465         long lineAscent, lineDescent;
466         if (!XGetFontProperty( font, XA_STRIKEOUT_ASCENT, &lineAscent ))
467             lineAscent = font->ascent / 3;
468         if (!XGetFontProperty( font, XA_STRIKEOUT_DESCENT, &lineDescent ))
469             lineDescent = -lineAscent;
470         XSetLineAttributes( display, dc->u.x.gc, lineAscent + lineDescent,
471                             LineSolid, CapRound, JoinBevel ); 
472         XDrawLine( display, dc->u.x.drawable, dc->u.x.gc,
473                    dc->w.DCOrgX + x, dc->w.DCOrgY + y - lineAscent,
474                    dc->w.DCOrgX + x + info.width, dc->w.DCOrgY + y - lineAscent );
475     }
476     if (flags & ETO_CLIPPED) RestoreVisRgn( hdc );
477     return TRUE;
478 }
479
480
481 /***********************************************************************
482  *           TextOut    (GDI.33)
483  */
484 BOOL TextOut( HDC hdc, short x, short y, LPSTR str, short count )
485 {
486     return ExtTextOut( hdc, x, y, 0, NULL, str, count, NULL );
487 }
488
489
490 /***********************************************************************
491  *              GrayString (USER.185)
492  */
493 BOOL GrayString(HDC hdc, HBRUSH hbr, FARPROC gsprc, LPARAM lParam, 
494                 INT cch, INT x, INT y, INT cx, INT cy)
495 {
496         int s, current_color;
497
498         if (gsprc) {
499                 return CallGrayStringProc(gsprc, hdc, lParam, 
500                                         cch ? cch : lstrlen((LPCSTR) lParam) );
501         } else {
502                 current_color = GetTextColor(hdc);
503                 SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT) );
504                 s = TextOut(hdc, x, y, (LPSTR) lParam, 
505                                 cch ? cch : lstrlen((LPCSTR) lParam) );
506                 SetTextColor(hdc, current_color);
507                 
508                 return s;
509         }
510 }
511
512
513 /***********************************************************************
514  *           TEXT_TabbedTextOut
515  *
516  * Helper function for TabbedTextOut() and GetTabbedTextExtent().
517  * Note: this doesn't work too well for text-alignment modes other
518  *       than TA_LEFT|TA_TOP. But we want bug-for-bug compatibility :-)
519  */
520 LONG TEXT_TabbedTextOut( HDC hdc, int x, int y, LPSTR lpstr, int count, 
521                          int cTabStops, LPINT lpTabPos, int nTabOrg,
522                          BOOL fDisplayText)
523 {
524     WORD defWidth;
525     DWORD extent;
526     int i, tabPos = 0;
527
528     if (cTabStops == 1) defWidth = *lpTabPos;
529     else
530     {
531         TEXTMETRIC tm;
532         GetTextMetrics( hdc, &tm );
533         defWidth = 8 * tm.tmAveCharWidth;
534     }
535     
536     while (count > 0)
537     {
538         for (i = 0; i < count; i++)
539             if (lpstr[i] == '\t') break;
540         extent = GetTextExtent( hdc, lpstr, i );
541         while ((cTabStops > 0) && (nTabOrg + *lpTabPos < x + LOWORD(extent)))
542         {
543             lpTabPos++;
544             cTabStops--;
545         }
546         if (lpstr[i] != '\t')
547             tabPos = x + LOWORD(extent);
548         else if (cTabStops > 0)
549             tabPos = nTabOrg + *lpTabPos;
550         else
551             tabPos = (x + LOWORD(extent) + defWidth - 1) / defWidth * defWidth;
552         if (fDisplayText)
553         {
554             RECT r;
555             SetRect( &r, x, y, tabPos, y+HIWORD(extent) );
556             ExtTextOut( hdc, x, y,
557                         GetBkMode(hdc) == OPAQUE ? ETO_OPAQUE : 0,
558                         &r, lpstr, i, NULL );
559         }
560         x = tabPos;
561         count -= i+1;
562         lpstr += i+1;
563     }
564     return tabPos;
565 }
566
567
568 /***********************************************************************
569  *           TabbedTextOut    (USER.196)
570  */
571 LONG TabbedTextOut( HDC hdc, short x, short y, LPSTR lpstr, short count, 
572                     short cTabStops, LPINT lpTabPos, short nTabOrg )
573 {
574     dprintf_text( stddeb, "TabbedTextOut: %x %d,%d '%*.*s' %d\n",
575                   hdc, x, y, count, count, lpstr, count );
576     return TEXT_TabbedTextOut( hdc, x, y, lpstr, count, cTabStops,
577                                lpTabPos, nTabOrg, TRUE );
578 }
579
580
581 /***********************************************************************
582  *           GetTabbedTextExtent    (USER.197)
583  */
584 DWORD GetTabbedTextExtent( HDC hdc, LPSTR lpstr, int count, 
585                           int cTabStops, LPINT lpTabPos )
586 {
587     dprintf_text( stddeb, "GetTabbedTextExtent: %x '%*.*s' %d\n",
588                   hdc, count, count, lpstr, count );
589     return TEXT_TabbedTextOut( hdc, 0, 0, lpstr, count, cTabStops,
590                                lpTabPos, 0, FALSE );
591 }