Tabs to spaces, more consistent formatting.
[AROS.git] / workbench / libs / muimaster / textengine.c
blob988c1617fd424a0b09c20ce5ce1a1cc90ec102c8
1 /*
2 Copyright © 1999, David Le Corfec.
3 Copyright © 2002-2011, The AROS Development Team.
4 All rights reserved.
6 $Id$
7 */
9 #include <ctype.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdio.h>
14 #include <exec/types.h>
15 #include <exec/memory.h>
17 #include <clib/alib_protos.h>
18 #include <intuition/screens.h>
19 #include <proto/exec.h>
20 #include <proto/dos.h>
21 #include <proto/graphics.h>
22 #include <proto/utility.h>
23 #include <proto/intuition.h>
24 #include <proto/muimaster.h>
26 #include "mui.h"
27 #include "textengine.h"
28 #include "imspec.h"
29 #include "support.h"
30 #include "listimage.h"
32 #include "muimaster_intern.h"
34 //#define MYDEBUG 1
35 #include "debug.h"
37 extern struct Library *MUIMasterBase;
39 /* A bit of explanation:
40 * The most important thing, after the datastructure, is the bounds
41 * calculation (especially chunk width).
42 * It will determine where to draw. Drawing is then straightforward.
43 * From the beginning:
44 * the input string is parsed line by line. Each line is formed of chunks
45 * of symbols having the same rendering style. As a special case,
46 * HiChar is a single letter chunk with underline style.
47 * Size calculation is done by calculating for each line the width and
48 * height of each chunk, then line width is the sum of widths and line height
49 * is the max height. Text width will be max line width, and text height
50 * will be the sum of line heights. Remember group layout.
51 * Chunk width is affected by soft style of text, and maybe other params.
52 * Drawing is done line by line, chunk by chunk, incrementing each time
53 * the offsets with chunk width or line height.
55 * Italic is looking ugly, no ?
58 #define ZTL_LEFT 1
59 #define ZTL_CENTER 2
60 #define ZTL_RIGHT 3
62 struct zune_context
64 LONG dripen;
65 LONG pen;
66 ULONG style;
67 UBYTE align;
69 ZTextLine *line;
70 CONST_STRPTR text_start;
71 CONST_STRPTR text;
72 CONST_STRPTR imspec;
73 Object *obj; /* Area subclass, see List_CreateImage */
76 ZText *zune_text_new(CONST_STRPTR preparse, CONST_STRPTR content,
77 int argtype, TEXT argbyte);
78 char *zune_text_iso_string(ZText * text);
79 void zune_text_destroy(ZText * text);
80 void zune_text_chunk_new(struct zune_context *zc);
81 static int strlenlf(const char *str);
82 static CONST_STRPTR parse_escape_code(ZTextLine * ztl,
83 struct zune_context *zc, CONST_STRPTR s);
84 static ZTextLine *zune_text_parse_line(CONST_STRPTR * s,
85 struct zune_context *zc, int *argtype, int arg);
86 void zune_text_get_bounds(ZText * text, Object * obj);
87 void zune_text_draw(ZText * text, Object * obj, WORD left, WORD right,
88 WORD top);
89 void zune_text_draw_cursor(ZText * text, Object * obj, WORD left,
90 WORD right, WORD top, LONG cursorx, LONG cursory);
91 void zune_text_draw_single(ZText * text, Object * obj, WORD left,
92 WORD right, WORD top, LONG xpos, LONG ypos, BOOL cursor);
93 int zune_text_get_char_pos(ZText * text, Object * obj, LONG x, LONG y,
94 struct ZTextLine **line_ptr, struct ZTextChunk **chunk_ptr,
95 int *offset_ptr, int *len_ptr);
96 int zune_text_get_line_len(ZText * text, Object * obj, LONG y);
97 int zune_get_xpos_of_line(ZText * text, Object * obj, LONG y, LONG xpixel);
98 int zune_text_get_lines(ZText * text);
99 int zune_make_cursor_visible(ZText * text, Object * obj, LONG cursorx,
100 LONG cursory, LONG left, LONG top, LONG right, LONG bottom);
101 int zune_text_merge(ZText * text, Object * obj, int x, int y,
102 ZText * tomerge);
105 /**************************************************************************
107 **************************************************************************/
108 ZText *zune_text_new(CONST_STRPTR preparse, CONST_STRPTR content,
109 int argtype, TEXT argbyte)
111 ZText *text;
112 /* STRPTR *lines; */
113 /* int i; */
114 STRPTR dup_content;
115 CONST_STRPTR buf;
116 int preparse_len;
117 struct zune_context zc;
118 int arg;
120 if (!(text = mui_alloc_struct(ZText)))
121 return NULL;
122 NewList((struct List *)&text->lines);
124 if (!content)
125 content = "";
126 preparse_len = preparse ? strlen(preparse) : 0;
127 if (!(dup_content = mui_alloc(preparse_len + strlen(content) + 1)))
129 mui_free(text);
130 return NULL;
133 if (preparse_len)
134 strcpy(dup_content, preparse);
135 strcpy(&dup_content[preparse_len], content);
137 buf = dup_content;
139 zc.pen = -1;
140 zc.dripen = -1;
141 zc.style = FS_NORMAL;
142 zc.align = ZTL_LEFT;
143 zc.imspec = NULL;
144 zc.obj = NULL;
146 if (argtype == ZTEXT_ARG_HICHAR)
148 /* store lower and upper case in arg */
149 arg = ToLower(argbyte) | (ToUpper(argbyte) << 8);
151 else
152 arg = argbyte;
154 /* the other elements are done by other functions */
156 while (1)
158 struct ZTextLine *ztl =
159 zune_text_parse_line(&buf, &zc, &argtype, arg);
160 if (ztl)
161 AddTail((struct List *)&text->lines, (struct Node *)ztl);
162 else
163 break;
164 if (*buf == '\n')
166 buf++;
167 continue;
169 if (!(*buf))
170 break;
173 mui_free(dup_content);
174 return text;
177 /**************************************************************************
178 Completly frees a ZText
179 **************************************************************************/
180 void zune_text_destroy(ZText * text)
182 struct ZTextLine *ztl;
183 struct ZTextChunk *ztc;
185 while ((ztl = (struct ZTextLine *)RemTail((struct List *)&text->lines)))
187 while ((ztc =
188 (struct ZTextChunk *)RemTail((struct List *)&ztl->
189 chunklist)))
191 if (ztc->str)
192 mui_free(ztc->str);
193 if (ztc->spec)
195 FreeVec((APTR) ztc->spec);
196 if (ztc->image)
198 zune_imspec_cleanup(ztc->image);
201 mui_free(ztc);
203 mui_free(ztl);
205 mui_free(text);
208 /************************************************************/
209 /* Parsing */
210 /************************************************************/
213 /**************************************************************************
214 Allocated and initialize a new text chunk and add it to the list
215 The context contains the values for the chunk to be created.
216 **************************************************************************/
217 void zune_text_chunk_new(struct zune_context *zc)
219 ZTextChunk *ztc;
221 /* No char has been processed so we needn't to allocate anything */
222 if (zc->text == zc->text_start)
223 return;
225 if (!(ztc = mui_alloc_struct(ZTextChunk)))
226 return;
228 ztc->style = zc->style;
229 ztc->dripen = zc->dripen;
230 ztc->pen = zc->pen;
231 if (zc->imspec)
233 D(bug("zune_text_chunk_new: imspec %s\n", zc->imspec));
234 ztc->spec = zc->imspec;
235 zc->imspec = NULL;
237 AddTail((struct List *)&zc->line->chunklist, (struct Node *)ztc);
239 else if (zc->obj)
241 ztc->obj = zc->obj;
242 zc->obj = NULL;
244 AddTail((struct List *)&zc->line->chunklist, (struct Node *)ztc);
246 else if ((ztc->str = (char *)mui_alloc(zc->text - zc->text_start + 1)))
248 strncpy(ztc->str, zc->text_start, zc->text - zc->text_start + 1);
249 ztc->str[zc->text - zc->text_start] = 0;
250 D(bug("zune_text_chunk_new: string %s\n", ztc->str));
252 AddTail((struct List *)&zc->line->chunklist, (struct Node *)ztc);
254 else
256 mui_free(ztc);
261 /**************************************************************************
262 Calculates the length of the string exluding line feed or 0 byte
263 **************************************************************************/
264 static int strlenlf(const char *str)
266 char c;
267 int len = 0;
268 while ((c = *str))
270 if (c == '\n')
271 break;
272 len++;
273 str++;
275 return len;
278 /**************************************************************************
279 Note: Only aligments at the beginning of a line should affect this line
280 (tested in MUI)
281 **************************************************************************/
282 static CONST_STRPTR parse_escape_code(ZTextLine * ztl,
283 struct zune_context *zc, CONST_STRPTR s)
285 unsigned char c;
286 c = *s;
288 zune_text_chunk_new(zc);
289 s++; /* s now points after the command */
290 zc->text_start = zc->text = s;
292 switch (c)
294 case 'c':
295 zc->align = ztl->align = ZTL_CENTER;
296 break;
297 case 'r':
298 zc->align = ztl->align = ZTL_RIGHT;
299 break;
300 case 'l':
301 zc->align = ztl->align = ZTL_LEFT;
302 break;
303 case 'n':
304 zc->style = FS_NORMAL;
305 break;
306 case 'u':
307 zc->style |= FSF_UNDERLINED;
308 break;
309 case 'b':
310 zc->style |= FSF_BOLD;
311 break;
312 case 'i':
313 zc->style |= FSF_ITALIC;
314 break;
315 case 'I': /* image spec */
317 char *t;
319 if (*s != '[')
320 break;
321 s++;
322 /* s points on the first char of imagespec.
323 * Extract it to the trailing ']'.
325 t = strchr(s, ']');
326 if (t == NULL)
327 break;
328 *t = 0;
329 D(bug("imspec = %s\n", s));
330 zc->imspec = StrDup(s);
331 *t = ']';
332 zc->text = t;
333 zune_text_chunk_new(zc);
334 zc->text_start = t + 1;
336 case 'O': /* pointer from List_CreateImage */
338 struct ListImage *li;
339 IPTR tmp;
340 char *t;
342 if (*s != '[')
343 break;
344 s++;
345 /* s points on the first char of pointer printed as %08lx.
346 * Extract it to the trailing ']'.
348 t = strchr(s, ']');
349 if (t == NULL)
350 break;
351 *t = 0;
352 if (HexToIPTR(s, &tmp) != -1)
354 D(bug("listimage = %lx\n", tmp));
355 if (tmp == 0)
357 /* According to the MUI autodocs, the result of
358 * CreateImage may be NULL, but then \33O[] has to
359 * simply draw nothing, so it shouldn't be considered
360 * an error.
361 * Without this, AROS crashed, if 00000000 was used.
363 *t = ']';
364 zc->text = t + 1;
365 zc->text_start = t + 1;
366 break;
368 li = (struct ListImage *)tmp;
369 zc->obj = li->obj;
371 *t = ']';
372 zc->text = t;
373 zune_text_chunk_new(zc);
374 zc->text_start = t + 1;
375 break;
377 case 'P': /* pen number */
379 LONG pen;
380 char *t;
382 if (*s != '[')
383 break;
384 s++;
385 /* s points on the first char of pen from ObtainPen printed as %ld.
386 * Extract it to the trailing ']'.
388 t = strchr(s, ']');
389 if (t == NULL)
390 break;
391 *t = 0;
392 if (StrToLong(s, &pen) != -1)
394 D(bug("pen = %ld\n", pen));
395 zc->pen = pen;
397 *t = ']';
398 zc->text = t;
399 zune_text_chunk_new(zc);
400 zc->text_start = t + 1;
401 break;
403 case '-':
404 zc->text += strlenlf(s);
405 break; /* disable engine */
407 default: /* some other ESC code ? */
408 if (isdigit(c)) /* pen */
410 zc->dripen = c - '0';
412 break;
414 return zc->text;
417 /**************************************************************************
418 Parse a text line, and create text chunks.
419 Whenever an escape code is encountered, the current chunk
420 is terminated, and the escape parameters are stacked until
421 a normal character is read. Then a new chunk begins with this char.
423 s_ptr is a pointer to the text string. After this function has been
424 executed it is changed to point to the end of the string (eighter the '\n'
425 or 0 byte)
427 This is probably a function to rewrite to deal with wide chars.
429 Note that the contents in s_ptr is overwritten.
430 **************************************************************************/
431 static ZTextLine *zune_text_parse_line(CONST_STRPTR * s_ptr,
432 struct zune_context *zc, int *argtype, int arg)
434 CONST_STRPTR s;
435 UBYTE c;
437 ZTextLine *ztl;
439 if (!s_ptr)
440 return NULL;
441 if (!(s = *s_ptr))
442 return NULL;
443 if (!(ztl = mui_alloc_struct(ZTextLine)))
444 return NULL;
445 NewList((struct List *)&ztl->chunklist);
447 ztl->align = zc->align;
448 zc->text_start = zc->text = s;
449 zc->line = ztl;
451 while ((c = *s))
453 if (c == '\n')
454 break;
456 if (c == '\33')
458 s++;
459 if (*s == 0)
460 break;
461 s = parse_escape_code(ztl, zc, s);
462 continue;
465 if (*argtype == ZTEXT_ARG_HICHAR && (((arg & 0xff) == c)
466 || (((arg >> 8) & 0xff) == c)))
468 ULONG styleback = zc->style;
469 zune_text_chunk_new(zc);
470 zc->style |= FSF_UNDERLINED;
471 zc->text_start = s;
472 zc->text = ++s;
473 zune_text_chunk_new(zc);
474 zc->text_start = s;
475 zc->style = styleback;
476 *argtype = ZTEXT_ARG_NONE;
477 continue;
480 if (*argtype == ZTEXT_ARG_HICHARIDX && arg == c)
482 ULONG styleback = zc->style;
483 /* underline next char */
484 zune_text_chunk_new(zc);
485 zc->style |= FSF_UNDERLINED;
486 zc->text_start = ++s;
487 zc->text = ++s;
488 zune_text_chunk_new(zc);
489 zc->text_start = s;
490 zc->style = styleback;
491 *argtype = ZTEXT_ARG_NONE;
494 zc->text = ++s;
495 } /* while */
496 zune_text_chunk_new(zc);
497 *s_ptr = s;
498 return ztl;
501 /************************************************************/
502 /* Bounds */
503 /************************************************************/
505 void zune_text_get_bounds(ZText * text, Object * obj)
507 struct RastPort rp;
508 struct TextFont *font;
510 ZTextLine *line_node;
511 ZTextChunk *chunk_node;
513 if (!text || !obj)
514 return;
516 text->width = 0;
517 text->height = 0;
519 font = _font(obj);
520 InitRastPort(&rp);
521 SetFont(&rp, font);
523 for (line_node = (ZTextLine *) text->lines.mlh_Head;
524 line_node->node.mln_Succ;
525 line_node = (ZTextLine *) line_node->node.mln_Succ)
527 line_node->lheight = font->tf_YSize;
528 line_node->lwidth = 0;
530 for (chunk_node = (ZTextChunk *) line_node->chunklist.mlh_Head;
531 chunk_node->node.mln_Succ;
532 chunk_node = (ZTextChunk *) chunk_node->node.mln_Succ)
534 if (chunk_node->spec)
536 struct MUI_MinMax minmax;
538 chunk_node->image =
539 zune_imspec_setup((IPTR) chunk_node->spec,
540 muiRenderInfo(obj));
541 if (!chunk_node->image)
542 return;
543 zune_imspec_askminmax(chunk_node->image, &minmax);
544 chunk_node->cwidth = minmax.DefWidth;
545 chunk_node->cheight = minmax.DefHeight;
546 line_node->lheight =
547 MAX(line_node->lheight, chunk_node->cheight);
549 else if (chunk_node->obj)
551 struct MUI_MinMax MinMax;
553 DoMethod(chunk_node->obj, MUIM_AskMinMax, (IPTR) & MinMax);
554 __area_finish_minmax(chunk_node->obj, &MinMax);
555 chunk_node->cwidth = MinMax.DefWidth;
556 chunk_node->cheight = MinMax.DefHeight;
557 line_node->lheight =
558 MAX(line_node->lheight, chunk_node->cheight);
560 else if (chunk_node->str)
562 chunk_node->cheight = font->tf_YSize;
563 chunk_node->cwidth =
564 TextLength(&rp, chunk_node->str,
565 strlen(chunk_node->str));
566 D(bug("zune_text_get_bounds(%s,%x) => cwidth=%d\n",
567 chunk_node->str, obj, chunk_node->cwidth));
569 line_node->lwidth += chunk_node->cwidth;
572 text->height += line_node->lheight;
573 text->width = MAX(text->width, line_node->lwidth);
577 /************************************************************/
578 /* Drawing */
579 /************************************************************/
581 // problem is cheight being seldom 0
583 void zune_text_draw(ZText * text, Object * obj, WORD left, WORD right,
584 WORD top)
586 struct RastPort *rp;
587 ULONG pensave;
588 ULONG style = FS_NORMAL;
590 ZTextLine *line_node;
591 ZTextChunk *chunk_node;
593 if (!text || !obj)
594 return;
596 D(bug("zune_text_draw(%p) %d %d %d\n", obj, left, right, top));
598 rp = _rp(obj);
599 SetFont(rp, _font(obj));
600 SetSoftStyle(rp, style, AskSoftStyle(rp));
602 for (line_node = (ZTextLine *) text->lines.mlh_Head;
603 line_node->node.mln_Succ;
604 line_node = (ZTextLine *) line_node->node.mln_Succ)
606 LONG x;
608 if (line_node->align == ZTL_CENTER)
609 x = (left + right + 1 - line_node->lwidth) / 2;
610 else if (line_node->align == ZTL_RIGHT)
611 x = right - line_node->lwidth + 1;
612 else
613 x = left;
615 // like MUI, never truncates the beginning of a line
616 if (x < left)
617 x = left;
619 //D(bug("zune_text_draw(%x,%d,%d,%d, align=%d) : x = %d\n",
620 //obj, left, right, line_node->lwidth, line_node->align, x));
622 for (chunk_node = (ZTextChunk *) line_node->chunklist.mlh_Head;
623 chunk_node->node.mln_Succ;
624 chunk_node = (ZTextChunk *) chunk_node->node.mln_Succ)
627 * Show/Hide stuff should be put in new api calls
629 if (chunk_node->image)
631 WORD top_im = top;
632 top_im += (line_node->lheight - chunk_node->cheight) / 2;
633 zune_imspec_show(chunk_node->image, obj);
634 pensave = GetAPen(rp);
635 zune_imspec_draw(chunk_node->image, muiRenderInfo(obj), x,
636 top_im, chunk_node->cwidth,
637 chunk_node->cheight, 0, 0, 0);
638 SetAPen(rp, pensave);
639 zune_imspec_hide(chunk_node->image);
641 else if (chunk_node->obj)
643 _left(chunk_node->obj) = x;
644 _top(chunk_node->obj) =
645 top + (line_node->lheight - chunk_node->cheight) / 2;
646 _width(chunk_node->obj) = chunk_node->cwidth;
647 _height(chunk_node->obj) = chunk_node->cheight;
648 DoShowMethod(chunk_node->obj);
649 pensave = GetAPen(rp);
650 MUI_Redraw(chunk_node->obj, MADF_DRAWOBJECT);
651 SetAPen(rp, pensave);
652 DoHideMethod(chunk_node->obj);
654 else if (chunk_node->str)
656 if (chunk_node->style != style)
658 SetSoftStyle(rp, chunk_node->style, 0xff);
659 style = chunk_node->style;
661 if (chunk_node->cheight == 0)
662 Move(rp, x, _font(obj)->tf_Baseline + top);
663 else
664 Move(rp, x,
665 _font(obj)->tf_Baseline + (line_node->lheight -
666 chunk_node->cheight) / 2 + top);
667 #if 0
668 D(bug
669 ("zune_text_draw(%p) Moved to %d (baseline=%d, lh=%d, ch=%d, top=%d)\n",
670 obj,
671 _font(obj)->tf_Baseline + (line_node->lheight -
672 chunk_node->cheight) / 2 + top,
673 _font(obj)->tf_Baseline, line_node->lheight,
674 chunk_node->cheight, top));
675 #endif
676 if (chunk_node->dripen != -1)
678 D(bug("chunk_node->dripen == %d\n",
679 chunk_node->dripen));
680 SetABPenDrMd(rp,
681 _dri(obj)->dri_Pens[chunk_node->dripen], 0, JAM1);
683 else if (chunk_node->pen != -1)
685 D(bug("chunk_node->pen == %d\n", chunk_node->pen));
686 SetABPenDrMd(rp, chunk_node->pen, 0, JAM1);
688 else
690 SetDrMd(rp, JAM1);
692 Text(rp, chunk_node->str, strlen(chunk_node->str));
694 x += chunk_node->cwidth;
696 top += line_node->lheight;