]> git.decadent.org.uk Git - ion3.git/blob - de/draw.c
Imported Upstream version 20090110
[ion3.git] / de / draw.c
1 /*
2  * ion/de/draw.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2009. 
5  *
6  * See the included file LICENSE for details.
7  */
8
9 #include <string.h>
10 #include <limits.h>
11
12 #include <ioncore/global.h>
13 #include <ioncore/common.h>
14 #include <ioncore/gr.h>
15 #include <ioncore/gr-util.h>
16 #include "brush.h"
17 #include "font.h"
18 #include "private.h"
19
20 #include <X11/extensions/shape.h>
21
22
23 /*{{{ Colour group lookup */
24
25
26 static DEColourGroup *destyle_get_colour_group2(DEStyle *style,
27                                                 const GrStyleSpec *a1,
28                                                 const GrStyleSpec *a2)
29 {
30     int i, score, maxscore=0;
31     DEColourGroup *maxg=&(style->cgrp);
32     
33     while(style!=NULL){
34         for(i=0; i<style->n_extra_cgrps; i++){
35             score=gr_stylespec_score2(&style->extra_cgrps[i].spec, a1, a2);
36             
37             if(score>maxscore){
38                 maxg=&(style->extra_cgrps[i]);
39                 maxscore=score;
40             }
41         }
42         style=style->based_on;
43     }
44     
45     return maxg;
46 }
47
48
49 DEColourGroup *debrush_get_colour_group2(DEBrush *brush, 
50                                          const GrStyleSpec *a1,
51                                          const GrStyleSpec *a2)
52 {
53     return destyle_get_colour_group2(brush->d, a1, a2);
54 }
55
56
57 DEColourGroup *debrush_get_colour_group(DEBrush *brush, const GrStyleSpec *attr)
58 {
59     return destyle_get_colour_group2(brush->d, attr, NULL);
60 }
61
62
63 DEColourGroup *debrush_get_current_colour_group(DEBrush *brush)
64 {
65     return debrush_get_colour_group(brush, debrush_get_current_attr(brush));
66 }
67
68
69 /*}}}*/
70
71
72 /*{{{ Borders */
73
74
75 /* Draw a border at x, y with outer width w x h. Top and left 'tl' pixels
76  * wide with color 'tlc' and bottom and right 'br' pixels with colors 'brc'.
77  */
78 static void do_draw_border(Window win, GC gc, int x, int y, int w, int h,
79                            uint tl, uint br, DEColour tlc, DEColour brc)
80 {
81     XPoint points[3];
82     uint i=0, a=0, b=0;
83     
84     w--;
85     h--;
86
87     XSetForeground(ioncore_g.dpy, gc, tlc);
88
89     
90     a=(br!=0);
91     b=0;
92     
93     for(i=0; i<tl; i++){
94         points[0].x=x+i;        points[0].y=y+h+1-b;
95         points[1].x=x+i;        points[1].y=y+i;
96         points[2].x=x+w+1-a;    points[2].y=y+i;
97
98         if(a<br)
99             a++;
100         if(b<br)
101             b++;
102     
103         XDrawLines(ioncore_g.dpy, win, gc, points, 3, CoordModeOrigin);
104     }
105
106     
107     XSetForeground(ioncore_g.dpy, gc, brc);
108
109     a=(tl!=0);
110     b=0;
111     
112     for(i=0; i<br; i++){
113         points[0].x=x+w-i;      points[0].y=y+b;
114         points[1].x=x+w-i;      points[1].y=y+h-i;
115         points[2].x=x+a;        points[2].y=y+h-i;
116     
117         if(a<tl)
118             a++;
119         if(b<tl)
120             b++;
121         
122         XDrawLines(ioncore_g.dpy, win, gc, points, 3, CoordModeOrigin);
123     }
124 }
125
126
127 static void draw_border(Window win, GC gc, WRectangle *geom,
128                         uint tl, uint br, DEColour tlc, DEColour brc)
129 {
130     do_draw_border(win, gc, geom->x, geom->y, geom->w, geom->h,
131                    tl, br, tlc, brc);
132     geom->x+=tl;
133     geom->y+=tl;
134     geom->w-=tl+br;
135     geom->h-=tl+br;
136 }
137
138
139 static void draw_borderline(Window win, GC gc, WRectangle *geom,
140                             uint tl, uint br, DEColour tlc, DEColour brc, 
141                             GrBorderLine line)
142 {
143     if(line==GR_BORDERLINE_LEFT && geom->h>0 && tl>0){
144         XSetForeground(ioncore_g.dpy, gc, tlc);
145         XSetBackground(ioncore_g.dpy, gc, tlc);
146         XFillRectangle(ioncore_g.dpy, win, gc, geom->x, geom->y, tl, geom->h);
147         geom->x+=tl;
148     }else if(line==GR_BORDERLINE_TOP && geom->w>0 && tl>0){
149         XSetForeground(ioncore_g.dpy, gc, tlc);
150         XSetBackground(ioncore_g.dpy, gc, tlc);
151         XFillRectangle(ioncore_g.dpy, win, gc, geom->x, geom->y, geom->w, tl);
152         geom->y+=tl;
153     }else if(line==GR_BORDERLINE_RIGHT && geom->h>0 && br>0){
154         XSetForeground(ioncore_g.dpy, gc, brc);
155         XSetBackground(ioncore_g.dpy, gc, brc);
156         XFillRectangle(ioncore_g.dpy, win, gc, geom->x+geom->w-br, geom->y, br, geom->h);
157         geom->w-=br;
158     }else if(line==GR_BORDERLINE_BOTTOM && geom->w>0 && br>0){
159         XSetForeground(ioncore_g.dpy, gc, brc);
160         XSetBackground(ioncore_g.dpy, gc, brc);
161         XFillRectangle(ioncore_g.dpy, win, gc, geom->x, geom->y+geom->h-br, geom->w, br);
162         geom->h-=br;
163     }
164 }
165
166
167 void debrush_do_draw_borderline(DEBrush *brush, WRectangle geom,
168                                 DEColourGroup *cg, GrBorderLine line)
169 {
170     DEBorder *bd=&(brush->d->border);
171     GC gc=brush->d->normal_gc;
172     Window win=brush->win;
173     
174     switch(bd->style){
175     case DEBORDER_RIDGE:
176         draw_borderline(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh, line);
177     case DEBORDER_INLAID:
178         draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
179         draw_borderline(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl, line);
180         break;
181     case DEBORDER_GROOVE:
182         draw_borderline(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl, line);
183         draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
184         draw_borderline(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh, line);
185         break;
186     case DEBORDER_ELEVATED:
187     default:
188         draw_borderline(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh, line);
189         draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
190         break;
191     }
192 }
193
194
195 void debrush_do_draw_padline(DEBrush *brush, WRectangle geom,
196                              DEColourGroup *cg, GrBorderLine line)
197 {
198     DEBorder *bd=&(brush->d->border);
199     GC gc=brush->d->normal_gc;
200     Window win=brush->win;
201     
202     draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
203 }
204
205
206 void debrush_draw_borderline(DEBrush *brush, const WRectangle *geom,
207                              GrBorderLine line)
208 {
209     DEColourGroup *cg=debrush_get_current_colour_group(brush);
210     if(cg!=NULL)
211         debrush_do_draw_borderline(brush, *geom, cg, line);
212 }
213
214
215 static void debrush_do_do_draw_border(DEBrush *brush, WRectangle geom, 
216                                       DEColourGroup *cg)
217 {
218     DEBorder *bd=&(brush->d->border);
219     GC gc=brush->d->normal_gc;
220     Window win=brush->win;
221     
222     switch(bd->style){
223     case DEBORDER_RIDGE:
224         draw_border(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh);
225     case DEBORDER_INLAID:
226         draw_border(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad);
227         draw_border(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl);
228         break;
229     case DEBORDER_GROOVE:
230         draw_border(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl);
231         draw_border(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad);
232         draw_border(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh);
233         break;
234     case DEBORDER_ELEVATED:
235     default:
236         draw_border(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh);
237         draw_border(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad);
238         break;
239     }
240 }
241
242
243 void debrush_do_draw_border(DEBrush *brush, WRectangle geom, 
244                             DEColourGroup *cg)
245 {
246     DEBorder *bd=&(brush->d->border);
247
248     switch(bd->sides){
249     case DEBORDER_ALL:
250         debrush_do_do_draw_border(brush, geom, cg);
251         break;
252     case DEBORDER_TB:
253         debrush_do_draw_padline(brush, geom, cg, GR_BORDERLINE_LEFT);
254         debrush_do_draw_padline(brush, geom, cg, GR_BORDERLINE_RIGHT);
255         debrush_do_draw_borderline(brush, geom, cg, GR_BORDERLINE_TOP);
256         debrush_do_draw_borderline(brush, geom, cg, GR_BORDERLINE_BOTTOM);
257         break;
258     case DEBORDER_LR:
259         debrush_do_draw_padline(brush, geom, cg, GR_BORDERLINE_TOP);
260         debrush_do_draw_padline(brush, geom, cg, GR_BORDERLINE_BOTTOM);
261         debrush_do_draw_borderline(brush, geom, cg, GR_BORDERLINE_LEFT);
262         debrush_do_draw_borderline(brush, geom, cg, GR_BORDERLINE_RIGHT);
263         break;
264     }
265 }
266
267     
268 void debrush_draw_border(DEBrush *brush, 
269                          const WRectangle *geom)
270 {
271     DEColourGroup *cg=debrush_get_current_colour_group(brush);
272     if(cg!=NULL)
273         debrush_do_draw_border(brush, *geom, cg);
274 }
275
276
277 /*}}}*/
278
279
280 /*{{{ Boxes */
281
282
283 static void copy_masked(DEBrush *brush, Drawable src, Drawable dst,
284                         int src_x, int src_y, int w, int h,
285                         int dst_x, int dst_y)
286 {
287     
288     GC copy_gc=brush->d->copy_gc;
289     
290     XSetClipMask(ioncore_g.dpy, copy_gc, src);
291     XSetClipOrigin(ioncore_g.dpy, copy_gc, dst_x, dst_y);
292     XCopyPlane(ioncore_g.dpy, src, dst, copy_gc, src_x, src_y, w, h,
293                dst_x, dst_y, 1);
294 }
295
296
297
298 #define ISSET(S, A) ((S)!=NULL && gr_stylespec_isset(S, A))
299
300
301 GR_DEFATTR(dragged);
302 GR_DEFATTR(tagged);
303 GR_DEFATTR(submenu);
304 GR_DEFATTR(numbered);
305 GR_DEFATTR(tabnumber);
306
307
308 static void ensure_attrs()
309 {
310     GR_ALLOCATTR_BEGIN;
311     GR_ALLOCATTR(dragged);
312     GR_ALLOCATTR(tagged);
313     GR_ALLOCATTR(submenu);
314     GR_ALLOCATTR(numbered);
315     GR_ALLOCATTR(tabnumber);
316     GR_ALLOCATTR_END;
317 }
318
319
320 static int get_ty(const WRectangle *g, const GrBorderWidths *bdw, 
321                   const GrFontExtents *fnte)
322 {
323     return (g->y+bdw->top+fnte->baseline
324             +(g->h-bdw->top-bdw->bottom-fnte->max_height)/2);
325 }
326
327
328 void debrush_tab_extras(DEBrush *brush, const WRectangle *g, 
329                         DEColourGroup *cg, const GrBorderWidths *bdw,
330                         const GrFontExtents *fnte,
331                         const GrStyleSpec *a1, 
332                         const GrStyleSpec *a2,
333                         bool pre, int index)
334 {
335     DEStyle *d=brush->d;
336     GC tmp;
337     /* Not thread-safe, but neither is the rest of the drawing code
338      * with shared GC:s.
339      */
340     static bool swapped=FALSE;
341
342     ensure_attrs();
343     
344     if(pre){
345         if(ISSET(a2, GR_ATTR(dragged)) || ISSET(a1, GR_ATTR(dragged))){
346             tmp=d->normal_gc;
347             d->normal_gc=d->stipple_gc;
348             d->stipple_gc=tmp;
349             swapped=TRUE;
350             XClearArea(ioncore_g.dpy, brush->win, g->x, g->y, g->w, g->h, False);
351         }
352         return;
353     }
354     
355     
356     if((ISSET(a1, GR_ATTR(numbered)) || ISSET(a2, GR_ATTR(numbered))) 
357        && index>=0){
358         
359         DEColourGroup *cg;
360         GrStyleSpec tmp;
361         
362         gr_stylespec_init(&tmp);
363         gr_stylespec_append(&tmp, a2);
364         gr_stylespec_set(&tmp, GR_ATTR(tabnumber));
365         
366         cg=debrush_get_colour_group2(brush, a1, &tmp);
367         
368         gr_stylespec_unalloc(&tmp);
369
370         if(cg!=NULL){
371             char *s=NULL;
372             
373             libtu_asprintf(&s, "[%d]", index+1);
374             
375             if(s!=NULL){
376                 int l=strlen(s);
377                 uint w=debrush_get_text_width(brush, s, l);
378                 if(w < g->w-bdw->right-bdw->left){
379                     int ty=get_ty(g, bdw, fnte);
380                     int tx=(d->textalign==DEALIGN_RIGHT
381                             ? g->x+bdw->left
382                             : g->x+g->w-bdw->right-w);
383                     debrush_do_draw_string(brush, tx, ty, s, l, TRUE, cg);
384                 }
385                 free(s);
386             }
387         }
388     }
389     
390     if(ISSET(a2, GR_ATTR(tagged)) || ISSET(a1, GR_ATTR(tagged))){
391         XSetForeground(ioncore_g.dpy, d->copy_gc, cg->fg);
392             
393         copy_masked(brush, d->tag_pixmap, brush->win, 0, 0,
394                     d->tag_pixmap_w, d->tag_pixmap_h,
395                     g->x+g->w-bdw->right-d->tag_pixmap_w, 
396                     g->y+bdw->top);
397     }
398     
399     if(swapped){
400         tmp=d->normal_gc;
401         d->normal_gc=d->stipple_gc;
402         d->stipple_gc=tmp;
403         swapped=FALSE;
404     }
405     /*if(MATCHES2("*-*-*-dragged", a1, a2)){
406         XFillRectangle(ioncore_g.dpy, win, d->stipple_gc, 
407                        g->x, g->y, g->w, g->h);
408     }*/
409 }
410
411
412 void debrush_menuentry_extras(DEBrush *brush, 
413                               const WRectangle *g, 
414                               DEColourGroup *cg, 
415                               const GrBorderWidths *bdw,
416                               const GrFontExtents *fnte,
417                               const GrStyleSpec *a1, 
418                               const GrStyleSpec *a2, 
419                               bool pre, int index)
420 {
421     int tx, ty;
422
423     if(pre)
424         return;
425     
426     ensure_attrs();
427     
428     if(ISSET(a2, GR_ATTR(submenu)) || ISSET(a1, GR_ATTR(submenu))){
429         ty=get_ty(g, bdw, fnte);
430         tx=g->x+g->w-bdw->right;
431
432         debrush_do_draw_string(brush, tx, ty, DE_SUB_IND, DE_SUB_IND_LEN, 
433                                FALSE, cg);
434     }
435 }
436
437
438 void debrush_do_draw_box(DEBrush *brush, const WRectangle *geom, 
439                          DEColourGroup *cg, bool needfill)
440 {
441     GC gc=brush->d->normal_gc;
442     
443     if(TRUE/*needfill*/){
444         XSetForeground(ioncore_g.dpy, gc, cg->bg);
445         XFillRectangle(ioncore_g.dpy, brush->win, gc, geom->x, geom->y, 
446                        geom->w, geom->h);
447     }
448     
449     debrush_do_draw_border(brush, *geom, cg);
450 }
451
452
453 static void debrush_do_draw_textbox(DEBrush *brush, 
454                                     const WRectangle *geom, 
455                                     const char *text, 
456                                     DEColourGroup *cg, 
457                                     bool needfill,
458                                     const GrStyleSpec *a1, 
459                                     const GrStyleSpec *a2,
460                                     int index)
461 {
462     uint len;
463     GrBorderWidths bdw;
464     GrFontExtents fnte;
465     uint tx, ty, tw;
466
467     grbrush_get_border_widths(&(brush->grbrush), &bdw);
468     grbrush_get_font_extents(&(brush->grbrush), &fnte);
469     
470     if(brush->extras_fn!=NULL)
471         brush->extras_fn(brush, geom, cg, &bdw, &fnte, a1, a2, TRUE, index);
472     
473     debrush_do_draw_box(brush, geom, cg, needfill);
474     
475     do{ /*...while(0)*/
476         if(text==NULL)
477             break;
478         
479         len=strlen(text);
480     
481         if(len==0)
482             break;
483     
484         if(brush->d->textalign!=DEALIGN_LEFT){
485             tw=grbrush_get_text_width((GrBrush*)brush, text, len);
486             
487             if(brush->d->textalign==DEALIGN_CENTER)
488                 tx=geom->x+bdw.left+(geom->w-bdw.left-bdw.right-tw)/2;
489             else
490                 tx=geom->x+geom->w-bdw.right-tw;
491         }else{
492             tx=geom->x+bdw.left;
493         }
494         
495         ty=get_ty(geom, &bdw, &fnte);
496         
497         debrush_do_draw_string(brush, tx, ty, text, len, FALSE, cg);
498     }while(0);
499     
500     if(brush->extras_fn!=NULL)
501         brush->extras_fn(brush, geom, cg, &bdw, &fnte, a1, a2, FALSE, index);
502 }
503
504
505 void debrush_draw_textbox(DEBrush *brush, const WRectangle *geom, 
506                           const char *text, bool needfill)
507 {
508     GrStyleSpec *attr=debrush_get_current_attr(brush);
509     DEColourGroup *cg=debrush_get_colour_group(brush, attr);
510     
511     if(cg!=NULL){
512         debrush_do_draw_textbox(brush, geom, text, cg, needfill, 
513                                 attr, NULL, -1);
514     }
515 }
516
517
518 void debrush_draw_textboxes(DEBrush *brush, const WRectangle *geom,
519                             int n, const GrTextElem *elem, 
520                             bool needfill)
521 {
522     GrStyleSpec *common_attrib;
523     WRectangle g=*geom;
524     DEColourGroup *cg;
525     GrBorderWidths bdw;
526     int i;
527     
528     common_attrib=debrush_get_current_attr(brush);
529     
530     grbrush_get_border_widths(&(brush->grbrush), &bdw);
531     
532     for(i=0; ; i++){
533         g.w=bdw.left+elem[i].iw+bdw.right;
534         cg=debrush_get_colour_group2(brush, common_attrib, &elem[i].attr);
535         
536         if(cg!=NULL){
537             debrush_do_draw_textbox(brush, &g, elem[i].text, cg, needfill,
538                                     common_attrib, &elem[i].attr, i);
539         }
540         
541         if(i==n-1)
542             break;
543         
544         g.x+=g.w;
545         if(bdw.spacing>0 && needfill){
546             XClearArea(ioncore_g.dpy, brush->win, g.x, g.y,
547                        brush->d->spacing, g.h, False);
548         }
549         g.x+=bdw.spacing;
550     }
551 }
552
553
554 /*}}}*/
555
556
557 /*{{{ Misc. */
558
559 #define MAXSHAPE 16
560
561 void debrush_set_window_shape(DEBrush *brush, bool rough,
562                               int n, const WRectangle *rects)
563 {
564     XRectangle r[MAXSHAPE];
565     int i;
566     
567     if(n>MAXSHAPE)
568         n=MAXSHAPE;
569     
570     if(n==0){
571         /* n==0 should clear the shape. As there's absolutely no
572          * documentation for XShape (as is typical of all sucky X
573          * extensions), I don't know how the shape should properly 
574          * be cleared. Thus we just use a huge rectangle.
575          */
576         n=1;
577         r[0].x=0;
578         r[0].y=0;
579         r[0].width=USHRT_MAX;
580         r[0].height=USHRT_MAX;
581     }else{
582         for(i=0; i<n; i++){
583             r[i].x=rects[i].x;
584             r[i].y=rects[i].y;
585             r[i].width=rects[i].w;
586             r[i].height=rects[i].h;
587         }
588     }
589     
590     XShapeCombineRectangles(ioncore_g.dpy, brush->win,
591                             ShapeBounding, 0, 0, r, n,
592                             ShapeSet, Unsorted);
593 }
594
595
596 void debrush_enable_transparency(DEBrush *brush, GrTransparency mode)
597 {
598     XSetWindowAttributes attr;
599     ulong attrflags=0;
600
601     if(mode==GR_TRANSPARENCY_DEFAULT)
602         mode=brush->d->transparency_mode;
603     
604     if(mode==GR_TRANSPARENCY_YES){
605         attrflags=CWBackPixmap;
606         attr.background_pixmap=ParentRelative;
607     }else{
608         attrflags=CWBackPixel;
609         attr.background_pixel=brush->d->cgrp.bg;
610     }
611     
612     XChangeWindowAttributes(ioncore_g.dpy, brush->win, attrflags, &attr);
613     XClearWindow(ioncore_g.dpy, brush->win);
614 }
615
616
617 void debrush_fill_area(DEBrush *brush, const WRectangle *geom)
618 {
619     DEColourGroup *cg=debrush_get_current_colour_group(brush);
620     GC gc=brush->d->normal_gc;
621
622     if(cg==NULL)
623         return;
624     
625     XSetForeground(ioncore_g.dpy, gc, cg->bg);
626     XFillRectangle(ioncore_g.dpy, brush->win, gc, 
627                    geom->x, geom->y, geom->w, geom->h);
628 }
629
630
631 void debrush_clear_area(DEBrush *brush, const WRectangle *geom)
632 {
633     XClearArea(ioncore_g.dpy, brush->win,
634                geom->x, geom->y, geom->w, geom->h, False);
635 }
636
637
638 /*}}}*/
639
640
641 /*{{{ Clipping rectangles */
642
643 /* Should actually set the clipping rectangle for all GC:s and use 
644  * window-specific GC:s to do this correctly...
645  */
646
647 static void debrush_set_clipping_rectangle(DEBrush *brush, 
648                                            const WRectangle *geom)
649 {
650     XRectangle rect;
651     
652     assert(!brush->clip_set);
653     
654     rect.x=geom->x;
655     rect.y=geom->y;
656     rect.width=geom->w;
657     rect.height=geom->h;
658     
659     XSetClipRectangles(ioncore_g.dpy, brush->d->normal_gc,
660                        0, 0, &rect, 1, Unsorted);
661     brush->clip_set=TRUE;
662 }
663
664
665 static void debrush_clear_clipping_rectangle(DEBrush *brush)
666 {
667     if(brush->clip_set){
668         XSetClipMask(ioncore_g.dpy, brush->d->normal_gc, None);
669         brush->clip_set=FALSE;
670     }
671 }
672
673
674 /*}}}*/
675
676
677 /*{{{ debrush_begin/end */
678
679
680 void debrush_begin(DEBrush *brush, const WRectangle *geom, int flags)
681 {
682     
683     if(flags&GRBRUSH_AMEND)
684         flags|=GRBRUSH_NO_CLEAR_OK;
685     
686     if(!(flags&GRBRUSH_KEEP_ATTR))
687         debrush_init_attr(brush, NULL);
688     
689     if(!(flags&GRBRUSH_NO_CLEAR_OK))
690         debrush_clear_area(brush, geom);
691     
692     if(flags&GRBRUSH_NEED_CLIP)
693         debrush_set_clipping_rectangle(brush, geom);
694 }
695
696
697 void debrush_end(DEBrush *brush)
698 {
699     debrush_clear_clipping_rectangle(brush);
700 }
701
702
703 /*}}}*/
704