| 1 | /* $Id$ $Revision$ */ | 
|---|
| 2 | /* vim:set shiftwidth=4 ts=8: */ | 
|---|
| 3 |  | 
|---|
| 4 | /************************************************************************* | 
|---|
| 5 | * Copyright (c) 2011 AT&T Intellectual Property | 
|---|
| 6 | * All rights reserved. This program and the accompanying materials | 
|---|
| 7 | * are made available under the terms of the Eclipse Public License v1.0 | 
|---|
| 8 | * which accompanies this distribution, and is available at | 
|---|
| 9 | * http://www.eclipse.org/legal/epl-v10.html | 
|---|
| 10 | * | 
|---|
| 11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ | 
|---|
| 12 | *************************************************************************/ | 
|---|
| 13 |  | 
|---|
| 14 |  | 
|---|
| 15 | #include "render.h" | 
|---|
| 16 |  | 
|---|
| 17 | #define EPSILON .0001 | 
|---|
| 18 |  | 
|---|
| 19 | /* standard arrow length in points */ | 
|---|
| 20 | #define ARROW_LENGTH 10. | 
|---|
| 21 |  | 
|---|
| 22 | #define NUMB_OF_ARROW_HEADS  4 | 
|---|
| 23 | /* each arrow in 8 bits.  Room for NUMB_OF_ARROW_HEADS arrows in 32 bit int. */ | 
|---|
| 24 |  | 
|---|
| 25 | #define BITS_PER_ARROW 8 | 
|---|
| 26 |  | 
|---|
| 27 | #define BITS_PER_ARROW_TYPE 4 | 
|---|
| 28 | /* arrow types (in BITS_PER_ARROW_TYPE bits) */ | 
|---|
| 29 | #define ARR_TYPE_NONE	  (ARR_NONE) | 
|---|
| 30 | #define ARR_TYPE_NORM	  1 | 
|---|
| 31 | #define ARR_TYPE_CROW	  2 | 
|---|
| 32 | #define ARR_TYPE_TEE	  3 | 
|---|
| 33 | #define ARR_TYPE_BOX	  4 | 
|---|
| 34 | #define ARR_TYPE_DIAMOND  5 | 
|---|
| 35 | #define ARR_TYPE_DOT      6 | 
|---|
| 36 | #define ARR_TYPE_CURVE    7 | 
|---|
| 37 | #define ARR_TYPE_GAP      8 | 
|---|
| 38 | /* Spare: 9-15 */ | 
|---|
| 39 |  | 
|---|
| 40 | /* arrow mods (in (BITS_PER_ARROW - BITS_PER_ARROW_TYPE) bits) */ | 
|---|
| 41 | #define ARR_MOD_OPEN      (1<<(BITS_PER_ARROW_TYPE+0)) | 
|---|
| 42 | #define ARR_MOD_INV       (1<<(BITS_PER_ARROW_TYPE+1)) | 
|---|
| 43 | #define ARR_MOD_LEFT      (1<<(BITS_PER_ARROW_TYPE+2)) | 
|---|
| 44 | #define ARR_MOD_RIGHT     (1<<(BITS_PER_ARROW_TYPE+3)) | 
|---|
| 45 | /* No spares */ | 
|---|
| 46 |  | 
|---|
| 47 | typedef struct arrowdir_t { | 
|---|
| 48 | char *dir; | 
|---|
| 49 | int sflag; | 
|---|
| 50 | int eflag; | 
|---|
| 51 | } arrowdir_t; | 
|---|
| 52 |  | 
|---|
| 53 | static arrowdir_t Arrowdirs[] = { | 
|---|
| 54 | { "forward", ARR_TYPE_NONE, ARR_TYPE_NORM}, | 
|---|
| 55 | { "back", ARR_TYPE_NORM, ARR_TYPE_NONE}, | 
|---|
| 56 | { "both", ARR_TYPE_NORM, ARR_TYPE_NORM}, | 
|---|
| 57 | { "none", ARR_TYPE_NONE, ARR_TYPE_NONE}, | 
|---|
| 58 | {(char *) 0, ARR_TYPE_NONE, ARR_TYPE_NONE} | 
|---|
| 59 | }; | 
|---|
| 60 |  | 
|---|
| 61 | typedef struct arrowname_t { | 
|---|
| 62 | char *name; | 
|---|
| 63 | int type; | 
|---|
| 64 | } arrowname_t; | 
|---|
| 65 |  | 
|---|
| 66 | static arrowname_t Arrowsynonyms[] = { | 
|---|
| 67 | /* synonyms for deprecated arrow names - included for backward compatibility */ | 
|---|
| 68 | /*  evaluated before primary names else "invempty" would give different results */ | 
|---|
| 69 | { "invempty", (ARR_TYPE_NORM | ARR_MOD_INV | ARR_MOD_OPEN)},	/* oinv     */ | 
|---|
| 70 | {(char *) 0, (ARR_TYPE_NONE)} | 
|---|
| 71 | }; | 
|---|
| 72 |  | 
|---|
| 73 | static arrowname_t Arrowmods[] = { | 
|---|
| 74 | { "o", ARR_MOD_OPEN}, | 
|---|
| 75 | { "r", ARR_MOD_RIGHT}, | 
|---|
| 76 | { "l", ARR_MOD_LEFT}, | 
|---|
| 77 | /* deprecated alternates for backward compat */ | 
|---|
| 78 | { "e", ARR_MOD_OPEN},	/* o  - needed for "ediamond" */ | 
|---|
| 79 | { "half", ARR_MOD_LEFT},	/* l  - needed for "halfopen" */ | 
|---|
| 80 | {(char *) 0, ARR_TYPE_NONE} | 
|---|
| 81 | }; | 
|---|
| 82 |  | 
|---|
| 83 | static arrowname_t Arrownames[] = { | 
|---|
| 84 | { "normal", ARR_TYPE_NORM}, | 
|---|
| 85 | { "crow", ARR_TYPE_CROW}, | 
|---|
| 86 | { "tee", ARR_TYPE_TEE}, | 
|---|
| 87 | { "box", ARR_TYPE_BOX}, | 
|---|
| 88 | { "diamond", ARR_TYPE_DIAMOND}, | 
|---|
| 89 | { "dot", ARR_TYPE_DOT}, | 
|---|
| 90 | //    {"none", ARR_TYPE_NONE}, | 
|---|
| 91 | { "none", ARR_TYPE_GAP}, | 
|---|
| 92 | //    {"gap", ARR_TYPE_GAP}, | 
|---|
| 93 | /* ARR_MOD_INV is used only here to define two additional shapes | 
|---|
| 94 | since not all types can use it */ | 
|---|
| 95 | { "inv", (ARR_TYPE_NORM | ARR_MOD_INV)}, | 
|---|
| 96 | { "vee", (ARR_TYPE_CROW | ARR_MOD_INV)}, | 
|---|
| 97 | /* WARNING ugly kludge to deal with "o" v "open" conflict */ | 
|---|
| 98 | /* Define "open" as just "pen" since "o" already taken as ARR_MOD_OPEN */ | 
|---|
| 99 | /* Note that ARR_MOD_OPEN has no meaning for ARR_TYPE_CROW shape */ | 
|---|
| 100 | { "pen", (ARR_TYPE_CROW | ARR_MOD_INV)}, | 
|---|
| 101 | /* WARNING ugly kludge to deal with "e" v "empty" conflict */ | 
|---|
| 102 | /* Define "empty" as just "mpty" since "e" already taken as ARR_MOD_OPEN */ | 
|---|
| 103 | /* Note that ARR_MOD_OPEN has expected meaning for ARR_TYPE_NORM shape */ | 
|---|
| 104 | { "mpty", ARR_TYPE_NORM}, | 
|---|
| 105 | { "curve", ARR_TYPE_CURVE}, | 
|---|
| 106 | { "icurve", (ARR_TYPE_CURVE | ARR_MOD_INV)}, | 
|---|
| 107 | {(char *) 0, ARR_TYPE_NONE} | 
|---|
| 108 | }; | 
|---|
| 109 |  | 
|---|
| 110 | typedef struct arrowtype_t { | 
|---|
| 111 | int type; | 
|---|
| 112 | double lenfact;		/* ratio of length of this arrow type to standard arrow */ | 
|---|
| 113 | void (*gen) (GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag);	/* generator function for type */ | 
|---|
| 114 | } arrowtype_t; | 
|---|
| 115 |  | 
|---|
| 116 | /* forward declaration of functions used in Arrowtypes[] */ | 
|---|
| 117 | static void arrow_type_normal(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag); | 
|---|
| 118 | static void arrow_type_crow(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag); | 
|---|
| 119 | static void arrow_type_tee(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag); | 
|---|
| 120 | static void arrow_type_box(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag); | 
|---|
| 121 | static void arrow_type_diamond(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag); | 
|---|
| 122 | static void arrow_type_dot(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag); | 
|---|
| 123 | static void arrow_type_curve(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag); | 
|---|
| 124 | static void arrow_type_gap(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag); | 
|---|
| 125 |  | 
|---|
| 126 | static arrowtype_t Arrowtypes[] = { | 
|---|
| 127 | {ARR_TYPE_NORM, 1.0, arrow_type_normal}, | 
|---|
| 128 | {ARR_TYPE_CROW, 1.0, arrow_type_crow}, | 
|---|
| 129 | {ARR_TYPE_TEE, 0.5, arrow_type_tee}, | 
|---|
| 130 | {ARR_TYPE_BOX, 1.0, arrow_type_box}, | 
|---|
| 131 | {ARR_TYPE_DIAMOND, 1.2, arrow_type_diamond}, | 
|---|
| 132 | {ARR_TYPE_DOT, 0.8, arrow_type_dot}, | 
|---|
| 133 | {ARR_TYPE_CURVE, 1.0, arrow_type_curve}, | 
|---|
| 134 | {ARR_TYPE_GAP, 0.5, arrow_type_gap}, | 
|---|
| 135 | {ARR_TYPE_NONE, 0.0, NULL} | 
|---|
| 136 | }; | 
|---|
| 137 |  | 
|---|
| 138 | static char *arrow_match_name_frag(char *name, arrowname_t * arrownames, int *flag) | 
|---|
| 139 | { | 
|---|
| 140 | arrowname_t *arrowname; | 
|---|
| 141 | size_t namelen = 0; | 
|---|
| 142 | char *rest = name; | 
|---|
| 143 |  | 
|---|
| 144 | for (arrowname = arrownames; arrowname->name; arrowname++) { | 
|---|
| 145 | namelen = strlen(arrowname->name); | 
|---|
| 146 | if (strncmp(name, arrowname->name, namelen) == 0) { | 
|---|
| 147 | *flag |= arrowname->type; | 
|---|
| 148 | rest += namelen; | 
|---|
| 149 | break; | 
|---|
| 150 | } | 
|---|
| 151 | } | 
|---|
| 152 | return rest; | 
|---|
| 153 | } | 
|---|
| 154 |  | 
|---|
| 155 | static char *arrow_match_shape(char *name, int *flag) | 
|---|
| 156 | { | 
|---|
| 157 | char *next, *rest; | 
|---|
| 158 | int f = ARR_TYPE_NONE; | 
|---|
| 159 |  | 
|---|
| 160 | rest = arrow_match_name_frag(name, Arrowsynonyms, &f); | 
|---|
| 161 | if (rest == name) { | 
|---|
| 162 | do { | 
|---|
| 163 | next = rest; | 
|---|
| 164 | rest = arrow_match_name_frag(next, Arrowmods, &f); | 
|---|
| 165 | } while (next != rest); | 
|---|
| 166 | rest = arrow_match_name_frag(rest, Arrownames, &f); | 
|---|
| 167 | } | 
|---|
| 168 | if (f && !(f & ((1 << BITS_PER_ARROW_TYPE) - 1))) | 
|---|
| 169 | f |= ARR_TYPE_NORM; | 
|---|
| 170 | *flag |= f; | 
|---|
| 171 | return rest; | 
|---|
| 172 | } | 
|---|
| 173 |  | 
|---|
| 174 | static void arrow_match_name(char *name, int *flag) | 
|---|
| 175 | { | 
|---|
| 176 | char *rest = name; | 
|---|
| 177 | char *next; | 
|---|
| 178 | int i, f; | 
|---|
| 179 |  | 
|---|
| 180 | *flag = 0; | 
|---|
| 181 | for (i = 0; *rest != '\0' && i < NUMB_OF_ARROW_HEADS; ) { | 
|---|
| 182 | f = ARR_TYPE_NONE; | 
|---|
| 183 | next = rest; | 
|---|
| 184 | rest = arrow_match_shape(next, &f); | 
|---|
| 185 | if (f == ARR_TYPE_NONE) { | 
|---|
| 186 | agerr(AGWARN, "Arrow type \"%s\" unknown - ignoring\n", next); | 
|---|
| 187 | return; | 
|---|
| 188 | } | 
|---|
| 189 | if (f == ARR_TYPE_GAP && i == (NUMB_OF_ARROW_HEADS -1)) | 
|---|
| 190 | f = ARR_TYPE_NONE; | 
|---|
| 191 | if ((f == ARR_TYPE_GAP) && (i == 0) && (*rest == '\0')) | 
|---|
| 192 | f = ARR_TYPE_NONE; | 
|---|
| 193 | if (f != ARR_TYPE_NONE) | 
|---|
| 194 | *flag |= (f << (i++ * BITS_PER_ARROW)); | 
|---|
| 195 | } | 
|---|
| 196 | } | 
|---|
| 197 |  | 
|---|
| 198 | void arrow_flags(Agedge_t * e, int *sflag, int *eflag) | 
|---|
| 199 | { | 
|---|
| 200 | char *attr; | 
|---|
| 201 | arrowdir_t *arrowdir; | 
|---|
| 202 |  | 
|---|
| 203 | *sflag = ARR_TYPE_NONE; | 
|---|
| 204 | *eflag = agisdirected(agraphof(e)) ? ARR_TYPE_NORM : ARR_TYPE_NONE; | 
|---|
| 205 | if (E_dir && ((attr = agxget(e, E_dir)))[0]) { | 
|---|
| 206 | for (arrowdir = Arrowdirs; arrowdir->dir; arrowdir++) { | 
|---|
| 207 | if (streq(attr, arrowdir->dir)) { | 
|---|
| 208 | *sflag = arrowdir->sflag; | 
|---|
| 209 | *eflag = arrowdir->eflag; | 
|---|
| 210 | break; | 
|---|
| 211 | } | 
|---|
| 212 | } | 
|---|
| 213 | } | 
|---|
| 214 | if (E_arrowhead && (*eflag == ARR_TYPE_NORM) && ((attr = agxget(e, E_arrowhead)))[0]) | 
|---|
| 215 | arrow_match_name(attr, eflag); | 
|---|
| 216 | if (E_arrowtail && (*sflag == ARR_TYPE_NORM) && ((attr = agxget(e, E_arrowtail)))[0]) | 
|---|
| 217 | arrow_match_name(attr, sflag); | 
|---|
| 218 | if (ED_conc_opp_flag(e)) { | 
|---|
| 219 | edge_t *f; | 
|---|
| 220 | int s0, e0; | 
|---|
| 221 | /* pick up arrowhead of opposing edge */ | 
|---|
| 222 | f = agfindedge(agraphof(aghead(e)), aghead(e), agtail(e)); | 
|---|
| 223 | arrow_flags(f, &s0, &e0); | 
|---|
| 224 | *eflag = *eflag | s0; | 
|---|
| 225 | *sflag = *sflag | e0; | 
|---|
| 226 | } | 
|---|
| 227 | } | 
|---|
| 228 |  | 
|---|
| 229 | double arrow_length(edge_t * e, int flag) | 
|---|
| 230 | { | 
|---|
| 231 | arrowtype_t *arrowtype; | 
|---|
| 232 | double lenfact = 0.0; | 
|---|
| 233 | int f, i; | 
|---|
| 234 |  | 
|---|
| 235 | for (i = 0; i < NUMB_OF_ARROW_HEADS; i++) { | 
|---|
| 236 | /* we don't simply index with flag because arrowtypes are not necessarily sorted */ | 
|---|
| 237 | f = (flag >> (i * BITS_PER_ARROW)) & ((1 << BITS_PER_ARROW_TYPE) - 1); | 
|---|
| 238 | for (arrowtype = Arrowtypes; arrowtype->gen; arrowtype++) { | 
|---|
| 239 | if (f == arrowtype->type) { | 
|---|
| 240 | lenfact += arrowtype->lenfact; | 
|---|
| 241 | break; | 
|---|
| 242 | } | 
|---|
| 243 | } | 
|---|
| 244 | } | 
|---|
| 245 | /* The original was missing the factor E_arrowsz, but I believe it | 
|---|
| 246 | should be here for correct arrow clipping */ | 
|---|
| 247 | return ARROW_LENGTH * lenfact * late_double(e, E_arrowsz, 1.0, 0.0); | 
|---|
| 248 | } | 
|---|
| 249 |  | 
|---|
| 250 | /* inside function for calls to bezier_clip */ | 
|---|
| 251 | static boolean inside(inside_t * inside_context, pointf p) | 
|---|
| 252 | { | 
|---|
| 253 | return DIST2(p, inside_context->a.p[0]) <= inside_context->a.r[0]; | 
|---|
| 254 | } | 
|---|
| 255 |  | 
|---|
| 256 | int arrowEndClip(edge_t* e, pointf * ps, int startp, | 
|---|
| 257 | int endp, bezier * spl, int eflag) | 
|---|
| 258 | { | 
|---|
| 259 | inside_t inside_context; | 
|---|
| 260 | pointf sp[4]; | 
|---|
| 261 | double elen, elen2; | 
|---|
| 262 |  | 
|---|
| 263 | elen = arrow_length(e, eflag); | 
|---|
| 264 | elen2 = elen * elen; | 
|---|
| 265 | spl->eflag = eflag, spl->ep = ps[endp + 3]; | 
|---|
| 266 | if (endp > startp && DIST2(ps[endp], ps[endp + 3]) < elen2) { | 
|---|
| 267 | endp -= 3; | 
|---|
| 268 | } | 
|---|
| 269 | sp[3] = ps[endp]; | 
|---|
| 270 | sp[2] = ps[endp + 1]; | 
|---|
| 271 | sp[1] = ps[endp + 2]; | 
|---|
| 272 | sp[0] = spl->ep;	/* ensure endpoint starts inside */ | 
|---|
| 273 |  | 
|---|
| 274 | inside_context.a.p = &sp[0]; | 
|---|
| 275 | inside_context.a.r = &elen2; | 
|---|
| 276 | bezier_clip(&inside_context, inside, sp, TRUE); | 
|---|
| 277 |  | 
|---|
| 278 | ps[endp] = sp[3]; | 
|---|
| 279 | ps[endp + 1] = sp[2]; | 
|---|
| 280 | ps[endp + 2] = sp[1]; | 
|---|
| 281 | ps[endp + 3] = sp[0]; | 
|---|
| 282 | return endp; | 
|---|
| 283 | } | 
|---|
| 284 |  | 
|---|
| 285 | int arrowStartClip(edge_t* e, pointf * ps, int startp, | 
|---|
| 286 | int endp, bezier * spl, int sflag) | 
|---|
| 287 | { | 
|---|
| 288 | inside_t inside_context; | 
|---|
| 289 | pointf sp[4]; | 
|---|
| 290 | double slen, slen2; | 
|---|
| 291 |  | 
|---|
| 292 | slen = arrow_length(e, sflag); | 
|---|
| 293 | slen2 = slen * slen; | 
|---|
| 294 | spl->sflag = sflag, spl->sp = ps[startp]; | 
|---|
| 295 | if (endp > startp && DIST2(ps[startp], ps[startp + 3]) < slen2) { | 
|---|
| 296 | startp += 3; | 
|---|
| 297 | } | 
|---|
| 298 | sp[0] = ps[startp + 3]; | 
|---|
| 299 | sp[1] = ps[startp + 2]; | 
|---|
| 300 | sp[2] = ps[startp + 1]; | 
|---|
| 301 | sp[3] = spl->sp;	/* ensure endpoint starts inside */ | 
|---|
| 302 |  | 
|---|
| 303 | inside_context.a.p = &sp[3]; | 
|---|
| 304 | inside_context.a.r = &slen2; | 
|---|
| 305 | bezier_clip(&inside_context, inside, sp, FALSE); | 
|---|
| 306 |  | 
|---|
| 307 | ps[startp] = sp[3]; | 
|---|
| 308 | ps[startp + 1] = sp[2]; | 
|---|
| 309 | ps[startp + 2] = sp[1]; | 
|---|
| 310 | ps[startp + 3] = sp[0]; | 
|---|
| 311 | return startp; | 
|---|
| 312 | } | 
|---|
| 313 |  | 
|---|
| 314 | /* arrowOrthoClip: | 
|---|
| 315 | * For orthogonal routing, we know each Bezier of spl is a horizontal or vertical | 
|---|
| 316 | * line segment. We need to guarantee the B-spline stays this way. At present, we shrink | 
|---|
| 317 | * the arrows if necessary to fit the last segment at either end. Alternatively, we could | 
|---|
| 318 | * maintain the arrow size by dropping the 3 points of spl, and adding a new spl encoding | 
|---|
| 319 | * the arrow, something "ex_0,y_0 x_1,y_1 x_1,y_1 x_1,y_1 x_1,y_1", when the last line | 
|---|
| 320 | * segment is x_1,y_1 x_2,y_2 x_3,y_3 x_0,y_0. With a good deal more work, we could guarantee | 
|---|
| 321 | * that the truncated spl clips to the arrow shape. | 
|---|
| 322 | */ | 
|---|
| 323 | void arrowOrthoClip(edge_t* e, pointf* ps, int startp, int endp, bezier* spl, int sflag, int eflag) | 
|---|
| 324 | { | 
|---|
| 325 | pointf p, q, r, s, t; | 
|---|
| 326 | double d, tlen, hlen, maxd; | 
|---|
| 327 |  | 
|---|
| 328 | if (sflag && eflag && (endp == startp)) { /* handle special case of two arrows on a single segment */ | 
|---|
| 329 | p = ps[endp]; | 
|---|
| 330 | q = ps[endp+3]; | 
|---|
| 331 | tlen = arrow_length (e, sflag); | 
|---|
| 332 | hlen = arrow_length (e, eflag); | 
|---|
| 333 | d = DIST(p, q); | 
|---|
| 334 | if (hlen + tlen >= d) { | 
|---|
| 335 | hlen = tlen = d/3.0; | 
|---|
| 336 | } | 
|---|
| 337 | if (p.y == q.y) { /* horz segment */ | 
|---|
| 338 | s.y = t.y = p.y; | 
|---|
| 339 | if (p.x < q.x) { | 
|---|
| 340 | t.x = q.x - hlen; | 
|---|
| 341 | s.x = p.x + tlen; | 
|---|
| 342 | } | 
|---|
| 343 | else { | 
|---|
| 344 | t.x = q.x + hlen; | 
|---|
| 345 | s.x = p.x - tlen; | 
|---|
| 346 | } | 
|---|
| 347 | } | 
|---|
| 348 | else {            /* vert segment */ | 
|---|
| 349 | s.x = t.x = p.x; | 
|---|
| 350 | if (p.y < q.y) { | 
|---|
| 351 | t.y = q.y - hlen; | 
|---|
| 352 | s.y = p.y + tlen; | 
|---|
| 353 | } | 
|---|
| 354 | else { | 
|---|
| 355 | t.y = q.y + hlen; | 
|---|
| 356 | s.y = p.y - tlen; | 
|---|
| 357 | } | 
|---|
| 358 | } | 
|---|
| 359 | ps[endp] = ps[endp + 1] = s; | 
|---|
| 360 | ps[endp + 2] = ps[endp + 3] = t; | 
|---|
| 361 | spl->eflag = eflag, spl->ep = p; | 
|---|
| 362 | spl->sflag = sflag, spl->sp = q; | 
|---|
| 363 | return; | 
|---|
| 364 | } | 
|---|
| 365 | if (eflag) { | 
|---|
| 366 | hlen = arrow_length(e, eflag); | 
|---|
| 367 | p = ps[endp]; | 
|---|
| 368 | q = ps[endp+3]; | 
|---|
| 369 | d = DIST(p, q); | 
|---|
| 370 | maxd = 0.9*d; | 
|---|
| 371 | if (hlen >= maxd) {   /* arrow too long */ | 
|---|
| 372 | hlen = maxd; | 
|---|
| 373 | } | 
|---|
| 374 | if (p.y == q.y) { /* horz segment */ | 
|---|
| 375 | r.y = p.y; | 
|---|
| 376 | if (p.x < q.x) r.x = q.x - hlen; | 
|---|
| 377 | else r.x = q.x + hlen; | 
|---|
| 378 | } | 
|---|
| 379 | else {            /* vert segment */ | 
|---|
| 380 | r.x = p.x; | 
|---|
| 381 | if (p.y < q.y) r.y = q.y - hlen; | 
|---|
| 382 | else r.y = q.y + hlen; | 
|---|
| 383 | } | 
|---|
| 384 | ps[endp + 1] = p; | 
|---|
| 385 | ps[endp + 2] = ps[endp + 3] = r; | 
|---|
| 386 | spl->eflag = eflag; | 
|---|
| 387 | spl->ep = q; | 
|---|
| 388 | } | 
|---|
| 389 | if (sflag) { | 
|---|
| 390 | tlen = arrow_length(e, sflag); | 
|---|
| 391 | p = ps[startp]; | 
|---|
| 392 | q = ps[startp+3]; | 
|---|
| 393 | d = DIST(p, q); | 
|---|
| 394 | maxd = 0.9*d; | 
|---|
| 395 | if (tlen >= maxd) {   /* arrow too long */ | 
|---|
| 396 | tlen = maxd; | 
|---|
| 397 | } | 
|---|
| 398 | if (p.y == q.y) { /* horz segment */ | 
|---|
| 399 | r.y = p.y; | 
|---|
| 400 | if (p.x < q.x) r.x = p.x + tlen; | 
|---|
| 401 | else r.x = p.x - tlen; | 
|---|
| 402 | } | 
|---|
| 403 | else {            /* vert segment */ | 
|---|
| 404 | r.x = p.x; | 
|---|
| 405 | if (p.y < q.y) r.y = p.y + tlen; | 
|---|
| 406 | else r.y = p.y - tlen; | 
|---|
| 407 | } | 
|---|
| 408 | ps[startp] = ps[startp + 1] = r; | 
|---|
| 409 | ps[startp + 2] = q; | 
|---|
| 410 | spl->sflag = sflag; | 
|---|
| 411 | spl->sp = p; | 
|---|
| 412 | } | 
|---|
| 413 | } | 
|---|
| 414 |  | 
|---|
| 415 | static void arrow_type_normal(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag) | 
|---|
| 416 | { | 
|---|
| 417 | pointf q, v, a[5]; | 
|---|
| 418 | double arrowwidth; | 
|---|
| 419 |  | 
|---|
| 420 | arrowwidth = 0.35; | 
|---|
| 421 | if (penwidth > 4) | 
|---|
| 422 | arrowwidth *= penwidth / 4; | 
|---|
| 423 |  | 
|---|
| 424 | v.x = -u.y * arrowwidth; | 
|---|
| 425 | v.y = u.x * arrowwidth; | 
|---|
| 426 | q.x = p.x + u.x; | 
|---|
| 427 | q.y = p.y + u.y; | 
|---|
| 428 | if (flag & ARR_MOD_INV) { | 
|---|
| 429 | a[0] = a[4] = p; | 
|---|
| 430 | a[1].x = p.x - v.x; | 
|---|
| 431 | a[1].y = p.y - v.y; | 
|---|
| 432 | a[2] = q; | 
|---|
| 433 | a[3].x = p.x + v.x; | 
|---|
| 434 | a[3].y = p.y + v.y; | 
|---|
| 435 | } else { | 
|---|
| 436 | a[0] = a[4] = q; | 
|---|
| 437 | a[1].x = q.x - v.x; | 
|---|
| 438 | a[1].y = q.y - v.y; | 
|---|
| 439 | a[2] = p; | 
|---|
| 440 | a[3].x = q.x + v.x; | 
|---|
| 441 | a[3].y = q.y + v.y; | 
|---|
| 442 | } | 
|---|
| 443 | if (flag & ARR_MOD_LEFT) | 
|---|
| 444 | gvrender_polygon(job, a, 3, !(flag & ARR_MOD_OPEN)); | 
|---|
| 445 | else if (flag & ARR_MOD_RIGHT) | 
|---|
| 446 | gvrender_polygon(job, &a[2], 3, !(flag & ARR_MOD_OPEN)); | 
|---|
| 447 | else | 
|---|
| 448 | gvrender_polygon(job, &a[1], 3, !(flag & ARR_MOD_OPEN)); | 
|---|
| 449 | } | 
|---|
| 450 |  | 
|---|
| 451 | static void arrow_type_crow(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag) | 
|---|
| 452 | { | 
|---|
| 453 | pointf m, q, v, w, a[9]; | 
|---|
| 454 | double arrowwidth, shaftwidth; | 
|---|
| 455 |  | 
|---|
| 456 | arrowwidth = 0.45; | 
|---|
| 457 | if (penwidth > (4 * arrowsize) && (flag & ARR_MOD_INV)) | 
|---|
| 458 | arrowwidth *= penwidth / (4 * arrowsize); | 
|---|
| 459 |  | 
|---|
| 460 | shaftwidth = 0; | 
|---|
| 461 | if (penwidth > 1 && (flag & ARR_MOD_INV)) | 
|---|
| 462 | shaftwidth = 0.05 * (penwidth - 1) / arrowsize;   /* arrowsize to cancel the arrowsize term already in u */ | 
|---|
| 463 |  | 
|---|
| 464 | v.x = -u.y * arrowwidth; | 
|---|
| 465 | v.y = u.x * arrowwidth; | 
|---|
| 466 | w.x = -u.y * shaftwidth; | 
|---|
| 467 | w.y = u.x * shaftwidth; | 
|---|
| 468 | q.x = p.x + u.x; | 
|---|
| 469 | q.y = p.y + u.y; | 
|---|
| 470 | m.x = p.x + u.x * 0.5; | 
|---|
| 471 | m.y = p.y + u.y * 0.5; | 
|---|
| 472 | if (flag & ARR_MOD_INV) {  /* vee */ | 
|---|
| 473 | a[0] = a[8] = p; | 
|---|
| 474 | a[1].x = q.x - v.x; | 
|---|
| 475 | a[1].y = q.y - v.y; | 
|---|
| 476 | a[2].x = m.x - w.x; | 
|---|
| 477 | a[2].y = m.y - w.y; | 
|---|
| 478 | a[3].x = q.x - w.x; | 
|---|
| 479 | a[3].y = q.y - w.y; | 
|---|
| 480 | a[4] = q; | 
|---|
| 481 | a[5].x = q.x + w.x; | 
|---|
| 482 | a[5].y = q.y + w.y; | 
|---|
| 483 | a[6].x = m.x + w.x; | 
|---|
| 484 | a[6].y = m.y + w.y; | 
|---|
| 485 | a[7].x = q.x + v.x; | 
|---|
| 486 | a[7].y = q.y + v.y; | 
|---|
| 487 | } else {                     /* crow */ | 
|---|
| 488 | a[0] = a[8] = q; | 
|---|
| 489 | a[1].x = p.x - v.x; | 
|---|
| 490 | a[1].y = p.y - v.y; | 
|---|
| 491 | a[2].x = m.x - w.x; | 
|---|
| 492 | a[2].y = m.y - w.y; | 
|---|
| 493 | a[3].x = p.x; | 
|---|
| 494 | a[3].y = p.y; | 
|---|
| 495 | a[4] = p; | 
|---|
| 496 | a[5].x = p.x; | 
|---|
| 497 | a[5].y = p.y; | 
|---|
| 498 | a[6].x = m.x + w.x; | 
|---|
| 499 | a[6].y = m.y + w.y; | 
|---|
| 500 | a[7].x = p.x + v.x; | 
|---|
| 501 | a[7].y = p.y + v.y; | 
|---|
| 502 | } | 
|---|
| 503 | if (flag & ARR_MOD_LEFT) | 
|---|
| 504 | gvrender_polygon(job, a, 6, 1); | 
|---|
| 505 | else if (flag & ARR_MOD_RIGHT) | 
|---|
| 506 | gvrender_polygon(job, &a[3], 6, 1); | 
|---|
| 507 | else | 
|---|
| 508 | gvrender_polygon(job, a, 9, 1); | 
|---|
| 509 | } | 
|---|
| 510 |  | 
|---|
| 511 | static void arrow_type_gap(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag) | 
|---|
| 512 | { | 
|---|
| 513 | pointf q, a[2]; | 
|---|
| 514 |  | 
|---|
| 515 | q.x = p.x + u.x; | 
|---|
| 516 | q.y = p.y + u.y; | 
|---|
| 517 | a[0] = p; | 
|---|
| 518 | a[1] = q; | 
|---|
| 519 | gvrender_polyline(job, a, 2); | 
|---|
| 520 | } | 
|---|
| 521 |  | 
|---|
| 522 | static void arrow_type_tee(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag) | 
|---|
| 523 | { | 
|---|
| 524 | pointf m, n, q, v, a[4]; | 
|---|
| 525 |  | 
|---|
| 526 | v.x = -u.y; | 
|---|
| 527 | v.y = u.x; | 
|---|
| 528 | q.x = p.x + u.x; | 
|---|
| 529 | q.y = p.y + u.y; | 
|---|
| 530 | m.x = p.x + u.x * 0.2; | 
|---|
| 531 | m.y = p.y + u.y * 0.2; | 
|---|
| 532 | n.x = p.x + u.x * 0.6; | 
|---|
| 533 | n.y = p.y + u.y * 0.6; | 
|---|
| 534 | a[0].x = m.x + v.x; | 
|---|
| 535 | a[0].y = m.y + v.y; | 
|---|
| 536 | a[1].x = m.x - v.x; | 
|---|
| 537 | a[1].y = m.y - v.y; | 
|---|
| 538 | a[2].x = n.x - v.x; | 
|---|
| 539 | a[2].y = n.y - v.y; | 
|---|
| 540 | a[3].x = n.x + v.x; | 
|---|
| 541 | a[3].y = n.y + v.y; | 
|---|
| 542 | if (flag & ARR_MOD_LEFT) { | 
|---|
| 543 | a[0] = m; | 
|---|
| 544 | a[3] = n; | 
|---|
| 545 | } else if (flag & ARR_MOD_RIGHT) { | 
|---|
| 546 | a[1] = m; | 
|---|
| 547 | a[2] = n; | 
|---|
| 548 | } | 
|---|
| 549 | gvrender_polygon(job, a, 4, 1); | 
|---|
| 550 | a[0] = p; | 
|---|
| 551 | a[1] = q; | 
|---|
| 552 | gvrender_polyline(job, a, 2); | 
|---|
| 553 | } | 
|---|
| 554 |  | 
|---|
| 555 | static void arrow_type_box(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag) | 
|---|
| 556 | { | 
|---|
| 557 | pointf m, q, v, a[4]; | 
|---|
| 558 |  | 
|---|
| 559 | v.x = -u.y * 0.4; | 
|---|
| 560 | v.y = u.x * 0.4; | 
|---|
| 561 | m.x = p.x + u.x * 0.8; | 
|---|
| 562 | m.y = p.y + u.y * 0.8; | 
|---|
| 563 | q.x = p.x + u.x; | 
|---|
| 564 | q.y = p.y + u.y; | 
|---|
| 565 | a[0].x = p.x + v.x; | 
|---|
| 566 | a[0].y = p.y + v.y; | 
|---|
| 567 | a[1].x = p.x - v.x; | 
|---|
| 568 | a[1].y = p.y - v.y; | 
|---|
| 569 | a[2].x = m.x - v.x; | 
|---|
| 570 | a[2].y = m.y - v.y; | 
|---|
| 571 | a[3].x = m.x + v.x; | 
|---|
| 572 | a[3].y = m.y + v.y; | 
|---|
| 573 | if (flag & ARR_MOD_LEFT) { | 
|---|
| 574 | a[0] = p; | 
|---|
| 575 | a[3] = m; | 
|---|
| 576 | } else if (flag & ARR_MOD_RIGHT) { | 
|---|
| 577 | a[1] = p; | 
|---|
| 578 | a[2] = m; | 
|---|
| 579 | } | 
|---|
| 580 | gvrender_polygon(job, a, 4, !(flag & ARR_MOD_OPEN)); | 
|---|
| 581 | a[0] = m; | 
|---|
| 582 | a[1] = q; | 
|---|
| 583 | gvrender_polyline(job, a, 2); | 
|---|
| 584 | } | 
|---|
| 585 |  | 
|---|
| 586 | static void arrow_type_diamond(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag) | 
|---|
| 587 | { | 
|---|
| 588 | pointf q, r, v, a[5]; | 
|---|
| 589 |  | 
|---|
| 590 | v.x = -u.y / 3.; | 
|---|
| 591 | v.y = u.x / 3.; | 
|---|
| 592 | r.x = p.x + u.x / 2.; | 
|---|
| 593 | r.y = p.y + u.y / 2.; | 
|---|
| 594 | q.x = p.x + u.x; | 
|---|
| 595 | q.y = p.y + u.y; | 
|---|
| 596 | a[0] = a[4] = q; | 
|---|
| 597 | a[1].x = r.x + v.x; | 
|---|
| 598 | a[1].y = r.y + v.y; | 
|---|
| 599 | a[2] = p; | 
|---|
| 600 | a[3].x = r.x - v.x; | 
|---|
| 601 | a[3].y = r.y - v.y; | 
|---|
| 602 | if (flag & ARR_MOD_LEFT) | 
|---|
| 603 | gvrender_polygon(job, &a[2], 3, !(flag & ARR_MOD_OPEN)); | 
|---|
| 604 | else if (flag & ARR_MOD_RIGHT) | 
|---|
| 605 | gvrender_polygon(job, a, 3, !(flag & ARR_MOD_OPEN)); | 
|---|
| 606 | else | 
|---|
| 607 | gvrender_polygon(job, a, 4, !(flag & ARR_MOD_OPEN)); | 
|---|
| 608 | } | 
|---|
| 609 |  | 
|---|
| 610 | static void arrow_type_dot(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag) | 
|---|
| 611 | { | 
|---|
| 612 | double r; | 
|---|
| 613 | pointf AF[2]; | 
|---|
| 614 |  | 
|---|
| 615 | r = sqrt(u.x * u.x + u.y * u.y) / 2.; | 
|---|
| 616 | AF[0].x = p.x + u.x / 2. - r; | 
|---|
| 617 | AF[0].y = p.y + u.y / 2. - r; | 
|---|
| 618 | AF[1].x = p.x + u.x / 2. + r; | 
|---|
| 619 | AF[1].y = p.y + u.y / 2. + r; | 
|---|
| 620 | gvrender_ellipse(job, AF, 2, !(flag & ARR_MOD_OPEN)); | 
|---|
| 621 | } | 
|---|
| 622 |  | 
|---|
| 623 |  | 
|---|
| 624 | /* Draw a concave semicircle using a single cubic bezier curve that touches p at its midpoint. | 
|---|
| 625 | * See http://digerati-illuminatus.blogspot.com.au/2008/05/approximating-semicircle-with-cubic.html for details. | 
|---|
| 626 | */ | 
|---|
| 627 | static void arrow_type_curve(GVJ_t* job, pointf p, pointf u, double arrowsize, double penwidth, int flag) | 
|---|
| 628 | { | 
|---|
| 629 | double arrowwidth = penwidth > 4 ? 0.5 * penwidth / 4 : 0.5; | 
|---|
| 630 | pointf q, v, w; | 
|---|
| 631 | pointf AF[4], a[2]; | 
|---|
| 632 |  | 
|---|
| 633 | q.x = p.x + u.x; | 
|---|
| 634 | q.y = p.y + u.y; | 
|---|
| 635 | v.x = -u.y * arrowwidth; | 
|---|
| 636 | v.y = u.x * arrowwidth; | 
|---|
| 637 | w.x = v.y; // same direction as u, same magnitude as v. | 
|---|
| 638 | w.y = -v.x; | 
|---|
| 639 | a[0] = p; | 
|---|
| 640 | a[1] = q; | 
|---|
| 641 |  | 
|---|
| 642 | AF[0].x = p.x + v.x + w.x; | 
|---|
| 643 | AF[0].y = p.y + v.y + w.y; | 
|---|
| 644 |  | 
|---|
| 645 | AF[3].x = p.x - v.x + w.x; | 
|---|
| 646 | AF[3].y = p.y - v.y + w.y; | 
|---|
| 647 |  | 
|---|
| 648 | if (flag & ARR_MOD_INV) {  /* ----(-| */ | 
|---|
| 649 | AF[1].x = p.x + 0.95 * v.x + w.x + w.x * 4.0 / 3.0; | 
|---|
| 650 | AF[1].y = AF[0].y + w.y * 4.0 / 3.0; | 
|---|
| 651 |  | 
|---|
| 652 | AF[2].x = p.x - 0.95 * v.x + w.x + w.x * 4.0 / 3.0; | 
|---|
| 653 | AF[2].y = AF[3].y + w.y * 4.0 / 3.0; | 
|---|
| 654 | } | 
|---|
| 655 | else {  /* ----)-| */ | 
|---|
| 656 | AF[1].x = p.x + 0.95 * v.x + w.x - w.x * 4.0 / 3.0; | 
|---|
| 657 | AF[1].y = AF[0].y - w.y * 4.0 / 3.0; | 
|---|
| 658 |  | 
|---|
| 659 | AF[2].x = p.x - 0.95 * v.x + w.x - w.x * 4.0 / 3.0; | 
|---|
| 660 | AF[2].y = AF[3].y - w.y * 4.0 / 3.0; | 
|---|
| 661 | } | 
|---|
| 662 |  | 
|---|
| 663 | gvrender_polyline(job, a, 2); | 
|---|
| 664 | if (flag & ARR_MOD_LEFT) | 
|---|
| 665 | Bezier(AF, 3, 0.5, NULL, AF); | 
|---|
| 666 | else if (flag & ARR_MOD_RIGHT) | 
|---|
| 667 | Bezier(AF, 3, 0.5, AF, NULL); | 
|---|
| 668 | gvrender_beziercurve(job, AF, sizeof(AF) / sizeof(pointf), FALSE, FALSE, FALSE); | 
|---|
| 669 | } | 
|---|
| 670 |  | 
|---|
| 671 |  | 
|---|
| 672 | static pointf arrow_gen_type(GVJ_t * job, pointf p, pointf u, double arrowsize, double penwidth, int flag) | 
|---|
| 673 | { | 
|---|
| 674 | int f; | 
|---|
| 675 | arrowtype_t *arrowtype; | 
|---|
| 676 |  | 
|---|
| 677 | f = flag & ((1 << BITS_PER_ARROW_TYPE) - 1); | 
|---|
| 678 | for (arrowtype = Arrowtypes; arrowtype->type; arrowtype++) { | 
|---|
| 679 | if (f == arrowtype->type) { | 
|---|
| 680 | u.x *= arrowtype->lenfact * arrowsize; | 
|---|
| 681 | u.y *= arrowtype->lenfact * arrowsize; | 
|---|
| 682 | (arrowtype->gen) (job, p, u, arrowsize, penwidth, flag); | 
|---|
| 683 | p.x = p.x + u.x; | 
|---|
| 684 | p.y = p.y + u.y; | 
|---|
| 685 | break; | 
|---|
| 686 | } | 
|---|
| 687 | } | 
|---|
| 688 | return p; | 
|---|
| 689 | } | 
|---|
| 690 |  | 
|---|
| 691 | boxf arrow_bb(pointf p, pointf u, double arrowsize, int flag) | 
|---|
| 692 | { | 
|---|
| 693 | double s; | 
|---|
| 694 | boxf bb; | 
|---|
| 695 | double ax,ay,bx,by,cx,cy,dx,dy; | 
|---|
| 696 | double ux2, uy2; | 
|---|
| 697 |  | 
|---|
| 698 | /* generate arrowhead vector */ | 
|---|
| 699 | u.x -= p.x; | 
|---|
| 700 | u.y -= p.y; | 
|---|
| 701 | /* the EPSILONs are to keep this stable as length of u approaches 0.0 */ | 
|---|
| 702 | s = ARROW_LENGTH * arrowsize / (sqrt(u.x * u.x + u.y * u.y) + EPSILON); | 
|---|
| 703 | u.x += (u.x >= 0.0) ? EPSILON : -EPSILON; | 
|---|
| 704 | u.y += (u.y >= 0.0) ? EPSILON : -EPSILON; | 
|---|
| 705 | u.x *= s; | 
|---|
| 706 | u.y *= s; | 
|---|
| 707 |  | 
|---|
| 708 | /* compute all 4 corners of rotated arrowhead bounding box */ | 
|---|
| 709 | ux2 = u.x / 2.; | 
|---|
| 710 | uy2 = u.y / 2.; | 
|---|
| 711 | ax = p.x - uy2; | 
|---|
| 712 | ay = p.y - ux2; | 
|---|
| 713 | bx = p.x + uy2; | 
|---|
| 714 | by = p.y + ux2; | 
|---|
| 715 | cx = ax + u.x; | 
|---|
| 716 | cy = ay + u.y; | 
|---|
| 717 | dx = bx + u.x; | 
|---|
| 718 | dy = by + u.y; | 
|---|
| 719 |  | 
|---|
| 720 | /* compute a right bb */ | 
|---|
| 721 | bb.UR.x = MAX(ax, MAX(bx, MAX(cx, dx))); | 
|---|
| 722 | bb.UR.y = MAX(ay, MAX(by, MAX(cy, dy))); | 
|---|
| 723 | bb.LL.x = MIN(ax, MIN(bx, MIN(cx, dx))); | 
|---|
| 724 | bb.LL.y = MIN(ay, MIN(by, MIN(cy, dy))); | 
|---|
| 725 |  | 
|---|
| 726 | return bb; | 
|---|
| 727 | } | 
|---|
| 728 |  | 
|---|
| 729 | void arrow_gen(GVJ_t * job, emit_state_t emit_state, pointf p, pointf u, double arrowsize, double penwidth, int flag) | 
|---|
| 730 | { | 
|---|
| 731 | obj_state_t *obj = job->obj; | 
|---|
| 732 | double s; | 
|---|
| 733 | int i, f; | 
|---|
| 734 | emit_state_t old_emit_state; | 
|---|
| 735 |  | 
|---|
| 736 | old_emit_state = obj->emit_state; | 
|---|
| 737 | obj->emit_state = emit_state; | 
|---|
| 738 |  | 
|---|
| 739 | /* Dotted and dashed styles on the arrowhead are ugly (dds) */ | 
|---|
| 740 | /* linewidth needs to be reset */ | 
|---|
| 741 | gvrender_set_style(job, job->gvc->defaultlinestyle); | 
|---|
| 742 |  | 
|---|
| 743 | gvrender_set_penwidth(job, penwidth); | 
|---|
| 744 |  | 
|---|
| 745 | /* generate arrowhead vector */ | 
|---|
| 746 | u.x -= p.x; | 
|---|
| 747 | u.y -= p.y; | 
|---|
| 748 | /* the EPSILONs are to keep this stable as length of u approaches 0.0 */ | 
|---|
| 749 | s = ARROW_LENGTH / (sqrt(u.x * u.x + u.y * u.y) + EPSILON); | 
|---|
| 750 | u.x += (u.x >= 0.0) ? EPSILON : -EPSILON; | 
|---|
| 751 | u.y += (u.y >= 0.0) ? EPSILON : -EPSILON; | 
|---|
| 752 | u.x *= s; | 
|---|
| 753 | u.y *= s; | 
|---|
| 754 |  | 
|---|
| 755 | /* the first arrow head - closest to node */ | 
|---|
| 756 | for (i = 0; i < NUMB_OF_ARROW_HEADS; i++) { | 
|---|
| 757 | f = (flag >> (i * BITS_PER_ARROW)) & ((1 << BITS_PER_ARROW) - 1); | 
|---|
| 758 | if (f == ARR_TYPE_NONE) | 
|---|
| 759 | break; | 
|---|
| 760 | p = arrow_gen_type(job, p, u, arrowsize, penwidth, f); | 
|---|
| 761 | } | 
|---|
| 762 |  | 
|---|
| 763 | obj->emit_state = old_emit_state; | 
|---|
| 764 | } | 
|---|
| 765 |  | 
|---|