Release 960506
[wine] / objects / text.c
1 /*
2  * text functions
3  *
4  * Copyright 1993, 1994 Alexandre Julliard
5  *
6  */
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 #include "xmalloc.h"
19
20 #define TAB     9
21 #define LF     10
22 #define CR     13
23 #define SPACE  32
24 #define PREFIX 38
25
26 #define SWAP_INT(a,b)  { int t = a; a = b; b = t; }
27
28 static int tabstop = 8;
29 static int tabwidth;
30 static int spacewidth;
31 static int prefix_offset;
32
33
34 static const char *TEXT_NextLine( HDC hdc, const char *str, int *count,
35                                   char *dest, int *len, int width, WORD format)
36 {
37     /* Return next line of text from a string.
38      * 
39      * hdc - handle to DC.
40      * str - string to parse into lines.
41      * count - length of str.
42      * dest - destination in which to return line.
43      * len - length of resultant line in dest in chars.
44      * width - maximum width of line in pixels.
45      * format - format type passed to DrawText.
46      *
47      * Returns pointer to next char in str after end of the line
48      * or NULL if end of str reached.
49      */
50
51     int i = 0, j = 0, k;
52     int plen = 0;
53     int numspaces;
54     SIZE size;
55     int lasttab = 0;
56     int wb_i = 0, wb_j = 0, wb_count = 0;
57
58     while (*count)
59     {
60         switch (str[i])
61         {
62         case CR:
63         case LF:
64             if (!(format & DT_SINGLELINE))
65             {
66                 if (str[i] == CR && str[i+1] == LF)
67                     i++;
68                 i++;
69                 *len = j;
70                 (*count)--;
71                 return (&str[i]);
72             }
73             dest[j++] = str[i++];
74             if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) ||
75                 (format & DT_WORDBREAK))
76             {
77                 if (!GetTextExtentPoint(hdc, &dest[j-1], 1, &size))
78                     return NULL;
79                 plen += size.cx;
80             }
81             break;
82             
83         case PREFIX:
84             if (!(format & DT_NOPREFIX))
85             {
86                 prefix_offset = j;
87                 i++;
88             }
89             else
90             {
91                 dest[j++] = str[i++];
92                 if (!(format & DT_NOCLIP) || (format & DT_WORDBREAK))
93                 {
94                     if (!GetTextExtentPoint(hdc, &dest[j-1], 1, &size))
95                         return NULL;
96                     plen += size.cx;
97                 }
98             }
99             break;
100             
101         case TAB:
102             if (format & DT_EXPANDTABS)
103             {
104                 wb_i = ++i;
105                 wb_j = j;
106                 wb_count = *count;
107
108                 if (!GetTextExtentPoint(hdc, &dest[lasttab], j - lasttab,
109                                                                  &size))
110                     return NULL;
111
112                 numspaces = (tabwidth - size.cx) / spacewidth;
113                 for (k = 0; k < numspaces; k++)
114                     dest[j++] = SPACE;
115                 plen += tabwidth - size.cx;
116                 lasttab = wb_j + numspaces;
117             }
118             else
119             {
120                 dest[j++] = str[i++];
121                 if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) ||
122                     (format & DT_WORDBREAK))
123                 {
124                     if (!GetTextExtentPoint(hdc, &dest[j-1], 1, &size))
125                         return NULL;
126                     plen += size.cx;
127                 }
128             }
129             break;
130
131         case SPACE:
132             dest[j++] = str[i++];
133             if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) ||
134                 (format & DT_WORDBREAK))
135             {
136                 wb_i = i;
137                 wb_j = j - 1;
138                 wb_count = *count;
139                 if (!GetTextExtentPoint(hdc, &dest[j-1], 1, &size))
140                     return NULL;
141                 plen += size.cx;
142             }
143             break;
144
145         default:
146             dest[j++] = str[i++];
147             if (!(format & DT_NOCLIP) || !(format & DT_NOPREFIX) ||
148                 (format & DT_WORDBREAK))
149             {
150                 if (!GetTextExtentPoint(hdc, &dest[j-1], 1, &size))
151                     return NULL;
152                 plen += size.cx;
153             }
154         }
155
156         (*count)--;
157         if (!(format & DT_NOCLIP) || (format & DT_WORDBREAK))
158         {
159             if (plen > width)
160             {
161                 if (format & DT_WORDBREAK)
162                 {
163                     if (wb_j)
164                     {
165                         *len = wb_j;
166                         *count = wb_count - 1;
167                         return (&str[wb_i]);
168                     }
169                 }
170                 else
171                 {
172                     *len = j;
173                     return (&str[i]);
174                 }
175             }
176         }
177     }
178     
179     *len = j;
180     return NULL;
181 }
182
183
184 /***********************************************************************
185  *           DrawText    (USER.85)
186  */
187 INT DrawText( HDC hdc, LPCSTR str, INT i_count, LPRECT rect, UINT flags )
188 {
189     SIZE size;
190     const char *strPtr;
191     static char line[1024];
192     int len, lh, count=i_count;
193     int prefix_x = 0;
194     int prefix_end = 0;
195     TEXTMETRIC tm;
196     int x = rect->left, y = rect->top;
197     int width = rect->right - rect->left;
198     int max_width = 0;
199
200     dprintf_text(stddeb,"DrawText: '%s', %d , [(%d,%d),(%d,%d)]\n", str,
201                  count, rect->left, rect->top, rect->right, rect->bottom);
202     
203     if (count == -1) count = strlen(str);
204     strPtr = str;
205
206     GetTextMetrics(hdc, &tm);
207     if (flags & DT_EXTERNALLEADING)
208         lh = tm.tmHeight + tm.tmExternalLeading;
209     else
210         lh = tm.tmHeight;
211
212     if (flags & DT_TABSTOP)
213         tabstop = flags >> 8;
214
215     if (flags & DT_EXPANDTABS)
216     {
217         GetTextExtentPoint(hdc, " ", 1, &size);
218         spacewidth = size.cx;
219         GetTextExtentPoint(hdc, "o", 1, &size);
220         tabwidth = size.cx * tabstop;
221     }
222
223     do
224     {
225         prefix_offset = -1;
226         strPtr = TEXT_NextLine(hdc, strPtr, &count, line, &len, width, flags);
227
228         if (prefix_offset != -1)
229         {
230             GetTextExtentPoint(hdc, line, prefix_offset, &size);
231             prefix_x = size.cx;
232             GetTextExtentPoint(hdc, line, prefix_offset + 1, &size);
233             prefix_end = size.cx - 1;
234         }
235
236         if (!GetTextExtentPoint(hdc, line, len, &size)) return 0;
237         if (flags & DT_CENTER) x = (rect->left + rect->right -
238                                     size.cx) / 2;
239         else if (flags & DT_RIGHT) x = rect->right - size.cx;
240
241         if (flags & DT_SINGLELINE)
242         {
243             if (flags & DT_VCENTER) y = rect->top + 
244                 (rect->bottom - rect->top) / 2 - size.cy / 2;
245             else if (flags & DT_BOTTOM) y = rect->bottom - size.cy;
246         }
247         if (!(flags & DT_CALCRECT))
248         {
249             if (!ExtTextOut( hdc, x, y, (flags & DT_NOCLIP) ? 0 : ETO_CLIPPED,
250                              rect, line, len, NULL )) return 0;
251         }
252         else if (size.cx > max_width)
253             max_width = size.cx;
254
255         if (prefix_offset != -1)
256         {
257             HPEN hpen = CreatePen( PS_SOLID, 1, GetTextColor(hdc) );
258             HPEN oldPen = SelectObject( hdc, hpen );
259             MoveTo(hdc, x + prefix_x, y + tm.tmAscent + 1 );
260             LineTo(hdc, x + prefix_end, y + tm.tmAscent + 1 );
261             SelectObject( hdc, oldPen );
262             DeleteObject( hpen );
263         }
264
265         y += lh;
266         if (strPtr)
267         {
268             if (!(flags & DT_NOCLIP) && !(flags & DT_CALCRECT))
269             {
270                 if (y > rect->bottom - lh)
271                     break;
272             }
273         }
274     }
275     while (strPtr);
276     if (flags & DT_CALCRECT)
277     {
278         rect->right = rect->left + max_width;
279         rect->bottom = y;
280     }
281     return 1;
282 }
283
284
285 /***********************************************************************
286  *           ExtTextOut    (GDI.351)
287  */
288 BOOL ExtTextOut( HDC hdc, short x, short y, WORD flags, LPRECT lprect,
289                  LPSTR str, WORD count, LPINT16 lpDx )
290 {
291     int dir, ascent, descent, i;
292     XCharStruct info;
293     XFontStruct *font;
294     RECT rect;
295
296     DC * dc = (DC *) GDI_GetObjPtr( hdc, DC_MAGIC );
297     if (!dc) 
298     {
299         dc = (DC *)GDI_GetObjPtr( hdc, METAFILE_DC_MAGIC );
300         if (!dc) return FALSE;
301         MF_TextOut( dc, x, y, str, count );
302         return TRUE;
303     }
304
305     if (!DC_SetupGCForText( dc )) return TRUE;
306     font = dc->u.x.font.fstruct;
307
308     dprintf_text(stddeb,"ExtTextOut: %d,%d '%*.*s', %d  flags=%d\n",
309             x, y, count, count, str, count, flags);
310     if (lprect != NULL) dprintf_text(stddeb, "rect %d %d %d %d\n",
311                                      lprect->left, lprect->top,
312                                      lprect->right, lprect->bottom );
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 = xmalloc( 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, LPINT16 lpTabPos, int nTabOrg,
522                          BOOL fDisplayText)
523 {
524     WORD defWidth;
525     DWORD extent = 0;
526     int i, tabPos = x;
527     int start = x;
528
529     if (cTabStops == 1)
530     {
531         defWidth = *lpTabPos;
532         cTabStops = 0;
533     }
534     else
535     {
536         TEXTMETRIC tm;
537         GetTextMetrics( hdc, &tm );
538         defWidth = 8 * tm.tmAveCharWidth;
539     }
540     
541     while (count > 0)
542     {
543         for (i = 0; i < count; i++)
544             if (lpstr[i] == '\t') break;
545         extent = GetTextExtent( hdc, lpstr, i );
546         while ((cTabStops > 0) && (nTabOrg + *lpTabPos <= x + LOWORD(extent)))
547         {
548             lpTabPos++;
549             cTabStops--;
550         }
551         if (i == count)
552             tabPos = x + LOWORD(extent);
553         else if (cTabStops > 0)
554             tabPos = nTabOrg + *lpTabPos;
555         else
556             tabPos = nTabOrg + ((x + LOWORD(extent) - nTabOrg) / defWidth + 1) * defWidth;
557         if (fDisplayText)
558         {
559             RECT r;
560             SetRect( &r, x, y, tabPos, y+HIWORD(extent) );
561             ExtTextOut( hdc, x, y,
562                         GetBkMode(hdc) == OPAQUE ? ETO_OPAQUE : 0,
563                         &r, lpstr, i, NULL );
564         }
565         x = tabPos;
566         count -= i+1;
567         lpstr += i+1;
568     }
569     return MAKELONG(tabPos - start, HIWORD(extent));
570 }
571
572
573 /***********************************************************************
574  *           TabbedTextOut    (USER.196)
575  */
576 LONG TabbedTextOut( HDC hdc, short x, short y, LPSTR lpstr, short count, 
577                     short cTabStops, LPINT16 lpTabPos, short nTabOrg )
578 {
579     dprintf_text( stddeb, "TabbedTextOut: %04x %d,%d '%*.*s' %d\n",
580                   hdc, x, y, count, count, lpstr, count );
581     return TEXT_TabbedTextOut( hdc, x, y, lpstr, count, cTabStops,
582                                lpTabPos, nTabOrg, TRUE );
583 }
584
585
586 /***********************************************************************
587  *           GetTabbedTextExtent    (USER.197)
588  */
589 DWORD GetTabbedTextExtent( HDC hdc, LPSTR lpstr, int count, 
590                           int cTabStops, LPINT16 lpTabPos )
591 {
592     dprintf_text( stddeb, "GetTabbedTextExtent: %04x '%*.*s' %d\n",
593                   hdc, count, count, lpstr, count );
594     return TEXT_TabbedTextOut( hdc, 0, 0, lpstr, count, cTabStops,
595                                lpTabPos, 0, FALSE );
596 }