Release 950606
[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, prefix_x, prefix_end;
192     TEXTMETRIC tm;
193     int x = rect->left, y = rect->top;
194     int width = rect->right - rect->left;
195     int max_width = 0;
196
197     dprintf_text(stddeb,"DrawText: '%s', %d , [(%d,%d),(%d,%d)]\n", str, count,
198            rect->left, rect->top, rect->right, rect->bottom);
199     
200     if (count == -1) count = strlen(str);
201     strPtr = str;
202
203     GetTextMetrics(hdc, &tm);
204     if (flags & DT_EXTERNALLEADING)
205         lh = tm.tmHeight + tm.tmExternalLeading;
206     else
207         lh = tm.tmHeight;
208
209     if (flags & DT_TABSTOP)
210         tabstop = flags >> 8;
211
212     if (flags & DT_EXPANDTABS)
213     {
214         GetTextExtentPoint(hdc, " ", 1, &size);
215         spacewidth = size.cx;
216         GetTextExtentPoint(hdc, "o", 1, &size);
217         tabwidth = size.cx * tabstop;
218     }
219
220     do
221     {
222         prefix_offset = -1;
223         strPtr = TEXT_NextLine(hdc, strPtr, &count, line, &len, width, flags);
224
225         if (prefix_offset != -1)
226         {
227             GetTextExtentPoint(hdc, line, prefix_offset, &size);
228             prefix_x = size.cx;
229             GetTextExtentPoint(hdc, line, prefix_offset + 1, &size);
230             prefix_end = size.cx - 1;
231         }
232
233         if (!GetTextExtentPoint(hdc, line, len, &size)) return 0;
234         if (flags & DT_CENTER) x = (rect->left + rect->right -
235                                     size.cx) / 2;
236         else if (flags & DT_RIGHT) x = rect->right - size.cx;
237
238         if (flags & DT_SINGLELINE)
239         {
240             if (flags & DT_VCENTER) y = rect->top + 
241                 (rect->bottom - rect->top) / 2 - size.cy / 2;
242             else if (flags & DT_BOTTOM) y = rect->bottom - size.cy;
243         }
244         if (!(flags & DT_CALCRECT))
245         {
246             if (!ExtTextOut( hdc, x, y, (flags & DT_NOCLIP) ? 0 : ETO_CLIPPED,
247                              rect, line, len, NULL )) return 0;
248         }
249         else if (size.cx > max_width)
250             max_width = size.cx;
251
252         if (prefix_offset != -1)
253         {
254             HPEN hpen = CreatePen( PS_SOLID, 1, GetTextColor(hdc) );
255             HPEN oldPen = SelectObject( hdc, hpen );
256             MoveTo(hdc, x + prefix_x, y + tm.tmAscent + 1 );
257             LineTo(hdc, x + prefix_end, y + tm.tmAscent + 1 );
258             SelectObject( hdc, oldPen );
259             DeleteObject( hpen );
260         }
261
262         y += lh;
263         if (strPtr)
264         {
265             if (!(flags & DT_NOCLIP))
266             {
267                 if (y > rect->bottom - lh)
268                     break;
269             }
270         }
271     }
272     while (strPtr);
273     if (flags & DT_CALCRECT)
274     {
275         rect->right = rect->left + max_width;
276         rect->bottom = y;
277     }
278     return 1;
279 }
280
281
282 /***********************************************************************
283  *           ExtTextOut    (GDI.351)
284  */
285 BOOL ExtTextOut( HDC hdc, short x, short y, WORD flags, LPRECT lprect,
286                  LPSTR str, WORD count, LPINT lpDx )
287 {
288     int dir, ascent, descent, i;
289     XCharStruct info;
290     XFontStruct *font;
291     RECT rect;
292
293     DC * dc = (DC *) GDI_GetObjPtr( hdc, DC_MAGIC );
294     if (!dc) 
295     {
296         dc = (DC *)GDI_GetObjPtr( hdc, METAFILE_DC_MAGIC );
297         if (!dc) return FALSE;
298         MF_TextOut( dc, x, y, str, count );
299         return TRUE;
300     }
301
302     if (!DC_SetupGCForText( dc )) return TRUE;
303     font = dc->u.x.font.fstruct;
304
305     dprintf_text(stddeb,"ExtTextOut: %d,%d '%*.*s', %d  flags=%d\n",
306             x, y, count, count, str, count, flags);
307     if (lprect != NULL) {
308       dprintf_text(stddeb, "rect %d %d %d %d\n",
309                    lprect->left, lprect->top, lprect->right, lprect->bottom );
310     }
311
312       /* Setup coordinates */
313
314     if (dc->w.textAlign & TA_UPDATECP)
315     {
316         x = dc->w.CursPosX;
317         y = dc->w.CursPosY;
318     }
319     x = XLPTODP( dc, x );
320     y = YLPTODP( dc, y );
321     if (flags & (ETO_OPAQUE | ETO_CLIPPED))  /* There's a rectangle */
322     {
323         rect.left   = XLPTODP( dc, lprect->left );
324         rect.right  = XLPTODP( dc, lprect->right );
325         rect.top    = YLPTODP( dc, lprect->top );
326         rect.bottom = YLPTODP( dc, lprect->bottom );
327         if (rect.right < rect.left) SWAP_INT( rect.left, rect.right );
328         if (rect.bottom < rect.top) SWAP_INT( rect.top, rect.bottom );
329     }
330
331       /* Draw the rectangle */
332
333     if (flags & ETO_OPAQUE)
334     {
335         XSetForeground( display, dc->u.x.gc, dc->w.backgroundPixel );
336         XFillRectangle( display, dc->u.x.drawable, dc->u.x.gc,
337                         dc->w.DCOrgX + rect.left, dc->w.DCOrgY + rect.top,
338                         rect.right-rect.left, rect.bottom-rect.top );
339     }
340     if (!count) return TRUE;  /* Nothing more to do */
341
342       /* Compute text starting position */
343
344     XTextExtents( font, str, count, &dir, &ascent, &descent, &info );
345     info.width += count*dc->w.charExtra + dc->w.breakExtra*dc->w.breakCount;
346     if (lpDx) for (i = 0; i < count; i++) info.width += lpDx[i];
347
348     switch( dc->w.textAlign & (TA_LEFT | TA_RIGHT | TA_CENTER) )
349     {
350       case TA_LEFT:
351           if (dc->w.textAlign & TA_UPDATECP)
352               dc->w.CursPosX = XDPTOLP( dc, x + info.width );
353           break;
354       case TA_RIGHT:
355           x -= info.width;
356           if (dc->w.textAlign & TA_UPDATECP) dc->w.CursPosX = XDPTOLP( dc, x );
357           break;
358       case TA_CENTER:
359           x -= info.width / 2;
360           break;
361     }
362     switch( dc->w.textAlign & (TA_TOP | TA_BOTTOM | TA_BASELINE) )
363     {
364       case TA_TOP:
365           y += font->ascent;
366           break;
367       case TA_BOTTOM:
368           y -= font->descent;
369           break;
370       case TA_BASELINE:
371           break;
372     }
373
374       /* Set the clip region */
375
376     if (flags & ETO_CLIPPED)
377     {
378         SaveVisRgn( hdc );
379         IntersectVisRect( hdc, rect.left, rect.top, rect.right, rect.bottom );
380     }
381
382       /* Draw the text background if necessary */
383
384     if (dc->w.backgroundMode != TRANSPARENT)
385     {
386           /* If rectangle is opaque and clipped, do nothing */
387         if (!(flags & ETO_CLIPPED) || !(flags & ETO_OPAQUE))
388         {
389               /* Only draw if rectangle is not opaque or if some */
390               /* text is outside the rectangle */
391             if (!(flags & ETO_OPAQUE) ||
392                 (x < rect.left) ||
393                 (x + info.width >= rect.right) ||
394                 (y-font->ascent < rect.top) ||
395                 (y+font->descent >= rect.bottom))
396             {
397                 XSetForeground( display, dc->u.x.gc, dc->w.backgroundPixel );
398                 XFillRectangle( display, dc->u.x.drawable, dc->u.x.gc,
399                                 dc->w.DCOrgX + x,
400                                 dc->w.DCOrgY + y - font->ascent,
401                                 info.width,
402                                 font->ascent + font->descent );
403             }
404         }
405     }
406     
407       /* Draw the text */
408
409     XSetForeground( display, dc->u.x.gc, dc->w.textPixel );
410     if (!dc->w.charExtra && !dc->w.breakExtra && !lpDx)
411     {
412         XDrawString( display, dc->u.x.drawable, dc->u.x.gc, 
413                      dc->w.DCOrgX + x, dc->w.DCOrgY + y, str, count );
414     }
415     else  /* Now the fun begins... */
416     {
417         XTextItem *items, *pitem;
418
419         items = malloc( count * sizeof(XTextItem) );
420         for (i = 0, pitem = items; i < count; i++, pitem++)
421         {
422             pitem->chars  = str + i;
423             pitem->nchars = 1;
424             pitem->font   = None;
425             if (i == 0)
426             {
427                 pitem->delta = 0;
428                 continue;  /* First iteration -> no delta */
429             }
430             pitem->delta = dc->w.charExtra;
431             if (str[i] == (char)dc->u.x.font.metrics.tmBreakChar)
432                 pitem->delta += dc->w.breakExtra;
433             if (lpDx)
434             {
435                 INT width;
436                 GetCharWidth( hdc, str[i], str[i], &width );
437                 pitem->delta += lpDx[i-1] - width;
438             }
439         }
440         XDrawText( display, dc->u.x.drawable, dc->u.x.gc,
441                    dc->w.DCOrgX + x, dc->w.DCOrgY + y, items, count );
442         free( items );
443     }
444
445       /* Draw underline and strike-out if needed */
446
447     if (dc->u.x.font.metrics.tmUnderlined)
448     {
449         long linePos, lineWidth;       
450         if (!XGetFontProperty( font, XA_UNDERLINE_POSITION, &linePos ))
451             linePos = font->descent-1;
452         if (!XGetFontProperty( font, XA_UNDERLINE_THICKNESS, &lineWidth ))
453             lineWidth = 0;
454         else if (lineWidth == 1) lineWidth = 0;
455         XSetLineAttributes( display, dc->u.x.gc, lineWidth,
456                             LineSolid, CapRound, JoinBevel ); 
457         XDrawLine( display, dc->u.x.drawable, dc->u.x.gc,
458                    dc->w.DCOrgX + x, dc->w.DCOrgY + y + linePos,
459                    dc->w.DCOrgX + x + info.width, dc->w.DCOrgY + y + linePos );
460     }
461     if (dc->u.x.font.metrics.tmStruckOut)
462     {
463         long lineAscent, lineDescent;
464         if (!XGetFontProperty( font, XA_STRIKEOUT_ASCENT, &lineAscent ))
465             lineAscent = font->ascent / 3;
466         if (!XGetFontProperty( font, XA_STRIKEOUT_DESCENT, &lineDescent ))
467             lineDescent = -lineAscent;
468         XSetLineAttributes( display, dc->u.x.gc, lineAscent + lineDescent,
469                             LineSolid, CapRound, JoinBevel ); 
470         XDrawLine( display, dc->u.x.drawable, dc->u.x.gc,
471                    dc->w.DCOrgX + x, dc->w.DCOrgY + y - lineAscent,
472                    dc->w.DCOrgX + x + info.width, dc->w.DCOrgY + y - lineAscent );
473     }
474     if (flags & ETO_CLIPPED) RestoreVisRgn( hdc );
475     return TRUE;
476 }
477
478
479 /***********************************************************************
480  *           TextOut    (GDI.33)
481  */
482 BOOL TextOut( HDC hdc, short x, short y, LPSTR str, short count )
483 {
484     return ExtTextOut( hdc, x, y, 0, NULL, str, count, NULL );
485 }
486
487
488 /***********************************************************************
489  *              GrayString (USER.185)
490  */
491 BOOL GrayString(HDC hdc, HBRUSH hbr, FARPROC gsprc, LPARAM lParam, 
492                 INT cch, INT x, INT y, INT cx, INT cy)
493 {
494         int s, current_color;
495
496         if (gsprc) {
497                 return CallGrayStringProc(gsprc, hdc, lParam, 
498                                         cch ? cch : lstrlen((LPCSTR) lParam) );
499         } else {
500                 current_color = GetTextColor(hdc);
501                 SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT) );
502                 s = TextOut(hdc, x, y, (LPSTR) lParam, 
503                                 cch ? cch : lstrlen((LPCSTR) lParam) );
504                 SetTextColor(hdc, current_color);
505                 
506                 return s;
507         }
508 }
509
510
511 /***********************************************************************
512  *           TEXT_TabbedTextOut
513  *
514  * Helper function for TabbedTextOut() and GetTabbedTextExtent().
515  * Note: this doesn't work too well for text-alignment modes other
516  *       than TA_LEFT|TA_TOP. But we want bug-for-bug compatibility :-)
517  */
518 LONG TEXT_TabbedTextOut( HDC hdc, int x, int y, LPSTR lpstr, int count, 
519                          int cTabStops, LPINT lpTabPos, int nTabOrg,
520                          BOOL fDisplayText)
521 {
522     WORD defWidth;
523     DWORD extent;
524     int i, tabPos = 0;
525
526     if (cTabStops == 1) defWidth = *lpTabPos;
527     else
528     {
529         TEXTMETRIC tm;
530         GetTextMetrics( hdc, &tm );
531         defWidth = 8 * tm.tmAveCharWidth;
532     }
533     
534     while (count > 0)
535     {
536         for (i = 0; i < count; i++)
537             if (lpstr[i] == '\t') break;
538         extent = GetTextExtent( hdc, lpstr, i );
539         while ((cTabStops > 0) && (nTabOrg + *lpTabPos < x + LOWORD(extent)))
540         {
541             lpTabPos++;
542             cTabStops--;
543         }
544         if (lpstr[i] != '\t')
545             tabPos = x + LOWORD(extent);
546         else if (cTabStops > 0)
547             tabPos = nTabOrg + *lpTabPos;
548         else
549             tabPos = (x + LOWORD(extent) + defWidth - 1) / defWidth * defWidth;
550         if (fDisplayText)
551         {
552             RECT r;
553             SetRect( &r, x, y, tabPos, y+HIWORD(extent) );
554             ExtTextOut( hdc, x, y,
555                         GetBkMode(hdc) == OPAQUE ? ETO_OPAQUE : 0,
556                         &r, lpstr, i, NULL );
557         }
558         x = tabPos;
559         count -= i+1;
560         lpstr += i+1;
561     }
562     return tabPos;
563 }
564
565
566 /***********************************************************************
567  *           TabbedTextOut    (USER.196)
568  */
569 LONG TabbedTextOut( HDC hdc, short x, short y, LPSTR lpstr, short count, 
570                     short cTabStops, LPINT lpTabPos, short nTabOrg )
571 {
572     dprintf_text( stddeb, "TabbedTextOut: %x %d,%d '%*.*s' %d\n",
573                   hdc, x, y, count, count, lpstr, count );
574     return TEXT_TabbedTextOut( hdc, x, y, lpstr, count, cTabStops,
575                                lpTabPos, nTabOrg, TRUE );
576 }
577
578
579 /***********************************************************************
580  *           GetTabbedTextExtent    (USER.197)
581  */
582 DWORD GetTabbedTextExtent( HDC hdc, LPSTR lpstr, int count, 
583                           int cTabStops, LPINT lpTabPos )
584 {
585     dprintf_text( stddeb, "GetTabbedTextExtent: %x '%*.*s' %d\n",
586                   hdc, count, count, lpstr, count );
587     return TEXT_TabbedTextOut( hdc, 0, 0, lpstr, count, cTabStops,
588                                lpTabPos, 0, FALSE );
589 }