add EPA stuff from 2.X
[ardour.git] / libs / gnomecanvas / libgnomecanvas / gnome-canvas-util.c
1 /*
2  * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
3  * All rights reserved.
4  *
5  * This file is part of the Gnome Library.
6  *
7  * The Gnome Library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * The Gnome Library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
19  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22 /*
23   @NOTATION@
24  */
25 /* Miscellaneous utility functions for the GnomeCanvas widget
26  *
27  * GnomeCanvas is basically a port of the Tk toolkit's most excellent canvas widget.  Tk is
28  * copyrighted by the Regents of the University of California, Sun Microsystems, and other parties.
29  *
30  *
31  * Author: Federico Mena <federico@nuclecu.unam.mx>
32  */
33
34 #include <config.h>
35
36 /* needed for M_PI_2 under 'gcc -ansi -predantic' on GNU/Linux */
37 #ifndef _BSD_SOURCE
38 #  define _BSD_SOURCE 1
39 #endif
40 #include <sys/types.h>
41
42 #include <glib.h>
43 #include <math.h>
44 #include "gnome-canvas.h"
45 #include "gnome-canvas-util.h"
46 #include <libart_lgpl/art_uta.h>
47 #include <libart_lgpl/art_svp.h>
48 #include <libart_lgpl/art_svp_ops.h>
49 #include <libart_lgpl/art_rgb.h>
50 #include <libart_lgpl/art_rgb_svp.h>
51 #include <libart_lgpl/art_uta_svp.h>
52 #include <libart_lgpl/art_rect_svp.h>
53
54 /**
55  * gnome_canvas_points_new:
56  * @num_points: The number of points to allocate space for in the array.
57  * 
58  * Creates a structure that should be used to pass an array of points to
59  * items.
60  * 
61  * Return value: A newly-created array of points.  It should be filled in
62  * by the user.
63  **/
64 GnomeCanvasPoints *
65 gnome_canvas_points_new (int num_points)
66 {
67         GnomeCanvasPoints *points;
68
69         g_return_val_if_fail (num_points > 1, NULL);
70
71         points = g_new (GnomeCanvasPoints, 1);
72         points->num_points = num_points;
73         points->coords = g_new (double, 2 * num_points);
74         points->ref_count = 1;
75
76         return points;
77 }
78
79 /**
80  * gnome_canvas_points_ref:
81  * @points: A canvas points structure.
82  * 
83  * Increases the reference count of the specified points structure.
84  * 
85  * Return value: The canvas points structure itself.
86  **/
87 GnomeCanvasPoints *
88 gnome_canvas_points_ref (GnomeCanvasPoints *points)
89 {
90         g_return_val_if_fail (points != NULL, NULL);
91
92         points->ref_count += 1;
93         return points;
94 }
95
96 /**
97  * gnome_canvas_points_free:
98  * @points: A canvas points structure.
99  * 
100  * Decreases the reference count of the specified points structure.  If it
101  * reaches zero, then the structure is freed.
102  **/
103 void
104 gnome_canvas_points_free (GnomeCanvasPoints *points)
105 {
106         g_return_if_fail (points != NULL);
107
108         points->ref_count -= 1;
109         if (points->ref_count == 0) {
110                 g_free (points->coords);
111                 g_free (points);
112         }
113 }
114
115 /**
116  * gnome_canvas_get_miter_points:
117  * @x1: X coordinate of the first point
118  * @y1: Y coordinate of the first point
119  * @x2: X coordinate of the second (angle) point
120  * @y2: Y coordinate of the second (angle) point
121  * @x3: X coordinate of the third point
122  * @y3: Y coordinate of the third point
123  * @width: Width of the line
124  * @mx1: The X coordinate of the first miter point is returned here.
125  * @my1: The Y coordinate of the first miter point is returned here.
126  * @mx2: The X coordinate of the second miter point is returned here.
127  * @my2: The Y coordinate of the second miter point is returned here.
128  * 
129  * Given three points forming an angle, computes the coordinates of the inside
130  * and outside points of the mitered corner formed by a line of a given width at
131  * that angle.
132  * 
133  * Return value: FALSE if the angle is less than 11 degrees (this is the same
134  * threshold as X uses.  If this occurs, the return points are not modified.
135  * Otherwise, returns TRUE.
136  **/
137 int
138 gnome_canvas_get_miter_points (double x1, double y1, double x2, double y2, double x3, double y3,
139                                double width,
140                                double *mx1, double *my1, double *mx2, double *my2)
141 {
142         double theta1;          /* angle of segment p2-p1 */
143         double theta2;          /* angle of segment p2-p3 */
144         double theta;           /* angle between line segments */
145         double theta3;          /* angle that bisects theta1 and theta2 and points to p1 */
146         double dist;            /* distance of miter points from p2 */
147         double dx, dy;          /* x and y offsets corresponding to dist */
148
149 #define ELEVEN_DEGREES (11.0 * G_PI / 180.0)
150
151         if (y2 == y1)
152                 theta1 = (x2 < x1) ? 0.0 : G_PI;
153         else if (x2 == x1)
154                 theta1 = (y2 < y1) ? G_PI_2 : -G_PI_2;
155         else
156                 theta1 = atan2 (y1 - y2, x1 - x2);
157
158         if (y3 == y2)
159                 theta2 = (x3 > x2) ? 0 : G_PI;
160         else if (x3 == x2)
161                 theta2 = (y3 > y2) ? G_PI_2 : -G_PI_2;
162         else
163                 theta2 = atan2 (y3 - y2, x3 - x2);
164
165         theta = theta1 - theta2;
166
167         if (theta > G_PI)
168                 theta -= 2.0 * G_PI;
169         else if (theta < -G_PI)
170                 theta += 2.0 * G_PI;
171
172         if ((theta < ELEVEN_DEGREES) && (theta > -ELEVEN_DEGREES))
173                 return FALSE;
174
175         dist = 0.5 * width / sin (0.5 * theta);
176         if (dist < 0.0)
177                 dist = -dist;
178
179         theta3 = (theta1 + theta2) / 2.0;
180         if (sin (theta3 - (theta1 + G_PI)) < 0.0)
181                 theta3 += G_PI;
182
183         dx = dist * cos (theta3);
184         dy = dist * sin (theta3);
185
186         *mx1 = x2 + dx;
187         *mx2 = x2 - dx;
188         *my1 = y2 + dy;
189         *my2 = y2 - dy;
190
191         return TRUE;
192 }
193
194 /**
195  * gnome_canvas_get_butt_points:
196  * @x1: X coordinate of first point in the line
197  * @y1: Y cooordinate of first point in the line
198  * @x2: X coordinate of second point (endpoint) of the line
199  * @y2: Y coordinate of second point (endpoint) of the line
200  * @width: Width of the line
201  * @project: Whether the butt points should project out by width/2 distance
202  * @bx1: X coordinate of first butt point is returned here
203  * @by1: Y coordinate of first butt point is returned here
204  * @bx2: X coordinate of second butt point is returned here
205  * @by2: Y coordinate of second butt point is returned here
206  * 
207  * Computes the butt points of a line segment.
208  **/
209 void
210 gnome_canvas_get_butt_points (double x1, double y1, double x2, double y2,
211                               double width, int project,
212                               double *bx1, double *by1, double *bx2, double *by2)
213 {
214         double length;
215         double dx, dy;
216
217         width *= 0.5;
218         dx = x2 - x1;
219         dy = y2 - y1;
220         length = sqrt (dx * dx + dy * dy);
221
222         if (length < GNOME_CANVAS_EPSILON) {
223                 *bx1 = *bx2 = x2;
224                 *by1 = *by2 = y2;
225         } else {
226                 dx = -width * (y2 - y1) / length;
227                 dy = width * (x2 - x1) / length;
228
229                 *bx1 = x2 + dx;
230                 *bx2 = x2 - dx;
231                 *by1 = y2 + dy;
232                 *by2 = y2 - dy;
233
234                 if (project) {
235                         *bx1 += dy;
236                         *bx2 += dy;
237                         *by1 -= dx;
238                         *by2 -= dx;
239                 }
240         }
241 }
242
243 /**
244  * gnome_canvas_polygon_to_point:
245  * @poly: Vertices of the polygon.  X coordinates are in the even indices, and Y
246  * coordinates are in the odd indices
247  * @num_points: Number of points in the polygon
248  * @x: X coordinate of the point
249  * @y: Y coordinate of the point
250  * 
251  * Computes the distance between a point and a polygon.
252  * 
253  * Return value: The distance from the point to the polygon, or zero if the
254  * point is inside the polygon.
255  **/
256 double
257 gnome_canvas_polygon_to_point (double *poly, int num_points, double x, double y)
258 {
259         double best;
260         int intersections;
261         int i;
262         double *p;
263         double dx, dy;
264
265         /* Iterate through all the edges in the polygon, updating best and intersections.
266          *
267          * When computing intersections, include left X coordinate of line within its range, but not
268          * Y coordinate.  Otherwise if the point lies exactly below a vertex we'll count it as two
269          * intersections.
270          */
271
272         best = 1.0e36;
273         intersections = 0;
274
275         for (i = num_points, p = poly; i > 1; i--, p += 2) {
276                 double px, py, dist;
277
278                 /* Compute the point on the current edge closest to the point and update the
279                  * intersection count.  This must be done separately for vertical edges, horizontal
280                  * edges, and others.
281                  */
282
283                 if (p[2] == p[0]) {
284                         /* Vertical edge */
285
286                         px = p[0];
287
288                         if (p[1] >= p[3]) {
289                                 py = MIN (p[1], y);
290                                 py = MAX (py, p[3]);
291                         } else {
292                                 py = MIN (p[3], y);
293                                 py = MAX (py, p[1]);
294                         }
295                 } else if (p[3] == p[1]) {
296                         /* Horizontal edge */
297
298                         py = p[1];
299
300                         if (p[0] >= p[2]) {
301                                 px = MIN (p[0], x);
302                                 px = MAX (px, p[2]);
303
304                                 if ((y < py) && (x < p[0]) && (x >= p[2]))
305                                         intersections++;
306                         } else {
307                                 px = MIN (p[2], x);
308                                 px = MAX (px, p[0]);
309
310                                 if ((y < py) && (x < p[2]) && (x >= p[0]))
311                                         intersections++;
312                         }
313                 } else {
314                         double m1, b1, m2, b2;
315                         int lower;
316
317                         /* Diagonal edge.  Convert the edge to a line equation (y = m1*x + b1), then
318                          * compute a line perpendicular to this edge but passing through the point,
319                          * (y = m2*x + b2).
320                          */
321
322                         m1 = (p[3] - p[1]) / (p[2] - p[0]);
323                         b1 = p[1] - m1 * p[0];
324
325                         m2 = -1.0 / m1;
326                         b2 = y - m2 * x;
327
328                         px = (b2 - b1) / (m1 - m2);
329                         py = m1 * px + b1;
330
331                         if (p[0] > p[2]) {
332                                 if (px > p[0]) {
333                                         px = p[0];
334                                         py = p[1];
335                                 } else if (px < p[2]) {
336                                         px = p[2];
337                                         py = p[3];
338                                 }
339                         } else {
340                                 if (px > p[2]) {
341                                         px = p[2];
342                                         py = p[3];
343                                 } else if (px < p[0]) {
344                                         px = p[0];
345                                         py = p[1];
346                                 }
347                         }
348
349                         lower = (m1 * x + b1) > y;
350
351                         if (lower && (x >= MIN (p[0], p[2])) && (x < MAX (p[0], p[2])))
352                                 intersections++;
353                 }
354
355                 /* Compute the distance to the closest point, and see if that is the best so far */
356
357                 dx = x - px;
358                 dy = y - py;
359                 dist = sqrt (dx * dx + dy * dy);
360                 if (dist < best)
361                         best = dist;
362         }
363
364         /* We've processed all the points.  If the number of intersections is odd, the point is
365          * inside the polygon.
366          */
367
368         if (intersections & 0x1)
369                 return 0.0;
370         else
371                 return best;
372 }
373
374 /* Here are some helper functions for aa rendering: */
375
376 /**
377  * gnome_canvas_render_svp:
378  * @buf: the canvas buffer to render over
379  * @svp: the vector path to render
380  * @rgba: the rgba color to render
381  *
382  * Render the svp over the buf.
383  **/
384 void
385 gnome_canvas_render_svp (GnomeCanvasBuf *buf, ArtSVP *svp, guint32 rgba)
386 {
387         guint32 fg_color, bg_color;
388         int alpha;
389
390         if (buf->is_bg) {
391                 bg_color = buf->bg_color;
392                 alpha = rgba & 0xff;
393                 if (alpha == 0xff)
394                         fg_color = rgba >> 8;
395                 else {
396                         /* composite over background color */
397                         int bg_r, bg_g, bg_b;
398                         int fg_r, fg_g, fg_b;
399                         int tmp;
400
401                         bg_r = (bg_color >> 16) & 0xff;
402                         fg_r = (rgba >> 24) & 0xff;
403                         tmp = (fg_r - bg_r) * alpha;
404                         fg_r = bg_r + ((tmp + (tmp >> 8) + 0x80) >> 8);
405
406                         bg_g = (bg_color >> 8) & 0xff;
407                         fg_g = (rgba >> 16) & 0xff;
408                         tmp = (fg_g - bg_g) * alpha;
409                         fg_g = bg_g + ((tmp + (tmp >> 8) + 0x80) >> 8);
410
411                         bg_b = bg_color & 0xff;
412                         fg_b = (rgba >> 8) & 0xff;
413                         tmp = (fg_b - bg_b) * alpha;
414                         fg_b = bg_b + ((tmp + (tmp >> 8) + 0x80) >> 8);
415
416                         fg_color = (fg_r << 16) | (fg_g << 8) | fg_b;
417                 }
418                 art_rgb_svp_aa (svp,
419                                 buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1,
420                                 fg_color, bg_color,
421                                 buf->buf, buf->buf_rowstride,
422                                 NULL);
423                 buf->is_bg = 0;
424                 buf->is_buf = 1;
425         } else {
426                 art_rgb_svp_alpha (svp,
427                                    buf->rect.x0, buf->rect.y0, buf->rect.x1, buf->rect.y1,
428                                    rgba,
429                                    buf->buf, buf->buf_rowstride,
430                                    NULL);
431         }
432 }
433
434 /**
435  * gnome_canvas_update_svp:
436  * @canvas: the canvas containing the svp that needs updating.
437  * @p_svp: a pointer to the existing svp
438  * @new_svp: the new svp
439  *
440  * Sets the svp to the new value, requesting repaint on what's changed. This
441  * function takes responsibility for freeing new_svp.
442  **/
443 void
444 gnome_canvas_update_svp (GnomeCanvas *canvas, ArtSVP **p_svp, ArtSVP *new_svp)
445 {
446         ArtSVP *old_svp;
447         ArtSVP *diff G_GNUC_UNUSED;
448         ArtUta *repaint_uta;
449
450         old_svp = *p_svp;
451
452         if (old_svp != NULL) {
453                 ArtDRect bb;
454                 art_drect_svp (&bb, old_svp);
455                 if ((bb.x1 - bb.x0) * (bb.y1 - bb.y0) > (64 * 64)) {
456                         repaint_uta = art_uta_from_svp (old_svp);
457                         gnome_canvas_request_redraw_uta (canvas, repaint_uta);
458                 } else {
459                         ArtIRect ib;
460                         art_drect_to_irect (&ib, &bb);
461                         gnome_canvas_request_redraw (canvas, ib.x0, ib.y0, ib.x1, ib.y1);
462                 }
463                 art_svp_free (old_svp);
464         }
465
466         if (new_svp != NULL) {
467                 ArtDRect bb;
468                 art_drect_svp (&bb, new_svp);
469                 if ((bb.x1 - bb.x0) * (bb.y1 - bb.y0) > (64 * 64)) {
470                         repaint_uta = art_uta_from_svp (new_svp);
471                         gnome_canvas_request_redraw_uta (canvas, repaint_uta);
472                 } else {
473                         ArtIRect ib;
474                         art_drect_to_irect (&ib, &bb);
475                         gnome_canvas_request_redraw (canvas, ib.x0, ib.y0, ib.x1, ib.y1);
476                 }
477         }
478
479         *p_svp = new_svp;
480 }
481
482 /**
483  * gnome_canvas_update_svp_clip:
484  * @canvas: the canvas containing the svp that needs updating.
485  * @p_svp: a pointer to the existing svp
486  * @new_svp: the new svp
487  * @clip_svp: a clip path, if non-null
488  *
489  * Sets the svp to the new value, clipping if necessary, and requesting repaint
490  * on what's changed. This function takes responsibility for freeing new_svp.
491  **/
492 void
493 gnome_canvas_update_svp_clip (GnomeCanvas *canvas, ArtSVP **p_svp, ArtSVP *new_svp, ArtSVP *clip_svp)
494 {
495         ArtSVP *clipped_svp;
496
497         if (clip_svp != NULL) {
498                 clipped_svp = art_svp_intersect (new_svp, clip_svp);
499                 art_svp_free (new_svp);
500         } else {
501                 clipped_svp = new_svp;
502         }
503         gnome_canvas_update_svp (canvas, p_svp, clipped_svp);
504 }
505
506 /**
507  * gnome_canvas_item_reset_bounds:
508  * @item: A canvas item
509  * 
510  * Resets the bounding box of a canvas item to an empty rectangle.
511  **/
512 void
513 gnome_canvas_item_reset_bounds (GnomeCanvasItem *item)
514 {
515         item->x1 = 0.0;
516         item->y1 = 0.0;
517         item->x2 = 0.0;
518         item->y2 = 0.0;
519 }
520
521 /**
522  * gnome_canvas_item_update_svp:
523  * @item: the canvas item containing the svp that needs updating.
524  * @p_svp: a pointer to the existing svp
525  * @new_svp: the new svp
526  *
527  * Sets the svp to the new value, requesting repaint on what's changed. This
528  * function takes responsibility for freeing new_svp. This routine also adds the
529  * svp's bbox to the item's.
530  **/
531 void
532 gnome_canvas_item_update_svp (GnomeCanvasItem *item, ArtSVP **p_svp, ArtSVP *new_svp)
533 {
534         ArtDRect bbox;
535
536         gnome_canvas_update_svp (item->canvas, p_svp, new_svp);
537         if (new_svp) {
538                 bbox.x0 = item->x1;
539                 bbox.y0 = item->y1;
540                 bbox.x1 = item->x2;
541                 bbox.y1 = item->y2;
542                 art_drect_svp_union (&bbox, new_svp);
543                 item->x1 = bbox.x0;
544                 item->y1 = bbox.y0;
545                 item->x2 = bbox.x1;
546                 item->y2 = bbox.y1;
547         }
548 }
549
550 /**
551  * gnome_canvas_item_update_svp_clip:
552  * @item: the canvas item containing the svp that needs updating.
553  * @p_svp: a pointer to the existing svp
554  * @new_svp: the new svp
555  * @clip_svp: a clip path, if non-null
556  *
557  * Sets the svp to the new value, clipping if necessary, and requesting repaint
558  * on what's changed. This function takes responsibility for freeing new_svp.
559  **/
560 void
561 gnome_canvas_item_update_svp_clip (GnomeCanvasItem *item, ArtSVP **p_svp, ArtSVP *new_svp,
562                                    ArtSVP *clip_svp)
563 {
564         ArtSVP *clipped_svp;
565
566         if (clip_svp != NULL) {
567                 clipped_svp = art_svp_intersect (new_svp, clip_svp);
568                 art_svp_free (new_svp);
569         } else {
570                 clipped_svp = new_svp;
571         }
572
573         gnome_canvas_item_update_svp (item, p_svp, clipped_svp);
574 }
575
576 /**
577  * gnome_canvas_item_request_redraw_svp
578  * @item: the item containing the svp
579  * @svp: the svp that needs to be redrawn
580  *
581  * Request redraw of the svp if in aa mode, or the entire item in in xlib mode.
582  **/ 
583 void
584 gnome_canvas_item_request_redraw_svp (GnomeCanvasItem *item, const ArtSVP *svp)
585 {
586         GnomeCanvas *canvas;
587         ArtUta *uta;
588
589         canvas = item->canvas;
590         if (canvas->aa) {
591                 if (svp != NULL) {
592                         uta = art_uta_from_svp (svp);
593                         gnome_canvas_request_redraw_uta (canvas, uta);
594                 }
595         } else {
596                 gnome_canvas_request_redraw (canvas, item->x1, item->y1, item->x2, item->y2);           
597         }
598 }
599
600 /**
601  * gnome_canvas_update_bbox:
602  * @item: the canvas item needing update
603  * @x1: Left coordinate of the new bounding box
604  * @y1: Top coordinate of the new bounding box
605  * @x2: Right coordinate of the new bounding box
606  * @y2: Bottom coordinate of the new bounding box
607  *
608  * Sets the bbox to the new value, requesting full repaint.
609  **/
610 void
611 gnome_canvas_update_bbox (GnomeCanvasItem *item, int x1, int y1, int x2, int y2)
612 {
613         gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2);
614         item->x1 = x1;
615         item->y1 = y1;
616         item->x2 = x2;
617         item->y2 = y2;
618         gnome_canvas_request_redraw (item->canvas, item->x1, item->y1, item->x2, item->y2);
619 }
620
621 /**
622  * gnome_canvas_buf_ensure_buf:
623  * @buf: the buf that needs to be represened in RGB format
624  *
625  * Ensure that the buffer is in RGB format, suitable for compositing.
626  **/
627 void
628 gnome_canvas_buf_ensure_buf (GnomeCanvasBuf *buf)
629 {
630         guchar *bufptr;
631         int y;
632
633         if (!buf->is_buf) {
634                 bufptr = buf->buf;
635                 for (y = buf->rect.y0; y < buf->rect.y1; y++) {
636                         art_rgb_fill_run (bufptr,
637                                           buf->bg_color >> 16,
638                                           (buf->bg_color >> 8) & 0xff,
639                                           buf->bg_color & 0xff,
640                                           buf->rect.x1 - buf->rect.x0);
641                         bufptr += buf->buf_rowstride;
642                 }
643                 buf->is_buf = 1;
644         }
645 }
646
647 /**
648  * gnome_canvas_join_gdk_to_art
649  * @gdk_join: a join type, represented in GDK format
650  *
651  * Convert from GDK line join specifier to libart.
652  *
653  * Return value: The line join specifier in libart format.
654  **/
655 ArtPathStrokeJoinType
656 gnome_canvas_join_gdk_to_art (GdkJoinStyle gdk_join)
657 {
658         switch (gdk_join) {
659         case GDK_JOIN_MITER:
660                 return ART_PATH_STROKE_JOIN_MITER;
661
662         case GDK_JOIN_ROUND:
663                 return ART_PATH_STROKE_JOIN_ROUND;
664
665         case GDK_JOIN_BEVEL:
666                 return ART_PATH_STROKE_JOIN_BEVEL;
667
668         default:
669                 g_assert_not_reached ();
670                 return ART_PATH_STROKE_JOIN_MITER; /* shut up the compiler */
671         }
672 }
673
674 /**
675  * gnome_canvas_cap_gdk_to_art
676  * @gdk_cap: a cap type, represented in GDK format
677  *
678  * Convert from GDK line cap specifier to libart.
679  *
680  * Return value: The line cap specifier in libart format.
681  **/
682 ArtPathStrokeCapType
683 gnome_canvas_cap_gdk_to_art (GdkCapStyle gdk_cap)
684 {
685         switch (gdk_cap) {
686         case GDK_CAP_BUTT:
687         case GDK_CAP_NOT_LAST:
688                 return ART_PATH_STROKE_CAP_BUTT;
689
690         case GDK_CAP_ROUND:
691                 return ART_PATH_STROKE_CAP_ROUND;
692
693         case GDK_CAP_PROJECTING:
694                 return ART_PATH_STROKE_CAP_SQUARE;
695
696         default:
697                 g_assert_not_reached ();
698                 return ART_PATH_STROKE_CAP_BUTT; /* shut up the compiler */
699         }
700 }